1
0
mirror of https://github.com/simh/simh.git synced 2026-04-29 05:05:14 +00:00

3b2: Gracefully handle clock reset

Clock reset was not being handled gracefully at all, leading some
diagnostic tests not to pass, or not to pass consistently.

This change will "kick" the system clock whenever the timer divider is
reset, so the new divider is picked up immediately.
This commit is contained in:
Seth Morabito
2017-11-24 12:00:01 -08:00
committed by Mark Pizzolato
parent b23ebbe312
commit 690c30b6f4
8 changed files with 82 additions and 59 deletions

View File

@@ -51,6 +51,8 @@ DEBTAB sys_deb_tab[] = {
{ NULL, 0 }
};
struct timer_ctr TIMERS[3];
uint32 *NVRAM = NULL;
extern DEVICE cpu_dev;
@@ -151,8 +153,7 @@ void csr_write(uint32 pa, uint32 val, size_t size)
csr_data &= ~CSRPARE;
break;
case 0x0b: /* Set System Reset Request */
iu_reset();
cpu_reset(&cpu_dev);
full_reset();
cpu_boot(0, &cpu_dev);
break;
case 0x0f: /* Clear Memory Alignment Fault */
@@ -171,9 +172,32 @@ void csr_write(uint32 pa, uint32 val, size_t size)
csr_data &= ~CSRFLOP;
break;
case 0x23: /* Set Inhibit Timers */
sim_debug(WRITE_MSG, &csr_dev,
"[%08x] SET INHIBIT TIMERS\n", R[NUM_PC]);
csr_data |= CSRITIM;
break;
case 0x27: /* Clear Inhibit Timers */
sim_debug(WRITE_MSG, &csr_dev,
"[%08x] CLEAR INHIBIT TIMERS\n", R[NUM_PC]);
/* A side effect of clearing the timer inhibit bit is to cause
* a simulated "tick" of any active timers. This is a hack to
* make diagnostics pass. This is not 100% accurate, but it
* makes SVR3 and DGMON tests happy.
*/
if (TIMERS[0].gate && TIMERS[0].enabled) {
TIMERS[0].val = TIMERS[0].divider - 1;
}
if (TIMERS[1].gate && TIMERS[1].enabled) {
TIMERS[1].val = TIMERS[1].divider - 1;
}
if (TIMERS[2].gate && TIMERS[2].enabled) {
TIMERS[2].val = TIMERS[2].divider - 1;
}
csr_data &= ~CSRITIM;
break;
case 0x2b: /* Set Inhibit Faults */
@@ -368,8 +392,6 @@ void nvram_write(uint32 pa, uint32 val, size_t size)
*
*/
struct timer_ctr TIMERS[3];
/*
* The three timers, (A, B, C) run at different
* programmatially controlled frequencies, so each must be
@@ -383,6 +405,8 @@ UNIT timer_unit[] = {
{ NULL }
};
UNIT *timer_clk_unit = &timer_unit[1];
REG timer_reg[] = {
{ HRDATAD(DIVA, TIMERS[0].divider, 16, "Divider A") },
{ HRDATAD(STA, TIMERS[0].mode, 16, "Mode A") },
@@ -401,27 +425,11 @@ DEVICE timer_dev = {
DEV_DEBUG, 0, sys_deb_tab
};
#define TIMER_STP_US 10 /* 10 us delay per timer step */
#define TIMER_STP_US 10 /* 10 us delay per timer step */
#define tmrnum u3
#define tmr up7
#define DECR_STEPS 400
/*
* This is a hack to make diagnostics pass. If read immediately after
* being set, a counter should always return the initial value. If a
* certain number of steps have passed, it should have decremented a
* little bit, so we return a value one less than the initial value.
* This is not 100% accurate, but it makes SVR3 and DGMON tests happy.
*/
static SIM_INLINE uint16 timer_current_val(struct timer_ctr *ctr)
{
if ((sim_gtime() - ctr->stime) > DECR_STEPS) {
return ctr->divider - 1;
} else {
return ctr->divider;
}
}
t_stat timer_reset(DEVICE *dptr) {
int32 i, t;
@@ -437,8 +445,8 @@ t_stat timer_reset(DEVICE *dptr) {
TIMERS[1].gate = 1;
if (!sim_is_running) {
t = sim_rtcn_init_unit(&timer_unit[1], TPS_CLK, TMR_CLK);
sim_activate_after(&timer_unit[1], 1000000 / t);
t = sim_rtcn_init_unit(timer_clk_unit, TPS_CLK, TMR_CLK);
sim_activate_after(timer_clk_unit, 1000000 / t);
}
return SCPE_OK;
@@ -475,9 +483,11 @@ t_stat timer1_svc(UNIT *uptr)
}
ticks = ctr->divider / TIMER_STP_US;
if (ticks == 0) {
if (ticks < CLK_MIN_TICKS) {
ticks = TPS_CLK;
}
t = sim_rtcn_calb(ticks, TMR_CLK);
sim_activate_after(uptr, (uint32) (1000000 / ticks));
@@ -517,10 +527,12 @@ uint32 timer_read(uint32 pa, size_t size)
case TIMER_REG_DIVA:
case TIMER_REG_DIVB:
case TIMER_REG_DIVC:
if (ctr->enabled && ctr->gate) {
ctr_val = timer_current_val(ctr);
} else {
ctr_val = ctr->divider;
ctr_val = ctr->val;
if (ctr_val != ctr->divider) {
sim_debug(READ_MSG, &timer_dev,
"[%08x] >>> ctr_val = %04x, ctr->divider = %04x\n",
R[NUM_PC], ctr_val, ctr->divider);
}
switch (ctr->mode & CLK_RW) {
@@ -565,24 +577,39 @@ void handle_timer_write(uint8 ctrnum, uint32 val)
case 0x10:
ctr->divider &= 0xff00;
ctr->divider |= val & 0xff;
ctr->val = ctr->divider;
ctr->enabled = TRUE;
ctr->stime = sim_gtime();
sim_cancel(timer_clk_unit);
sim_activate_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
break;
case 0x20:
ctr->divider &= 0x00ff;
ctr->divider |= (val & 0xff) << 8;
ctr->val = ctr->divider;
ctr->enabled = TRUE;
ctr->stime = sim_gtime();
/* Kick the timer to get the new divider value */
sim_cancel(timer_clk_unit);
sim_activate_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
break;
case 0x30:
if (ctr->lmb) {
ctr->lmb = FALSE;
ctr->divider = (uint16) ((ctr->divider & 0x00ff) | ((val & 0xff) << 8));
ctr->val = ctr->divider;
ctr->enabled = TRUE;
ctr->stime = sim_gtime();
sim_debug(READ_MSG, &timer_dev,
"[%08x] Write timer %d val LMB (MSB): %02x\n",
R[NUM_PC], ctrnum, val & 0xff);
/* Kick the timer to get the new divider value */
sim_cancel(timer_clk_unit);
sim_activate_abs(timer_clk_unit, ctr->divider * TIMER_STP_US);
} else {
ctr->lmb = TRUE;
ctr->divider = (ctr->divider & 0xff00) | (val & 0xff);
ctr->val = ctr->divider;
}
break;
default:
@@ -652,7 +679,7 @@ t_stat tod_reset(DEVICE *dptr)
if (!sim_is_running) {
t = sim_rtcn_init_unit(&tod_unit, TPS_TOD, TMR_TOD);
sim_activate_after(&tod_unit, 1000000 / TPS_TOD);
sim_activate_after(&tod_unit, 1000000 / t);
}
return SCPE_OK;