1
0
mirror of https://github.com/simh/simh.git synced 2026-05-05 23:34:21 +00:00

3B2-700 Initial Public Release

This commit introduces dozens of changes to make the 3B2-700 simulator
fully functional and ready for wider use. In addition to 3B2-700
availability, this commit includes a tremendous amount of refactoring
of the 3B2-400 and common code to make the project structure easier to
maintain and reason about.
This commit is contained in:
Seth Morabito
2022-11-07 10:49:45 -10:00
committed by Mark Pizzolato
parent 48f1430bd0
commit 88916c7bf1
60 changed files with 4595 additions and 4593 deletions

View File

@@ -1,6 +1,6 @@
/* 3b2_stddev.c: AT&T 3B2 miscellaneous system board devices.
/* 3b2_stddev.c: Miscellaneous System Board Devices
Copyright (c) 2017, Seth J. Morabito
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
@@ -33,14 +33,15 @@
following 3B2 devices:
- nvram Non-Volatile RAM
- tod MM58174A Real-Time-Clock
- flt Fault Register
- 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" },
@@ -124,23 +125,19 @@ t_stat nvram_reset(DEVICE *dptr)
const char *nvram_description(DEVICE *dptr)
{
return "Non-volatile memory, used to store system state between boots.\n";
return "Non-Volatile RAM.\n";
}
t_stat nvram_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf(st,
"The NVRAM holds system state between boots. On initial startup,\n"
"if no valid NVRAM file is attached, you will see the message:\n"
"\n"
" FW ERROR 1-01: NVRAM SANITY FAILURE\n"
" DEFAULT VALUES ASSUMED\n"
" IF REPEATED, CHECK THE BATTERY\n"
"\n"
"To avoid this message on subsequent boots, attach a new NVRAM file\n"
"with the SIMH command:\n"
"\n"
" sim> ATTACH NVRAM <filename>\n");
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;
}
@@ -177,7 +174,6 @@ t_stat nvram_detach(UNIT *uptr)
return r;
}
uint32 nvram_read(uint32 pa, size_t size)
{
uint32 offset = pa - NVRBASE;
@@ -229,25 +225,103 @@ void nvram_write(uint32 pa, uint32 val, size_t size)
}
/*
* MM58174A Time Of Day Clock
* 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(NULL, UNIT_FIX+UNIT_BINK, sizeof(TOD_DATA))
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, NULL, NULL,
"TOD", &tod_unit, tod_reg, NULL,
1, 16, 8, 4, 16, 32,
NULL, NULL, &tod_reset,
NULL, &tod_attach, &tod_detach,
NULL, 0, 0, sys_deb_tab, NULL, NULL,
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) {
@@ -255,6 +329,14 @@ t_stat tod_reset(DEVICE *dptr)
}
}
/* 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;
}
@@ -288,124 +370,191 @@ t_stat tod_detach(UNIT *uptr)
return r;
}
/*
* Re-set the tod_data registers based on the current simulated time.
*/
void tod_resync()
static t_stat tod_svc(UNIT *uptr)
{
struct timespec now;
struct tm tm;
time_t sec;
TOD_DATA *td = (TOD_DATA *)tod_unit.filebuf;
TOD_DATA *td = (TOD_DATA *)uptr->filebuf;
int32 t;
sim_rtcn_get_time(&now, TMR_CLK);
sec = now.tv_sec - td->delta;
/* Re-sync the recorded system time once every second */
if (tod_enabled) {
tod_tick(uptr);
/* Populate the tm struct based on current sim_time */
tm = *gmtime(&sec);
td->tsec = 0;
td->unit_sec = tm.tm_sec % 10;
td->ten_sec = tm.tm_sec / 10;
td->unit_min = tm.tm_min % 10;
td->ten_min = tm.tm_min / 10;
td->unit_hour = tm.tm_hour % 10;
td->ten_hour = tm.tm_hour / 10;
/* tm struct stores as 0-11, tod struct as 1-12 */
td->unit_mon = (tm.tm_mon + 1) % 10;
td->ten_mon = (tm.tm_mon + 1) / 10;
td->unit_day = tm.tm_mday % 10;
td->ten_day = tm.tm_mday / 10;
td->year = 1 << ((tm.tm_year - 1) % 4);
}
/*
* Re-calculate the delta between real time and simulated time
*/
void tod_update_delta()
{
struct timespec now;
struct tm tm = {0};
time_t ssec;
TOD_DATA *td = (TOD_DATA *)tod_unit.filebuf;
sim_rtcn_get_time(&now, TMR_CLK);
/* Let the host decide if it is DST or not */
tm.tm_isdst = -1;
/* Compute the simulated seconds value */
tm.tm_sec = (td->ten_sec * 10) + td->unit_sec;
tm.tm_min = (td->ten_min * 10) + td->unit_min;
tm.tm_hour = (td->ten_hour * 10) + td->unit_hour;
/* tm struct stores as 0-11, tod struct as 1-12 */
tm.tm_mon = ((td->ten_mon * 10) + td->unit_mon) - 1;
tm.tm_mday = (td->ten_day * 10) + td->unit_day;
/* We're forced to do this weird arithmetic because the TOD chip
* used by the 3B2 does not store the year. It only stores the
* offset from the nearest leap year. */
switch(td->year) {
case 1: /* Leap Year - 3 */
tm.tm_year = 85;
break;
case 2: /* Leap Year - 2 */
tm.tm_year = 86;
break;
case 4: /* Leap Year - 1 */
tm.tm_year = 87;
break;
case 8: /* Leap Year */
tm.tm_year = 88;
break;
default:
break;
if (td->tsec == 0) {
tod_resync(uptr);
}
}
ssec = mktime(&tm);
td->delta = (int32)(now.tv_sec - ssec);
t = sim_rtcn_calb(CLK_TPS, TMR_CLK);
sim_activate_after(uptr, 1000000/CLK_TPS);
tmr_poll = t;
tmxr_poll = t;
AIO_SET_INTERRUPT_LATENCY(tmr_poll * CLK_TPS);
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;
uint8 reg, val;
TOD_DATA *td = (TOD_DATA *)(tod_unit.filebuf);
tod_resync();
reg = pa - TODBASE;
reg = pa & 0xfc;
switch(reg) {
case 0x04: /* 1/10 Sec */
return td->tsec;
case 0x08: /* 1 Sec */
return td->unit_sec;
case 0x0c: /* 10 Sec */
return td->ten_sec;
case 0x10: /* 1 Min */
return td->unit_min;
case 0x14: /* 10 Min */
return td->ten_min;
case 0x18: /* 1 Hour */
return td->unit_hour;
case 0x1c: /* 10 Hour */
return td->ten_hour;
case 0x20: /* 1 Day */
return td->unit_day;
case 0x24: /* 10 Day */
return td->ten_day;
case 0x28: /* Day of Week */
return td->wday;
case 0x2c: /* 1 Month */
return td->unit_mon;
case 0x30: /* 10 Month */
return td->ten_mon;
case 0x34: /* Year */
return td->year;
#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 0;
return val;
}
void tod_write(uint32 pa, uint32 val, size_t size)
@@ -413,52 +562,83 @@ 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 - TODBASE; */
reg = pa & 0xfc;
switch(reg) {
case 0x04: /* 1/10 Sec */
td->tsec = (uint8) val;
break;
case 0x08: /* 1 Sec */
td->unit_sec = (uint8) val;
break;
case 0x0c: /* 10 Sec */
td->ten_sec = (uint8) val;
break;
case 0x10: /* 1 Min */
td->unit_min = (uint8) val;
break;
case 0x14: /* 10 Min */
td->ten_min = (uint8) val;
break;
case 0x18: /* 1 Hour */
td->unit_hour = (uint8) val;
break;
case 0x1c: /* 10 Hour */
td->ten_hour = (uint8) val;
break;
case 0x20: /* 1 Day */
td->unit_day = (uint8) val;
break;
case 0x24: /* 10 Day */
td->ten_day = (uint8) val;
break;
case 0x28: /* Day of Week */
td->wday = (uint8) val;
break;
case 0x2c: /* 1 Month */
td->unit_mon = (uint8) val;
break;
case 0x30: /* 10 Month */
td->ten_mon = (uint8) val;
break;
case 0x34: /* Year */
td->year = (uint8) val;
break;
case 0x38:
if (val & 1) {
tod_update_delta();
#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;
@@ -467,27 +647,33 @@ void tod_write(uint32 pa, uint32 val, size_t size)
const char *tod_description(DEVICE *dptr)
{
return "Time-of-Day clock, used to store system time between boots.\n";
#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)
{
fprintf(st,
"The TOD is a battery-backed time-of-day clock that holds system\n"
"time between boots. In order to store the time, a file must be\n"
"attached to the TOD device with the SIMH command:\n"
"\n"
" sim> ATTACH TOD <filename>\n"
"\n"
"On a newly installed System V Release 3 UNIX system, no system\n"
"time will be stored in the TOD clock. In order to set the system\n"
"time, run the following command from within UNIX (as root):\n"
"\n"
" # sysadm datetime\n"
"\n"
"On subsequent boots, the correct system time will restored from\n"
"from the TOD.\n");
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;
}
@@ -500,44 +686,130 @@ t_stat tod_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr
* 0x4C000 and 0x4D000. These latch state of the last address to cause
* a CPU fault.
*
* Bits 00-25: Physical memory address bits 00-25
* 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_1 = 0;
uint32 flt_2 = 0;
uint32 flt[2] = {0, 0};
UNIT flt_unit = {
UDATA(NULL, UNIT_FIX+UNIT_BINK, 8)
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, 8, 4, 16, 32,
1, 16, 32, 1, 16, 32,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, DEV_DEBUG, 0, sys_deb_tab, NULL, NULL,
NULL, NULL, 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(READ_MSG, &flt_dev,
"[%08x] Read from FLT Register at %x\n",
R[NUM_PC], pa);
return 0;
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(WRITE_MSG, &flt_dev,
"[%08x] Write to FLT Register at %x (val=%x)\n",
R[NUM_PC], pa, val);
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