1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-26 09:29:45 +00:00
CLK/Components/68901/MFP68901.cpp

375 lines
13 KiB
C++
Raw Normal View History

//
// MFP68901.cpp
// Clock Signal
//
// Created by Thomas Harte on 06/10/2019.
// Copyright © 2019 Thomas Harte. All rights reserved.
//
#include "MFP68901.hpp"
#include <algorithm>
#include <cstring>
2020-01-01 17:38:30 +00:00
#ifndef NDEBUG
#define NDEBUG
2020-01-01 17:38:30 +00:00
#endif
#define LOG_PREFIX "[MFP] "
#include "../../Outputs/Log.hpp"
using namespace Motorola::MFP68901;
2020-05-10 01:22:51 +00:00
ClockingHint::Preference MFP68901::preferred_clocking() const {
// Rule applied: if any timer is actively running and permitted to produce an
// interrupt, request real-time running.
return
(timers_[0].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerA) ||
(timers_[1].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerB) ||
(timers_[2].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerC) ||
(timers_[3].mode >= TimerMode::Delay && interrupt_enable_&Interrupt::TimerD)
? ClockingHint::Preference::RealTime : ClockingHint::Preference::JustInTime;
}
uint8_t MFP68901::read(int address) {
address &= 0x1f;
// Interrupt block: various bits of state can be read, all passively.
if(address >= 0x03 && address <= 0x0b) {
const int shift = (address&1) << 3;
switch(address) {
case 0x03: case 0x04: return uint8_t(interrupt_enable_ >> shift);
case 0x05: case 0x06: return uint8_t(interrupt_pending_ >> shift);
case 0x07: case 0x08: return uint8_t(interrupt_in_service_ >> shift);
case 0x09: case 0x0a: return uint8_t(interrupt_mask_ >> shift);
case 0x0b: return interrupt_vector_;
default: break;
}
}
switch(address) {
// GPIP block: input, and configured active edge and direction values.
case 0x00: return (gpip_input_ & ~gpip_direction_) | (gpip_output_ & gpip_direction_);
case 0x01: return gpip_active_edge_;
case 0x02: return gpip_direction_;
/* Interrupt block dealt with above. */
default: break;
// Timer block: read back A, B and C/D control, and read current timer values.
case 0x0c: case 0x0d: return timer_ab_control_[address - 0xc];
case 0x0e: return timer_cd_control_;
case 0x0f: case 0x10:
case 0x11: case 0x12: return get_timer_data(address - 0xf);
// USART block: TODO.
case 0x13: LOG("Read: sync character generator"); break;
case 0x14: LOG("Read: USART control"); break;
case 0x15: LOG("Read: receiver status"); break;
case 0x16: LOG("Read: transmitter status"); break;
case 0x17: LOG("Read: USART data"); break;
}
return 0x00;
}
void MFP68901::write(int address, uint8_t value) {
address &= 0x1f;
// Interrupt block: enabled and masked interrupts can be set; pending and in-service interrupts can be masked.
if(address >= 0x03 && address <= 0x0b) {
const int shift = (address&1) << 3;
const int preserve = 0xff00 >> shift;
const int word_value = value << shift;
switch(address) {
default: break;
case 0x03: case 0x04: // Adjust enabled interrupts; disabled ones also cease to be pending.
interrupt_enable_ = (interrupt_enable_ & preserve) | word_value;
interrupt_pending_ &= interrupt_enable_;
break;
case 0x05: case 0x06: // Resolve pending interrupts.
interrupt_pending_ &= (preserve | word_value);
break;
case 0x07: case 0x08: // Resolve in-service interrupts.
interrupt_in_service_ &= (preserve | word_value);
break;
case 0x09: case 0x0a: // Adjust interrupt mask.
interrupt_mask_ = (interrupt_mask_ & preserve) | word_value;
break;
case 0x0b: // Set the interrupt vector, possibly changing end-of-interrupt mode.
interrupt_vector_ = value;
// If automatic end-of-interrupt mode has now been enabled, clear
// the in-process mask and re-evaluate.
if(interrupt_vector_ & 0x08) return;
interrupt_in_service_ = 0;
break;
}
// Whatever just happened may have affected the state of the interrupt line.
update_interrupts();
update_clocking_observer();
return;
}
constexpr int timer_prescales[] = {
1, 4, 10, 16, 50, 64, 100, 200
};
switch(address) {
// GPIP block: output and configuration of active edge and direction values.
case 0x00:
gpip_output_ = value;
break;
case 0x01:
gpip_active_edge_ = value;
reevaluate_gpip_interrupts();
break;
case 0x02:
gpip_direction_ = value;
reevaluate_gpip_interrupts();
break;
/* Interrupt block dealt with above. */
default: break;
// Timer block.
case 0x0c:
case 0x0d: {
const auto timer = address - 0xc;
const bool reset = value & 0x10;
timer_ab_control_[timer] = value;
switch(value & 0xf) {
case 0x0: set_timer_mode(timer, TimerMode::Stopped, 1, reset); break;
case 0x1: set_timer_mode(timer, TimerMode::Delay, 4, reset); break;
case 0x2: set_timer_mode(timer, TimerMode::Delay, 10, reset); break;
case 0x3: set_timer_mode(timer, TimerMode::Delay, 16, reset); break;
case 0x4: set_timer_mode(timer, TimerMode::Delay, 50, reset); break;
case 0x5: set_timer_mode(timer, TimerMode::Delay, 64, reset); break;
case 0x6: set_timer_mode(timer, TimerMode::Delay, 100, reset); break;
case 0x7: set_timer_mode(timer, TimerMode::Delay, 200, reset); break;
case 0x8: set_timer_mode(timer, TimerMode::EventCount, 1, reset); break;
case 0x9: set_timer_mode(timer, TimerMode::PulseWidth, 4, reset); break;
case 0xa: set_timer_mode(timer, TimerMode::PulseWidth, 10, reset); break;
case 0xb: set_timer_mode(timer, TimerMode::PulseWidth, 16, reset); break;
case 0xc: set_timer_mode(timer, TimerMode::PulseWidth, 50, reset); break;
case 0xd: set_timer_mode(timer, TimerMode::PulseWidth, 64, reset); break;
case 0xe: set_timer_mode(timer, TimerMode::PulseWidth, 100, reset); break;
case 0xf: set_timer_mode(timer, TimerMode::PulseWidth, 200, reset); break;
}
} break;
case 0x0e:
timer_cd_control_ = value;
set_timer_mode(3, (value & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[value & 7], false);
set_timer_mode(2, ((value >> 4) & 7) ? TimerMode::Delay : TimerMode::Stopped, timer_prescales[(value >> 4) & 7], false);
break;
case 0x0f: case 0x10: case 0x11: case 0x12:
set_timer_data(address - 0xf, value);
break;
// USART block: TODO.
case 0x13: LOG("Write: sync character generator"); break;
case 0x14: LOG("Write: USART control"); break;
case 0x15: LOG("Write: receiver status"); break;
case 0x16: LOG("Write: transmitter status"); break;
case 0x17: LOG("Write: USART data"); break;
}
update_clocking_observer();
}
void MFP68901::run_for(HalfCycles time) {
cycles_left_ += time;
const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
if(!cycles) return;
for(int c = 0; c < 4; ++c) {
if(timers_[c].mode >= TimerMode::Delay) {
// This code applies the timer prescaling only. prescale_count is used to count
// upwards rather than downwards for simplicity, but on the real hardware it's
// pretty safe to assume it actually counted downwards. So the clamp to 0 is
// because gymnastics may need to occur when the prescale value is altered, e.g.
// if a prescale of 256 is set and the prescale_count is currently 2 then the
// counter should roll over in 254 cycles. If the user at that point changes the
// prescale_count to 1 then the counter will need to be altered to -253 and
// allowed to keep counting up until it crosses both 0 and 1.
const int dividend = timers_[c].prescale_count + cycles;
const int decrements = std::max(dividend / timers_[c].prescale, 0);
if(decrements) {
decrement_timer(c, decrements);
timers_[c].prescale_count = dividend % timers_[c].prescale;
} else {
timers_[c].prescale_count += cycles;
}
}
}
}
HalfCycles MFP68901::get_next_sequence_point() {
return HalfCycles(-1);
}
// MARK: - Timers
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale);
timers_[timer].mode = mode;
if(reset_timer) {
timers_[timer].prescale_count = 0;
timers_[timer].value = timers_[timer].reload_value;
} else {
// This hoop is because the prescale_count here goes upward but I'm assuming it goes downward in
// real hardware. Therefore this deals with the "switched to a lower prescaling" case whereby the
// old cycle should be allowed naturally to expire.
timers_[timer].prescale_count = prescale - (timers_[timer].prescale - timers_[timer].prescale_count);
}
timers_[timer].prescale = prescale;
}
void MFP68901::set_timer_data(int timer, uint8_t value) {
if(timers_[timer].mode == TimerMode::Stopped) {
timers_[timer].value = value;
}
timers_[timer].reload_value = value;
}
uint8_t MFP68901::get_timer_data(int timer) {
return timers_[timer].value;
}
void MFP68901::set_timer_event_input(int channel, bool value) {
if(timers_[channel].event_input == value) return;
timers_[channel].event_input = value;
if(timers_[channel].mode == TimerMode::EventCount && (value == !!(gpip_active_edge_ & (0x10 >> channel)))) {
// "The active state of the signal on TAI or TBI is dependent upon the associated
// Interrupt Channels edge bit (GPIP 4 for TAI and GPIP 3 for TBI [...] ).
// If the edge bit associated with the TAI or TBI input is a one, it will be active high.
decrement_timer(channel, 1);
}
// TODO:
//
// Altering the edge bit while the timer is in the event count mode can produce a count pulse.
// The interrupt channel associated with the input (I3 for I4 for TAI) is allowed to function normally.
// To count transitions reliably, the input must remain in each state (1/O) for a length of time equal
// to four periods of the timer clock.
//
// (the final bit probably explains 13 cycles of the DE to interrupt latency; not sure about the other ~15)
}
void MFP68901::decrement_timer(int timer, int amount) {
while(amount--) {
--timers_[timer].value;
if(timers_[timer].value < 1) {
switch(timer) {
case 0: begin_interrupts(Interrupt::TimerA); break;
case 1: begin_interrupts(Interrupt::TimerB); break;
case 2: begin_interrupts(Interrupt::TimerC); break;
case 3: begin_interrupts(Interrupt::TimerD); break;
}
// Re: reloading when in event counting mode; I found the data sheet thoroughly unclear on
// this, but it appears empirically to be correct. See e.g. Pompey Pirates menu 27.
if(timers_[timer].mode == TimerMode::Delay || timers_[timer].mode == TimerMode::EventCount) {
timers_[timer].value += timers_[timer].reload_value; // TODO: properly.
}
}
}
}
// MARK: - GPIP
void MFP68901::set_port_input(uint8_t input) {
gpip_input_ = input;
reevaluate_gpip_interrupts();
}
uint8_t MFP68901::get_port_output() {
2019-11-20 03:32:07 +00:00
return 0xff; // TODO.
}
void MFP68901::reevaluate_gpip_interrupts() {
const uint8_t gpip_state = (gpip_input_ & ~gpip_direction_) ^ gpip_active_edge_;
// An interrupt is detected on any falling edge.
const uint8_t new_interrupt_mask = (gpip_state ^ gpip_interrupt_state_) & gpip_interrupt_state_;
if(new_interrupt_mask) {
begin_interrupts(
(new_interrupt_mask & 0x0f) |
2019-10-25 02:37:32 +00:00
((new_interrupt_mask & 0x30) << 2) |
((new_interrupt_mask & 0xc0) << 8)
);
}
gpip_interrupt_state_ = gpip_state;
}
// MARK: - Interrupts
void MFP68901::begin_interrupts(int interrupt) {
interrupt_pending_ |= interrupt & interrupt_enable_;
update_interrupts();
}
void MFP68901::end_interrupts(int interrupt) {
interrupt_pending_ &= ~interrupt;
update_interrupts();
}
void MFP68901::update_interrupts() {
const auto old_interrupt_line = interrupt_line_;
2019-10-27 04:02:55 +00:00
const auto firing_interrupts = interrupt_pending_ & interrupt_mask_;
2019-10-27 04:02:55 +00:00
if(!firing_interrupts) {
interrupt_line_ = false;
} else {
if(interrupt_vector_ & 0x8) {
// Software interrupt mode: permit only if neither this interrupt
// nor a higher interrupt is currently in service.
2019-11-09 21:03:00 +00:00
const int highest_bit = msb16(firing_interrupts);
interrupt_line_ = !(interrupt_in_service_ & ~(highest_bit + highest_bit - 1));
} else {
// Auto-interrupt mode; just signal.
interrupt_line_ = true;
}
}
// Update the delegate if necessary.
if(interrupt_delegate_ && interrupt_line_ != old_interrupt_line) {
interrupt_delegate_->mfp68901_did_change_interrupt_status(this);
}
}
bool MFP68901::get_interrupt_line() {
return interrupt_line_;
}
int MFP68901::acknowledge_interrupt() {
if(!(interrupt_pending_ & interrupt_mask_)) {
return NoAcknowledgement;
}
2019-11-09 21:03:00 +00:00
const int mask = msb16(interrupt_pending_ & interrupt_mask_);
// Clear the pending bit regardless.
interrupt_pending_ &= ~mask;
// If this is software interrupt mode, set the in-service bit.
if(interrupt_vector_ & 0x8) {
interrupt_in_service_ |= mask;
}
update_interrupts();
2019-11-09 21:03:00 +00:00
int selected = 0;
while((1 << selected) != mask) ++selected;
// LOG("Interrupt acknowledged: " << selected);
return (interrupt_vector_ & 0xf0) | uint8_t(selected);
}
void MFP68901::set_interrupt_delegate(InterruptDelegate *delegate) {
interrupt_delegate_ = delegate;
}