mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-28 07:29:45 +00:00
Merge pull request #222 from TomHarte/6845GetState
Refines observable 6845 behaviour
This commit is contained in:
commit
97f57a3948
@ -28,7 +28,19 @@ struct BusState {
|
|||||||
|
|
||||||
class BusHandler {
|
class BusHandler {
|
||||||
public:
|
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 {
|
enum Personality {
|
||||||
@ -41,18 +53,18 @@ enum Personality {
|
|||||||
template <class T> class CRTC6845 {
|
template <class T> class CRTC6845 {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
CRTC6845(Personality p, T &bus_handler) :
|
CRTC6845(Personality p, T &bus_handler) noexcept :
|
||||||
personality_(p), bus_handler_(bus_handler) {}
|
personality_(p), bus_handler_(bus_handler) {}
|
||||||
|
|
||||||
void select_register(uint8_t r) {
|
void select_register(uint8_t r) {
|
||||||
selected_register_ = r;
|
selected_register_ = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_status() {
|
uint8_t get_status() const {
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_register() {
|
uint8_t get_register() const {
|
||||||
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
if(selected_register_ < 12 || selected_register_ > 17) return 0xff;
|
||||||
return registers_[selected_register_];
|
return registers_[selected_register_];
|
||||||
}
|
}
|
||||||
@ -73,21 +85,19 @@ template <class T> class CRTC6845 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void run_for(Cycles cycles) {
|
void run_for(Cycles cycles) {
|
||||||
static int c = 0;
|
|
||||||
c++;
|
|
||||||
|
|
||||||
int cyles_remaining = cycles.as_int();
|
int cyles_remaining = cycles.as_int();
|
||||||
while(cyles_remaining--) {
|
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
|
// check for start of horizontal sync
|
||||||
if(character_counter_ == registers_[2]) {
|
if(character_counter_ == registers_[2]) {
|
||||||
hsync_counter_ = 0;
|
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
|
// check for end of visible characters
|
||||||
@ -97,8 +107,8 @@ template <class T> class CRTC6845 {
|
|||||||
end_of_line_address_ = bus_state_.refresh_address;
|
end_of_line_address_ = bus_state_.refresh_address;
|
||||||
}
|
}
|
||||||
|
|
||||||
perform_bus_cycle();
|
perform_bus_cycle_phase1();
|
||||||
bus_state_.refresh_address++;
|
bus_state_.refresh_address = (bus_state_.refresh_address + 1) & 0x3fff;
|
||||||
|
|
||||||
// check for end-of-line
|
// check for end-of-line
|
||||||
if(character_counter_ == registers_[0]) {
|
if(character_counter_ == registers_[0]) {
|
||||||
@ -109,14 +119,24 @@ template <class T> class CRTC6845 {
|
|||||||
// increment counter
|
// increment counter
|
||||||
character_counter_++;
|
character_counter_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
perform_bus_cycle_phase2();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BusState &get_bus_state() const {
|
||||||
|
return bus_state_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline void perform_bus_cycle() {
|
inline void perform_bus_cycle_phase1() {
|
||||||
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
bus_state_.display_enable = character_is_visible_ && line_is_visible_;
|
||||||
bus_state_.refresh_address &= 0x3fff;
|
bus_handler_.perform_bus_cycle_phase1(bus_state_);
|
||||||
bus_handler_.perform_bus_cycle(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() {
|
inline void do_end_of_line() {
|
||||||
|
@ -172,10 +172,10 @@ class CRTCBusHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
The CRTC entry function; takes the current bus state and determines what output
|
The CRTC entry function for the main part of each clock cycle; takes the current
|
||||||
to produce based on the current palette and mode.
|
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
|
// 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,
|
// caps output at 4µs. Since the clock rate is 1Mhz, that's 2 and 4 cycles,
|
||||||
// respectively.
|
// 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
|
// 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
|
// modes, and should also be sent on to the interrupt timer
|
||||||
if(was_hsync_ && !state.hsync) {
|
if(was_hsync_ && !state.hsync) {
|
||||||
@ -327,11 +333,6 @@ class CRTCBusHandler {
|
|||||||
next_mode_ = mode;
|
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.
|
/// Palette management: selects a pen to modify.
|
||||||
void select_pen(int pen) {
|
void select_pen(int pen) {
|
||||||
pen_ = pen;
|
pen_ = pen;
|
||||||
@ -597,11 +598,11 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
public:
|
public:
|
||||||
i8255PortHandler(
|
i8255PortHandler(
|
||||||
KeyboardState &key_state,
|
KeyboardState &key_state,
|
||||||
const CRTCBusHandler &crtc_bus_handler,
|
const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc,
|
||||||
AYDeferrer &ay,
|
AYDeferrer &ay,
|
||||||
Storage::Tape::BinaryTapePlayer &tape_player) :
|
Storage::Tape::BinaryTapePlayer &tape_player) :
|
||||||
key_state_(key_state),
|
key_state_(key_state),
|
||||||
crtc_bus_handler_(crtc_bus_handler),
|
crtc_(crtc),
|
||||||
ay_(ay),
|
ay_(ay),
|
||||||
tape_player_(tape_player) {}
|
tape_player_(tape_player) {}
|
||||||
|
|
||||||
@ -642,7 +643,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
switch(port) {
|
switch(port) {
|
||||||
case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY
|
case 0: return ay_.ay()->get_data_output(); // Port A is wired to the AY
|
||||||
case 1: return
|
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.
|
(tape_player_.get_input() ? 0x80 : 0x00) | // Bit 7 returns cassette input.
|
||||||
0x7e; // Bits unimplemented:
|
0x7e; // Bits unimplemented:
|
||||||
//
|
//
|
||||||
@ -657,7 +658,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
|
|||||||
private:
|
private:
|
||||||
AYDeferrer &ay_;
|
AYDeferrer &ay_;
|
||||||
KeyboardState &key_state_;
|
KeyboardState &key_state_;
|
||||||
const CRTCBusHandler &crtc_bus_handler_;
|
const Motorola::CRTC::CRTC6845<CRTCBusHandler> &crtc_;
|
||||||
Storage::Tape::BinaryTapePlayer &tape_player_;
|
Storage::Tape::BinaryTapePlayer &tape_player_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -676,7 +677,7 @@ class ConcreteMachine:
|
|||||||
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
|
crtc_(Motorola::CRTC::HD6845S, crtc_bus_handler_),
|
||||||
crtc_bus_handler_(ram_, interrupt_timer_),
|
crtc_bus_handler_(ram_, interrupt_timer_),
|
||||||
i8255_(i8255_port_handler_),
|
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) {
|
tape_player_(8000000) {
|
||||||
// primary clock is 4Mhz
|
// primary clock is 4Mhz
|
||||||
set_clock_rate(4000000);
|
set_clock_rate(4000000);
|
||||||
@ -705,7 +706,10 @@ class ConcreteMachine:
|
|||||||
crtc_counter_ += cycle.length;
|
crtc_counter_ += cycle.length;
|
||||||
Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4));
|
Cycles crtc_cycles = crtc_counter_.divide_cycles(Cycles(4));
|
||||||
if(crtc_cycles > Cycles(0)) crtc_.run_for(crtc_cycles);
|
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
|
// TODO (in the player, not here): adapt it to accept an input clock rate and
|
||||||
// run_for as HalfCycles
|
// run_for as HalfCycles
|
||||||
|
Loading…
Reference in New Issue
Block a user