When the 3B2 simulator was set to allow idling, there was significant clock drift related to the primary timer unit. It turns out that the simulator was using `AIO_SET_INTERRUPT_LATENCY` and `sim_rtcn_tick_ack` incorrectly. They are not needed with the structure of system timers in the 3B2 architecture.
814 lines
22 KiB
C
814 lines
22 KiB
C
/* 3b2_stddev.c: Miscellaneous System Board Devices
|
|
|
|
Copyright (c) 2017-2022, Seth J. Morabito
|
|
|
|
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 AUTHORS OR COPYRIGHT HOLDERS
|
|
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.
|
|
*/
|
|
|
|
/*
|
|
This file contains system-specific registers and devices for the
|
|
following 3B2 devices:
|
|
|
|
- nvram Non-Volatile RAM
|
|
- tod MM58174A and MM58274C Real-Time-Clock
|
|
- flt Fault Register (Rev 3 only)
|
|
*/
|
|
|
|
#include "3b2_stddev.h"
|
|
|
|
#include "3b2_cpu.h"
|
|
#include "3b2_csr.h"
|
|
#include "3b2_timer.h"
|
|
|
|
DEBTAB sys_deb_tab[] = {
|
|
{ "INIT", INIT_MSG, "Init" },
|
|
{ "READ", READ_MSG, "Read activity" },
|
|
{ "WRITE", WRITE_MSG, "Write activity" },
|
|
{ "EXECUTE", EXECUTE_MSG, "Execute activity" },
|
|
{ "IRQ", IRQ_MSG, "Interrupt activity"},
|
|
{ "TRACE", TRACE_DBG, "Detailed activity" },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
uint32 *NVRAM = NULL;
|
|
|
|
/* NVRAM */
|
|
UNIT nvram_unit = {
|
|
UDATA(NULL, UNIT_FIX+UNIT_BINK, NVRSIZE)
|
|
};
|
|
|
|
REG nvram_reg[] = {
|
|
{ NULL }
|
|
};
|
|
|
|
DEVICE nvram_dev = {
|
|
"NVRAM", &nvram_unit, nvram_reg, NULL,
|
|
1, 16, 8, 4, 16, 32,
|
|
&nvram_ex, &nvram_dep, &nvram_reset,
|
|
NULL, &nvram_attach, &nvram_detach,
|
|
NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL,
|
|
&nvram_help, NULL, NULL,
|
|
&nvram_description
|
|
};
|
|
|
|
t_stat nvram_ex(t_value *vptr, t_addr exta, UNIT *uptr, int32 sw)
|
|
{
|
|
uint32 addr = (uint32) exta;
|
|
|
|
if ((vptr == NULL) || (addr & 03)) {
|
|
return SCPE_ARG;
|
|
}
|
|
|
|
if (addr >= NVRSIZE) {
|
|
return SCPE_NXM;
|
|
}
|
|
|
|
*vptr = NVRAM[addr >> 2];
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat nvram_dep(t_value val, t_addr exta, UNIT *uptr, int32 sw)
|
|
{
|
|
uint32 addr = (uint32) exta;
|
|
|
|
if (addr & 03) {
|
|
return SCPE_ARG;
|
|
}
|
|
|
|
if (addr >= NVRSIZE) {
|
|
return SCPE_NXM;
|
|
}
|
|
|
|
NVRAM[addr >> 2] = (uint32) val;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat nvram_reset(DEVICE *dptr)
|
|
{
|
|
if (NVRAM == NULL) {
|
|
NVRAM = (uint32 *)calloc(NVRSIZE >> 2, sizeof(uint32));
|
|
memset(NVRAM, 0, sizeof(uint32) * NVRSIZE >> 2);
|
|
nvram_unit.filebuf = NVRAM;
|
|
}
|
|
|
|
if (NVRAM == NULL) {
|
|
return SCPE_MEM;
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
const char *nvram_description(DEVICE *dptr)
|
|
{
|
|
return "Non-Volatile RAM.\n";
|
|
}
|
|
|
|
t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
|
|
{
|
|
fprintf(st, "Non-Volatile RAM\n\n");
|
|
fprintf(st, "The %s device is a small battery-backed, non-volatile RAM\n", dptr->name);
|
|
fprintf(st, "used by the 3B2 to hold system configuration and diagnostic data.\n\n");
|
|
fprintf(st, "In order for the simulator to keep track of this data while not\n");
|
|
fprintf(st, "running, the %s device may be attached to a file, e.g.\n\n", dptr->name);
|
|
fprintf(st, " sim> ATTACH NVRAM <filename>\n");
|
|
fprint_show_help(st, dptr);
|
|
fprint_reg_help(st, dptr);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat nvram_attach(UNIT *uptr, CONST char *cptr)
|
|
{
|
|
t_stat r;
|
|
|
|
/* If we've been asked to attach, make sure the ATTABLE
|
|
and BUFABLE flags are set on the unit */
|
|
uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE);
|
|
|
|
r = attach_unit(uptr, cptr);
|
|
|
|
if (r != SCPE_OK) {
|
|
/* Unset the ATTABLE and BUFABLE flags if we failed. */
|
|
uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE);
|
|
} else {
|
|
uptr->hwmark = (uint32) uptr->capac;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
t_stat nvram_detach(UNIT *uptr)
|
|
{
|
|
t_stat r;
|
|
|
|
r = detach_unit(uptr);
|
|
|
|
if ((uptr->flags & UNIT_ATT) == 0) {
|
|
uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
uint32 nvram_read(uint32 pa, size_t size)
|
|
{
|
|
uint32 offset = pa - NVRBASE;
|
|
uint32 data = 0;
|
|
uint32 sc = (~(offset & 3) << 3) & 0x1f;
|
|
|
|
switch(size) {
|
|
case 8:
|
|
data = (NVRAM[offset >> 2] >> sc) & BYTE_MASK;
|
|
break;
|
|
case 16:
|
|
if (offset & 2) {
|
|
data = NVRAM[offset >> 2] & HALF_MASK;
|
|
} else {
|
|
data = (NVRAM[offset >> 2] >> 16) & HALF_MASK;
|
|
}
|
|
break;
|
|
case 32:
|
|
data = NVRAM[offset >> 2];
|
|
break;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
void nvram_write(uint32 pa, uint32 val, size_t size)
|
|
{
|
|
uint32 offset = pa - NVRBASE;
|
|
uint32 index = offset >> 2;
|
|
uint32 sc, mask;
|
|
|
|
switch(size) {
|
|
case 8:
|
|
sc = (~(pa & 3) << 3) & 0x1f;
|
|
mask = (uint32) (0xff << sc);
|
|
NVRAM[index] = (NVRAM[index] & ~mask) | (val << sc);
|
|
break;
|
|
case 16:
|
|
if (offset & 2) {
|
|
NVRAM[index] = (NVRAM[index] & ~HALF_MASK) | val;
|
|
} else {
|
|
NVRAM[index] = (NVRAM[index] & HALF_MASK) | (val << 16);
|
|
}
|
|
break;
|
|
case 32:
|
|
NVRAM[index] = val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* MM58174A and MM58274C Time Of Day Clock.
|
|
*
|
|
* In addition to keeping track of time of day in tenths of seconds,
|
|
* this device is also used as the simulator's primary calibrated
|
|
* real-time clock. It operates at the speed of 100Hz, with every
|
|
* tenth step incrementing the time-of-day counter.
|
|
*/
|
|
|
|
#define TOD_12H(TD) (((TD)->clkset & 1) == 0)
|
|
#define TOD_BCDH(V) (((V) / 10) & 0xf)
|
|
#define TOD_BCDL(V) (((V) % 10) & 0xf)
|
|
#define TOD_LYEAR(TD) ((TD)->lyear == 0)
|
|
#define TOD_LYEAR_INC(TD) \
|
|
do { \
|
|
(TD)->lyear = (((TD)->lyear + 1) & 0x3); \
|
|
(TD)->clkset &= 3; \
|
|
(TD)->clkset |= (TD)->lyear << 2; \
|
|
} while(0)
|
|
|
|
#define CTRL_DISABLE 0x4
|
|
#define CLKSET_PM 0x2
|
|
#define FLAG_DATA_CHANGED 0x4
|
|
#define FLAG_INTERRUPT 0x1
|
|
#define MIN_DIFF 5l
|
|
#define MAX_DIFF 157680000l
|
|
|
|
#define CLK_DELAY 10000 /* 10 milliseconds per tick */
|
|
#define CLK_TPS 100l /* 100 ticks per second */
|
|
|
|
static void tod_resync(UNIT *uptr);
|
|
static void tod_tick(UNIT *uptr);
|
|
static t_stat tod_svc(UNIT *uptr);
|
|
static t_bool tod_enabled;
|
|
|
|
int32 tmr_poll = CLK_DELAY;
|
|
int32 tmxr_poll = CLK_DELAY;
|
|
|
|
UNIT tod_unit = {
|
|
UDATA(&tod_svc, UNIT_FIX|UNIT_BINK|UNIT_IDLE, sizeof(TOD_DATA)), CLK_DELAY
|
|
};
|
|
|
|
REG tod_reg[] = {
|
|
{ DRDATAD(POLL, tmr_poll, 24, "Calibrated poll interval") },
|
|
{ 0 }
|
|
};
|
|
|
|
DEVICE tod_dev = {
|
|
"TOD", &tod_unit, tod_reg, NULL,
|
|
1, 16, 8, 4, 16, 32,
|
|
NULL, NULL, &tod_reset,
|
|
NULL, &tod_attach, &tod_detach,
|
|
NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL,
|
|
&tod_help, NULL, NULL,
|
|
&tod_description
|
|
};
|
|
|
|
/*
|
|
* Attempt to re-sync the TOD by catching up (if lagging) and updating
|
|
* the current time stored in the TOD state.
|
|
*
|
|
* Because this process may be expensive when catching up following a
|
|
* very long time without the simulator running, the process will
|
|
* short-circuit if the delta is longer than 5 years, or if no
|
|
* previous time was recorded.
|
|
*/
|
|
static void tod_resync(UNIT *uptr)
|
|
{
|
|
TOD_DATA *td;
|
|
time_t delta;
|
|
uint32 catchup_ticks;
|
|
|
|
if (!(uptr->flags & UNIT_ATT) || uptr->filebuf == NULL) {
|
|
return;
|
|
}
|
|
|
|
td = (TOD_DATA *)uptr->filebuf;
|
|
|
|
if (td->time > 0) {
|
|
delta = time(NULL) - td->time;
|
|
if (delta > MIN_DIFF && delta < MAX_DIFF) {
|
|
catchup_ticks = (uint32) delta * CLK_TPS;
|
|
sim_debug(EXECUTE_MSG, &tod_dev,
|
|
"Catching up with a delta of %ld seconds (%d ticks).\n",
|
|
delta, catchup_ticks);
|
|
while (catchup_ticks-- > 0) {
|
|
tod_tick(&tod_unit);
|
|
}
|
|
}
|
|
}
|
|
|
|
td->time = time(NULL);
|
|
}
|
|
|
|
t_stat tod_reset(DEVICE *dptr)
|
|
{
|
|
int32 t;
|
|
|
|
if (tod_unit.filebuf == NULL) {
|
|
tod_unit.filebuf = calloc(sizeof(TOD_DATA), 1);
|
|
if (tod_unit.filebuf == NULL) {
|
|
return SCPE_MEM;
|
|
}
|
|
}
|
|
|
|
/* We start in a running state */
|
|
tod_enabled = TRUE;
|
|
|
|
t = sim_rtcn_init_unit(&tod_unit, tod_unit.wait, TMR_CLK);
|
|
sim_activate_after(&tod_unit, 1000000/CLK_TPS);
|
|
tmr_poll = t;
|
|
tmxr_poll = t;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat tod_attach(UNIT *uptr, CONST char *cptr)
|
|
{
|
|
t_stat r;
|
|
|
|
uptr->flags = uptr->flags | (UNIT_ATTABLE | UNIT_BUFABLE);
|
|
|
|
r = attach_unit(uptr, cptr);
|
|
|
|
if (r != SCPE_OK) {
|
|
uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE);
|
|
} else {
|
|
uptr->hwmark = (uint32) uptr->capac;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
t_stat tod_detach(UNIT *uptr)
|
|
{
|
|
t_stat r;
|
|
|
|
r = detach_unit(uptr);
|
|
|
|
if ((uptr->flags & UNIT_ATT) == 0) {
|
|
uptr->flags = uptr->flags & (uint32) ~(UNIT_ATTABLE | UNIT_BUFABLE);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static t_stat tod_svc(UNIT *uptr)
|
|
{
|
|
TOD_DATA *td = (TOD_DATA *)uptr->filebuf;
|
|
int32 t;
|
|
|
|
/* Re-sync the recorded system time once every second */
|
|
if (tod_enabled) {
|
|
tod_tick(uptr);
|
|
|
|
if (td->tsec == 0) {
|
|
tod_resync(uptr);
|
|
}
|
|
}
|
|
|
|
t = sim_rtcn_calb(CLK_TPS, TMR_CLK);
|
|
sim_activate_after(uptr, 1000000/CLK_TPS);
|
|
tmr_poll = t;
|
|
tmxr_poll = t;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
* The MM58174 and MM58274 consist of a set of failry "dumb" roll-over
|
|
* counters. In an ideal world, we'd just look at the real system time
|
|
* and translate that into whatever read the host needs.
|
|
* Unfortunately, since the Day-of-Week and Leap Year registers are
|
|
* totally independent of whatever the "real" date and time should be,
|
|
* this doesn't map very well, and DGMON hardware diagnostics fail.
|
|
*
|
|
* Instead, we model the behavior of the chip accurately here. Each
|
|
* rollover is cascaded to the next highest register, using the same
|
|
* logic the chip uses.
|
|
*/
|
|
static void tod_tick(UNIT *uptr)
|
|
{
|
|
TOD_DATA *td = (TOD_DATA *)uptr->filebuf;
|
|
|
|
if (++td->tsec > 99) {
|
|
td->tsec = 0;
|
|
td->flags |= FLAG_DATA_CHANGED;
|
|
if (++td->sec > 59) {
|
|
td->sec = 0;
|
|
if (++td->min > 59) {
|
|
td->min = 0;
|
|
td->hour++;
|
|
|
|
/* 12-hour clock cycles from 1-12, 24-hour clock cycles from 00-23 */
|
|
if (TOD_12H(td)) {
|
|
if (td->hour == 12) {
|
|
td->clkset ^= CLKSET_PM;
|
|
}
|
|
if (td->hour > 12) {
|
|
td->hour = 1;
|
|
}
|
|
} else if (td->hour > 23) {
|
|
td->hour = 0;
|
|
}
|
|
|
|
if ((TOD_12H(td) && td->hour == 12) || (!TOD_12H(td) && td->hour == 0)) {
|
|
/* Manage day-of-week */
|
|
td->wday++;
|
|
if (td->wday > 7) {
|
|
td->wday = 1;
|
|
}
|
|
td->day++;
|
|
switch(td->mon) {
|
|
case 2: /* FEB */
|
|
if (TOD_LYEAR(td)) {
|
|
if (td->day > 29) {
|
|
td->day = 1;
|
|
}
|
|
} else {
|
|
if (td->day > 28) {
|
|
td->day = 1;
|
|
}
|
|
}
|
|
break;
|
|
case 4: /* APR */
|
|
case 6: /* JUN */
|
|
case 9: /* SEP */
|
|
case 11: /* NOV */
|
|
if (td->day > 30) {
|
|
td->day = 1;
|
|
}
|
|
break;
|
|
case 1: /* JAN */
|
|
case 3: /* MAR */
|
|
case 5: /* MAY */
|
|
case 7: /* JUL */
|
|
case 8: /* AUG */
|
|
case 10: /* OCT */
|
|
case 12: /* DEC */
|
|
if (td->day > 31) {
|
|
td->day = 1;
|
|
}
|
|
break;
|
|
}
|
|
if (td->day == 1) {
|
|
if (++td->mon > 12) {
|
|
td->mon = 1;
|
|
TOD_LYEAR_INC(td);
|
|
if (++td->year > 99) {
|
|
td->year = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
uint32 tod_read(uint32 pa, size_t size)
|
|
{
|
|
uint8 reg, val;
|
|
TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf);
|
|
|
|
reg = pa & 0xfc;
|
|
|
|
switch(reg) {
|
|
#if defined(REV3)
|
|
case TOD_CTRL:
|
|
val = td->flags;
|
|
td->flags &= ~(FLAG_DATA_CHANGED);
|
|
break;
|
|
#endif
|
|
case TOD_TSEC:
|
|
val = TOD_BCDH(td->tsec);
|
|
break;
|
|
case TOD_1SEC:
|
|
val = TOD_BCDL(td->sec);
|
|
break;
|
|
case TOD_10SEC:
|
|
val = TOD_BCDH(td->sec);
|
|
break;
|
|
case TOD_1MIN:
|
|
val = TOD_BCDL(td->min);
|
|
break;
|
|
case TOD_10MIN:
|
|
val = TOD_BCDH(td->min);
|
|
break;
|
|
case TOD_1HOUR:
|
|
val = TOD_BCDL(td->hour);
|
|
break;
|
|
case TOD_10HOUR:
|
|
val = TOD_BCDH(td->hour);
|
|
break;
|
|
case TOD_1DAY:
|
|
val = TOD_BCDL(td->day);
|
|
break;
|
|
case TOD_10DAY:
|
|
val = TOD_BCDH(td->day);
|
|
break;
|
|
case TOD_1MON:
|
|
val = TOD_BCDL(td->mon);
|
|
break;
|
|
case TOD_10MON:
|
|
val = TOD_BCDH(td->mon);
|
|
break;
|
|
case TOD_WDAY:
|
|
val = td->wday;
|
|
break;
|
|
case TOD_1YEAR:
|
|
#if defined(REV3)
|
|
val = TOD_BCDL(td->year);
|
|
#else
|
|
val = td->lyear;
|
|
#endif
|
|
break;
|
|
#if defined(REV3)
|
|
case TOD_10YEAR:
|
|
val = TOD_BCDH(td->year);
|
|
break;
|
|
case TOD_SET_INT:
|
|
val = td->clkset;
|
|
break;
|
|
#endif
|
|
default:
|
|
val = 0;
|
|
break;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
void tod_write(uint32 pa, uint32 val, size_t size)
|
|
{
|
|
uint32 reg;
|
|
TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf);
|
|
|
|
/* reg = pa - TODBASE; */
|
|
reg = pa & 0xfc;
|
|
|
|
switch(reg) {
|
|
#if defined(REV3)
|
|
case TOD_CTRL:
|
|
td->ctrl = (uint8) val;
|
|
if (val & CTRL_DISABLE) {
|
|
tod_enabled = FALSE;
|
|
td->tsec = 0;
|
|
} else {
|
|
tod_enabled = TRUE;
|
|
}
|
|
#else
|
|
case TOD_TEST:
|
|
/* test mode */
|
|
#endif
|
|
break;
|
|
case TOD_TSEC:
|
|
td->tsec = (uint8) val * 10;
|
|
break;
|
|
case TOD_1SEC:
|
|
td->sec = ((td->sec / 10) * 10) + (uint8) val;
|
|
break;
|
|
case TOD_10SEC:
|
|
td->sec = ((uint8) val * 10) + (td->sec % 10);
|
|
break;
|
|
case TOD_1MIN:
|
|
td->min = ((td->min / 10) * 10) + (uint8) val;
|
|
break;
|
|
case TOD_10MIN:
|
|
td->min = ((uint8) val * 10) + (td->min % 10);
|
|
break;
|
|
case TOD_1HOUR:
|
|
td->hour = ((td->hour / 10) * 10) + (uint8) val;
|
|
break;
|
|
case TOD_10HOUR:
|
|
td->hour = ((uint8) val * 10) + (td->hour % 10);
|
|
break;
|
|
case TOD_1DAY:
|
|
td->day = ((td->day / 10) * 10) + (uint8) val;
|
|
break;
|
|
case TOD_10DAY:
|
|
td->day = ((uint8) val * 10) + (td->day % 10);
|
|
break;
|
|
case TOD_1MON:
|
|
td->mon = ((td->mon / 10) * 10) + (uint8) val;
|
|
break;
|
|
case TOD_10MON:
|
|
td->mon = ((uint8) val * 10) + (td->mon % 10);
|
|
break;
|
|
case TOD_1YEAR:
|
|
#if defined(REV3)
|
|
td->year = ((td->year / 10) * 10) + (uint8) val;
|
|
#else
|
|
td->lyear = (uint8) val;
|
|
#endif
|
|
break;
|
|
#if defined(REV3)
|
|
case TOD_10YEAR:
|
|
td->year = ((uint8) val * 10) + (td->year % 10);
|
|
break;
|
|
case TOD_SET_INT:
|
|
td->clkset = (uint8) val;
|
|
if (!TOD_12H(td)) {
|
|
/* The AM/PM indicator is always 0 if not in 12H mode */
|
|
td->clkset &= ~(CLKSET_PM);
|
|
}
|
|
td->lyear = (val >> 2) & 3;
|
|
break;
|
|
#else
|
|
case TOD_STARTSTOP:
|
|
tod_enabled = val & 1;
|
|
break;
|
|
#endif
|
|
case TOD_WDAY:
|
|
td->wday = (uint8)val & 0x7;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const char *tod_description(DEVICE *dptr)
|
|
{
|
|
#if defined(REV3)
|
|
return("MM58274C real time clock");
|
|
#else
|
|
return("MM58174A real time clock");
|
|
#endif
|
|
}
|
|
|
|
t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
|
|
{
|
|
char dname[10];
|
|
|
|
#if defined(REV3)
|
|
snprintf(dname, 10, "MM58274C");
|
|
#else
|
|
snprintf(dname, 10, "MM58174A");
|
|
#endif
|
|
|
|
fprintf(st, "%s Time-Of-Day Clock (%s)\n\n", dname, dptr->name);
|
|
fprintf(st, "The %s controller simulates a National Semiconductor %s\n", dptr->name, dname);
|
|
fprintf(st, "real time clock. This clock keeps track of the current system time\n");
|
|
fprintf(st, "and date.\n\n");
|
|
fprintf(st, "In order to preserve simulated calendar time between simulator runs,\n");
|
|
fprintf(st, "the %s clock may be attached to a file which stores its state while\n", dptr->name);
|
|
fprintf(st, "the simulator is not running, e.g.:\n\n");
|
|
fprintf(st, " sim> ATTACH TOD <filename>\n");
|
|
fprint_show_help(st, dptr);
|
|
fprint_reg_help(st, dptr);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
#if defined(REV3)
|
|
|
|
/*
|
|
* Fault Register
|
|
*
|
|
* The Fault Register is composed of two 32-bit registers at addresses
|
|
* 0x4C000 and 0x4D000. These latch state of the last address to cause
|
|
* a CPU fault.
|
|
*
|
|
* Bits 00-25: Physical memory address bits
|
|
*
|
|
* Fault Register 2 does double duty. It actually consists of four
|
|
* words, each of which maps to a memory slot on the system board. If
|
|
* occupied, it records the size of memory equipped in the slot, as
|
|
* well as information about any memory faults.
|
|
*
|
|
*
|
|
* Bits Active Purpose
|
|
* ----------------------------------------------------
|
|
* 31-26 H Upper Halfword ECC Syndrome Bits
|
|
* 25-20 H Lower Halfword ECC Syndeome Bits
|
|
* 19 L I/O Bus or BUB master on Fault
|
|
* 18 H Invert low addr bit 2 (DWORD1)
|
|
* 17 H Decrement low addr by 4
|
|
* 16 L I/O Bus Master on Fault
|
|
* 15 L CPU accessing I/O Peripheral
|
|
* 14 L BUB Slot 0 master on fault
|
|
* 13 L CPU Accessing BUB Peripheral
|
|
* 12-11 H BUB peripheral accessed by CPU
|
|
* 10 L BUB Slot 1 master on fault
|
|
* 9 L BUB Slot 2 master on fault
|
|
* 8 L BUB Slot 3 master on fault
|
|
* 7-3 N/A Not Used
|
|
* 2 L Memory Equipped
|
|
* 1-0 H Equipped Memory Size
|
|
*
|
|
*/
|
|
|
|
uint32 flt[2] = {0, 0};
|
|
|
|
UNIT flt_unit = {
|
|
UDATA(NULL, UNIT_FIX+UNIT_BINK, 64)
|
|
};
|
|
|
|
REG flt_reg[] = {
|
|
{ HRDATAD(FLT1, flt[0], 32, "Fault Register 1") },
|
|
{ HRDATAD(FLT2, flt[1], 32, "Fault Register 2") },
|
|
{ NULL }
|
|
};
|
|
|
|
DEVICE flt_dev = {
|
|
"FLT", &flt_unit, flt_reg, NULL,
|
|
1, 16, 32, 1, 16, 32,
|
|
NULL, NULL, NULL,
|
|
NULL, NULL, NULL,
|
|
NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL,
|
|
&flt_help, NULL, NULL,
|
|
&flt_description
|
|
};
|
|
|
|
/*
|
|
* Return the configured memory size for a given backplane location.
|
|
*/
|
|
static uint32 mem_size(uint8 slot) {
|
|
switch(MEM_SIZE) {
|
|
case MSIZ_8M:
|
|
if (slot <= 1) {
|
|
return MEM_EQP|MEM_4M;
|
|
} else {
|
|
return 0;
|
|
}
|
|
case MSIZ_16M:
|
|
return MEM_EQP|MEM_4M;
|
|
case MSIZ_32M:
|
|
if (slot <= 1) {
|
|
return MEM_EQP|MEM_16M;
|
|
} else {
|
|
return 0;
|
|
}
|
|
case MSIZ_64M:
|
|
return MEM_EQP|MEM_16M;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32 flt_read(uint32 pa, size_t size)
|
|
{
|
|
sim_debug(EXECUTE_MSG, &flt_dev,
|
|
"Read from FLT Register at %x\n",
|
|
pa);
|
|
|
|
switch(pa) {
|
|
case FLTLBASE:
|
|
return flt[0];
|
|
case FLTHBASE:
|
|
return (flt[1] & FLT_MSK) | mem_size(0);
|
|
case FLTHBASE + 4:
|
|
return (flt[1] & FLT_MSK) | mem_size(1);
|
|
case FLTHBASE + 8:
|
|
return (flt[1] & FLT_MSK) | mem_size(2);
|
|
case FLTHBASE + 12:
|
|
return (flt[1] & FLT_MSK) | mem_size(3);
|
|
default:
|
|
sim_debug(EXECUTE_MSG, &flt_dev,
|
|
"Read from FLT Register at %x: FAILURE, NO DATA!!!!\n",
|
|
pa);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void flt_write(uint32 pa, uint32 val, size_t size)
|
|
{
|
|
sim_debug(EXECUTE_MSG, &flt_dev,
|
|
"Write to FLT Register at %x (val=%x)\n",
|
|
pa, val);
|
|
|
|
return;
|
|
}
|
|
|
|
t_stat flt_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
|
|
{
|
|
fprintf(st, "Fault Register\n\n");
|
|
fprintf(st, "The %s device is a pair of 32-bit registers that hold information about\n", dptr->name);
|
|
fprintf(st, "system memory faults.\n");
|
|
fprint_show_help(st, dptr);
|
|
fprint_reg_help(st, dptr);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
const char *flt_description(DEVICE *dptr)
|
|
{
|
|
return "Fault Register";
|
|
}
|
|
|
|
#endif
|