PDP-10 Update: TIM

Fix subtle errors in timer/timebase implementation.
Document mechanisms.
Reduce magic constants by using what the OS sets via wrint.
This commit is contained in:
Timothe Litt 2013-07-05 00:03:39 -04:00
parent 6243f9314f
commit 6e78905ecc

View file

@ -41,42 +41,102 @@
#include "pdp10_defs.h" #include "pdp10_defs.h"
#include <time.h> #include <time.h>
/* The KS timer works off a 4.100 MHz (243.9024 nsec) oscillator that
* is independent of all other system timing.
*
* Two pieces of timekeeping hardware are exposed to the OS.
* o The interval timer, which can interrupt at a programmed interval.
* o The timebase, which records time (71 bits).
*
* The clock is architecturally readable in units of 243.9024 nsec via
* the timebase. The implementation is somewhat different.
*
* The instructions that update the clocks specify time in these units.
*
* However, both timekeepers are incremented by the microcode when
* a 12 bit counter overflows; e.g. at a period of 999.0244 usec.
* Thus, the granularity of timer interrupts is approximately 1 msec.
*
* The OS programs the interval timer to interrupt as though the
* the 12 least significant bits mattered. Thus, for a (roughly)
* 1 msec interval, it would program 1 * 4096 into the interval timer.
* The sign bit is not used, so 35-12 = 23 bits for the maximum interval,
* which is 139.674 minutes. If any of the least significant bits
* are non-zero, the interval is extended by 1 * 4096 counts.
*
* The timer merely sets the INTERVAL DONE flag in the APR flags.
* Whether that actually causes an interrupt is controlled by the
* APR interrupt enable for the flag and by the PI system.
*
* The flag is readable as an APR condition by RDAPR, and CONSO/Z APR,.
* The flag is cleared by WRAPR 1b22!1b30 (clear, count done).
*
* The timebase is maintained with the 12 LSB zero in a workspace
* register. When read by the OS, the actual value of the 10 MSB of
* the hardware counter is inserted into those bits, providing increased
* resolution. Although the system reference manual says otherwise, the
* two LSB of the counter are read as zero by the microcode (DPM2), so
* bits <70:71> of the timebase are also read as zero by software.
*
* When the OS sets the timebase, the 12 LSB that it supplies are ignored.
*
* The timebase is typically used for accurate time of day and CPU runtime
* accounting. The simulator adjusts the equivalent of the 12-bit counter,
* so CPU time will reflect simulator wall clock, not simulated machine cycles.
* Since time of day must be accurate, this may result in the OS reporting
* CPU times that are unrealistically faster - or slower - than on the
* real hardware.
*
* This module also implements the TCU, a battery backed-up TOY clock
* that was supported by TOPS-10, but not sold by DEC.
*/
/* Invariants */ /* Invariants */
#define TIM_HW_FREQ 4100000 /* 4.1Mhz */ #define TIM_HW_FREQ 4100000 /* 4.1Mhz */
#define TIM_HWRE_MASK 07777 #define TIM_HWRE_MASK 07777 /* Timer field of timebase */
#define TIM_BASE_RAZ 03 /* Timer bits read as zero by ucode */
#define UNIT_V_Y2K (UNIT_V_UF + 0) /* Y2K compliant OS */ #define UNIT_V_Y2K (UNIT_V_UF + 0) /* Y2K compliant OS */
#define UNIT_Y2K (1u << UNIT_V_Y2K) #define UNIT_Y2K (1u << UNIT_V_Y2K)
#define TIM_TMXR_FREQ 60 /* Target frequency (HZ) for tmxr polls */
/* Estimate of simulator instructions/sec for initialization and fixed timing.
* This came from prior magic constant of 8000 at 60 tics/sec.
* The machine was marketed as ~ 300KIPs, which would imply 3 usec/instr.
* So 8,000 instructions should take ~24 msec. This would indicate that
* the simulator from which this came was ~1.4 x the speed of the real
* hardware. Current milage will vary.
*/
#define TIM_WAIT_IPS 480000
/* Clock mode TOPS-10/ITS */ /* Clock mode TOPS-10/ITS */
#define TIM_TPS_T10 60 #define TIM_TPS_T10 60 /* Initial frequency guess for TOPS-10 (close, not exact) */
#define TIM_WAIT_T10 8000 #define TIM_ITS_QUANT (TIM_HW_FREQ / TIM_TPS_T10) /* ITS PC sampling and user runtime interval */
#define TIM_MULT_T10 1
#define TIM_ITS_QUANT (TIM_HW_FREQ / TIM_TPS_T10)
/* Clock mode TOPS-20/KLAD */ /* Clock mode TOPS-20/KLAD */
#define TIM_TPS_T20 1001 #define TIM_TPS_T20 1000 /* Initial estimate for TOPS-20 - 1msec seems fast? */
#define TIM_WAIT_T20 500
#define TIM_MULT_T20 16
/* Probability function for TOPS-20 idlelock */ /* Probability function for TOPS-20 idlelock */
#define PROB(x) (((rand() * 100) / RAND_MAX) >= (x)) #define PROB(x) (((rand() * 100) / RAND_MAX) >= (x))
d10 tim_base[2] = { 0, 0 }; /* 71b timebase */ static d10 tim_base[2] = { 0, 0 }; /* 71b timebase */
d10 tim_ttg = 0; /* time to go */ static d10 tim_interval = 0; /* value programmed into the clock */
d10 tim_period = 0; /* period */ static d10 tim_period = 0; /* period in HW ticks adjusted for non-zero LSBs */
static d10 tim_new_period = 0; /* period for the next interval */
static int32 tim_mult; /* Multiple of interval timer period at which tmxr is polled */
d10 quant = 0; /* ITS quantum */ d10 quant = 0; /* ITS quantum */
int32 tim_mult = TIM_MULT_T10; /* tmxr poll mult */ static int32 tim_t20_prob = 33; /* TOPS-20 prob */
int32 tim_t20_prob = 33; /* TOPS-20 prob */
/* Exported variables */ /* Exported variables - initialized by set CPU model and reset */
int32 clk_tps = TIM_TPS_T10; /* clock ticks/sec */ int32 clk_tps; /* Interval clock ticks/sec */
int32 tmr_poll = TIM_WAIT_T10; /* clock poll */ int32 tmr_poll; /* SimH instructions/clock service */
int32 tmxr_poll = TIM_WAIT_T10 * TIM_MULT_T10; /* term mux poll */ int32 tmxr_poll; /* SimH instructions/term mux poll */
extern int32 apr_flg, pi_act; extern int32 apr_flg, pi_act;
extern UNIT cpu_unit; extern UNIT cpu_unit;
@ -85,10 +145,11 @@ extern a10 pager_PC;
extern int32 t20_idlelock; extern int32 t20_idlelock;
DEVICE tim_dev; DEVICE tim_dev;
t_stat tcu_rd (int32 *data, int32 PA, int32 access); static t_stat tcu_rd (int32 *data, int32 PA, int32 access);
t_stat tim_svc (UNIT *uptr); static t_stat tim_svc (UNIT *uptr);
t_stat tim_reset (DEVICE *dptr); static t_stat tim_reset (DEVICE *dptr);
void tim_incr_base (d10 *base, d10 incr); static t_bool update_interval (d10 new_interval);
static void tim_incr_base (d10 *base, d10 incr);
extern d10 Read (a10 ea, int32 prv); extern d10 Read (a10 ea, int32 prv);
extern d10 ReadM (a10 ea, int32 prv); extern d10 ReadM (a10 ea, int32 prv);
@ -106,11 +167,10 @@ extern t_stat wr_nop (int32 data, int32 PA, int32 access);
DIB tcu_dib = { IOBA_TCU, IOLN_TCU, &tcu_rd, &wr_nop, 0 }; DIB tcu_dib = { IOBA_TCU, IOLN_TCU, &tcu_rd, &wr_nop, 0 };
UNIT tim_unit = { UDATA (&tim_svc, UNIT_IDLE, 0), TIM_WAIT_T10 }; static UNIT tim_unit = { UDATA (&tim_svc, UNIT_IDLE, 0), 0 };
REG tim_reg[] = { static REG tim_reg[] = {
{ BRDATA (TIMEBASE, tim_base, 8, 36, 2) }, { BRDATA (TIMEBASE, tim_base, 8, 36, 2) },
{ ORDATA (TTG, tim_ttg, 36) },
{ ORDATA (PERIOD, tim_period, 36) }, { ORDATA (PERIOD, tim_period, 36) },
{ ORDATA (QUANT, quant, 36) }, { ORDATA (QUANT, quant, 36) },
{ DRDATA (TIME, tim_unit.wait, 24), REG_NZ + PV_LEFT }, { DRDATA (TIME, tim_unit.wait, 24), REG_NZ + PV_LEFT },
@ -122,7 +182,7 @@ REG tim_reg[] = {
{ NULL } { NULL }
}; };
MTAB tim_mod[] = { static MTAB tim_mod[] = {
{ UNIT_Y2K, 0, "non Y2K OS", "NOY2K", NULL }, { UNIT_Y2K, 0, "non Y2K OS", "NOY2K", NULL },
{ UNIT_Y2K, UNIT_Y2K, "Y2K OS", "Y2K", NULL }, { UNIT_Y2K, UNIT_Y2K, "Y2K OS", "Y2K", NULL },
{ MTAB_XTD|MTAB_VDV, 000, "ADDRESS", NULL, { MTAB_XTD|MTAB_VDV, 000, "ADDRESS", NULL,
@ -140,26 +200,53 @@ DEVICE tim_dev = {
/* Timer instructions */ /* Timer instructions */
/* Timer - if the timer is running at less than hardware frequency, /* Timebase - the timer is always running at less than hardware frequency,
need to interpolate the value by calculating how much of the current * need to interpolate the value by calculating how much of the current
clock tick has elapsed, and what that equates to in msec. */ * clock tick has elapsed, and what that equates to in sysfreq units.
*/
t_bool rdtim (a10 ea, int32 prv) t_bool rdtim (a10 ea, int32 prv)
{ {
d10 tempbase[2]; d10 tempbase[2];
int32 used;
d10 incr;
ReadM (INCA (ea), prv); /* check 2nd word */
tempbase[0] = tim_base[0]; /* copy time base */ tempbase[0] = tim_base[0]; /* copy time base */
tempbase[1] = tim_base[1]; tempbase[1] = tim_base[1];
if (tim_mult != TIM_MULT_T20) { /* interpolate? */
int32 used; /* used = time remaining in this service interval (instructions)
d10 incr; * poll = instructions in the whole service interval.
used = tmr_poll - (sim_activate_time (&tim_unit) - 1); * incr = fraction of interval consumed * HW ticks per interval.
incr = (d10) (((double) used * TIM_HW_FREQ) / * Thus, incr is approximate number of HW ticks to add to the timebase
((double) tmr_poll * (double) clk_tps)); * value returned. This does NOT update the timebase.
tim_incr_base (tempbase, incr); */
} used = tmr_poll - (sim_activate_time (&tim_unit) - 1);
tempbase[0] = tempbase[0] & ~((d10) TIM_HWRE_MASK); /* clear low 12b */ incr = (d10) (((double) used * TIM_HW_FREQ) /
((double) tmr_poll * (double) tim_period));
tim_incr_base (tempbase, incr);
/* Although the two LSB of the counter contribute carry to the
* value, they are read as zero by microcode, and thus cleared here.
*
* The reason that these bits are forced to zero in the hardware is
* that the counter is in a different clock domain from the microcode.
* To make the domain crossing, the microcode reads the counter
* until two consecutive values match.
*
* Since the microcode cycle time is 300 nsec, the LSBs of the
* counter run too fast (244 nsec) for the strategy to work.
* Ignoring the two LSB ensures that the value can't change any
* faster than ~976 nsec, which guarantees a stable value can be
* obtained in at most three attempts.
*/
tempbase[1] &= ~((d10) TIM_BASE_RAZ);
/* If the destination is arranged so that the first word is OK, but
* the second pagefaults, the value will be half-written. As we
* expect the PFH to restart the instruction, the both halves will
* be written the second time. We could read and write back both
* halves to avoid this, but the hardware doesn't seem to either.
*/
Write (ea, tempbase[0], prv); Write (ea, tempbase[0], prv);
Write (INCA(ea), tempbase[1], prv); Write (INCA(ea), tempbase[1], prv);
return FALSE; return FALSE;
@ -168,35 +255,67 @@ return FALSE;
t_bool wrtim (a10 ea, int32 prv) t_bool wrtim (a10 ea, int32 prv)
{ {
tim_base[0] = Read (ea, prv); tim_base[0] = Read (ea, prv);
tim_base[1] = CLRS (Read (INCA (ea), prv)); tim_base[1] = CLRS (Read (INCA (ea), prv) & ~((d10) TIM_HWRE_MASK));
return FALSE; return FALSE;
} }
t_bool rdint (a10 ea, int32 prv) t_bool rdint (a10 ea, int32 prv)
{ {
Write (ea, tim_period, prv); Write (ea, tim_interval, prv);
return FALSE; return FALSE;
} }
/* write a new interval timer period (in timer ticks).
* This does not clear the harware counter, so the first
* completion can come up to ~1 msc later than the new
* period.
*/
t_bool wrint (a10 ea, int32 prv) t_bool wrint (a10 ea, int32 prv)
{ {
tim_period = Read (ea, prv); tim_interval = CLRS (Read (ea, prv));
tim_ttg = tim_period; return update_interval (tim_interval);
}
static t_bool update_interval (d10 new_interval)
{
tim_new_period = CLRS (new_interval + ((new_interval & TIM_HWRE_MASK)? 010000 : 0));
if (!(tim_new_period & ~TIM_HWRE_MASK))
tim_new_period = 010000;
clk_tps = (int32) (0.5 + ( ((double)TIM_HW_FREQ) / (double)tim_new_period ));
/* tmxr is polled every tim_mult clks. Compute the divisor matching the target. */
tim_mult = (clk_tps <= TIM_TMXR_FREQ)? 1 : ((clk_tps + (TIM_TMXR_FREQ-1)) / TIM_TMXR_FREQ);
/* Estimate instructions/tick for fixed timing */
tim_unit.wait = TIM_WAIT_IPS / clk_tps;
tmxr_poll = tim_unit.wait * tim_mult;
/* The next tim_svc will update the activation time.
*
*/
return FALSE; return FALSE;
} }
/* Timer service - the timer is only serviced when the 'ttg' register /* Timer service - the timer is only serviced when the interval
has reached 0 based on the expected frequency of clock interrupts. */ * programmed in tim_period by wrint expires. If the interval
* changes, the timebase update is based on the previous interval.
* The interval calibration is based on what the new interval will be.
*/
t_stat tim_svc (UNIT *uptr) static t_stat tim_svc (UNIT *uptr)
{ {
if (cpu_unit.flags & UNIT_KLAD) /* diags? */ if (cpu_unit.flags & UNIT_KLAD) /* diags? */
tmr_poll = uptr->wait; /* fixed clock */ tmr_poll = uptr->wait; /* fixed clock */
else tmr_poll = sim_rtc_calb (clk_tps); /* else calibrate */ else tmr_poll = sim_rtc_calb (clk_tps); /* else calibrate */
sim_activate (uptr, tmr_poll); /* reactivate unit */ sim_activate (uptr, tmr_poll); /* reactivate unit */
tmxr_poll = tmr_poll * tim_mult; /* set mux poll */ tmxr_poll = tmr_poll * tim_mult; /* set mux poll */
tim_incr_base (tim_base, tim_period); /* incr time base */ tim_incr_base (tim_base, tim_period); /* incr time base based on period of expired interval */
tim_ttg = tim_period; /* reload */ tim_period = tim_new_period; /* If interval has changed, update period */
apr_flg = apr_flg | APRF_TIM; /* request interrupt */ apr_flg = apr_flg | APRF_TIM; /* request interrupt */
if (Q_ITS) { /* ITS? */ if (Q_ITS) { /* ITS? */
if (pi_act == 0) if (pi_act == 0)
@ -211,7 +330,7 @@ else if (t20_idlelock && PROB (100 - tim_t20_prob))
return SCPE_OK; return SCPE_OK;
} }
void tim_incr_base (d10 *base, d10 incr) static void tim_incr_base (d10 *base, d10 incr)
{ {
base[1] = base[1] + incr; /* add on incr */ base[1] = base[1] + incr; /* add on incr */
base[0] = base[0] + (base[1] >> 35); /* carry to high */ base[0] = base[0] + (base[1] >> 35); /* carry to high */
@ -222,12 +341,28 @@ return;
/* Timer reset */ /* Timer reset */
t_stat tim_reset (DEVICE *dptr) static t_stat tim_reset (DEVICE *dptr)
{ {
sim_register_clock_unit (&tim_unit); /* declare clock unit */ sim_register_clock_unit (&tim_unit); /* declare clock unit */
tim_period = 0; /* clear timer */
tim_ttg = 0; tim_base[0] = tim_base[1] = 0; /* clear timebase (HW does) */
/* HW does not initialize the interval timer, so the rate at which the timer flag
* sets is random. No sensible user would enable interrupts or check the flag without
* setting an interval. The timebase is intialized to zero by microcode intialization.
* It increments based on the overflow, so it would be reasonable for a user to just
* read it twice and subtract the values to determine elapsed time.
*
* Simply to keep the simulator overhead down until the interval timer is initialized
* by the OS or diagnostic, we will set the internal interval to ~17 msec here.
* This allows the service routine to increment the timebase, and gives RDTIME an
* baseline for its interpolation.
*/
tim_interval = 0;
clk_tps = 60;
update_interval(17*4096);
apr_flg = apr_flg & ~APRF_TIM; /* clear interrupt */ apr_flg = apr_flg & ~APRF_TIM; /* clear interrupt */
tmr_poll = sim_rtc_init (tim_unit.wait); /* init timer */ tmr_poll = sim_rtc_init (tim_unit.wait); /* init timer */
sim_activate (&tim_unit, tmr_poll); /* activate unit */ sim_activate (&tim_unit, tmr_poll); /* activate unit */
tmxr_poll = tmr_poll * tim_mult; /* set mux poll */ tmxr_poll = tmr_poll * tim_mult; /* set mux poll */
@ -240,27 +375,32 @@ t_stat tim_set_mod (UNIT *uptr, int32 val, char *cptr, void *desc)
{ {
if (val & (UNIT_T20|UNIT_KLAD)) { if (val & (UNIT_T20|UNIT_KLAD)) {
clk_tps = TIM_TPS_T20; clk_tps = TIM_TPS_T20;
uptr->wait = TIM_WAIT_T20; update_interval(((d10)(1000*4096))/clk_tps);
tmr_poll = TIM_WAIT_T20; tmr_poll = tim_unit.wait;
tim_mult = TIM_MULT_T20;
uptr->flags = uptr->flags | UNIT_Y2K; uptr->flags = uptr->flags | UNIT_Y2K;
} }
else { else {
clk_tps = TIM_TPS_T10; clk_tps = TIM_TPS_T10;
uptr->wait = TIM_WAIT_T10; update_interval (((d10)(1000*4096))/clk_tps);
tmr_poll = TIM_WAIT_T10; tmr_poll = tim_unit.wait;
tim_mult = TIM_MULT_T10;
if (Q_ITS) if (Q_ITS)
uptr->flags = uptr->flags | UNIT_Y2K; uptr->flags = uptr->flags | UNIT_Y2K;
else uptr->flags = uptr->flags & ~UNIT_Y2K; else uptr->flags = uptr->flags & ~UNIT_Y2K;
} }
tmxr_poll = tmr_poll * tim_mult;
return SCPE_OK; return SCPE_OK;
} }
/* Time of year clock */ /* Time of year clock
*
* The hardware clock was never sold by DEC, but support for it exists
* in TOPS-10. Code was also available for RSX20F to read and report the
* to the OS via its20F's SETSPD task. This implements only the read functions.
*
* The manufacturer's manual can be found at
* http://bitsavers.trailing-edge.com/pdf/digitalPathways/tcu-150.pdf
*/
t_stat tcu_rd (int32 *data, int32 PA, int32 access) static t_stat tcu_rd (int32 *data, int32 PA, int32 access)
{ {
time_t curtim; time_t curtim;
struct tm *tptr; struct tm *tptr;