diff --git a/Components/6845/CRTC6845.hpp b/Components/6845/CRTC6845.hpp index 44eee1f80..ecc468da1 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 { @@ -41,18 +53,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,21 +85,19 @@ 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 - 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 @@ -97,8 +107,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]) { @@ -109,14 +119,24 @@ template class CRTC6845 { // increment counter character_counter_++; } + + perform_bus_cycle_phase2(); } } + const BusState &get_bus_state() const { + return bus_state_; + } + 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() { diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 40b4515ed..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) { @@ -327,11 +333,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 +598,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 +643,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 +658,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 +677,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); @@ -705,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