942 lines
46 KiB
C
942 lines
46 KiB
C
/* 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;
|
|
}
|