VAX780: Fix interval timer to correctly time variable microsecond granularity intervals.

The original implementation coupled the elapsed time measurement
to the 100Hz internal calibration clock.  This worked well enough for very
long intervals but not well at all for any intervals less than 50ms.  The net
result is that it couldn't usefully be used to produce the 60Hz clock ticks
which Unix 32V used it for.  It now leverages the microsecond timing
provided by sim_activate_after().  This problem is reported in #253
This commit is contained in:
Mark Pizzolato 2015-12-10 10:09:23 -08:00
parent de0d251c75
commit 54bec5c184
2 changed files with 42 additions and 83 deletions

View file

@ -151,7 +151,7 @@ extern t_stat vax780_fload (int32 flag, char *cptr);
extern int32 intexc (int32 vec, int32 cc, int32 ipl, int ei);
extern int32 iccs_rd (void);
extern int32 nicr_rd (void);
extern int32 icr_rd (t_bool interp);
extern int32 icr_rd (void);
extern int32 todr_rd (void);
extern int32 rxcs_rd (void);
extern int32 rxdb_rd (void);
@ -331,7 +331,7 @@ switch (rg) {
break;
case MT_ICR: /* ICR */
val = icr_rd (FALSE);
val = icr_rd ();
break;
case MT_TODR: /* TODR */

View file

@ -193,9 +193,8 @@ int32 tmr_iccs = 0; /* interval timer csr */
uint32 tmr_icr = 0; /* curr interval */
uint32 tmr_nicr = 0; /* next interval */
uint32 tmr_inc = 0; /* timer increment */
int32 tmr_sav = 0; /* timer save */
uint32 tmr_sav = 0; /* timer save */
int32 tmr_int = 0; /* interrupt */
int32 tmr_use_100hz = 1; /* use 100Hz for timer */
int32 clk_tps = 100; /* ticks/second */
int32 tmxr_poll = CLK_DELAY * TMXR_MULT; /* term mux poll */
int32 tmr_poll = CLK_DELAY; /* pgm timer poll */
@ -242,9 +241,8 @@ t_stat clk_detach (UNIT *uptr);
t_stat tmr_reset (DEVICE *dptr);
t_stat fl_svc (UNIT *uptr);
t_stat fl_reset (DEVICE *dptr);
int32 icr_rd (t_bool interp);
void tmr_incr (uint32 inc);
void tmr_sched (void);
int32 icr_rd ();
void tmr_sched (uint32 incr);
t_stat todr_resync (void);
t_stat fl_wr_txdb (int32 data);
t_bool fl_test_xfr (UNIT *uptr, t_bool wr);
@ -357,7 +355,6 @@ REG tmr_reg[] = {
{ FLDATAD (INT, tmr_int, 0, "interrupt request") },
{ HRDATA (INCR, tmr_inc, 32), REG_HIDDEN },
{ HRDATA (SAVE, tmr_sav, 32), REG_HIDDEN },
{ FLDATA (USE100HZ, tmr_use_100hz, 0), REG_HIDDEN },
{ NULL }
};
@ -580,22 +577,12 @@ return "console terminal output";
The architected VAX timer, which increments at 1Mhz, cannot be
accurately simulated due to the overhead that would be required
for 1M clock events per second. Instead, a hidden calibrated
100Hz timer is run (because that's what VMS expects), and a
hack is used for the interval timer.
When the timer is started, the timer interval is inspected.
if the interval is >= 10msec, then the 100Hz timer drives the
next interval
if the interval is < 10mec, then count instructions
100Hz timer is run (because that's what VMS expects), and 1Mhz
intervals are derived from the calibrated instruction execution
rate.
If the interval register is read, then its value between events
is interpolated using the current instruction count versus the
count when the most recent event started, the result is scaled
to the calibrated system clock, unless the interval being timed
is less than a calibrated system clock tick (or the calibrated
clock is running very slowly) at which time the result will be
the elapsed instruction count.
is interpolated relative to the elapsed instruction count.
*/
int32 iccs_rd (void)
@ -607,24 +594,31 @@ void iccs_wr (int32 val)
{
if ((val & TMR_CSR_RUN) == 0) { /* clearing run? */
sim_cancel (&tmr_unit); /* cancel timer */
tmr_use_100hz = 0;
if (tmr_iccs & TMR_CSR_RUN) /* run 1 -> 0? */
tmr_icr = icr_rd (TRUE); /* update itr */
tmr_icr = icr_rd (); /* update itr */
}
tmr_iccs = tmr_iccs & ~(val & TMR_CSR_W1C); /* W1C csr */
tmr_iccs = (tmr_iccs & ~TMR_CSR_WR) | /* new r/w */
(val & TMR_CSR_WR);
if (val & TMR_CSR_XFR) tmr_icr = tmr_nicr; /* xfr set? */
if (val & TMR_CSR_XFR) /* xfr set? */
tmr_icr = tmr_nicr;
if (val & TMR_CSR_RUN) { /* run? */
if (val & TMR_CSR_XFR) /* new tir? */
sim_cancel (&tmr_unit); /* stop prev */
if (!sim_is_active (&tmr_unit)) /* not running? */
tmr_sched (); /* activate */
tmr_sched (tmr_nicr); /* activate */
}
else if (val & TMR_CSR_SGL) { /* single step? */
tmr_incr (1); /* incr tmr */
if (tmr_icr == 0) /* if ovflo, */
tmr_icr = tmr_nicr; /* reload tir */
else {
if (val & TMR_CSR_XFR) /* xfr set? */
tmr_icr = tmr_nicr;
if (val & TMR_CSR_SGL) { /* single step? */
tmr_icr = tmr_inc + 1; /* incr tmr */
if (tmr_icr == 0) { /* if ovflo, */
if (tmr_iccs & TMR_CSR_IE) /* ie? */
tmr_int = 1; /* set int req */
tmr_icr = tmr_nicr; /* reload tir */
}
}
}
if ((tmr_iccs & (TMR_CSR_DON | TMR_CSR_IE)) != /* update int */
(TMR_CSR_DON | TMR_CSR_IE))
@ -632,19 +626,13 @@ if ((tmr_iccs & (TMR_CSR_DON | TMR_CSR_IE)) != /* update int */
return;
}
int32 icr_rd (t_bool interp)
int32 icr_rd ()
{
uint32 delta;
uint32 delta = sim_grtime() - tmr_sav;
if (interp || (tmr_iccs & TMR_CSR_RUN)) { /* interp, running? */
delta = sim_grtime () - tmr_sav; /* delta inst */
if (tmr_use_100hz && (tmr_poll > TMR_INC)) /* scale large int */
delta = (uint32) ((((double) delta) * TMR_INC) / tmr_poll);
if (delta >= tmr_inc)
delta = tmr_inc - 1;
return tmr_icr + delta;
}
return tmr_icr;
if (tmr_iccs & TMR_CSR_RUN) /* running? */
return (int32)(tmr_nicr + ((1000000.0 * delta) / sim_timer_inst_per_sec ()));
return (int32)tmr_icr;
}
int32 nicr_rd (void)
@ -665,8 +653,6 @@ tmr_poll = sim_rtcn_calb (clk_tps, TMR_CLK); /* calibrate clock */
sim_activate_after (uptr, 1000000/clk_tps); /* reactivate unit */
tmxr_poll = tmr_poll * TMXR_MULT; /* set mux poll */
AIO_SET_INTERRUPT_LATENCY(tmr_poll*clk_tps); /* set interrrupt latency */
if ((tmr_iccs & TMR_CSR_RUN) && tmr_use_100hz) /* timer on, std intvl? */
tmr_incr (TMR_INC); /* do timer service */
return SCPE_OK;
}
@ -674,50 +660,25 @@ return SCPE_OK;
t_stat tmr_svc (UNIT *uptr)
{
tmr_incr (tmr_inc); /* incr timer */
if (tmr_iccs & TMR_CSR_DON) /* done? set err */
tmr_iccs = tmr_iccs | TMR_CSR_ERR;
else
tmr_iccs = tmr_iccs | TMR_CSR_DON; /* set done */
if (tmr_iccs & TMR_CSR_RUN) /* run? */
tmr_sched (tmr_nicr); /* reactivate */
if (tmr_iccs & TMR_CSR_IE) /* ie? set int req */
tmr_int = 1;
else
tmr_int = 0;
return SCPE_OK;
}
/* Timer increment */
void tmr_incr (uint32 inc)
{
uint32 new_icr = (tmr_icr + inc) & LMASK; /* add incr */
if (new_icr < tmr_icr) { /* ovflo? */
tmr_icr = 0; /* now 0 */
if (tmr_iccs & TMR_CSR_DON) /* done? set err */
tmr_iccs = tmr_iccs | TMR_CSR_ERR;
else tmr_iccs = tmr_iccs | TMR_CSR_DON; /* set done */
if (tmr_iccs & TMR_CSR_RUN) { /* run? */
tmr_icr = tmr_nicr; /* reload */
tmr_sched (); /* reactivate */
}
if (tmr_iccs & TMR_CSR_IE) /* ie? set int req */
tmr_int = 1;
else tmr_int = 0;
}
else {
tmr_icr = new_icr; /* no, update icr */
if (tmr_iccs & TMR_CSR_RUN) /* still running? */
tmr_sched (); /* reactivate */
}
return;
}
/* Timer scheduling */
void tmr_sched (void)
void tmr_sched (uint32 nicr)
{
tmr_sav = sim_grtime (); /* save intvl base */
tmr_inc = (~tmr_icr + 1); /* inc = interval */
if (tmr_inc == 0) tmr_inc = 1;
if (tmr_inc < TMR_INC) { /* 100Hz multiple? */
sim_activate (&tmr_unit, tmr_inc); /* schedule timer */
tmr_use_100hz = 0;
}
else tmr_use_100hz = 1; /* let clk handle */
return;
sim_activate_after (&tmr_unit, (nicr) ? (~nicr + 1) : 0xFFFFFFFF);
tmr_sav = sim_grtime();
}
/* 100Hz clock reset */
@ -807,10 +768,8 @@ return r;
t_stat tmr_reset (DEVICE *dptr)
{
tmr_iccs = 0;
tmr_icr = 0;
tmr_nicr = 0;
tmr_int = 0;
tmr_use_100hz = 1;
sim_cancel (&tmr_unit); /* cancel timer */
todr_resync (); /* resync TODR */
return SCPE_OK;