Cog/Frameworks/libsidplay/sidplay-residfp-code/.svn/pristine/4c/4cad0e68a8c6cf4cca5bf6be924780812b324831.svn-base
2014-12-07 22:26:31 -08:00

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