diff --git a/PDP10/pdp10_tim.c b/PDP10/pdp10_tim.c index 28970fc7..d5de0a21 100644 --- a/PDP10/pdp10_tim.c +++ b/PDP10/pdp10_tim.c @@ -41,42 +41,102 @@ #include "pdp10_defs.h" #include +/* 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 */ #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_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 */ -#define TIM_TPS_T10 60 -#define TIM_WAIT_T10 8000 -#define TIM_MULT_T10 1 -#define TIM_ITS_QUANT (TIM_HW_FREQ / TIM_TPS_T10) +#define TIM_TPS_T10 60 /* Initial frequency guess for TOPS-10 (close, not exact) */ +#define TIM_ITS_QUANT (TIM_HW_FREQ / TIM_TPS_T10) /* ITS PC sampling and user runtime interval */ /* Clock mode TOPS-20/KLAD */ -#define TIM_TPS_T20 1001 -#define TIM_WAIT_T20 500 -#define TIM_MULT_T20 16 +#define TIM_TPS_T20 1000 /* Initial estimate for TOPS-20 - 1msec seems fast? */ /* Probability function for TOPS-20 idlelock */ #define PROB(x) (((rand() * 100) / RAND_MAX) >= (x)) -d10 tim_base[2] = { 0, 0 }; /* 71b timebase */ -d10 tim_ttg = 0; /* time to go */ -d10 tim_period = 0; /* period */ +static d10 tim_base[2] = { 0, 0 }; /* 71b timebase */ +static d10 tim_interval = 0; /* value programmed into the clock */ +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 */ -int32 tim_mult = TIM_MULT_T10; /* tmxr poll mult */ -int32 tim_t20_prob = 33; /* TOPS-20 prob */ +static 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 tmr_poll = TIM_WAIT_T10; /* clock poll */ -int32 tmxr_poll = TIM_WAIT_T10 * TIM_MULT_T10; /* term mux poll */ +int32 clk_tps; /* Interval clock ticks/sec */ +int32 tmr_poll; /* SimH instructions/clock service */ +int32 tmxr_poll; /* SimH instructions/term mux poll */ extern int32 apr_flg, pi_act; extern UNIT cpu_unit; @@ -85,10 +145,11 @@ extern a10 pager_PC; extern int32 t20_idlelock; DEVICE tim_dev; -t_stat tcu_rd (int32 *data, int32 PA, int32 access); -t_stat tim_svc (UNIT *uptr); -t_stat tim_reset (DEVICE *dptr); -void tim_incr_base (d10 *base, d10 incr); +static t_stat tcu_rd (int32 *data, int32 PA, int32 access); +static t_stat tim_svc (UNIT *uptr); +static t_stat tim_reset (DEVICE *dptr); +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 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 }; -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) }, - { ORDATA (TTG, tim_ttg, 36) }, { ORDATA (PERIOD, tim_period, 36) }, { ORDATA (QUANT, quant, 36) }, { DRDATA (TIME, tim_unit.wait, 24), REG_NZ + PV_LEFT }, @@ -122,7 +182,7 @@ REG tim_reg[] = { { NULL } }; -MTAB tim_mod[] = { +static MTAB tim_mod[] = { { UNIT_Y2K, 0, "non Y2K OS", "NOY2K", NULL }, { UNIT_Y2K, UNIT_Y2K, "Y2K OS", "Y2K", NULL }, { MTAB_XTD|MTAB_VDV, 000, "ADDRESS", NULL, @@ -140,26 +200,53 @@ DEVICE tim_dev = { /* Timer instructions */ -/* Timer - if the timer is running at less than hardware frequency, - need to interpolate the value by calculating how much of the current - clock tick has elapsed, and what that equates to in msec. */ +/* Timebase - the timer is always running at less than hardware frequency, + * need to interpolate the value by calculating how much of the current + * clock tick has elapsed, and what that equates to in sysfreq units. + */ t_bool rdtim (a10 ea, int32 prv) { d10 tempbase[2]; +int32 used; +d10 incr; -ReadM (INCA (ea), prv); /* check 2nd word */ tempbase[0] = tim_base[0]; /* copy time base */ tempbase[1] = tim_base[1]; -if (tim_mult != TIM_MULT_T20) { /* interpolate? */ - int32 used; - d10 incr; - used = tmr_poll - (sim_activate_time (&tim_unit) - 1); - incr = (d10) (((double) used * TIM_HW_FREQ) / - ((double) tmr_poll * (double) clk_tps)); - tim_incr_base (tempbase, incr); - } -tempbase[0] = tempbase[0] & ~((d10) TIM_HWRE_MASK); /* clear low 12b */ + +/* used = time remaining in this service interval (instructions) + * poll = instructions in the whole service interval. + * incr = fraction of interval consumed * HW ticks per interval. + * Thus, incr is approximate number of HW ticks to add to the timebase + * value returned. This does NOT update the timebase. + */ +used = tmr_poll - (sim_activate_time (&tim_unit) - 1); +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 (INCA(ea), tempbase[1], prv); return FALSE; @@ -168,35 +255,67 @@ return FALSE; t_bool wrtim (a10 ea, int32 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; } t_bool rdint (a10 ea, int32 prv) { -Write (ea, tim_period, prv); +Write (ea, tim_interval, prv); 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) { -tim_period = Read (ea, prv); -tim_ttg = tim_period; +tim_interval = CLRS (Read (ea, prv)); +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; } -/* Timer service - the timer is only serviced when the 'ttg' register - has reached 0 based on the expected frequency of clock interrupts. */ +/* Timer service - the timer is only serviced when the interval + * 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? */ tmr_poll = uptr->wait; /* fixed clock */ else tmr_poll = sim_rtc_calb (clk_tps); /* else calibrate */ sim_activate (uptr, tmr_poll); /* reactivate unit */ tmxr_poll = tmr_poll * tim_mult; /* set mux poll */ -tim_incr_base (tim_base, tim_period); /* incr time base */ -tim_ttg = tim_period; /* reload */ +tim_incr_base (tim_base, tim_period); /* incr time base based on period of expired interval */ +tim_period = tim_new_period; /* If interval has changed, update period */ apr_flg = apr_flg | APRF_TIM; /* request interrupt */ if (Q_ITS) { /* ITS? */ if (pi_act == 0) @@ -211,7 +330,7 @@ else if (t20_idlelock && PROB (100 - tim_t20_prob)) 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[0] = base[0] + (base[1] >> 35); /* carry to high */ @@ -222,12 +341,28 @@ return; /* Timer reset */ -t_stat tim_reset (DEVICE *dptr) +static t_stat tim_reset (DEVICE *dptr) { 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 */ + tmr_poll = sim_rtc_init (tim_unit.wait); /* init timer */ sim_activate (&tim_unit, tmr_poll); /* activate unit */ 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)) { clk_tps = TIM_TPS_T20; - uptr->wait = TIM_WAIT_T20; - tmr_poll = TIM_WAIT_T20; - tim_mult = TIM_MULT_T20; + update_interval(((d10)(1000*4096))/clk_tps); + tmr_poll = tim_unit.wait; uptr->flags = uptr->flags | UNIT_Y2K; } else { clk_tps = TIM_TPS_T10; - uptr->wait = TIM_WAIT_T10; - tmr_poll = TIM_WAIT_T10; - tim_mult = TIM_MULT_T10; + update_interval (((d10)(1000*4096))/clk_tps); + tmr_poll = tim_unit.wait; if (Q_ITS) uptr->flags = uptr->flags | UNIT_Y2K; else uptr->flags = uptr->flags & ~UNIT_Y2K; } -tmxr_poll = tmr_poll * tim_mult; 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; struct tm *tptr;