From 6e99169348905f3f559a96326bbbce3e4771394e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 26 Aug 2017 12:59:59 -0400 Subject: [PATCH 1/4] Permits the 6845's bus state to be examined by an owner, eliminating the need to buffer it in the bus handler. But more than that it allows the CRTC to decide when it adjusts the various outputs respective to the main phase. So a net effect of the change is that the CPC now sees vsync a cycle earlier, because my current reading of the 6845 datasheet is that it is set at the end of phase 1, not the beginning of the next phase 1. --- Components/6845/CRTC6845.hpp | 13 +++++++------ Machines/AmstradCPC/AmstradCPC.cpp | 15 +++++---------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 44eee1f80..2f4a76dad 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -41,18 +41,18 @@ enum Personality { template class CRTC6845 { public: - CRTC6845(Personality p, T &bus_handler) : + CRTC6845(Personality p, T &bus_handler) noexcept : personality_(p), bus_handler_(bus_handler) {} void select_register(uint8_t r) { selected_register_ = r; } - uint8_t get_status() { + uint8_t get_status() const { return 0xff; } - uint8_t get_register() { + uint8_t get_register() const { if(selected_register_ < 12 || selected_register_ > 17) return 0xff; return registers_[selected_register_]; } @@ -73,9 +73,6 @@ template class CRTC6845 { } void run_for(Cycles cycles) { - static int c = 0; - c++; - int cyles_remaining = cycles.as_int(); while(cyles_remaining--) { // check for end of horizontal sync @@ -112,6 +109,10 @@ template class CRTC6845 { } } + const BusState &get_bus_state() const { + return bus_state_; + } + private: inline void perform_bus_cycle() { bus_state_.display_enable = character_is_visible_ && line_is_visible_; diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 40b4515ed..14f44d983 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -327,11 +327,6 @@ class CRTCBusHandler { next_mode_ = mode; } - /// @returns the current value of the CRTC's vertical sync output. - bool get_vsync() const { - return was_vsync_; - } - /// Palette management: selects a pen to modify. void select_pen(int pen) { pen_ = pen; @@ -597,11 +592,11 @@ class i8255PortHandler : public Intel::i8255::PortHandler { public: i8255PortHandler( KeyboardState &key_state, - const CRTCBusHandler &crtc_bus_handler, + const Motorola::CRTC::CRTC6845 &crtc, AYDeferrer &ay, Storage::Tape::BinaryTapePlayer &tape_player) : key_state_(key_state), - crtc_bus_handler_(crtc_bus_handler), + crtc_(crtc), ay_(ay), tape_player_(tape_player) {} @@ -642,7 +637,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { switch(port) { case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY case 1: return - (crtc_bus_handler_.get_vsync() ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync. + (crtc_.get_bus_state().vsync ? 0x01 : 0x00) | // Bit 0 returns CRTC vsync. (tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input. 0x7e; // Bits unimplemented: // @@ -657,7 +652,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler { private: AYDeferrer &ay_; KeyboardState &key_state_; - const CRTCBusHandler &crtc_bus_handler_; + const Motorola::CRTC::CRTC6845 &crtc_; Storage::Tape::BinaryTapePlayer &tape_player_; }; @@ -676,7 +671,7 @@ class ConcreteMachine: crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_), crtc_bus_handler_(ram_, interrupt_timer_), i8255_(i8255_port_handler_), - i8255_port_handler_(key_state_, crtc_bus_handler_, ay_, tape_player_), + i8255_port_handler_(key_state_, crtc_, ay_, tape_player_), tape_player_(8000000) { // primary clock is 4Mhz set_clock_rate(4000000); From 28550c0227fd68890bf6ebfdb27a37bad10c5954 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 26 Aug 2017 13:56:23 -0400 Subject: [PATCH 2/4] Breaks the 6845 bus cycle into a phase 1 and a phase 2 per the belief that sync line changes, which are observable, happen at the end of the first phase rather than at the beginning of the next. This may have interrupt timing effects, as machines often derive an interrupt from sync. --- Components/6845/CRTC6845.hpp | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 2f4a76dad..f8a60e17c 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -28,7 +28,19 @@ struct BusState { class BusHandler { public: - void perform_bus_cycle(const BusState &) {} + /*! + Performs the first phase of a 6845 bus cycle; this is the phase in which it is intended that + systems using the 6845 respect the bus state and produce pixels, sync or whatever they require. + */ + void perform_bus_cycle_phase1(const BusState &) {} + + /*! + Performs the second phase of a 6845 bus cycle. Some bus state — including sync — is updated + directly after phase 1 and hence is visible to an observer during phase 2. Handlers may therefore + implement @c perform_bus_cycle_phase2 to be notified of the availability of that state without + having to wait until the next cycle has begun. + */ + void perform_bus_cycle_phase2(const BusState &) {} }; enum Personality { @@ -94,8 +106,8 @@ template class CRTC6845 { end_of_line_address_ = bus_state_.refresh_address; } - perform_bus_cycle(); - bus_state_.refresh_address++; + perform_bus_cycle_phase1(); + bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff; // check for end-of-line if(character_counter_ == registers_[0]) { @@ -106,6 +118,8 @@ template class CRTC6845 { // increment counter character_counter_++; } + + perform_bus_cycle_phase2(); } } @@ -114,10 +128,14 @@ template class CRTC6845 { } private: - inline void perform_bus_cycle() { + inline void perform_bus_cycle_phase1() { bus_state_.display_enable = character_is_visible_ && line_is_visible_; - bus_state_.refresh_address &= 0x3fff; - bus_handler_.perform_bus_cycle(bus_state_); + bus_handler_.perform_bus_cycle_phase1(bus_state_); + } + + inline void perform_bus_cycle_phase2() { + bus_state_.display_enable = character_is_visible_ && line_is_visible_; + bus_handler_.perform_bus_cycle_phase2(bus_state_); } inline void do_end_of_line() { From e7ad79c79ab2ee6553f34e75cc38f3a227ca439a Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 26 Aug 2017 14:07:51 -0400 Subject: [PATCH 3/4] Breaks apart the CPC's 6845 bus handler to obey phase 1 and phase 2, and now back-dates interrupts when appropriate. --- Machines/AmstradCPC/AmstradCPC.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 14f44d983..1d6ff8458 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -172,10 +172,10 @@ class CRTCBusHandler { } /*! - The CRTC entry function; takes the current bus state and determines what output - to produce based on the current palette and mode. + The CRTC entry function for the main part of each clock cycle; takes the current + bus state and determines what output to produce based on the current palette and mode. */ - forceinline void perform_bus_cycle(const Motorola::CRTC::BusState &state) { + forceinline void perform_bus_cycle_phase1(const Motorola::CRTC::BusState &state) { // The gate array waits 2µs to react to the CRTC's vsync signal, and then // caps output at 4µs. Since the clock rate is 1Mhz, that's 2 and 4 cycles, // respectively. @@ -268,7 +268,13 @@ class CRTCBusHandler { } } } + } + /*! + The CRTC entry function for phase 2 of each bus cycle — in which the next sync line state becomes + visible early. The CPC uses changes in sync to clock the interrupt timer. + */ + void perform_bus_cycle_phase2(const Motorola::CRTC::BusState &state) { // check for a trailing CRTC hsync; if one occurred then that's the trigger potentially to change // modes, and should also be sent on to the interrupt timer if(was_hsync_ && !state.hsync) { @@ -700,7 +706,10 @@ class ConcreteMachine: crtc_counter_ += cycle.length; Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4)); if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles); - if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request()); + + // Check whether that prompted a change in the interrupt line. If so then date + // it to whenever the cycle was triggered. + if(interrupt_timer_.request_has_changed()) z80_.set_interrupt_line(interrupt_timer_.get_request(), -crtc_counter_); // TODO (in the player, not here): adapt it to accept an input clock rate and // run_for as HalfCycles From 7d7aa2f5d5a85af15561ee43473b3cac123cd4f4 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 26 Aug 2017 14:37:03 -0400 Subject: [PATCH 4/4] Eliminates repetition of the unpacking of register 3 into a horizontal sync count. --- Components/6845/CRTC6845.hpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index f8a60e17c..ecc468da1 100644 --- a/Components/6845/CRTC6845.hpp +++ b/Components/6845/CRTC6845.hpp @@ -87,16 +87,17 @@ template class CRTC6845 { void run_for(Cycles cycles) { int cyles_remaining = cycles.as_int(); while(cyles_remaining--) { - // check for end of horizontal sync - if(bus_state_.hsync) { - hsync_counter_ = (hsync_counter_ + 1) & 15; - bus_state_.hsync = hsync_counter_ != (registers_[3] & 15); - } - // check for start of horizontal sync if(character_counter_ == registers_[2]) { hsync_counter_ = 0; - if(registers_[3] & 15) bus_state_.hsync = true; + bus_state_.hsync = true; + } + + // check for end of horizontal sync; note that a sync time of zero will result in an immediate + // cancellation of the plan to perform sync + if(bus_state_.hsync) { + bus_state_.hsync = hsync_counter_ != (registers_[3] & 15); + hsync_counter_ = (hsync_counter_ + 1) & 15; } // check for end of visible characters