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 {
|
||||
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() {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user