282 lines
7.5 KiB
Text
282 lines
7.5 KiB
Text
/*
|
|
* This file is part of libsidplayfp, a SID player engine.
|
|
*
|
|
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
|
* Copyright 2007-2010 Antti Lankila
|
|
* Copyright 2000 Simon White
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifndef TIMER_H
|
|
#define TIMER_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "sidplayfp/event.h"
|
|
#include "EventScheduler.h"
|
|
|
|
#include "sidcxx11.h"
|
|
|
|
namespace libsidplayfp
|
|
{
|
|
|
|
class MOS6526;
|
|
|
|
/**
|
|
* This is the base class for the MOS6526 timers.
|
|
*
|
|
* @author Ken Händel
|
|
*/
|
|
class Timer : private Event
|
|
{
|
|
protected:
|
|
static const int_least32_t CIAT_CR_START = 0x01;
|
|
static const int_least32_t CIAT_STEP = 0x04;
|
|
static const int_least32_t CIAT_CR_ONESHOT = 0x08;
|
|
static const int_least32_t CIAT_CR_FLOAD = 0x10;
|
|
static const int_least32_t CIAT_PHI2IN = 0x20;
|
|
static const int_least32_t CIAT_CR_MASK = CIAT_CR_START | CIAT_CR_ONESHOT | CIAT_CR_FLOAD | CIAT_PHI2IN;
|
|
|
|
static const int_least32_t CIAT_COUNT2 = 0x100;
|
|
static const int_least32_t CIAT_COUNT3 = 0x200;
|
|
|
|
static const int_least32_t CIAT_ONESHOT0 = 0x08 << 8;
|
|
static const int_least32_t CIAT_ONESHOT = 0x08 << 16;
|
|
static const int_least32_t CIAT_LOAD1 = 0x10 << 8;
|
|
static const int_least32_t CIAT_LOAD = 0x10 << 16;
|
|
|
|
static const int_least32_t CIAT_OUT = 0x80000000;
|
|
|
|
private:
|
|
EventCallback<Timer> m_cycleSkippingEvent;
|
|
|
|
/// Event context.
|
|
EventContext &event_context;
|
|
|
|
/**
|
|
* This is a tri-state:
|
|
*
|
|
* - when -1: cia is completely stopped
|
|
* - when 0: cia 1-clock events are ticking.
|
|
* - otherwise: cycleskipevent is ticking, and the value is the first
|
|
* phi1 clock of skipping.
|
|
*/
|
|
event_clock_t ciaEventPauseTime;
|
|
|
|
/// Current timer value.
|
|
uint_least16_t timer;
|
|
|
|
/// Timer start value (Latch).
|
|
uint_least16_t latch;
|
|
|
|
/// PB6/PB7 Flipflop to signal underflows.
|
|
bool pbToggle;
|
|
|
|
/// Copy of regs[CRA/B]
|
|
uint8_t lastControlValue;
|
|
|
|
protected:
|
|
/// Pointer to the MOS6526 which this Timer belongs to.
|
|
MOS6526* const parent;
|
|
|
|
/// CRA/CRB control register / state.
|
|
int_least32_t state;
|
|
|
|
private:
|
|
/**
|
|
* Perform scheduled cycle skipping, and resume.
|
|
*/
|
|
void cycleSkippingEvent();
|
|
|
|
/**
|
|
* Execute one CIA state transition.
|
|
*/
|
|
void clock();
|
|
|
|
/**
|
|
* Reschedule CIA event at the earliest interesting time.
|
|
* If CIA timer is stopped or is programmed to just count down,
|
|
* the events are paused.
|
|
*/
|
|
inline void reschedule();
|
|
|
|
/**
|
|
* Timer ticking event.
|
|
*/
|
|
void event() override;
|
|
|
|
/**
|
|
* Signal timer underflow.
|
|
*/
|
|
virtual void underFlow() =0;
|
|
|
|
/**
|
|
* Handle the serial port.
|
|
*/
|
|
virtual void serialPort() {}
|
|
|
|
protected:
|
|
/**
|
|
* Create a new timer.
|
|
*
|
|
* @param name component name
|
|
* @param context event context
|
|
* @param parent the MOS6526 which this Timer belongs to
|
|
*/
|
|
Timer(const char* name, EventContext *context, MOS6526* parent) :
|
|
Event(name),
|
|
m_cycleSkippingEvent("Skip CIA clock decrement cycles", *this, &Timer::cycleSkippingEvent),
|
|
event_context(*context),
|
|
timer(0),
|
|
latch(0),
|
|
pbToggle(false),
|
|
lastControlValue(0),
|
|
parent(parent),
|
|
state(0) {}
|
|
|
|
public:
|
|
/**
|
|
* Set CRA/CRB control register.
|
|
*
|
|
* @param cr control register value
|
|
*/
|
|
void setControlRegister(uint8_t cr);
|
|
|
|
/**
|
|
* Perform cycle skipping manually.
|
|
*
|
|
* Clocks the CIA up to the state it should be in, and stops all events.
|
|
*/
|
|
void syncWithCpu();
|
|
|
|
/**
|
|
* Counterpart of syncWithCpu(),
|
|
* starts the event ticking if it is needed.
|
|
* No clock() call or anything such is permissible here!
|
|
*/
|
|
void wakeUpAfterSyncWithCpu();
|
|
|
|
/**
|
|
* Reset timer.
|
|
*/
|
|
void reset();
|
|
|
|
/**
|
|
* Set low byte of Timer start value (Latch).
|
|
*
|
|
* @param data
|
|
* low byte of latch
|
|
*/
|
|
void latchLo(uint8_t data);
|
|
|
|
/**
|
|
* Set high byte of Timer start value (Latch).
|
|
*
|
|
* @param data
|
|
* high byte of latch
|
|
*/
|
|
void latchHi(uint8_t data);
|
|
|
|
/**
|
|
* Set PB6/PB7 Flipflop state.
|
|
*
|
|
* @param state
|
|
* PB6/PB7 flipflop state
|
|
*/
|
|
inline void setPbToggle(bool state) { pbToggle = state; }
|
|
|
|
/**
|
|
* Get current state value.
|
|
*
|
|
* @return current state value
|
|
*/
|
|
inline int_least32_t getState() const { return state; }
|
|
|
|
/**
|
|
* Get current timer value.
|
|
*
|
|
* @return current timer value
|
|
*/
|
|
inline uint_least16_t getTimer() const { return timer; }
|
|
|
|
/**
|
|
* Get PB6/PB7 Flipflop state.
|
|
*
|
|
* @param reg value of the control register
|
|
* @return PB6/PB7 flipflop state
|
|
*/
|
|
inline bool getPb(uint8_t reg) const { return (reg & 0x04) ? pbToggle : (state & CIAT_OUT); }
|
|
};
|
|
|
|
void Timer::reschedule()
|
|
{
|
|
/* There are only two subcases to consider.
|
|
*
|
|
* - are we counting, and if so, are we going to
|
|
* continue counting?
|
|
* - have we stopped, and are there no conditions to force a new beginning?
|
|
*
|
|
* Additionally, there are numerous flags that are present only in passing manner,
|
|
* but which we need to let cycle through the CIA state machine.
|
|
*/
|
|
const int_least32_t unwanted = CIAT_OUT | CIAT_CR_FLOAD | CIAT_LOAD1 | CIAT_LOAD;
|
|
if ((state & unwanted) != 0)
|
|
{
|
|
event_context.schedule(*this, 1);
|
|
return;
|
|
}
|
|
|
|
if ((state & CIAT_COUNT3) != 0)
|
|
{
|
|
/* Test the conditions that keep COUNT2 and thus COUNT3 alive, and also
|
|
* ensure that all of them are set indicating steady state operation. */
|
|
|
|
const int_least32_t wanted = CIAT_CR_START | CIAT_PHI2IN | CIAT_COUNT2 | CIAT_COUNT3;
|
|
if (timer > 2 && (state & wanted) == wanted)
|
|
{
|
|
/* we executed this cycle, therefore the pauseTime is +1. If we are called
|
|
* to execute on the very next clock, we need to get 0 because there's
|
|
* another timer-- in it. */
|
|
ciaEventPauseTime = event_context.getTime(EVENT_CLOCK_PHI1) + 1;
|
|
/* execute event slightly before the next underflow. */
|
|
event_context.schedule(m_cycleSkippingEvent, timer - 1);
|
|
return;
|
|
}
|
|
|
|
/* play safe, keep on ticking. */
|
|
event_context.schedule(*this, 1);
|
|
}
|
|
else
|
|
{
|
|
/* Test conditions that result in CIA activity in next clocks.
|
|
* If none, stop. */
|
|
const int_least32_t unwanted1 = CIAT_CR_START | CIAT_PHI2IN;
|
|
const int_least32_t unwanted2 = CIAT_CR_START | CIAT_STEP;
|
|
|
|
if ((state & unwanted1) == unwanted1
|
|
|| (state & unwanted2) == unwanted2)
|
|
{
|
|
event_context.schedule(*this, 1);
|
|
return;
|
|
}
|
|
|
|
ciaEventPauseTime = -1;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endif // TIMER_H
|