1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-27 16:31:31 +00:00

Merge pull request #222 from TomHarte/6845GetState

Refines observable 6845 behaviour
This commit is contained in:
Thomas Harte 2017-08-26 14:46:29 -04:00 committed by GitHub
commit 97f57a3948
2 changed files with 57 additions and 33 deletions

View File

@ -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 T> 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 T> 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 T> 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 T> 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() {

View File

@ -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<CRTCBusHandler> &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<CRTCBusHandler> &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