blob: 1015da6d99c828bc3afd63ae21376805bce1f09c [file] [log] [blame] [raw]
/* hp3000_clk.c: HP 3000 30135A System Clock/Fault Logging Interface simulator
Copyright (c) 2016, J. David Bryan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of the author shall not be used
in advertising or otherwise to promote the sale, use or other dealings in
this Software without prior written authorization from the author.
CLK HP 30135A System Clock/Fault Logging Interface
12-Sep-16 JDB Changed DIB register macro usage from SRDATA to DIB_REG
11-Jul-16 JDB Change "clk_unit" from a UNIT to an array of one UNIT
08-Jul-16 JDB Added REG entry to save the unit wait field
09-Jun-16 JDB Clarified the IRQ FF set code in DRESETINT
08-Jun-16 JDB Corrected %d format to %u for unsigned values
21-Mar-16 JDB Changed inbound_value and outbound_value types to HP_WORD
08-Jun-15 JDB First release version
12-Aug-14 JDB Passed the system clock diagnostic (D426A)
05-Jul-14 JDB Created
References:
- Stand-Alone System Clock Diagnostic
(32230-90005, January 1979)
- HP 3000 Series III Engineering Diagrams Set
(30000-90141, April 1980)
The HP 30135A System Clock/Fault Logging Interface is used with Series II and
III systems and provides two devices on a single I/O card: a programmable
interval clock employed as the MPE system clock and an interface to the ECC
fault logging RAMs on the semiconductor main memory arrays. This replaced
the earlier 30031A System Clock/Console Interface that had been used with the
CX and Series I machines, which used core memory. As part of this change,
the system console moved from the dedicated card to ATC port 0.
The clock provides programmable periods of 10 microseconds to 10 seconds in
decade increments. Each "tick" of the clock increments a presettable counter
that may be compared to a selected limit value. The clock may request an
interrupt when the values are equal, and a status indication is provided if
the counter reaches the limit a second time without acknowledgement.
The clock simulation provides both a REALTIME mode that establishes periods
in terms of event intervals, based on an average instruction time of 2.5
microseconds, and a CALTIME mode that calibrates the time delays to match
wall-clock time. As an example, in the former mode, a 1 millisecond period
will elapse after 400 instructions are executed, whereas in the latter mode,
the same period will elapse after 1 millisecond of wall-clock time. As the
simulator is generally one or two orders of magnitude faster than a real HP
3000, the real-time mode will satisfy the expectations of software that times
external events, such as a disc seek, via a delay loop, whereas the
calibrated mode will update a time-of-day clock as expected by users of the
system. In practice, this means that setting REALTIME mode is necessary to
satisfy the hardware diagnostics, and setting CALTIME mode is necessary when
running MPE.
Currently, the Fault Logging Interface simulator is not implemented. This
interface is accessed via DRT 2 by the MPE memory logging process, MEMLOGP,
but the process is smart enough to terminate if DRT 2 does not respond. As
the simulator relies on a host memory array to simulate RAM and does not
simulate the ECC check bits, an FLI implementation would always return a "no
errors detected" condition.
The clock interface responds only to direct I/O instructions, as follows:
Control Word Format (CIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M / rate | E | - | irq reset | C | L | A | - - - - | I |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
M = master reset (if bit 3 = 0)
E = master reset enable/load count rate (0/1)
C = reset count register after LR=CR interrupt
L = a WIO addresses the limit/count (0/1) register
A = reset all interrupts
I = enable clock interrupts
Count Rate Selection:
000 = unused
001 = 10 microseconds
010 = 100 microseconds
011 = 1 millisecond
100 = 10 milliseconds
101 = 100 milliseconds
110 = 1 second
111 = 10 seconds
IRQ Reset:
000 = none
001 = clear LR = CR interrupt
010 = clear LR = CR overflow interrupt
011 = clear I/O system interrupt (SIN)
100 = unused
101 = unused
110 = unused
111 = unused
Status Word Format (TIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| S | D | rate | - - - - - | C | F | - | I | L | R |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Where:
S = SIO OK (always 0)
D = direct read/write I/O OK (always 1)
C = limit register = count register
F = limit register = count register overflow (lost tick)
I = I/O system interrupt request (SIN)
L = limit/count (0/1) register selected
R = reset count register after interrupt
Output Data Word Format (WIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| limit register value/count register reset |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
If control word bit 9 is 0, the value is written to the limit register. If
control word bit 9 is 1, the count register is cleared to zero; the output
value is ignored.
Input Data Word Format (RIO):
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| count register value |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Implementation notes:
1. MPE sets the system clock to a 1 millisecond period and a 100 millisecond
limit to achieve the 10 interrupts per second rate required by the
time-of-day clock maintained by the OS. The short period precludes
idling. Therefore, this configuration is detected and implemented
internally as a 10 millisecond service time with the counter incremented
by 10 for each event service. In addition, the clock service is
synchronized with the CPU process clock service and the ATC poll service
to improve idling.
2. If the clock is calibrated, a prescaler is used to achieve the 1 second
and 10 second periods while the event service time remains at 100
milliseconds. For periods shorter than 1 second, and for all realtime
periods, the prescaler is not used. The prescaler is necessary because
the "sim_rtcn_calb" routine in the sim_timer library requires an integer
ticks-per-second parameter.
*/
#include "hp3000_defs.h"
#include "hp3000_io.h"
/* Program constants */
#define CLK_MULTIPLIER 10 /* number of MPE clock ticks per service */
#define CLK_RATE (1000 / CLK_MULTIPLIER) /* MPE clock rate in ticks per second */
static const int32 delay [8] = { /* clock delays, in event ticks per interval */
0, /* 000 = unused */
uS (10), /* 001 = 10 microseconds */
uS (100), /* 010 = 100 microseconds */
mS (1), /* 011 = 1 millisecond */
mS (10), /* 100 = 10 milliseconds */
mS (100), /* 101 = 100 milliseconds */
S (1), /* 110 = 1 second */
S (10) /* 111 = 10 seconds */
};
static const int32 ticks [8] = { /* clock ticks per second */
0, /* 000 = unused */
100000, /* 001 = 10 microseconds */
10000, /* 010 = 100 microseconds */
1000, /* 011 = 1 millisecond */
100, /* 100 = 10 milliseconds */
10, /* 101 = 100 milliseconds */
10, /* 110 = 1 second */
10 /* 111 = 10 seconds */
};
static const int32 scale [8] = { /* prescaler counts per clock tick */
1, /* 000 = unused */
1, /* 001 = 10 microseconds */
1, /* 010 = 100 microseconds */
1, /* 011 = 1 millisecond */
1, /* 100 = 10 milliseconds */
1, /* 101 = 100 milliseconds */
10, /* 110 = 1 second */
100 /* 111 = 10 seconds */
};
/* Unit flags */
#define UNIT_CALTIME_SHIFT (UNIT_V_UF + 0) /* calibrated timing mode */
#define UNIT_CALTIME (1u << UNIT_CALTIME_SHIFT)
/* Debug flags */
#define DEB_CSRW (1u << 0) /* trace commands received and status returned */
#define DEB_PSERV (1u << 1) /* trace unit service scheduling calls */
#define DEB_IOB (1u << 2) /* trace I/O bus signals and data words exchanged */
/* Control word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| M / rate | E | - | irq reset | C | L | A | - - - - | I |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define CN_MR 0100000u /* (M) master reset (if bit 3 = 0) */
#define CN_RATE_MASK 0160000u /* clock rate selector mask (if bit 3 = 1) */
#define CN_RESET_LOAD_SEL 0010000u /* (E) select reset/load rate (0/1) */
#define CN_IRQ_RESET_MASK 0003400u /* interrupt request reset selector mask */
#define CN_COUNT_RESET 0000200u /* (C) reset count register after LR=CR interrupt */
#define CN_LIMIT_COUNT_SEL 0000100u /* (L) select limit/count (0/1) register */
#define CN_IRQ_RESET_ALL 0000040u /* (A) reset all interrupt requests */
#define CN_IRQ_ENABLE 0000001u /* (I) enable clock interrupts */
#define CN_RATE_SHIFT 13 /* clock rate alignment shift */
#define CN_IRQ_RESET_SHIFT 8 /* interrupt request reset alignment shift */
#define CN_RATE(c) (((c) & CN_RATE_MASK) >> CN_RATE_SHIFT)
#define CN_RESET(c) (((c) & CN_IRQ_RESET_MASK) >> CN_IRQ_RESET_SHIFT)
static const char *const rate_name [8] = { /* clock rate selector names */
"unused", /* 000 = unused */
"10 microsecond", /* 001 = 10 microseconds */
"100 microsecond", /* 010 = 100 microseconds */
"1 millisecond", /* 011 = 1 millisecond */
"10 millisecond", /* 100 = 10 milliseconds */
"100 millisecond", /* 101 = 100 milliseconds */
"1 second", /* 110 = 1 second */
"10 second" /* 111 = 10 seconds */
};
static const char *const irq_reset_name [8] = { /* IRQ reset selector names */
"", /* 000 = none */
" | reset LR = CR irq", /* 001 = LR equal CR */
" | reset LR = CR overflow irq", /* 010 = LR equal CR overflow */
" | reset SIN irq", /* 011 = I/O system */
"", /* 100 = unused */
"", /* 101 = unused */
"", /* 110 = unused */
"" /* 111 = unused */
};
static const BITSET_NAME control_names [] = { /* Control word names */
"master reset", /* bit 0 */
NULL, /* bit 1 */
NULL, /* bit 2 */
"load rate", /* bit 3 */
NULL, /* bit 4 */
NULL, /* bit 5 */
NULL, /* bit 6 */
NULL, /* bit 7 */
"reset count", /* bit 8 */
"\1select count\0select limit", /* bit 9 */
"reset interrupts", /* bit 10 */
NULL, /* bit 11 */
NULL, /* bit 12 */
NULL, /* bit 13 */
NULL, /* bit 14 */
"enable interrupts" /* bit 15 */
};
static const BITSET_FORMAT control_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (control_names, 0, msb_first, has_alt, no_bar) };
/* Status word.
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - | D | rate | - - - - - | C | F | - | I | L | R |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
*/
#define ST_DIO_OK 0040000u /* (D) direct I/O OK to use */
#define ST_RATE_MASK 0034000u /* clock rate mask */
#define ST_LR_EQ_CR 0000040u /* (C) limit register = count register */
#define ST_LR_EQ_CR_OVFL 0000020u /* (F) limit register = count register overflow */
#define ST_SYSTEM_IRQ 0000004u /* (I) I/O system interrupt request */
#define ST_LIMIT_COUNT_SEL 0000002u /* (L) limit/count (0/1) register selected */
#define ST_COUNT_RESET 0000001u /* (R) count register is reset after LR=CR interrupt */
#define ST_RATE_SHIFT 11 /* clock rate alignment shift */
#define ST_RATE(r) ((r) << ST_RATE_SHIFT & ST_RATE_MASK)
#define ST_TO_RATE(s) (((s) & ST_RATE_MASK) >> ST_RATE_SHIFT)
static const BITSET_NAME status_names [] = { /* Status word names */
"DIO OK", /* bit 1 */
NULL, /* bit 2 */
NULL, /* bit 3 */
NULL, /* bit 4 */
NULL, /* bit 5 */
NULL, /* bit 6 */
NULL, /* bit 7 */
NULL, /* bit 8 */
NULL, /* bit 9 */
"LR = CR", /* bit 10 */
"LR = CR overflow", /* bit 11 */
NULL, /* bit 12 */
"system interrupt", /* bit 13 */
"\1count selected\0limit selected", /* bit 14 */
"reset after interrupt" /* bit 15 */
};
static const BITSET_FORMAT status_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (status_names, 0, msb_first, has_alt, append_bar) };
/* System clock state */
static FLIP_FLOP system_irq = CLEAR; /* SIN interrupt request flip-flop */
static FLIP_FLOP limit_irq = CLEAR; /* limit = count interrupt request flip-flop */
static FLIP_FLOP lost_tick_irq = CLEAR; /* limit = count overflow interrupt request flip-flop */
static HP_WORD control_word; /* control word */
static HP_WORD status_word; /* status word */
static HP_WORD count_register; /* counter register */
static HP_WORD limit_register; /* limit register */
static uint32 rate; /* clock rate */
static uint32 prescaler; /* clock rate prescaler */
static uint32 increment = 1; /* count register increment */
static t_bool coschedulable = FALSE; /* TRUE if the clock can be coscheduled with PCLK */
static t_bool coscheduled = FALSE; /* TRUE if the clock is coscheduled with PCLK */
/* System clock local SCP support routines */
static CNTLR_INTRF clk_interface;
static t_stat clk_service (UNIT *uptr);
static t_stat clk_reset (DEVICE *dptr);
/* System clock local utility routines */
static void resync_clock (void);
/* System clock SCP interface data structures */
/* Device information block */
static DIB clk_dib = {
&clk_interface, /* device interface */
3, /* device number */
SRNO_UNUSED, /* service request number */
1, /* interrupt priority */
INTMASK_UNUSED /* interrupt mask */
};
/* Unit list */
static UNIT clk_unit [] = {
{ UDATA (&clk_service, UNIT_IDLE | UNIT_CALTIME, 0) }
};
/* Register list */
static REG clk_reg [] = {
/* Macro Name Location Width Offset Flags */
/* ------ ------ ----------------- ----- ------ ------------------ */
{ ORDATA (CNTL, control_word, 16) },
{ ORDATA (STAT, status_word, 16) },
{ ORDATA (COUNT, count_register, 16) },
{ ORDATA (LIMIT, limit_register, 16) },
{ ORDATA (RATE, rate, 3) },
{ FLDATA (SYSIRQ, system_irq, 0) },
{ FLDATA (LIMIRQ, limit_irq, 0) },
{ FLDATA (OVFIRQ, lost_tick_irq, 0) },
{ DRDATA (SCALE, prescaler, 16), REG_HRO },
{ DRDATA (INCR, increment, 16), REG_HRO },
{ FLDATA (COSOK, coschedulable, 0), REG_HRO },
{ FLDATA (COSCH, coscheduled, 0), REG_HRO },
{ DRDATA (UWAIT, clk_unit [0].wait, 32), PV_LEFT | REG_HRO },
DIB_REGS (clk_dib),
{ NULL }
};
/* Modifier list */
static MTAB clk_mod [] = {
/* Mask Value Match Value Print String Match String Validation Display Descriptor */
/* ------------ ------------ ------------------- ------------ ---------- ------- ---------- */
{ UNIT_CALTIME, UNIT_CALTIME, "calibrated timing", "CALTIME", NULL, NULL, NULL },
{ UNIT_CALTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL },
/* Entry Flags Value Print String Match String Validation Display Descriptor */
/* ----------- ----------- ------------ ------------ ------------ ------------- ----------------- */
{ MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &clk_dib },
{ MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &clk_dib },
{ 0 }
};
/* Debugging trace list */
static DEBTAB clk_deb [] = {
{ "CSRW", DEB_CSRW }, /* interface control, status, read, and write actions */
{ "PSERV", DEB_PSERV }, /* clock unit service scheduling calls */
{ "IOBUS", DEB_IOB }, /* interface I/O bus signals and data words */
{ NULL, 0 }
};
/* Device descriptor */
DEVICE clk_dev = {
"CLK", /* device name */
clk_unit, /* unit array */
clk_reg, /* register array */
clk_mod, /* modifier array */
1, /* number of units */
8, /* address radix */
PA_WIDTH, /* address width */
1, /* address increment */
8, /* data radix */
DV_WIDTH, /* data width */
NULL, /* examine routine */
NULL, /* deposit routine */
&clk_reset, /* reset routine */
NULL, /* boot routine */
NULL, /* attach routine */
NULL, /* detach routine */
&clk_dib, /* device information block pointer */
DEV_DISABLE | DEV_DEBUG, /* device flags */
0, /* debug control flags */
clk_deb, /* debug flag name array */
NULL, /* memory size change routine */
NULL /* logical device name */
};
/* System clock global routines */
/* Update the counter register.
If the clock is currently coscheduled with the CPU process clock, then the
service interval is actually ten times the programmed rate. To present the
correct value when the counter register is read, this routine is called to
increment the count by an amount proportional to the fraction of the service
interval that has elapsed. In addition, it's called by the CPU instruction
postlude, so that the counter will have the correct value if it's examined
from the SCP command prompt.
This routine is also called when the counter is to be reset. This ensures
that the increment is reduced by the time elapsed before the counter is
zeroed.
*/
void clk_update_counter (void)
{
int32 elapsed, ticks;
if (coscheduled) { /* if the clock is coscheduled, then adjust the count */
elapsed = clk_unit [0].wait /* the elapsed time is the original wait time */
- sim_activate_time (&clk_unit [0]); /* less the time remaining before the next service */
ticks = (elapsed * CLK_MULTIPLIER) / clk_unit [0].wait /* the adjustment is the elapsed fraction of the multiplier */
- (CLK_MULTIPLIER - increment); /* less the amount of any adjustment already made */
count_register = count_register + ticks & R_MASK; /* update the clock counter with rollover */
increment = increment - ticks; /* and reduce the amount remaining to add at service */
}
return;
}
/* System clock local SCP support routines */
/* System clock interface.
The system clock is installed on the IOP bus and receives direct I/O commands
from the IOP. It does not respond to Programmed I/O (SIO) commands.
In simulation, the asserted signals on the bus are represented as bits in the
inbound_signals set. Each signal is processed sequentially in numerical
order, and a set of similar outbound_signals is assembled and returned to the
caller, simulating assertion of the corresponding bus signals.
There is no interrupt mask; interrupts are always unmasked, and the interface
does not respond to the SMSK I/O order.
Implementation notes:
1. In hardware, setting the tick rate in the control word addresses a
multiplexer that selects one of the 10 MHz clock division counter outputs
as the clock source for the count register. Setting the rate bits to 0
inhibits the count register, although the division counter continues to
run. In simulation, setting a new rate stops and then restarts the event
service with the new delay time, equivalent in hardware to clearing the
clock division counter.
2. Receipt of a DRESETINT signal clears the interrupt request and active
flip-flops but does not cancel a request that is pending but not yet
serviced by the IOP. However, when the IOP does service the request by
asserting INTPOLLIN, the interface routine returns INTPOLLOUT, which will
cancel the request.
3. The "%.0s" print specification in the DCONTSTB trace call absorbs the
rate name parameter without printing when the rate is not specified.
*/
static SIGNALS_DATA clk_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
{
INBOUND_SIGNAL signal;
INBOUND_SET working_set = inbound_signals;
HP_WORD outbound_value = 0;
OUTBOUND_SET outbound_signals = NO_SIGNALS;
dprintf (clk_dev, DEB_IOB, "Received data %06o with signals %s\n",
inbound_value, fmt_bitset (inbound_signals, inbound_format));
while (working_set) {
signal = IONEXTSIG (working_set); /* isolate the next signal */
switch (signal) { /* dispatch an I/O signal */
case DCONTSTB:
control_word = inbound_value; /* save the control word */
if (control_word & CN_RESET_LOAD_SEL) { /* if the reset/load selector is set */
rate = CN_RATE (control_word); /* then load the clock rate */
if (clk_unit [0].flags & UNIT_CALTIME) /* if in calibrated timing mode */
prescaler = scale [rate]; /* then set the prescaler */
else /* otherwise */
prescaler = 1; /* the prescaler isn't used */
sim_cancel (&clk_unit [0]); /* changing the rate restarts the timing divider */
if (rate > 0) { /* if the rate is valid */
clk_unit [0].wait = delay [rate]; /* then set the initial service delay */
sim_rtcn_init (clk_unit [0].wait, TMR_CLK); /* initialize the clock */
resync_clock (); /* and reschedule the service */
}
}
else if (control_word & CN_MR) { /* otherwise, if the master reset bit is set */
clk_reset (&clk_dev); /* then reset the interface */
control_word = 0; /* (which clears the other settings) */
}
if (control_word & CN_IRQ_RESET_ALL) { /* if a reset of all interrupts is requested */
limit_irq = CLEAR; /* then clear the limit = count, */
lost_tick_irq = CLEAR; /* limit = count overflow, */
system_irq = CLEAR; /* and system flip-flops */
}
else if (control_word & CN_IRQ_RESET_MASK) /* otherwise if any single resets are requested */
switch (CN_RESET (control_word)) { /* then reset the specified flip-flop */
case 1:
limit_irq = CLEAR; /* clear the limit = count interrupt request */
break;
case 2:
lost_tick_irq = CLEAR; /* clear the limit = count overflow interrupt request */
break;
case 3:
system_irq = CLEAR; /* clear the system interrupt request */
break;
default: /* the rest of the values do nothing */
break;
}
if (dibptr->interrupt_active == CLEAR) /* if no interrupt is active */
working_set |= DRESETINT; /* then recalculate interrupt requests */
dprintf (clk_dev, DEB_CSRW, (inbound_value & CN_RESET_LOAD_SEL
? "Control is %s | %s rate%s\n"
: "Control is %s%.0s%s\n"),
fmt_bitset (inbound_value, control_format),
rate_name [CN_RATE (inbound_value)],
irq_reset_name [CN_RESET (inbound_value)]);
break;
case DSTATSTB:
status_word = ST_DIO_OK | ST_RATE (rate); /* set the clock rate */
if (limit_irq) /* if the limit = count flip-flop is set */
status_word |= ST_LR_EQ_CR; /* set the corresponding status bit */
if (lost_tick_irq) /* if the limit = count overflow flip-flop is set */
status_word |= ST_LR_EQ_CR_OVFL; /* set the corresponding status bit */
if (system_irq) /* if the system interrupt request flip-flop is set */
status_word |= ST_SYSTEM_IRQ; /* set the corresponding status bit */
if (control_word & CN_LIMIT_COUNT_SEL) /* if the limit/count selector is set */
status_word |= ST_LIMIT_COUNT_SEL; /* set the corresponding status bit */
if (control_word & CN_COUNT_RESET) /* if the reset-after-interrupt selector is set */
status_word |= ST_COUNT_RESET; /* set the corresponding status bit */
outbound_value = status_word; /* return the status word */
dprintf (clk_dev, DEB_CSRW, "Status is %s%s rate\n",
fmt_bitset (outbound_value, status_format),
rate_name [ST_TO_RATE (outbound_value)]);
break;
case DREADSTB:
clk_update_counter (); /* update the clock counter register */
outbound_value = LOWER_WORD (count_register); /* and then read it */
dprintf (clk_dev, DEB_CSRW, "Count register value %u returned\n",
count_register);
break;
case DWRITESTB:
if (control_word & CN_LIMIT_COUNT_SEL) { /* if the limit/count selector is set */
clk_update_counter (); /* then update the clock counter register */
count_register = 0; /* and then clear it */
dprintf (clk_dev, DEB_CSRW, "Count register cleared\n");
}
else { /* otherwise */
limit_register = inbound_value; /* set the limit register to the supplied value */
dprintf (clk_dev, DEB_CSRW, "Limit register value %u set\n",
limit_register);
coschedulable = (ticks [rate] == 1000 /* the clock can be coscheduled if the rate */
&& limit_register == 100); /* is 1 msec and the limit is 100 ticks */
}
break;
case DSETINT:
system_irq = SET; /* set the system interrupt request flip-flop */
dibptr->interrupt_request = SET; /* request an interrupt */
outbound_signals |= INTREQ; /* and notify the IOP */
break;
case DRESETINT:
dibptr->interrupt_active = CLEAR; /* clear the Interrupt Active flip-flop */
if ((limit_irq == SET || lost_tick_irq == SET) /* if the limit or lost tick flip-flops are set */
&& control_word & CN_IRQ_ENABLE) /* and interrupts are enabled */
dibptr->interrupt_request = SET; /* then set the interrupt request flip-flop */
else /* otherwise */
dibptr->interrupt_request = system_irq; /* request an interrupt if the system flip-flop is set */
if (dibptr->interrupt_request) /* if a request is pending */
outbound_signals |= INTREQ; /* then notify the IOP */
break;
case INTPOLLIN:
if (dibptr->interrupt_request) { /* if a request is pending */
dibptr->interrupt_request = CLEAR; /* then clear it */
dibptr->interrupt_active = SET; /* and mark it as now active */
outbound_signals |= INTACK; /* acknowledge the interrupt */
outbound_value = dibptr->device_number; /* and return our device number */
}
else /* otherwise the request has been reset */
outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */
break;
case DSTARTIO: /* not used by this interface */
case DSETMASK: /* not used by this interface */
case ACKSR: /* not used by this interface */
case TOGGLESR: /* not used by this interface */
case SETINT: /* not used by this interface */
case PCMD1: /* not used by this interface */
case PCONTSTB: /* not used by this interface */
case SETJMP: /* not used by this interface */
case PSTATSTB: /* not used by this interface */
case PWRITESTB: /* not used by this interface */
case PREADSTB: /* not used by this interface */
case EOT: /* not used by this interface */
case TOGGLEINXFER: /* not used by this interface */
case TOGGLEOUTXFER: /* not used by this interface */
case READNEXTWD: /* not used by this interface */
case TOGGLESIOOK: /* not used by this interface */
case DEVNODB: /* not used by this interface */
case XFERERROR: /* not used by this interface */
case CHANSO: /* not used by this interface */
case PFWARN: /* not used by this interface */
break;
}
IOCLEARSIG (working_set, signal); /* remove the current signal from the set */
}
dprintf (clk_dev, DEB_IOB, "Returned data %06o with signals %s\n",
outbound_value, fmt_bitset (outbound_signals, outbound_format));
return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */
}
/* Service the system clock unit.
At each "tick" of the clock, the count register is incremented and compared
to the limit register. If they are equal, then the counter is cleared (if
enabled) and an interrupt is generated (if enabled).
If the clock is calibrated, a prescaler is used to achieve the 1 second and
10 second periods while the event time remains at 100 milliseconds. For
periods shorter than 1 second, and for all realtime periods, the prescaler is
not used (by setting the value to 1).
If the clock is currently coscheduled with the CPU process clock, then the
service interval is actually ten times the programmed rate, so the count
register increment per service entry is 10 instead of 1.
Implementation notes:
1. The count/limit comparison hardware provides only an equal condition. If
the limit register is set to a value below the current count, or the
LR=CR interrupt is not enabled until after the count register value has
exceeded the limit, comparison will not occur until the count register
overflows and again reaches the limit.
*/
static t_stat clk_service (UNIT *uptr)
{
dprintf (clk_dev, DEB_PSERV, "Service entered with counter %u increment %u limit %u\n",
count_register, increment, limit_register);
prescaler = prescaler - 1; /* decrement the prescaler count */
if (prescaler == 0) { /* if the prescaler count has expired */
count_register = count_register + increment & R_MASK; /* then the count register counts up */
if (count_register == limit_register) { /* if the limit has been reached */
if (limit_irq == SET) /* then if the last limit interrupt wasn't serviced */
lost_tick_irq = SET; /* then set the overflow interrupt */
else /* otherwise */
limit_irq = SET; /* set the limit interrupt */
if (control_word & CN_COUNT_RESET) /* if the counter reset option is selected */
count_register = 0; /* then clear the count register */
if (control_word & CN_IRQ_ENABLE /* if clock interrupts are enabled */
&& clk_dib.interrupt_active == CLEAR) { /* and the interrupt active flip-flop is clear */
clk_dib.interrupt_request = SET; /* then request an interrupt */
iop_assert_INTREQ (&clk_dib); /* and notify the IOP of the INTREQ signal */
}
}
if (uptr->flags & UNIT_CALTIME) /* if in calibrated timing mode */
prescaler = scale [rate]; /* then reset the prescaler */
else /* otherwise */
prescaler = 1; /* the prescaler isn't used */
}
if (!(uptr->flags & UNIT_CALTIME)) { /* if the clock is in real timing mode */
uptr->wait = delay [rate]; /* then set an event-based delay */
increment = 1; /* equal to the selected period */
coscheduled = FALSE; /* the clock is not coscheduled with the process clock */
}
else if (coschedulable && cpu_is_calibrated) { /* otherwise if the process clock is calibrated */
uptr->wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */
increment = CLK_MULTIPLIER; /* at one-tenth of the selected period */
coscheduled = TRUE; /* the clock is coscheduled with the process clock */
}
else { /* otherwise */
uptr->wait = sim_rtcn_calb (ticks [rate], TMR_CLK); /* calibrate the clock to a delay */
increment = 1; /* equal to the selected period */
coscheduled = FALSE; /* the clock is not coscheduled with the process clock */
}
dprintf (clk_dev, DEB_PSERV, "Rate %s delay %d service %s\n",
rate_name [rate], uptr->wait,
(coscheduled ? "coscheduled" : "scheduled"));
return sim_activate (uptr, uptr->wait); /* activate the unit and return the status */
}
/* Device reset.
This routine is called for a RESET or RESET CLK command. It is the
simulation equivalent of the IORESET signal, which is asserted by the front
panel LOAD and DUMP switches.
For this interface, IORESET is identical to a Programmed Master Reset
(control word bit 0 set with bit 3 clear).
A master reset is generated either by an IORESET signal or a Direct I/O
Master Reset (control word bit 0 set with bit 3 clear).
Implementation notes:
1. In simulation, the Enable Clock Interrupts flip-flop, the Reset Count
Register after LR=CR Interrupt flip-flop, and the Address Limit/Count
Register flip-flop are maintained in the control word rather than as
separate values.
2. The hardware interrupt circuitry contains an Interrupt Active flip-flop
and an Interrupt Priority latch but no Interrupt Request flip-flop.
Instead, the INTREQ signal is the logical OR of the LR=CR Interrupt and
LR=CR Overflow Interrupt flip-flops (if enabled by the Enable Clock
Interrupts flip-flop) with the the System Interrupt flip-flop. In
simulation, the interrupt_request flip-flop in the Device Information
Block is set explicitly to reflect this logic. Clearing the three
interrupt source flip-flops therefore clears the interrupt_request
flip-flop as well.
3. In simulation, the clock division counters are represented by the event
service delay. Stopping and restarting the delay is equivalent to
clearing the division counters.
*/
static t_stat clk_reset (DEVICE *dptr)
{
count_register = 0; /* clear the count */
limit_register = 0; /* and limit registers */
rate = 0; /* clear the clock rate */
prescaler = 1; /* and set the clock prescaler */
sim_cancel (dptr->units); /* clearing the rate stops the clock */
clk_dib.interrupt_request = CLEAR; /* clear any current */
clk_dib.interrupt_active = CLEAR; /* interrupt request */
system_irq = CLEAR; /* clear the system, */
limit_irq = CLEAR; /* limit = count, */
lost_tick_irq = CLEAR; /* and limit = count overflow flip-flops */
control_word = 0; /* clear the enable, write select, and count reset actions */
return SCPE_OK;
}
/* System clock local utility routines */
/* Resynchronize the clock.
After changing the rate or the limit, the new values are examined to see if
the clock may be coscheduled with the process clock to permit idling. If
coscheduling is possible and both the system clock and the CPU process clock
are calibrated, then the clock event service is synchronized with the process
clock service. Otherwise, the service time is set up but is otherwise
asynchronous with the process clock.
Implementation notes:
1. To synchronize events, the clock must be activated absolutely, as a
service event may already be scheduled, and normal activation will not
disturb an existing event.
*/
static void resync_clock (void)
{
coschedulable = (ticks [rate] == 1000 /* the clock can be coscheduled if the rate */
&& limit_register == 100); /* is 1 msec and the limit is 100 ticks */
if (clk_unit [0].flags & UNIT_CALTIME /* if the clock is in calibrated timing mode */
&& coschedulable /* and may be coscheduled with the process clock */
&& cpu_is_calibrated) { /* and the process clock is calibrated */
clk_unit [0].wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */
coscheduled = TRUE; /* the clock is coscheduled with the process clock */
}
else { /* otherwise */
clk_unit [0].wait = delay [rate]; /* set up an independent clock */
coscheduled = FALSE; /* the clock is not coscheduled with the process clock */
}
dprintf (clk_dev, DEB_PSERV, "Rate %s delay %d service rescheduled\n",
rate_name [rate], clk_unit [0].wait);
sim_activate_abs (&clk_unit [0], clk_unit [0].wait); /* restart the clock */
return;
}