diff --git a/HP3000/hp3000_atc.c b/HP3000/hp3000_atc.c new file mode 100644 index 00000000..9b5ec6f3 --- /dev/null +++ b/HP3000/hp3000_atc.c @@ -0,0 +1,2849 @@ +/* hp3000_atc.c: HP 3000 30032B Asynchronous Terminal Controller simulator + + Copyright (c) 2014-2016, J. David Bryan + Copyright (c) 2002-2012, Robert M Supnik + + 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 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 names of the authors 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 authors. + + ATCD,ATCC HP 30032B Asynchronous Terminal Controller + + 26-Aug-15 JDB First release version + 31-Jul-15 JDB Passes the terminal control diagnostic (D438A) + 11-Aug-14 JDB Passes the terminal data diagnostic (D427A) + 28-Jul-14 JDB Created from the HP 2100 12920A MUX device simulator + + References: + - 30032B Asynchronous Terminal Controller Installation and Service Manual + (30032-90004, February 1977) + - Stand-Alone HP 30032B Terminal Data Interface Diagnostic + (30032-90011, October 1980) + - Stand-Alone HP 30061A Terminal Controller Interface Diagnostic + (30060-90004, February 1976) + - HP 3000 Series III Engineering Diagrams Set + (30000-90141, April 1980) + + + The HP 30032B Asynchronous Terminal Controller is a 16-channel terminal + multiplexer used with the HP 3000 CX through Series III systems. The ATC + connects from 1 to 16 serial terminals or modems to the HP 3000 at + programmable baud rates from 75 to 2400 bits per second. Character sizes are + also programmable from 5 to 12 bits in length, including the start and stop + bits. Each channel can be independently configured, including for separate + send and receive rates. The ATC is not buffered, so the CPU has to retrieve + each character from a given channel before the next character arrives. To + avoid saturating the CPU with interrupt requests, the ATC maintains an + internal "mini-interrupt" system that queues requests and holds additional + interrupts off until the CPU acknowledges the current request. + + The HP 3000CX and Series I use a dedicated serial interface for the system + console, while user terminals are connected to the ATC. For the Series II + and III, the separate card is eliminated, and channel 0 of the ATC is + reserved for the console. + + This module is an adaptation of the code originally written by Bob Supnik for + the HP2100 MUX simulator. The MUX device simulates an HP 12920A interface + for an HP 2100/1000 computer. The 12920A is an ATC with the HP 3000 I/O bus + connection replaced by an HP 2100 I/O bus connection. Programming and + operation of the two multiplexers are virtually identical. + + The ATC consists of a Terminal Data Interface, which provides direct + connection for 16 serial terminals, and one or two optional Terminal Control + Interfaces, which provides control and status lines for Bell 103 and 202 data + sets, respectively. The ATC base product, order number 30032, consisted of + one TDI card. Option -001 added one TCI, and option -002 added two. A + second ATC subsystem could be added to support an additional 16 terminals or + modems. + + This simulation provides one TDI and one optional TCI. Each of the channels + may be connected either to a Telnet session or a serial port on the host + machine. Channel 0 is connected to the simulation console, which initially + performs I/O to the controlling window but may be rerouted instead to a + Telnet session or serial port, if desired. Additional channel configuration + options select the input mode (upshifted or normal), output mode (8-bit, + 7-bit, printable, or upshifted), and whether the HP-standard ENQ/ACK + handshaking is done by the external device or internally by the simulator. + + A device mode specifies whether terminals or diagnostic loopback cables are + connected to the TDI and TCI. Enabling the diagnostic mode simulates the + installation of eight HP 30062-60003 diagnostic test (loopback) cables + between channels 0-1, 2-3, etc., as required by the multiplexer diagnostics. + In this mode, sending data on one channel automatically receives the same + data on the alternate channel. In addition, all Telnet and serial sessions + are disconnected, and the TDI is detached from the listening port. While in + diagnostic mode, the ATTACH command is not allowed. Enabling terminal mode + allows the TDI to be attached to accept incoming connections again. + + Another device mode specifies whether the TDI operates in real-time or + optimized ("fast") timing mode. In the former, character send and receive + events occur at approximately the same rate (in machine instructions) as in + hardware. The latter mode increases the rate to the maximum value consistent + with correct operation in MPE. + + Both the TDI and TCI are normally enabled, although the TCI will not be used + unless MPE is configured to use data sets on one or more channels. When so + configured, logging off will cause the channel to disconnect the Telnet + session or drop the Data Terminal Ready signal on the serial port. A channel + controlled by the TCI will be marked as "data set" in a unit listing; + channels not controlled will be marked as "direct". + + The TDI and TCI may be disabled, if desired, although the TDI must be + detached from the listening port first. Disabling the TDI does not affect + the simulation console, as the CPU process clock will take over console + polling automatically. + + The Terminal Data Interface provides 16 send channels, 16 receive channels, + and 5 auxiliary channels. The auxiliary channels are receive-only and do + not connect to external devices. Rather, they may be connected as a group to + one or more of the other channels. Their primary purpose is to diagnose + conditions (e.g., baud rate) on the connected channel(s). + + In hardware, a recirculating memory stores seven 8-bit words of data, + parameters, and status for each of the 37 channels. A set of registers form + a "window" into the recirculating memory, and the memory makes one complete + pass every 69.44 microseconds. Serial transfer rates are determined by each + channel's parameter word, which specifies the number of recirculations that + occur for each bit sent or received. + + In simulation, the memory is represented by separate buffer, parameter, and + status arrays. Recirculation is simulated by indexing through each of the + arrays in sequence. + + + The TDI responds only to direct I/O instructions, as follows: + + TDI Control Word Format (CIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M | R | channel number | - - - - - - - | E | A | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + M = master reset + R = reset interrupts + E = enable store of preceding data or parameter word to memory + A = acknowledge interrupt + + + TDI Status Word Format (TIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | S | D | I | - | C | R | L | B | - - - - - - - - | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + S = SIO OK (always 0) + D = direct read/write I/O OK + I = interrupt request + C = read/write completion flag + R = receive/send (0/1) character interrupt + L = character lost + B = break status + + + TDI Parameter Word Format (WIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 1 | R | I | E | D | char size | baud rate | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + R = receive/send (0/1) configuration + I = enable channel completion interrupt + E = enable echo (receive) or generate parity (send) + D = diagnose using the auxiliary channels + + Character size: + The three least-significant bits of the sum of the data, parity, and stop + bits. For example, 7E1 is 1001, so 001 is coded. + + Baud rate: + The value (14400 / device bit rate) - 1. For example, 2400 baud is 005. + + + TDI Output Data Word Format (WIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | - - | S | send data | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + S = sync bit + data = right-justified with leading ones + + + TDI Input Data Word Format (RIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | channel | P | receive data | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + P = computed parity + data = right-justified with leading ones + + + The Terminal Control Interface provides two serial control outputs and two + serial status inputs for each of 16 channels. The first TCI connects to the + Request to Send (CA) and Data Terminal Ready (CD) control lines and the Data + Carrier Detect (CF) and Data Set Ready (CC) status lines. Addressable + latches hold the control line values and assert them continuously to the 16 + channels. In addition, a 16-word by 4-bit RAM holds the expected state for + each channel's status lines and the corresponding interrupt enable bits to + provide notification if those lines change. + + The TCI responds only to direct I/O instructions, as follows: + + + TCI Control Word Format (CIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M | R | S | U | channel | W | X | Q | T | Y | Z | C | D | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + M = master reset + R = reset interrupts + S = scan status + U = enable DCD/DSR state update + W = enable RTS change + X = enable DTR change + Q = new RTS state + T = new DTR state + Y = DCD interrupt enabled + Z = DSR interrupt enabled + C = expected DCD state + D = expected DSR state + + + TCI Status Word Format (TIO or RIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | I | 1 | channel | 0 | 0 | J | K | Y | Z | C | D | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + I = interrupt request + J = DCD interrupt present + K = DSR interrupt present + Y = DCD interrupt enabled + Z = DSR interrupt enabled + C = current DCD state + D = current DSR state + + + Implementation notes: + + 1. The UNIT_MODEM flag indicates that a channel is controlled by the TCI. + However, no modifier entry is provided, nor is one needed, as the flag is + set automatically when the TCI first initializes the channel. MPE + defines separate terminal subtype numbers for directly connected + terminals and modem-connected terminals, which are set at system + generation time. + + 2. Both TMXR_VALID and SCPE_KFLAG are set on internally generated ACKs only + so that a debug trace will record the generation correctly. +*/ + + + +#include + +#include "hp3000_defs.h" +#include "hp3000_io.h" + +#include "sim_tmxr.h" + + + +/* Program limits */ + +#define TERM_COUNT 16 /* number of terminal channels */ +#define AUX_COUNT 5 /* number of auxiliary channels */ +#define POLL_COUNT 1 /* number of poll units */ + +#define RECV_CHAN_COUNT (TERM_COUNT + AUX_COUNT) /* number of receive channels */ +#define SEND_CHAN_COUNT TERM_COUNT /* number of send channels */ +#define UNIT_COUNT (TERM_COUNT + POLL_COUNT) /* number of units */ + +#define FIRST_TERM 0 /* first terminal index */ +#define LAST_TERM (FIRST_TERM + TERM_COUNT - 1) /* last terminal index */ +#define FIRST_AUX TERM_COUNT /* first auxiliary index */ +#define LAST_AUX (FIRST_AUX + AUX_COUNT - 1) /* last auxiliary index */ + + +/* Program constants */ + +#define FAST_IO_TIME 500 /* initial fast receive/send time in event ticks */ + +#define POLL_RATE 100 /* poll 100 times per second (unless synchronized) */ +#define POLL_TIME mS (10) /* poll time is 10 milliseconds */ + +#define NUL '\000' /* null */ +#define ENQ '\005' /* enquire */ +#define ACK '\006' /* acknowledge */ +#define ASCII_MASK 000177 /* 7-bit ASCII character set mask */ + +#define GEN_ACK (TMXR_VALID | SCPE_KFLAG | ACK) /* a generated ACK character */ + +#define SCAN_ALL (-1) /* scan all channels for completion */ + + +/* Parity functions derived from the global lookup table */ + +#define RECV_PARITY(c) (odd_parity [(c) & D8_MASK] ? 0 : DDR_PARITY) +#define SEND_PARITY(c) (odd_parity [(c) & D8_MASK] ? 0 : DDS_PARITY) + + +/* Debug flags */ + +#define DEB_CSRW (1 << 0) /* trace command initiations and completions */ +#define DEB_XFER (1 << 1) /* trace data receptions and transmissions */ +#define DEB_IOB (1 << 2) /* trace I/O bus signals and data words */ +#define DEB_SERV (1 << 3) /* trace channel service scheduling calls */ +#define DEB_PSERV (1 << 4) /* trace poll service scheduling calls */ + + +/* Per-unit state */ + +#define recv_time u3 /* realistic receive time in event ticks */ +#define send_time u4 /* realistic send time in event ticks */ +#define stop_bits u5 /* stop bits to be added to each character received */ + + +/* Device flags */ + +#define DEV_DIAG_SHIFT (DEV_V_UF + 0) /* diagnostic loopback */ +#define DEV_REALTIME_SHIFT (DEV_V_UF + 1) /* timing mode is realistic */ + +#define DEV_DIAG (1 << DEV_DIAG_SHIFT) /* diagnostic mode flag */ +#define DEV_REALTIME (1 << DEV_REALTIME_SHIFT) /* realistic timing flag */ + + +/* Unit flags */ + +#define UNIT_CAPSLOCK_SHIFT (TTUF_V_UF + 0) /* caps lock mode */ +#define UNIT_LOCALACK_SHIFT (TTUF_V_UF + 1) /* local ACK mode */ +#define UNIT_MODEM_SHIFT (TTUF_V_UF + 2) /* modem control */ + +#define UNIT_CAPSLOCK (1 << UNIT_CAPSLOCK_SHIFT) /* caps lock is down flag */ +#define UNIT_LOCALACK (1 << UNIT_LOCALACK_SHIFT) /* ENQ/ACK mode is local flag */ +#define UNIT_MODEM (1 << UNIT_MODEM_SHIFT) /* channel connects to a data set flag */ + + +/* Unit references */ + +#define line_unit atcd_unit /* receive/send channel units */ +#define poll_unit atcd_unit [LAST_TERM + 1] /* input polling unit */ + + +/* Activation reasons */ + +typedef enum { + Receive, + Send, + Loop, + Stall + } ACTIVATOR; + + +/* TDI control word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M | R | channel number | - - - - - - - | E | A | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define DCN_MR 0100000 /* (M) master reset */ +#define DCN_IRQ_RESET 0040000 /* (R) interrupt request reset */ +#define DCN_CHAN_MASK 0037000 /* channel number mask */ +#define DCN_ENABLE 0000002 /* (E) enable store of preceding data or parameter word */ +#define DCN_ACKN 0000001 /* (A) acknowledge interrupt */ + +#define DCN_CHAN_SHIFT 9 /* channel number alignment shift */ + +#define DCN_CHAN(c) (((c) & DCN_CHAN_MASK) >> DCN_CHAN_SHIFT) + +static const BITSET_NAME tdi_control_names [] = { /* TDI control word names */ + "master reset", /* bit 0 */ + "reset interrupt", /* 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 */ + NULL, /* bit 10 */ + NULL, /* bit 11 */ + NULL, /* bit 12 */ + NULL, /* bit 13 */ + "store word", /* bit 14 */ + "acknowledge interrupt" /* bit 15 */ + }; + +static const BITSET_FORMAT tdi_control_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_control_names, 0, msb_first, no_alt, no_bar) }; + + +/* TDI status word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | - | D | I | - | C | R | L | B | - - - - - - - - | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define DST_DIO_OK 0040000 /* (D) direct I/O OK to use */ +#define DST_IRQ 0020000 /* (I) interrupt requested */ +#define DST_COMPLETE 0004000 /* (C) operation is complete and channel is ready to interrupt */ +#define DST_SEND_IRQ 0002000 /* (R) interrupt request is for character sent */ +#define DST_CHAR_LOST 0001000 /* (L) character was lost */ +#define DST_BREAK 0000400 /* (B) break occurred */ +#define DST_DIAGNOSE 0000000 /* status is from an auxiliary channel (not used on ATC) */ + +#define DST_CHAN(n) 0 /* position channel number for status (not used on ATC) */ + +static const BITSET_NAME tdi_status_names [] = { /* TDI status word names */ + "DIO OK", /* bit 1 */ + "interrupt", /* bit 2 */ + NULL, /* bit 3 */ + "complete", /* bit 4 */ + "\1send\0receive", /* bit 5 */ + "lost", /* bit 6 */ + "break" /* bit 7 */ + }; + +static const BITSET_FORMAT tdi_status_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_status_names, 8, msb_first, has_alt, no_bar) }; + + + +/* TDI parameter word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 1 | R | I | E | D | char size | baud rate | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + The baud rate is encoded as 14400 / device_bit_rate - 1, but the manual says + to round the result, so that, e.g., the 110 baud rate encoding of 129.91 is + rounded to 130. To reconstruct the rate without floating-point calculations, + the parameter print routine uses: + + baud_rate = (2 * 14400 / (encoded_rate + 1) + 1) / 2 + + ...which is equivalent to: + + baud_rate = (int) (14400 / (encoded_rate + 1) + 0.5) + + The multiplexer pads the received character data to the left with one-bits. + + The PAD_BITS function generates the pad bits, assuming that the received + character transmission has one stop bit. This isn't always correct, e.g., a + Teleprinter uses two stop bits at 110 baud, but there's no way to reconstruct + the number of stop bits from the receive parameter word. +*/ + +#define DPI_IS_PARAM 0100000 /* value is a parameter (always set) */ +#define DPI_IS_SEND 0040000 /* (R) value is a send parameter */ +#define DPI_ENABLE_IRQ 0020000 /* (I) enable interrupt requests */ +#define DPI_ENABLE_PARITY 0010000 /* (E) enable parity for send */ +#define DPI_ENABLE_ECHO 0010000 /* (E) enable echo for receive */ +#define DPI_DIAGNOSE 0004000 /* (D) connect to the auxiliary channels */ +#define DPI_SIZE_MASK 0003400 /* character size mask */ +#define DPI_RATE_MASK 0000377 /* baud rate mask */ + +#define DPI_CHAR_CONFIG (DPI_SIZE_MASK | DPI_RATE_MASK) /* character configuration data */ + +#define DPI_SIZE_SHIFT 8 /* character size alignment shift */ +#define DPI_RATE_SHIFT 0 /* baud rate alignment shift */ + +#define DPI_CHAR_SIZE(p) (((p) & DPI_SIZE_MASK) >> DPI_SIZE_SHIFT) +#define DPI_BAUD_RATE(p) (((p) & DPI_RATE_MASK) >> DPI_RATE_SHIFT) + +#define BAUD_RATE(p) ((28800 / (DPI_BAUD_RATE (p) + 1) + 1) / 2) + +#define PAD_BITS(c) (~((1 << bits_per_char [DPI_CHAR_SIZE (c)] - 2) - 1)) + + +static const uint32 bits_per_char [8] = { /* bits per character, indexed by DPI_CHAR_SIZE encoding */ + 9, 10, 11, 12, 5, 6, 7, 8 + }; + +static const BITSET_NAME tdi_parameter_names [] = { /* TDI parameter word names */ + "\1send\0receive", /* bit 1 */ + "enable interrupt", /* bit 2 */ + "enable parity/echo", /* bit 3 */ + "diagnose" /* bit 4 */ + }; + +static const BITSET_FORMAT tdi_parameter_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_parameter_names, 11, msb_first, has_alt, append_bar) }; + + +/* TDI output (send) data word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | - - | S | send data | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define DDS_IS_SEND 0040000 /* value is a send data word (always set) */ +#define DDS_SYNC 0004000 /* (S) sync */ +#define DDS_DATA_MASK 0003777 /* data value mask */ +#define DDS_PARITY 0000200 /* data parity bit */ + +#define DDS_MARK (DDS_SYNC | DDS_DATA_MASK) /* all-mark character */ + +#define DDS_DATA(d) ((d) & DDS_DATA_MASK) + + +static const BITSET_NAME tdi_output_data_names [] = { /* TDI output data word names */ + "send", /* bit 1 */ + NULL, /* bit 2 */ + NULL, /* bit 3 */ + "sync" /* bit 4 */ + }; + +static const BITSET_FORMAT tdi_output_data_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_output_data_names, 11, msb_first, no_alt, append_bar) }; + + +/* TDI input (receive) data word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | channel | P | receive data | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define DDR_CHAN_MASK 0174000 /* channel number mask */ +#define DDR_PARITY 0002000 /* (P) computed parity bit */ +#define DDR_DATA_MASK 0001777 /* data value mask */ + +#define DDR_CHAN_SHIFT 11 /* channel number alignment shift */ +#define DDR_DATA_SHIFT 0 /* data alignment shift */ + +#define DDR_CHAN(n) ((n) << DDR_CHAN_SHIFT & DDR_CHAN_MASK) +#define DDR_DATA(d) ((d) << DDR_DATA_SHIFT & DDR_DATA_MASK) + +#define DDR_TO_CHAN(w) (((w) & DDR_CHAN_MASK) >> DDR_CHAN_SHIFT) +#define DDR_TO_DATA(w) (((w) & DDR_DATA_MASK) >> DDR_DATA_SHIFT) + + +static const BITSET_NAME tdi_input_data_names [] = { /* TDI input data word names */ + "\1odd parity\0even parity", /* bit 5 */ + }; + +static const BITSET_FORMAT tdi_input_data_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_input_data_names, 10, msb_first, has_alt, append_bar) }; + + +/* TCI control word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M | R | S | U | channel | W | X | Q | T | Y | Z | C | D | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define CCN_MR 0100000 /* (M) master reset */ +#define CCN_IRQ_RESET 0040000 /* (R) interrupt request reset */ +#define CCN_SCAN 0020000 /* (S) scan enable */ +#define CCN_UPDATE 0010000 /* (U) update enable */ +#define CCN_CHAN_MASK 0007400 /* channel number mask */ +#define CCN_ECX_MASK 0000300 /* control output enable mask */ +#define CCN_EC2 0000200 /* (W) C2 output enable */ +#define CCN_EC1 0000100 /* (X) C1 output enable */ +#define CCN_CX_MASK 0000060 /* output mask */ +#define CCN_C2 0000040 /* (Q) C2 output [RTS] */ +#define CCN_C1 0000020 /* (T) C1 output [DTR] */ +#define CCN_STAT_MASK 0000017 /* status RAM mask */ +#define CCN_ESX_MASK 0000014 /* status interrupt enable mask */ +#define CCN_ES2 0000010 /* (Y) S2 interrupt enable */ +#define CCN_ES1 0000004 /* (Z) S1 interrupt enable */ +#define CCN_SX_MASK 0000003 /* status mask */ +#define CCN_S2 0000002 /* (C) S2 status [DCD]*/ +#define CCN_S1 0000001 /* (D) S1 status [DSR] */ + +#define CCN_CHAN_SHIFT 8 /* channel number alignment shift */ +#define CCN_CX_SHIFT 4 /* control alignment shift */ +#define CCN_ECX_SHIFT 2 /* control output enable alignment shift (to Cx) */ +#define CCN_ESX_SHIFT 2 /* status interrupt enable alignment shift */ + +#define CCN_CHAN(c) (((c) & CCN_CHAN_MASK) >> CCN_CHAN_SHIFT) +#define CCN_ECX(c) (((c) & CCN_ECX_MASK) >> CCN_ECX_SHIFT) +#define CCN_CX(c) (((c) & CCN_CX_MASK) >> CCN_CX_SHIFT) +#define CCN_ESX(c) (((c) & CCN_ESX_MASK) >> CCN_ESX_SHIFT) + +static const BITSET_NAME tci_control_names [] = { /* TCI control word names */ + "master reset", /* bit 0 */ + "reset interrupt", /* bit 1 */ + "scan", /* bit 2 */ + "update", /* bit 3 */ + NULL, /* bit 4 */ + NULL, /* bit 5 */ + NULL, /* bit 6 */ + NULL, /* bit 7 */ + "EC2", /* bit 8 */ + "EC1", /* bit 9 */ + "\1C2\0~C2", /* bit 10 */ + "\1C1\0~C1", /* bit 11 */ + "ES2", /* bit 12 */ + "ES1", /* bit 13 */ + "\1S2\0~S2", /* bit 14 */ + "\1S1\0~S1" /* bit 15 */ + }; + +static const BITSET_FORMAT tci_control_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tci_control_names, 0, msb_first, has_alt, no_bar) }; + + +/* TCI status word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | - | 1 | I | 1 | channel | - | - | J | K | Y | Z | C | D | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define CST_DIO_OK 0040000 /* direct I/O OK to use (always set) */ +#define CST_IRQ 0020000 /* (I) interrupt request */ +#define CST_ON 0010000 /* (always set) */ +#define CST_CHAN_MASK 0007400 /* channel number mask */ +#define CST_IX_MASK 0000060 /* status interrupt mask */ +#define CST_I2 0000040 /* (J) S2 interrupt */ +#define CST_I1 0000020 /* (K) S1 interrupt */ +#define CST_ESX_MASK 0000014 /* status interrupt enable mask */ +#define CST_ES2 0000010 /* (Y) S2 interrupt enable */ +#define CST_ES1 0000004 /* (Z) S1 interrupt enable */ +#define CST_SX_MASK 0000003 /* status mask */ +#define CST_S2 0000002 /* (C) S2 status [DCD] */ +#define CST_S1 0000001 /* (D) S1 status [DSR] */ + +#define CST_CHAN_SHIFT 8 /* channel number alignment shift */ +#define CST_IX_SHIFT 4 /* status interrupt alignment shift */ + +#define CST_CHAN(n) ((n) << CST_CHAN_SHIFT & CST_CHAN_MASK) +#define CST_IX(i) ((i) << CST_IX_SHIFT & CST_IX_MASK) + +static const BITSET_NAME tci_status_names [] = { /* TCI status word names */ + "interrupt", /* bit 2 */ + NULL, /* bit 3 */ + NULL, /* bit 4 */ + NULL, /* bit 5 */ + NULL, /* bit 6 */ + NULL, /* bit 7 */ + NULL, /* bit 8 */ + NULL, /* bit 9 */ + "I2", /* bit 10 */ + "I1", /* bit 11 */ + "ES2", /* bit 12 */ + "ES1", /* bit 13 */ + "\1S2\0~S2", /* bit 14 */ + "\1S1\0~S1" /* bit 15 */ + }; + +static const BITSET_FORMAT tci_status_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tci_status_names, 0, msb_first, has_alt, no_bar) }; + + +/* TCI #1 serial line bits */ + +#define RTS CCN_C2 /* TCI #1 C2 = Request to Send */ +#define DTR CCN_C1 /* TCI #1 C1 = Data Terminal Ready */ +#define DCD CCN_S2 /* TCI #1 S2 = Data Carrier Detect */ +#define DSR CCN_S1 /* TCI #1 S1 = Data Set Ready */ + +static const BITSET_NAME tci_line_names [] = { /* TCI serial line status names */ + "RTS", /* bit 10 */ + "DTR", /* bit 11 */ + NULL, /* bit 12 */ + NULL, /* bit 13 */ + "DCD", /* bit 14 */ + "DSR" /* bit 15 */ + }; + +static const BITSET_FORMAT tci_line_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tci_line_names, 0, msb_first, no_alt, no_bar) }; + + +/* ATC global state */ + +t_bool atc_is_polling = TRUE; /* TRUE if the ATC is polling for the simulation console */ + + +/* TDI interface state */ + +static uint16 tdi_control_word = 0; /* control word */ +static uint16 tdi_status_word = 0; /* status word */ +static uint16 tdi_read_word = 0; /* read word */ +static uint16 tdi_write_word = 0; /* write word */ + +static FLIP_FLOP tdi_interrupt_mask = CLEAR; /* interrupt mask flip-flop */ +static FLIP_FLOP tdi_data_flag = CLEAR; /* data flag */ + +static int32 fast_data_time = FAST_IO_TIME; /* fast receive/send time */ + + +/* TDI per-channel state */ + +static uint16 recv_status [RECV_CHAN_COUNT]; /* receive status words */ +static uint16 recv_param [RECV_CHAN_COUNT]; /* receive parameter words */ +static uint16 recv_buffer [RECV_CHAN_COUNT]; /* receive character buffers */ + +static uint16 send_status [SEND_CHAN_COUNT]; /* send status words */ +static uint16 send_param [SEND_CHAN_COUNT]; /* send parameter words */ +static uint16 send_buffer [SEND_CHAN_COUNT]; /* send character buffers */ + + +/* TCI interface state */ + +static uint16 tci_control_word = 0; /* control word */ +static uint16 tci_status_word = 0; /* status word */ +static uint32 tci_cntr = 0; /* channel counter */ + +static FLIP_FLOP tci_interrupt_mask = CLEAR; /* interrupt mask flip-flop */ +static FLIP_FLOP tci_scan = CLEAR; /* scanning enabled flip-flop */ + + +/* TCI per-channel state */ + +static uint8 cntl_status [TERM_COUNT]; /* C2/C1/S2/S1 line status */ +static uint8 cntl_param [TERM_COUNT]; /* ES2/ES1/S2/S1 parameter RAM */ + + +/* ATC local SCP support routines */ + +static CNTLR_INTRF atcd_interface; +static CNTLR_INTRF atcc_interface; + +static t_stat atc_set_endis (UNIT *uptr, int32 value, char *cptr, void *desc); +static t_stat atc_set_mode (UNIT *uptr, int32 value, char *cptr, void *desc); +static t_stat atc_show_mode (FILE *st, UNIT *uptr, int32 value, void *desc); +static t_stat atc_show_status (FILE *st, UNIT *uptr, int32 value, void *desc); + +static t_stat atcd_reset (DEVICE *dptr); +static t_stat atcc_reset (DEVICE *dptr); + +static t_stat atcd_attach (UNIT *uptr, char *cptr); +static t_stat atcd_detach (UNIT *uptr); + + +/* ATC local utility routines */ + +static void tdi_set_interrupt (void); +static void tdi_master_reset (void); +static void tci_master_reset (void); + +static t_stat line_service (UNIT *uptr); +static t_stat poll_service (UNIT *uptr); +static t_stat activate_unit (UNIT *uptr, ACTIVATOR reason); +static uint32 service_time (uint16 control, ACTIVATOR reason); +static void store (uint16 control, uint16 data); +static void receive (int32 channel, int32 data, t_bool loopback); +static void diagnose (uint16 control, int32 data); +static void scan_channels (int32 channel); +static uint16 scan_status (void); + + +/* ATC SCP data structures */ + +DEVICE atcd_dev; /* incomplete device structure */ +DEVICE atcc_dev; /* incomplete device structure */ + + +/* Terminal multiplexer library structures. + + The ATC uses the connection line order feature to bypass channel 0, which is + dedicated to the system console. For convenience, the system console is + connected to the simulation console. As such, it calls the console I/O + routines instead of the terminal multiplexer routines. + + User-defined line order is not supported. +*/ + +static int32 atcd_order [TERM_COUNT] = { /* line connection order */ + 1, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15 }; + +static TMLN atcd_ldsc [TERM_COUNT] = { /* line descriptors */ + { 0 } + }; + +static TMXR atcd_mdsc = { /* multiplexer descriptor */ + TERM_COUNT, /* number of terminal lines */ + 0, /* listening port (reserved) */ + 0, /* master socket (reserved) */ + atcd_ldsc, /* line descriptors */ + atcd_order, /* line connection order */ + NULL /* multiplexer device (derived internally) */ + }; + + +/* Device information blocks */ + +static DIB atcd_dib = { + &atcd_interface, /* device interface */ + 7, /* device number */ + SRNO_UNUSED, /* service request number */ + 0, /* interrupt priority */ + INTMASK_E /* interrupt mask */ + }; + +static DIB atcc_dib = { + &atcc_interface, /* device interface */ + 8, /* device number */ + SRNO_UNUSED, /* service request number */ + 8, /* interrupt priority */ + INTMASK_E /* interrupt mask */ + }; + + +/* Unit lists. + + The first sixteen TDI units correspond to the sixteen multiplexer main + send/receive channels. These handle character I/O via the Telnet library. A + seventeenth unit is responsible for polling for connections and socket I/O. + It also holds the master socket. + + Channel 0 is reserved for the system console and is connected to the + simulation console. As such, it's not likely to be using an HP terminal + emulator, so the default is CAPSLOCK input mode and 7P output mode. The + remainder of the channels default to NOCAPSLOCK and 7B, as they're likely to + be connected to HP terminals or terminal emulators. All channels initially + omit the UNIT_MODEM flag to allow the MPE terminal subtype configuration to + determine which channels support data sets and which do not. + + The TDI line service routine runs only when there are characters to read or + write. It is scheduled either at a realistic rate corresponding to the + programmed baud rate of the channel to be serviced, or at a somewhat faster + optimized rate. The multiplexer connection and input poll must run + continuously, but it may operate much more slowly, as the only requirement is + that it must not present a perceptible lag to human input. It is coscheduled + with the process clock to permit idling. The poll unit is hidden by + disabling it, so as to present a logical picture of the multiplexer to the + user. + + The TCI does not use any units, but a dummy one is defined to satisfy SCP + requirements. + + + Implementation notes: + + 1. There are no units corresponding to the auxiliary receive channels. This + is because reception isn't scheduled on these channels but instead occurs + concurrently with the main channel that is connected to the auxiliary + channels. +*/ + +static UNIT atcd_unit [UNIT_COUNT] = { + { UDATA (&line_service, TT_MODE_7P | UNIT_LOCALACK | UNIT_CAPSLOCK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&poll_service, UNIT_ATTABLE | UNIT_DIS | UNIT_IDLE, POLL_TIME) } /* multiplexer poll unit */ + }; + +static UNIT atcc_unit [] = { /* a dummy unit to satisfy SCP requirements */ + { UDATA (NULL, 0, 0) } + }; + + +/* Register lists. + + The internal state of the TDI and TCI are exposed to the user and to ensure + that SAVE and RESTORE pick up the values. The user may set FTIME explicitly + as needed to accommodate software that does not work with the default + setting. + + + Implementation notes: + + 1. The TCI control and status line register definitions use the VM-defined + FBDATA macro. This macro defines a bit slice longitudinally through an + array. +*/ + +static REG atcd_reg [] = { +/* Macro Name Location Radix Width Offset Depth Flags */ +/* ------ ------ ------------------- ----- ----- ------ ---------------- --------------- */ + { ORDATA (CNTL, tdi_control_word, 16), REG_FIT }, + { ORDATA (STAT, tdi_status_word, 16), REG_FIT }, + { ORDATA (READ, tdi_read_word, 16), REG_A | REG_FIT }, + { ORDATA (WRITE, tdi_write_word, 16), REG_A | REG_FIT }, + { FLDATA (FLAG, tdi_data_flag, 0) }, + { FLDATA (MASK, tdi_interrupt_mask, 0) }, + { DRDATA (FTIME, fast_data_time, 24), PV_LEFT }, + { BRDATA (RSTAT, recv_status, 8, 16, RECV_CHAN_COUNT) }, + { BRDATA (RPARM, recv_param, 8, 16, RECV_CHAN_COUNT) }, + { BRDATA (RBUFR, recv_buffer, 8, 16, RECV_CHAN_COUNT), REG_A }, + { BRDATA (SSTAT, send_status, 8, 16, SEND_CHAN_COUNT) }, + { BRDATA (SPARM, send_param, 8, 16, SEND_CHAN_COUNT) }, + { BRDATA (SBUFR, send_buffer, 8, 16, SEND_CHAN_COUNT), REG_A }, + { FLDATA (POLL, atc_is_polling, 0), REG_HRO }, + { SRDATA (DIB, atcd_dib), REG_HRO }, + + { NULL } + }; + +static REG atcc_reg [] = { +/* Macro Name Location Width Offset Depth Flags */ +/* ------ ------ ------------------- ----- ------ ---------- ------- */ + { ORDATA (CNTL, tci_control_word, 16), REG_FIT }, + { ORDATA (STAT, tci_status_word, 16), REG_FIT }, + { DRDATA (CNTR, tci_cntr, 4) }, + { FLDATA (SCAN, tci_scan, 0) }, + { FLDATA (MASK, tci_interrupt_mask, 0) }, + + { FBDATA (C2, cntl_status, 5, TERM_COUNT) }, + { FBDATA (C1, cntl_status, 4, TERM_COUNT) }, + { FBDATA (S2, cntl_status, 1, TERM_COUNT) }, + { FBDATA (S1, cntl_status, 0, TERM_COUNT) }, + + { FBDATA (ES2, cntl_param, 3, TERM_COUNT) }, + { FBDATA (ES1, cntl_param, 2, TERM_COUNT) }, + { FBDATA (MS2, cntl_param, 1, TERM_COUNT) }, + { FBDATA (MS1, cntl_param, 0, TERM_COUNT) }, + + { SRDATA (DIB, atcc_dib), REG_HRO }, + + { NULL } + }; + + +/* Modifier lists */ + +typedef enum { + Fast_Time, + Real_Time, + Terminal, + Diagnostic + } DEVICE_MODES; + + +static MTAB atcd_mod [] = { +/* Mask Value Match Value Print String Match String Validation Display Descriptor */ +/* ------------- ------------- ---------------- ------------ ---------- ------- ---------- */ + { UNIT_MODEM, UNIT_MODEM, "data set", NULL, NULL, NULL, NULL }, + { UNIT_MODEM, 0, "direct", NULL, NULL, NULL, NULL }, + + { UNIT_LOCALACK, UNIT_LOCALACK, "local ENQ/ACK", "LOCALACK", NULL, NULL, NULL }, + { UNIT_LOCALACK, 0, "remote ENQ/ACK", "REMOTEACK", NULL, NULL, NULL }, + + { UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL }, + { UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL }, + + { TT_MODE, TT_MODE_UC, "UC output", "UC", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_7B, "7b output", "7B", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_7P, "7p output", "7P", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_8B, "8b output", "8B", NULL, NULL, NULL }, + +/* Entry Flags Value Print String Match String Validation Display Descriptor */ +/* -------------------- ----------- ------------- ------------ --------------- ---------------- ------------------- */ + { MTAB_XUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, (void *) &atcd_mdsc }, + { MTAB_XUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, (void *) &atcd_mdsc }, + { MTAB_XUN, 0, NULL, "DISCONNECT", &tmxr_dscln, NULL, (void *) &atcd_mdsc }, + + { MTAB_XDV, Fast_Time, NULL, "FASTTIME", &atc_set_mode, NULL, (void *) &atcd_dev }, + { MTAB_XDV, Real_Time, NULL, "REALTIME", &atc_set_mode, NULL, (void *) &atcd_dev }, + { MTAB_XDV, Terminal, NULL, "TERMINAL", &atc_set_mode, NULL, (void *) &atcd_dev }, + { MTAB_XDV, Diagnostic, NULL, "DIAGNOSTIC", &atc_set_mode, NULL, (void *) &atcd_dev }, + { MTAB_XDV, 0, "MODES", NULL, NULL, &atc_show_mode, (void *) &atcd_dev }, + + { MTAB_XDV, 0, "", NULL, NULL, &atc_show_status, (void *) &atcd_mdsc }, + { MTAB_XDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, (void *) &atcd_mdsc }, + { MTAB_XDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, (void *) &atcd_mdsc }, + + { MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib }, + { MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib }, + { MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib }, + + { MTAB_XDV | MTAB_NMO, 1, NULL, "ENABLED", &atc_set_endis, NULL, NULL }, + { MTAB_XDV | MTAB_NMO, 0, NULL, "DISABLED", &atc_set_endis, NULL, NULL }, + { 0 } + }; + +static MTAB atcc_mod [] = { +/* Entry Flags Value Print String Match String Validation Display Descriptor */ +/* ----------- ----------- ------------ ------------ ------------- -------------- ------------------ */ + { MTAB_XDV, Terminal, NULL, "TERMINAL", &atc_set_mode, NULL, (void *) &atcc_dev }, + { MTAB_XDV, Diagnostic, NULL, "DIAGNOSTIC", &atc_set_mode, NULL, (void *) &atcc_dev }, + { MTAB_XDV, 1, "MODES", NULL, NULL, &atc_show_mode, (void *) &atcc_dev }, + + { MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib }, + { MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib }, + { MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib }, + { 0 } + }; + + +/* Debugging trace lists */ + +static DEBTAB atcd_deb [] = { + { "CSRW", DEB_CSRW }, /* Interface control, status, read, and write actions */ + { "SERV", DEB_SERV }, /* Channel unit service scheduling calls */ + { "PSERV", DEB_PSERV }, /* Poll unit service scheduling calls */ + { "XFER", DEB_XFER }, /* Data receptions and transmissions */ + { "IOBUS", DEB_IOB }, /* Interface I/O bus signals and data words */ + { NULL, 0 } + }; + +static DEBTAB atcc_deb [] = { + { "CSRW", DEB_CSRW }, /* Interface control, status, read, and write actions */ + { "PSERV", DEB_PSERV }, /* Poll unit service scheduling calls */ + { "XFER", DEB_XFER }, /* Control and status line changes */ + { "IOBUS", DEB_IOB }, /* Interface I/O bus signals and data words */ + { NULL, 0 } + }; + + +/* Device descriptors. + + Both devices may be disabled. However, we want to be able to disable the TDI + while it is polling for the simulation console, which the standard SCP + routine will not do (it refuses if any unit is active). So we define our own + DISABLED and ENABLED modifiers and a validation routine that sets or clears + the DEV_DIS flag and then calls atcd_reset. The reset routine cancels or + reenables the poll as indicated. + + + Implementation notes: + + 1. The ATCD device does not specify the DEV_DISABLE flag to avoid the + DISABLED and ENABLED modifiers from being listed twice for a SHOW ATCD + MODIFIERS command. SIMH 3.9 tested for user-defined ENABLED/DISABLED + modifiers and skipped the printing that results from specifying + DEV_DISABLE. SIMH 4.0 no longer does this, so we omit the flag to + suppress the duplicate printing (the flag is otherwise used only to + validate the SET DISABLED command). +*/ + +DEVICE atcd_dev = { + "ATCD", /* device name */ + atcd_unit, /* unit array */ + atcd_reg, /* register array */ + atcd_mod, /* modifier array */ + UNIT_COUNT, /* number of units */ + 10, /* address radix */ + PA_WIDTH, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + DV_WIDTH, /* data width */ + &tmxr_ex, /* examine routine */ + &tmxr_dep, /* deposit routine */ + &atcd_reset, /* reset routine */ + NULL, /* boot routine */ + &atcd_attach, /* attach routine */ + &atcd_detach, /* detach routine */ + &atcd_dib, /* device information block pointer */ + DEV_DEBUG, /* device flags */ + 0, /* debug control flags */ + atcd_deb, /* debug flag name array */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + +DEVICE atcc_dev = { + "ATCC", /* device name */ + atcc_unit, /* unit array */ + atcc_reg, /* register array */ + atcc_mod, /* modifier array */ + 1, /* number of units */ + 10, /* address radix */ + PA_WIDTH, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + DV_WIDTH, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &atcc_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &atcc_dib, /* device information block pointer */ + DEV_DEBUG | DEV_DISABLE, /* device flags */ + 0, /* debug control flags */ + atcc_deb, /* debug flag name array */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + + + +/* ATC local SCP support routines */ + + + +/* TDI interface. + + The interface is installed on the IOP bus and receives direct I/O commands + from the IOP. 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 backplane + signals. + + Before a channel can receive or send, it must be configured. The number of + the channel to configure is set via a CIO instruction, followed by parameters + for baud rate and character size via WIO instructions. Data to be sent is + passed to the interface via WIO, while received data is picked up with RIO + instructions. + + When a channel has completed sending or receiving a character, it will set + its completion flag. If the TDI data flag is clear, indicating that all + prior interrupts have been serviced, a scan of the serviced channel is made + to see if the channel is enabled to interrupt. If it is, the TDI data flag + will be set, the channel flag will be cleared, and an interrupt will be + requested. When the interrupt is serviced and acknowledged, the flag will be + cleared, and the scan will continue to look for other channel flags. + + The status word is set during the scan to reflect the interrupting channel + status. If status bit 3 (DST_COMPLETE) is clear, then status bits 5, 6, and + 7 (DST_SEND_IRQ, DST_CHAR_LOST, and DST_BREAK) retain their values from the + prior send or receive interrupt. + + + Implementation notes: + + 1. In hardware, the DIO OK status bit (bit 1) is denied when a store to the + recirculating memory is pending and is reasserted once the designated + channel rotates into the window and the parameter or data is stored. The + duration of the denial varies from 0 to 69.44 microseconds, depending on + the location of the window in memory when DWRITESTB is asserted. In + simulation, DIO OK is always asserted. + + 2. Receipt of a DRESETINT signal clears the interrupt request and active + flip-flops but does not cancel a request 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. +*/ + +static SIGNALS_DATA atcd_interface (DIB *dibptr, INBOUND_SET inbound_signals, uint16 inbound_value) +{ +INBOUND_SIGNAL signal; +INBOUND_SET working_set = inbound_signals; +uint16 outbound_value = 0; +OUTBOUND_SET outbound_signals = NO_SIGNALS; + +dprintf (atcd_dev, DEB_IOB, "Received data %06o with signals %s\n", + inbound_value, fmt_bitset (inbound_signals, inbound_format)); + +while (working_set) { /* while signals remain */ + signal = IONEXTSIG (working_set); /* isolate the next signal */ + + switch (signal) { /* dispatch the I/O signal */ + + case DCONTSTB: + dprintf (atcd_dev, DEB_CSRW, (inbound_value & DCN_ENABLE + ? "Control is %s | channel %d\n" + : "Control is %s\n"), + fmt_bitset (inbound_value, tdi_control_format), + DCN_CHAN (inbound_value)); + + tdi_control_word = inbound_value; /* save the control word */ + + if (tdi_control_word & DCN_MR) /* if master reset is requested */ + tdi_master_reset (); /* then perform an I/O reset */ + + if (tdi_control_word & DCN_IRQ_RESET) /* if reset interrupt is requested */ + dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */ + + if (tdi_control_word & DCN_ENABLE) /* if output is enabled */ + store (tdi_control_word, tdi_write_word); /* then store the parameter or data word */ + + if (tdi_control_word & DCN_ACKN) { /* if acknowledge interrupt is requested */ + tdi_data_flag = CLEAR; /* then clear the data flag */ + + scan_channels (SCAN_ALL); /* scan all channels for a new interrupt request */ + } + break; + + + case DSTATSTB: + tdi_status_word |= DST_DIO_OK; /* the interface is always ready for commands */ + + if (dibptr->interrupt_request == SET) /* reflect the interrupt request value */ + tdi_status_word |= DST_IRQ; /* in the status word */ + else /* to indicate */ + tdi_status_word &= ~DST_IRQ; /* whether or not a request is pending */ + + if (tdi_data_flag == SET) /* reflect the data flag value */ + tdi_status_word |= DST_COMPLETE; /* in the status word */ + else /* to indicate */ + tdi_status_word &= ~DST_COMPLETE; /* whether or not a channel has completed */ + + outbound_value = (uint16) tdi_status_word; /* return the status word */ + + dprintf (atcd_dev, DEB_CSRW, "Status is %s\n", + fmt_bitset (outbound_value, tdi_status_format)); + break; + + + case DWRITESTB: + tdi_write_word = inbound_value; /* save the data or parameter word */ + + if (DPRINTING (atcd_dev, DEB_CSRW)) + if (inbound_value & DPI_IS_PARAM) + hp_debug (&atcd_dev, DEB_CSRW, "Parameter is %s%d bits | %d baud\n", + fmt_bitset (inbound_value, tdi_parameter_format), + bits_per_char [DPI_CHAR_SIZE (inbound_value)], + BAUD_RATE (inbound_value)); + + else + hp_debug (&atcd_dev, DEB_CSRW, "Output data is %s%04o\n", + fmt_bitset (inbound_value, tdi_output_data_format), + DDS_DATA (inbound_value)); + break; + + + case DREADSTB: + outbound_value = tdi_read_word; /* return the data word */ + + dprintf (atcd_dev, DEB_CSRW, "Input data is channel %d | %s%04o\n", + DDR_TO_CHAN (outbound_value), + fmt_bitset (outbound_value, tdi_input_data_format), + DDR_TO_DATA (outbound_value)); + break; + + + case DSETINT: + dibptr->interrupt_request = SET; /* request an interrupt */ + + if (tdi_interrupt_mask) /* if the interrupt mask is satisfied */ + outbound_signals |= INTREQ; /* then assert the INTREQ signal */ + break; + + + case DRESETINT: + dibptr->interrupt_active = CLEAR; /* reset the interrupt active flip-flop */ + 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 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 DSETMASK: + tdi_interrupt_mask = /* set the mask flip-flop */ + D_FF (dibptr->interrupt_mask & inbound_value); /* from the mask bit and the mask value */ + + if (tdi_interrupt_mask && dibptr->interrupt_request) /* if the mask is enabled and a request is pending */ + outbound_signals |= INTREQ; /* then assert INTREQ */ + break; + + + case DSTARTIO: /* not used by this interface */ + case XFERERROR: /* not used by this interface */ + case ACKSR: /* not used by this interface */ + case TOGGLESR: /* not used by this interface */ + case TOGGLESIOOK: /* 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 PREADSTB: /* not used by this interface */ + case PWRITESTB: /* not used by this interface */ + case PCMD1: /* not used by this interface */ + case PCONTSTB: /* not used by this interface */ + case PSTATSTB: /* not used by this interface */ + case DEVNODB: /* not used by this interface */ + case SETINT: /* not used by this interface */ + case EOT: /* not used by this interface */ + case SETJMP: /* 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 (atcd_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 */ +} + + +/* TCI interface. + + The interface is installed on the IOP bus and receives direct I/O commands + from the IOP. 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 backplane + signals. For this interface, a read order executes identically to a test + order, and a write order is ignored. + + The control word contains three independent enables that affect the + interpretation of the rest of the word. Bit 3 (CCN_UPDATE) must be set to + enable storing bits 12-15 (CCN_ES2/1 and CCN_S2/1) into the state RAM. Bits + 8 (CCN_EC2) and 9 (CCN_EC1) must be set to enable storing bits 10 (CCN_C2) + and 11 (CCN_C1), respectively, into the addressable latch. If none of these + enables are set, then only bits 0-2 are interpreted. + + + Implementation notes: + + 1. The cntl_status array contains the values for the serial device control + and status lines. The line bit positions in the array correspond to the + C2/C1 and S2/S1 positions in the control word. + + 2. A control word write directed to a given channel sets that channel's + UNIT_MODEM flag to indicate that the serial line status should be + updated at each input poll service. + + 3. The terminal multiplexer library will disconnect an associated Telnet + session if DTR is dropped. + + 4. Receipt of a DRESETINT signal clears the interrupt request and active + flip-flops but does not cancel a request 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. +*/ + +static SIGNALS_DATA atcc_interface (DIB *dibptr, INBOUND_SET inbound_signals, uint16 inbound_value) +{ +INBOUND_SIGNAL signal; +INBOUND_SET working_set = inbound_signals; +uint16 outbound_value = 0; +OUTBOUND_SET outbound_signals = NO_SIGNALS; +int32 set_lines, clear_lines; + +dprintf (atcc_dev, DEB_IOB, "Received data %06o with signals %s\n", + inbound_value, fmt_bitset (inbound_signals, inbound_format)); + +while (working_set) { /* while signals remain */ + signal = IONEXTSIG (working_set); /* isolate the next signal */ + + switch (signal) { /* dispatch the I/O signal */ + + case DCONTSTB: + tci_cntr = CCN_CHAN (inbound_value); /* set the counter to the target channel */ + + dprintf (atcc_dev, DEB_CSRW, "Control is channel %d | %s\n", + tci_cntr, fmt_bitset (inbound_value, tci_control_format)); + + tci_control_word = inbound_value; /* save the control word */ + + line_unit [tci_cntr].flags |= UNIT_MODEM; /* set the modem control flag on this unit */ + + if (tci_control_word & CCN_MR) /* if master reset is requested */ + tci_master_reset (); /* then perform an I/O reset */ + + if (tci_control_word & CCN_IRQ_RESET) /* if reset interrupt is requested */ + dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */ + + cntl_status [tci_cntr] = cntl_status [tci_cntr] /* set the control lines */ + & ~CCN_ECX (tci_control_word) /* that are enabled for output */ + | CCN_CX_MASK /* to the control bits */ + & CCN_ECX (tci_control_word) /* that are enabled */ + & tci_control_word; /* in the control word */ + + dprintf (atcc_dev, DEB_XFER, "Channel %d line status is %s\n", + tci_cntr, fmt_bitset (cntl_status [tci_cntr], tci_line_format)); + + if (atcc_dev.flags & DEV_DIAG) { /* if the interface is in diagnostic mode */ + cntl_status [tci_cntr ^ 1] = /* then loop the control lines */ + cntl_status [tci_cntr ^ 1] & ~CCN_SX_MASK /* back to the alternate channel */ + | CCN_CX (cntl_status [tci_cntr]); /* from the selected channel */ + + dprintf (atcc_dev, DEB_XFER, "Channel %d line status is %s\n", + tci_cntr ^ 1, fmt_bitset (cntl_status [tci_cntr ^ 1], tci_line_format)); + } + + else if (tci_control_word & CCN_ECX_MASK) { /* otherwise if either control line is enabled */ + set_lines = 0; /* then prepare the multiplexer library to set */ + clear_lines = 0; /* the modem status (either real or simulated) */ + + if (tci_control_word & CCN_EC2) /* if control line 2 is enabled for output */ + if (RTS & cntl_status [tci_cntr]) /* then if the line is asserted */ + set_lines |= TMXR_MDM_RTS; /* then set the RTS line up */ + else /* otherwise */ + clear_lines |= TMXR_MDM_RTS; /* set it down */ + + if (tci_control_word & CCN_EC1) /* if control line 1 is enabled for output */ + if (DTR & cntl_status [tci_cntr]) /* then if the line is asserted */ + set_lines |= TMXR_MDM_DTR; /* then set the DTR line up */ + else { /* otherwise */ + clear_lines |= TMXR_MDM_DTR; /* set it down */ + + if (cntl_status [tci_cntr] & DCD) /* setting DTR down will disconnect the channel */ + dprintf (atcc_dev, DEB_CSRW, "Channel %d disconnected by DTR drop\n", + tci_cntr); + } + + tmxr_set_get_modem_bits (&atcd_ldsc [tci_cntr], /* tell the multiplexer library */ + set_lines, clear_lines, /* to set or clear the indicated lines */ + NULL); /* and omit returning the current status */ + } + + if (tci_control_word & CCN_UPDATE) /* if the status output is enabled */ + cntl_param [tci_cntr] = tci_control_word /* then store the status line enables and states */ + & CCN_STAT_MASK; /* in the parameter RAM */ + + tci_scan = D_FF (tci_control_word & CCN_SCAN); /* set or clear the scan flip-flop as directed */ + + if (tci_scan) /* if scanning is enabled */ + scan_status (); /* then look for channel status changes */ + break; + + + case DREADSTB: /* RIO and TIO return the same value */ + case DSTATSTB: + tci_status_word = CST_DIO_OK | CST_ON /* form the status word */ + | CST_CHAN (tci_cntr) + | cntl_param [tci_cntr] & CST_ESX_MASK + | cntl_status [tci_cntr] & CST_SX_MASK + | scan_status (); + + if (dibptr->interrupt_request == SET) /* reflect the interrupt request value */ + tci_status_word |= CST_IRQ; /* in the status word */ + + outbound_value = (uint16) tci_status_word; /* return the status word */ + + dprintf (atcc_dev, DEB_CSRW, "Status is channel %d | %s\n", + tci_cntr, fmt_bitset (outbound_value, tci_status_format)); + break; + + + case DSETINT: + dibptr->interrupt_request = SET; /* request an interrupt */ + + if (tci_interrupt_mask) /* if the interrupt mask is satisfied */ + outbound_signals |= INTREQ; /* then assert the INTREQ signal */ + break; + + + case DRESETINT: + dibptr->interrupt_active = CLEAR; /* reset the interrupt active flip-flop */ + 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 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 DSETMASK: + tci_interrupt_mask = /* set the mask flip-flop */ + D_FF (dibptr->interrupt_mask & inbound_value); /* from the mask bit and the mask value */ + + if (tci_interrupt_mask && dibptr->interrupt_request) /* if the mask is enabled and a request is pending */ + outbound_signals |= INTREQ; /* then assert INTREQ */ + break; + + + case DWRITESTB: /* not used by this interface */ + case DSTARTIO: /* not used by this interface */ + case XFERERROR: /* not used by this interface */ + case ACKSR: /* not used by this interface */ + case TOGGLESR: /* not used by this interface */ + case TOGGLESIOOK: /* 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 PREADSTB: /* not used by this interface */ + case PWRITESTB: /* not used by this interface */ + case PCMD1: /* not used by this interface */ + case PCONTSTB: /* not used by this interface */ + case PSTATSTB: /* not used by this interface */ + case DEVNODB: /* not used by this interface */ + case SETINT: /* not used by this interface */ + case EOT: /* not used by this interface */ + case SETJMP: /* 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 (atcc_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 */ +} + + +/* Enable or disable the TDI. + + This validation routine is entered with "value" set to 1 for an ENABLE and 0 + for a DISABLE, and "cptr" pointing to the next character after the keyword. + If the TDI is already enabled or disabled, respectively, the routine returns + with no further action. Otherwise, if "value" is 1, the device is enabled by + clearing the DEV_DIS flag, and the polling flag is set TRUE to indicate that + the TDI is polling for the simulation console. If "value" is 0, a check is + made to see if the TDI is listening for connections. If it is, the disable + request is rejected; the device must be detached first. Otherwise, the + device is disabled by setting the DEV_DIS flag, and the polling flag is set + FALSE to indicate that the TDI is no longer polling for the simulation + console (the PCLK device will take over when the polling flag is FALSE). + + In either case, the device is reset, which will restart or cancel the poll, + as appropriate. +*/ + +static t_stat atc_set_endis (UNIT *uptr, int32 value, char *cptr, void *desc) +{ +if (value) /* if this is an ENABLE request */ + if (atcd_dev.flags & DEV_DIS) { /* then if the device is disabled */ + atcd_dev.flags &= ~DEV_DIS; /* then reenable it */ + atc_is_polling = TRUE; /* and set the polling flag */ + } + + else /* otherwise the device is already enabled */ + return SCPE_OK; /* so there's nothing to do */ + +else /* otherwise this is a DISABLE request */ + if (atcd_dev.flags & DEV_DIS) /* so if the device is already disabled */ + return SCPE_OK; /* so there's nothing to do */ + + else if (poll_unit.flags & UNIT_ATT) /* otherwise if the poll unit is still attached */ + return SCPE_NOFNC; /* then report that the command failed */ + + else { /* otherwise */ + atcd_dev.flags |= DEV_DIS; /* disable the device */ + atc_is_polling = FALSE; /* and clear the polling flag */ + } + +return atcd_reset (&atcd_dev); /* reset the TDI and restart or cancel polling */ +} + + +/* Set the device modes. + + The device flag implied by the DEVICE_MODES "value" passed to the routine is + set or cleared in the device specified by the "desc" parameter. The unit and + character pointers are not used. + + + Implementation notes: + + 1. In hardware, terminals and modems must be disconnected from the ATC and + loopback cables installed between each pair or channels when the + diagnostic is run. In simulation, setting DIAG mode detaches any + existing listening port, so that Telnet sessions will not interfere with + the internal loopback connections from the send to the receive channels. +*/ + +static t_stat atc_set_mode (UNIT *uptr, int32 value, char *cptr, void *desc) +{ +DEVICE * const dptr = (DEVICE *) desc; /* a pointer to the device */ + +switch ((DEVICE_MODES) value) { /* dispatch based on the mode to set */ + + case Fast_Time: /* entering optimized timing mode */ + dptr->flags &= ~DEV_REALTIME; /* so clear the real-time flag */ + break; + + + case Real_Time: /* entering realistic timing mode */ + dptr->flags |= DEV_REALTIME; /* so set the flag */ + break; + + + case Terminal: /* entering terminal mode */ + dptr->flags &= ~DEV_DIAG; /* so clear the diagnostic flag */ + break; + + + case Diagnostic: /* entering the diagnostic mode */ + dptr->flags |= DEV_DIAG; /* so set the flag */ + + if (dptr == &atcd_dev) /* if we're setting the TDI mode */ + atcd_detach (&poll_unit); /* then detach any existing connections */ + break; + } + +return SCPE_OK; +} + + +/* Show the device modes. + + The output stream and device pointer are passed in the "st" and "desc" + parameters, respectively. If "value" is 0, then all of the flags are checked + for the TDI. If "value" is 1, then only the diagnostic flag is checked for + the TCI. The unit pointer is not used. +*/ + +static t_stat atc_show_mode (FILE *st, UNIT *uptr, int32 value, void *desc) +{ +DEVICE * const dptr = (DEVICE *) desc; /* a pointer to the device */ + +if (value == 0) /* if this is the TDI */ + if (dptr->flags & DEV_REALTIME) /* then if the real-time flag is set */ + fputs ("realistic timing, ", st); /* then report that we are using realistic timing */ + else /* otherwise */ + fputs ("fast timing, ", st); /* report that we are using optimized timing */ + +if (dptr->flags & DEV_DIAG) /* if the diagnostic flag is set */ + fputs ("diagnostic mode", st); /* then report that we're in loopback mode */ +else /* otherwise */ + fputs ("terminal mode", st); /* we're in normal (terminal) mode */ + +return SCPE_OK; +} + + +/* Show the TDI device status. + + The attachment condition and connection count are printed to the stream + specified by "st" as part of the ATCD device display. The "desc" parameter + is a pointer to the terminal multiplexer library descriptor; the unit pointer + and value parameters are not used. +*/ + +static t_stat atc_show_status (FILE *st, UNIT *uptr, int32 value, void *desc) +{ +if (poll_unit.flags & UNIT_ATT) /* if the poll unit is attached */ + fprintf (st, "attached to port %s, ", /* then report it */ + poll_unit.filename); /* with the listening port number */ +else /* otherwise */ + fprintf (st, "not attached, "); /* report the condition */ + +tmxr_show_summ (st, uptr, value, desc); /* also report the count of connections */ + +return SCPE_OK; +} + + +/* TDI device reset. + + This routine is called for a RESET or RESET ATCD command. It is the + simulation equivalent of the IORESET signal, which is asserted by the front + panel LOAD and DUMP switches. + + If a power-on reset (RESET -P) is being done, the poll timer is initialized. + In addition, the original FASTTIME setting is restored, in case it's been + changed by the user. + + If the polling flag is set, then start or resynchronize the poll unit with + the process clock to enable idling. If the CPU process clock is calibrated, + then the poll event service is synchronized with the process clock service. + Otherwise, the service time is set up but is otherwise asynchronous with the + process clock. + + If the polling flag is clear, then the poll is stopped, as it's not needed. + + + Implementation notes: + + 1. To synchronize events, the poll must be activated absolutely, as a + service event may already be scheduled, and normal activation will not + disturb an existing event. +*/ + +static t_stat atcd_reset (DEVICE *dptr) +{ +tdi_master_reset (); /* perform a master reset */ + +if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */ + sim_rtcn_init (poll_unit.wait, TMR_ATC); /* then initialize the poll timer */ + fast_data_time = FAST_IO_TIME; /* restore the initial fast data time */ + } + +if (atc_is_polling) { /* if we're polling for the simulation console */ + if (cpu_is_calibrated) /* then if the process clock is calibrated */ + poll_unit.wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */ + else /* otherwise */ + poll_unit.wait = POLL_TIME; /* set up an independent poll time */ + + sim_activate_abs (&poll_unit, poll_unit.wait); /* restart the poll timer */ + } + +else /* otherwise */ + sim_cancel (&poll_unit); /* cancel the poll */ + +return SCPE_OK; +} + + +/* TCI device reset. + + This routine is called for a RESET or RESET ATCC command. It is the + simulation equivalent of the IORESET signal, which is asserted by the front + panel LOAD and DUMP switches. + + If a power-on reset (RESET -P) is being done, local modem control is + established, and DTR is set on all channels. This is necessary so that + channels not controlled by the TCI will be able to connect (TCI-controlled + channels will have their DTR and RTS state set by the MPE TCI initialization + routine). +*/ + +static t_stat atcc_reset (DEVICE *dptr) +{ +uint32 channel; +t_stat status = SCPE_OK; + +tci_master_reset (); /* perform a master reset */ + +if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */ + status = tmxr_set_modem_control_passthru (&atcd_mdsc); /* then establish local modem control */ + + for (channel = 0; channel < TERM_COUNT; channel++) /* for each terminal channel */ + tmxr_set_get_modem_bits (&atcd_ldsc [channel], /* set the DTR line on */ + TMXR_MDM_DTR, /* to allow non-TCI channels to connect */ + 0, NULL); + } + +return status; +} + + +/* Attach the TDI to a Telnet listening port. + + This routine is called by the ATTACH ATCD command to attach the TDI to + the listening port indicated by . Logically, it is the ATCD device + that is attached; however, SIMH only allows units to be attached. This makes + sense for devices such as tape drives, where the attached media is a property + of a specific drive. In our case, though, the listening port is a property + of the TDI card, not of any given serial line. As ATTACH ATCD is equivalent + to ATTACH ATCD0, the port would, by default, be attached to the first channel + and be reported there in a SHOW ATCD command. + + To preserve the logical picture, we attach the port to the Telnet poll unit, + which is normally disabled to inhibit its display. Attaching to a disabled + unit is not allowed, so we first enable the unit, then attach it, then + disable it again. Attachment is reported by the "atc_show_status" routine. + + A direct attach to the poll unit is allowed only when restoring a previously + saved session via the RESTORE command. +*/ + +static t_stat atcd_attach (UNIT *uptr, char *cptr) +{ +t_stat status; + +if (atcd_dev.flags & DEV_DIAG) /* if the TDI is in diagnostic mode */ + return SCPE_NOFNC; /* then the command is not allowed */ + +if (uptr != line_unit /* if we're not attaching unit 0 */ + && (uptr != &poll_unit || !(sim_switches & SIM_SW_REST))) /* and not we're not restoring the poll unit */ + return SCPE_NOATT; /* then the unit specified is not attachable */ + +poll_unit.flags &= ~UNIT_DIS; /* enable the poll unit */ +status = tmxr_attach (&atcd_mdsc, &poll_unit, cptr); /* and attach it to the specified listening port */ +poll_unit.flags |= UNIT_DIS; /* and then disable it again */ + +return status; +} + + +/* Detach the TDI. + + Normally, this routine is called by the DETACH ATCD command, which is + equivalent to DETACH ATCD0. However, it may be called with other units in + two cases. + + A DETACH ALL command will call us for unit 16 (the poll unit) if it is + attached. Also, during simulator shutdown, we will be called for units 0-15 + (detach_all in scp.c calls the detach routines of all units that do NOT have + UNIT_ATTABLE), as well as for unit 16 if it is attached. In both cases, it + is imperative that we return SCPE_OK; otherwise any remaining device detaches + will not be performed. +*/ + +static t_stat atcd_detach (UNIT *uptr) +{ +uint32 channel; +t_stat status = SCPE_OK; + +if (uptr == line_unit || uptr == &poll_unit) { /* if we're detaching the base unit or poll unit */ + status = tmxr_detach (&atcd_mdsc, &poll_unit); /* then detach the listening port */ + + for (channel = 0; channel < TERM_COUNT; channel++) { /* for each terminal channel */ + atcd_ldsc [channel].rcve = FALSE; /* disable reception */ + sim_cancel (&line_unit [channel]); /* and cancel any transfer in progress */ + } + } + +return status; +} + + + +/* ATC local utility routines */ + + + +/* Request a TDI interrupt. + + The data flag and interrupt request flip-flops are set. If the interrupt + mask permits, the interrupt request is passed to the IOP. +*/ + +static void tdi_set_interrupt (void) +{ +tdi_data_flag = SET; /* set the data flag */ + +atcd_dib.interrupt_request = SET; /* request an interrupt */ + +if (tdi_interrupt_mask) /* if the interrupt mask is satisfied */ + iop_assert_INTREQ (&atcd_dib); /* then assert the INTREQ signal to the IOP */ + +return; +} + + +/* TDI master reset. + + A master reset is generated either by an IORESET signal or a programmed + master reset (CIO bit 0 set). It clears any pending or active interrupt, + sets the interrupt mask, clears the status word and data flag, and resets all + channels to their initial, unconfigured state. + + + Implementation notes: + + 1. In hardware, a master reset sets the Initialize flip-flop. This causes a + direct clear of the recirculating memory window registers, thereby + clearing each channel's buffer, parameter, and status values as they + pass through the window. The flip-flop is cleared when a control word is + sent with the master clear bit (CIO bit 0) cleared. A full recirculation + takes 69.44 microseconds, so the CPU must allow at least this time for + each channel to pass through the window to ensure that all memory + locations are reset. In simulation, the clear occurs "instantaneously." +*/ + +static void tdi_master_reset (void) +{ +uint32 chan; + +atcd_dib.interrupt_request = CLEAR; /* clear any current */ +atcd_dib.interrupt_active = CLEAR; /* interrupt request */ + +tdi_interrupt_mask = SET; /* set the interrupt mask */ + +tdi_status_word = 0; /* clear the status word */ +tdi_data_flag = 0; /* and the data flag */ + +for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) { /* for each terminal channel */ + recv_buffer [chan] = 0; /* clear the receive data buffer */ + recv_param [chan] = 0; /* and parameter */ + recv_status [chan] = 0; /* and status */ + + send_buffer [chan] = 0; /* also clear the send data buffer */ + send_param [chan] = 0; /* and parameter */ + send_status [chan] = 0; /* and status */ + + sim_cancel (&line_unit [chan]); /* cancel any transfer in progress */ + } + +for (chan = FIRST_AUX; chan <= LAST_AUX; chan++) { /* for each auxiliary channel */ + recv_buffer [chan] = 0; /* clear the receive data buffer */ + recv_param [chan] = 0; /* and parameter */ + recv_status [chan] = 0; /* and status */ + } + +return; +} + + +/* TCI master reset. + + A master reset is generated either by an IORESET signal or a programmed + master reset (CIO bit 0 set). It clears any pending or active interrupt, + sets the interrupt mask, clears the control word and channel counter, and + resets all channels to their initial, unconfigured state. + + + Implementation notes: + + 1. In hardware, a master reset sets the Status Clear flip-flop. This causes + a direct clear of the Control Word Holding Register and enables writing + into each location of the addressable latches and state RAM. The + flip-flop is reset automatically when the channel counter rolls over. + This takes approximately 12 microseconds, so the CPU must allow at least + this time before sending new control information. In simulation, the + master reset occurs "instantaneously." + + 2. In hardware, the C2 and C1 control line outputs are cleared by a master + clear. In simulation, we also clear the S2 and S1 status line input + values. This is OK, because they will be reestablished at the next poll + service entry. +*/ + + +static void tci_master_reset (void) +{ +uint32 chan; + +atcc_dib.interrupt_request = CLEAR; /* clear any current */ +atcc_dib.interrupt_active = CLEAR; /* interrupt request */ + +tci_interrupt_mask = SET; /* set the interrupt mask */ + +tci_control_word = 0; /* clear the control word */ +tci_cntr = 0; /* and the channel counter */ + +for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) { /* for each terminal channel */ + cntl_status [chan] = 0; /* clear all serial line values */ + cntl_param [chan] = 0; /* and the parameter RAM */ + } + +return; +} + + +/* Multiplexer channel service. + + The channel service routine runs only when there are characters to read or + write. It is scheduled either at a realistic rate corresponding to the + programmed baud rate of the channel to be serviced, or at a somewhat faster + optimized rate. It is entered when a channel buffer is ready for output or + when the poll routine determines that there are characters ready for input. + + On entry, the receive channel buffer is checked for a character. If one is + not already present, then the terminal multiplexer library is called to + retrieve the waiting character. If a valid character is now available, it is + processed. If the receive channel has its "diagnose" bit set, the character + is also passed to the auxiliary channels. + + The send channel buffer is then checked for a character to output. If one is + present, then if it is an all-mark (sync) character, it is discarded, as the + receiver would never see it. Otherwise, if the TDI is in diagnostic mode, + then the character is looped back to the associated receive channel by + storing it in that channel's receive buffer and then recursively calling the + routine for that channel. + + If the TDI is in terminal mode, then if the channel flag is set for local + ENQ/ACK handshaking, and the character is an ENQ, it is discarded, an ACK is + stored in the channel's receive buffer, and its reception is scheduled. + Otherwise, the character is processed and then transmitted either to the + simulation console (if output is to channel 0) or to the terminal multiplexer + library for output via Telnet or a serial port on the host machine. If the + channel has its "diagnose" bit set, the character is also passed to the + auxiliary channels. + + If the data flag is clear, the indicated receive and send channels are + checked for completion flags. If either is set, an interrupt is requested. + + + Implementation notes: + + 1. Calling "tmxr_getc_ln" for channel 0 is OK, as reception is disabled by + default and therefore will return 0. + + 2. The send channel buffer will always be non-zero if a character is present + (even a NUL) because the data word will have DDS_IS_SEND set. + + The receive buffer will always be non-zero if a character is present + (even a NUL) because characters from the console will have SCPE_KFLAG + set, characters from the terminal multiplexer library will have + TMXR_VALID set, and characters looped back from sending will have + DDS_IS_SEND set. + + 3. Reception of a loopback character is performed immediately because the + reception occurs concurrently with transmission. Reception of a locally + generated ACK is scheduled with a one-character delay to reflect the + remote device transmission delay. + + 4. If storing an ACK locally overwrites a character already present but not + yet processed, then the receive routine will set the character lost flag. + + 5. Both TMXR_VALID and SCPE_KFLAG are set on internally generated ACKs only + so that a debug trace will record the generation correctly. + + 6. The console library "sim_putchar_s" routine and the terminal multiplexer + library "tmxr_putc_ln" routine return SCPE_STALL if the Telnet output + buffer is full. In this case, transmission is rescheduled with a delay + to allow the buffer to drain. + + They also return SCPE_LOST if the line has been dropped on the remote + end. We ignore the error here to allow the simulation to continue while + ignoring the output. + + 7. The receive/send completion flag (buffer flag) will not set unless the + interrupt enable flag for that channel is also set. If enable is not + set, the completion indication will be lost. +*/ + +static t_stat line_service (UNIT *uptr) +{ +const int32 channel = uptr - line_unit; /* channel number */ +const int32 alt_channel = channel ^ 1; /* alternate channel number for diagnostic mode */ +const t_bool loopback = (atcd_dev.flags & DEV_DIAG) != 0; /* device is set for diagnostic mode */ +int32 recv_data, send_data, char_data, cvtd_data; +t_stat result = SCPE_OK; + +dprintf (atcd_dev, DEB_SERV, "Channel %d service entered\n", + channel); + + +/* Reception service */ + +recv_data = recv_buffer [channel]; /* get the current buffer character */ + +if (recv_data == 0) /* if there's none present */ + recv_data = tmxr_getc_ln (&atcd_ldsc [channel]); /* then see if there's a character ready via Telnet */ + +if (recv_data & ~DDR_DATA_MASK) { /* if we now have a valid character */ + receive (channel, recv_data, loopback); /* then process the reception */ + + if (recv_param [channel] & DPI_DIAGNOSE) /* if a diagnosis is requested */ + diagnose (recv_param [channel], recv_data); /* then route the data to the auxiliary channels */ + } + +/* Transmission service */ + +if (send_buffer [channel]) { /* if data is available to send */ + send_data = DDS_DATA (send_buffer [channel]); /* then pick up the data and stop bits */ + char_data = send_data & ASCII_MASK; /* and also the ASCII character value */ + + if (send_status [channel] & DST_COMPLETE) { /* if the last completion hasn't been acknowledged */ + send_status [channel] |= DST_CHAR_LOST; /* then indicate an overrun condition */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d send data overrun\n", + channel); + } + + if ((send_buffer [channel] & DDS_MARK) == DDS_MARK) { /* if it's an all-mark character */ + send_buffer [channel] = 0; /* then the receiver won't see it */ + + if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */ + send_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + + dprintf (atcd_dev, DEB_XFER, (loopback + ? "Channel %d sync character sent to channel %d\n" + : "Channel %d sync character sent\n"), + channel, alt_channel); + } + + else if (loopback) { /* otherwise if the device is in loopback mode */ + if (send_param [channel] & DPI_DIAGNOSE) /* then if a diagnosis is requested */ + diagnose (send_param [channel], send_data); /* then route the data to the auxiliary channels */ + + if ((send_buffer [channel] & DDR_DATA_MASK) == 0) /* if all bits are clear */ + recv_buffer [alt_channel] = SCPE_BREAK; /* then it will be seen as a BREAK */ + else /* otherwise a character will be received */ + recv_buffer [alt_channel] = send_buffer [channel]; /* so store it in the buffer */ + + send_buffer [channel] = 0; /* clear the send buffer */ + + if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */ + send_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + + dprintf (atcd_dev, DEB_XFER, "Channel %d character %s sent to channel %d\n", + channel, fmt_char (char_data), alt_channel); + + line_service (&line_unit [alt_channel]); /* receive the character on the alternate channel */ + } + + else if (char_data == ENQ && uptr->flags & UNIT_LOCALACK) { /* otherwise if it's an ENQ and local reply is enabled */ + recv_buffer [channel] = GEN_ACK; /* then "receive" an ACK on the channel */ + + send_buffer [channel] = 0; /* discard the ENQ */ + + if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */ + send_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + + dprintf (atcd_dev, DEB_XFER, "Channel %d character ENQ absorbed internally\n", + channel); + + activate_unit (uptr, Receive); /* schedule the reception */ + } + + else { /* otherwise it's a normal character */ + cvtd_data = sim_tt_outcvt (LOWER_BYTE (send_data), /* so convert it as directed */ + TT_GET_MODE (uptr->flags)); /* by the output mode flag */ + + if (cvtd_data >= 0) /* if the converted character is printable */ + if (channel == 0) /* then if we are writing to channel 0 */ + result = sim_putchar_s (cvtd_data); /* then output it to the simulation console */ + else /* otherwise */ + result = tmxr_putc_ln (&atcd_ldsc [channel], /* output it to the multiplexer line */ + cvtd_data); + + if (result == SCPE_STALL) { /* if the buffer is full */ + activate_unit (uptr, Stall); /* then retry the output a while later */ + result = SCPE_OK; /* and return OK to continue */ + } + + else if (result == SCPE_OK || result == SCPE_LOST) { /* otherwise if the character is queued to transmit */ + tmxr_poll_tx (&atcd_mdsc); /* then send (or ignore) it */ + + if (DPRINTING (atcd_dev, DEB_XFER)) + if (result == SCPE_LOST) + hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s discarded by connection loss\n", + channel, fmt_char (char_data)); + + else if (cvtd_data >= 0) + hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s sent\n", + channel, fmt_char (cvtd_data)); + + else + hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s discarded by output filter\n", + channel, fmt_char (char_data)); + + if (send_param [channel] & DPI_DIAGNOSE) /* if a diagnosis is requested */ + diagnose (send_param [channel], send_data); /* then route the data to the auxiliary channels */ + + send_buffer [channel] = 0; /* clear the buffer */ + + if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */ + send_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + + result = SCPE_OK; /* return OK in case the connection was lost */ + } + } + } + + +if (tdi_data_flag == CLEAR) /* if an interrupt is not currently pending */ + scan_channels (channel); /* then scan the channels for completion flags */ + +return result; /* return the result of the service */ +} + + +/* Multiplexer poll service. + + The poll service routine is used to poll for Telnet connections and incoming + characters. It also polls the simulation console for channel 0. Polling + starts at simulator startup or when the TDI is enabled and stops when it is + disabled. + + + Implementation notes: + + 1. The poll service routine may be entered with the TCI either enabled or + disabled. It will not be entered if the TDI is disabled, as it may be + disabled only when it is detached from a listening port. + + 2. If a character is received on the simulation console, we must call the + channel 0 line service directly. This is necessary because the poll time + may be shorter than the channel service time, and as the console provides + no buffering, a second character received before the channel service had + been entered would be lost. +*/ + +static t_stat poll_service (UNIT *uptr) +{ +int32 chan, line_state; +t_stat status = SCPE_OK; + +dprintf (atcd_dev, DEB_PSERV, "Poll service entered\n"); + +if ((atcc_dev.flags & DEV_DIS) == 0) + dprintf (atcc_dev, DEB_PSERV, "Poll service entered\n"); + +if ((atcd_dev.flags & DEV_DIAG) == 0) { /* if we're not in diagnostic mode */ + chan = tmxr_poll_conn (&atcd_mdsc); /* then check for a new multiplex connection */ + + if (chan != -1) { /* if a new connection was established */ + atcd_ldsc [chan].rcve = TRUE; /* then enable the channel to receive */ + + dprintf (atcc_dev, DEB_XFER, "Channel %d connected\n", + chan); + } + } + +tmxr_poll_rx (&atcd_mdsc); /* poll the multiplex connections for input */ + +if ((atcc_dev.flags & (DEV_DIAG | DEV_DIS)) == 0) /* if we're not in diagnostic mode or are disabled */ + for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) /* then scan the channels for line state changes */ + if (line_unit [chan].flags & UNIT_MODEM) { /* if the channel is controlled by the TCI */ + tmxr_set_get_modem_bits (&atcd_ldsc [chan], /* then get the current line state */ + 0, 0, &line_state); + + if (line_state & TMXR_MDM_DCD) /* if DCD is set */ + cntl_status [chan] |= DCD; /* then set the corresponding line flag */ + + else { /* otherwise DCD is clear */ + if (cntl_status [chan] & DCD) /* and a disconnect occurred if DCD was previously set */ + dprintf (atcc_dev, DEB_XFER, "Channel %d disconnect dropped DCD and DSR\n", + chan); + + cntl_status [chan] &= ~DCD; /* clear the corresponding flag */ + } + + if (line_state & TMXR_MDM_DSR) /* if DSR is set */ + cntl_status [chan] |= DSR; /* then set the corresponding line flag */ + else /* otherwise */ + cntl_status [chan] &= ~DSR; /* clear the flag */ + } + +status = sim_poll_kbd (); /* poll the simulation console keyboard for input */ + +if (status >= SCPE_KFLAG) { /* if a character was present */ + recv_buffer [0] = (uint16) status; /* then save it for processing */ + status = SCPE_OK; /* and then clear the status */ + + line_service (&line_unit [0]); /* run the system console's I/O service */ + } + +for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) /* check each of the receive channels for available input */ + if (tmxr_rqln (&atcd_ldsc [chan])) /* if characters are available on this channel */ + activate_unit (&line_unit [chan], Receive); /* then activate the channel's I/O service */ + +if (cpu_is_calibrated) /* if the process clock is calibrated */ + uptr->wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */ +else /* otherwise */ + uptr->wait = sim_rtcn_calb (POLL_RATE, TMR_ATC); /* calibrate the poll timer independently */ + +sim_activate (uptr, uptr->wait); /* continue polling */ + +if (tci_scan) /* if scanning is active */ + scan_status (); /* then check for line status changes */ + +return status; /* return the service status */ +} + + +/* Activate a channel unit. + + The specified unit is activated to receive or send a character. The reason + for the activation is specified by the "reason" parameter. If the TDI is in + real-time mode, the previously calculated service time is used to schedule + the event. Otherwise, the current value of the optimized timing delay is + used. If tracing is enabled, the activation is logged to the debug file. + + + Implementation notes: + + 1. The loopback time is the difference between the reception and + transmission times, as the latter event has already occurred when we are + called. +*/ + +static t_stat activate_unit (UNIT *uptr, ACTIVATOR reason) +{ +const int32 channel = uptr - line_unit; /* the channel number */ +int32 delay = 0; + +if (atcd_dev.flags & (DEV_DIAG | DEV_REALTIME)) /* if either diagnostic or real-time mode is set */ + switch (reason) { /* then dispatch the REALTIME activation */ + + case Receive: /* reception event */ + delay = uptr->recv_time; /* schedule for the realistic reception time */ + break; + + case Send: /* transmission event */ + delay = uptr->send_time; /* schedule for the realistic transmission time */ + break; + + case Loop: /* diagnostic loopback reception event */ + delay = uptr->recv_time - uptr->send_time; /* schedule the additional reception overhead */ + + if (delay < 0) /* if the receive time is less than the send time */ + delay = 0; /* then schedule the reception immediately */ + break; + + case Stall: /* transmission stall event */ + delay = uptr->send_time / 10; /* reschedule the transmission after a delay */ + break; + } + +else /* otherwise, we are in optimized timing mode */ + switch (reason) { /* so dispatch the FASTTIME activation */ + + case Receive: /* reception event */ + case Send: /* transmission event */ + delay = fast_data_time; /* use the optimized timing value */ + break; + + case Loop: /* diagnostic loopback reception event */ + delay = 1; /* use a nominal delay */ + break; + + case Stall: /* transmission stall event */ + delay = fast_data_time / 10; /* reschedule the transmission after a delay */ + break; + } + +dprintf (atcd_dev, DEB_SERV, "Channel %d delay %d service scheduled\n", + channel, delay); + +return sim_activate (uptr, delay); /* activate the unit and return the activation status */ +} + + +/* Calculate the service time. + + The realistic channel service time in event ticks per character is calculated + from the encoded character size and baud rate in the supplied control word. + The time consists of the transfer time plus a small overhead, which is + different for receiving and sending. + + The character size field in the control word is generated by this equation: + + encoded_size = (bits_per_character - 1) AND 7 + + That is, the encoded character size is the value expressed by the three + least-significant bits of the count of the data and stop bits. Therefore, + the actual number of bits per character (including the start bit) is encoded + as: + + Actual Encoded + ------ ------- + 5 4 + 6 5 + 7 6 + 8 7 + 9 0 + 10 1 + 11 2 + 12 3 + + The baud rate field in the control word is generated by this equation: + + 14400 + encoded_rate = --------- - 1 + baud_rate + + The transmission and overhead times are related to the recirculation of the + multiplexer's internal memory, which contains the data, parameters, and + status for each of the 16 send channels, 16 receive channels, and 5 auxiliary + channels. Data for a given channel can be accessed only once per + recirculation, which takes 69.44 microseconds (1/14400 of a second). The + encoded rate plus one gives the number of recirculations corresponding to a + single bit time; multiplying by the number of bits per character gives the + number of recirculations to send or receive an entire character. + + All operations encounter two overhead delays. First, an average of one-half + of a recirculation must occur to align the memory with the channel of + interest. Second, a full recirculation is required after receiving or + sending is complete before an interrupt may be generated. + + For receiving, there is an additional delay to right-justify the received + character in the data accumulator. The accumulator is a 12-bit shift + register, with received data bits are shifted from left to right. When the + final bit is entered, the register must be shifted additionally until the + first data bit is in the LSB (i.e., until the start bit is shifted out of the + register). One shift per recirculation is performed, and the number of + additional shifts required is 12 + 1 - the number of bits per character. + + Justification begins immediately after the stop bit has been received, so the + full set of recirculations for that bit are skipped in lieu of justification. + Also, reception of the start bit is delayed by one-half of the bit time to + improve noise immunity. + + Therefore, given R = encoded_rate + 1 and B = bits_per_character, the number + of memory recirculations required for sending is: + + 0.5 to align memory with the target channel (on average) + + R * B to send the start, data, and stop bits + + 1 to set the data flag to request an interrupt + + For example, at 2400 baud (encoded rate 5), a 10-bit character size, and + 69.44 microseconds per circulation, the service time would be: + + 34.7 usec to align + 4166.7 usec to send the start, data, and stop bits + 69.4 usec to set the data flag + =========== + 4270.8 usec from initiation to data flag + + The number of memory recirculations required for receiving is: + + 0.5 to align memory with the target channel (on average) + + R / 2 to receive the start bit + + R * (B - 1) to receive the data and stop bits + + (12 - B + 1) to right-justify the data + + 1 to set the data flag to request an interrupt + + Using the same example as above, the service time would be: + + 34.7 usec to align + 208.3 usec to receive the start bit + 3750.0 usec to receive the data and stop bits + 208.3 usec to right-justify the data + 69.4 usec to set the data flag + =========== + 4270.7 usec from initiation to data flag + + + Implementation notes: + + 1. The multiplexer uses an 8-bit field to set the baud rate. In practice, + only the common rates (110, 150, 300, 600, 1200, 2400) will be used. + Still, the real-time calculation must accommodate any valid setting, so a + lookup table is infeasible. + + 2. The receive calculation is simplified by noting that R / 2 + R * (B - 1) + is equivalent to R * B - R / 2, so the send calculation may be reused. + Note that the receive time may be less than the send time, typically when + the baud rate is low, so that the time to send the stop bits is longer + than the time to right-justify the reception. This means that the + "addition" of the receive overhead may actually be a subtraction. +*/ + +static uint32 service_time (uint16 control, ACTIVATOR reason) +{ +const double recirc_time = 69.44; /* microseconds per memory recirculation */ +const uint32 recirc_per_bit = DPI_BAUD_RATE (control) + 1; /* number of memory recirculations per bit */ +const uint32 char_size = bits_per_char [DPI_CHAR_SIZE (control)]; /* number of bits per character */ +double usec_per_char; + +usec_per_char = recirc_time * /* calculate the overhead for sending */ + (char_size * recirc_per_bit + 1.5); + +if (reason == Receive) /* if we're receiving */ + usec_per_char += recirc_time * /* then add the additional receiving overhead */ + (12 - char_size + 1 + - recirc_per_bit / 2.0); + +return (uint32) (usec_per_char / USEC_PER_EVENT); /* return the service time for indicated rate */ +} + + +/* Store a word in the recirculating memory. + + A parameter or data word is stored in the recirculating memory for the + channel indicated by the associated field of the "control" parameter. If the + channel number is out of range, the store is ignored. + + For receive and send parameters, the realistic service time is calculated and + stored in the unit for use when a receive or send event is scheduled. For + send data, parity is calculated and added if specified by the channel's + parameter, and the transmission event is scheduled. For a receive parameter, + the pad bits that would normally be added during right-justification after + reception are calculated and stored in the unit. + + + Implementation notes: + + 1. Service times are not calculated or set for auxiliary channels because + events are not scheduled on them (and so no units are allocated for + them). + + 2. Pad bits begin with the stop bit and continue until the character is + right-justified in the receive buffer. The calculation assumes one stop + bit, but there is no way of ascertaining the actual number of stop bits + from the parameter word. +*/ + +static void store (uint16 control, uint16 data) +{ +const uint32 channel = DCN_CHAN (control); /* current channel number */ + +if (data & DDS_IS_SEND) /* if this is a send parameter or data */ + if (channel > LAST_TERM) /* then report if the channel number is out of range */ + dprintf (atcd_dev, DEB_CSRW, "Send channel %d invalid\n", + channel); + + else if (data & DPI_IS_PARAM) { /* otherwise if this is a parameter store */ + send_param [channel] = data; /* then save it */ + line_unit [channel].send_time = /* and set the service time */ + service_time (data, Send); + + dprintf (atcd_dev, DEB_CSRW, "Channel %d send parameter %06o stored\n", + channel, data); + } + + else { /* otherwise this is a data store */ + if (send_param [channel] & DPI_ENABLE_PARITY) /* if parity is enabled */ + data = data & ~DDS_PARITY /* then replace the parity bit */ + | SEND_PARITY (data); /* with the calculated value */ + + send_buffer [channel] = data; /* store it in the buffer */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d send data %06o stored\n", + channel, data); + + activate_unit (&line_unit [channel], Send); /* schedule the transmission event */ + } + +else /* otherwise this is a receive parameter */ + if (channel >= RECV_CHAN_COUNT) /* report if the channel number is out of range */ + dprintf (atcd_dev, DEB_CSRW, "Receive channel %d invalid\n", + channel); + + else if (data & DPI_IS_PARAM) { /* otherwise this is a parameter store */ + recv_param [channel] = data; /* then save it */ + + if (channel <= LAST_TERM) { /* if this is a terminal channel */ + line_unit [channel].recv_time = /* and not an auxiliary channel */ + service_time (data, Receive); /* then set the service time */ + + line_unit [channel].stop_bits = /* set the stop bits mask for reception */ + PAD_BITS (data); + } + + dprintf (atcd_dev, DEB_CSRW, "Channel %d receive parameter %06o stored\n", + channel, data); + } + + else /* otherwise a data store to a receive channel is invalid */ + dprintf (atcd_dev, DEB_CSRW, "Channel %d receive output data word %06o invalid\n", + channel, data); +} + + +/* Process a character received from a channel. + + This routine is called to process received data on a channel, typically when + a character exists in the channel's receive buffer, but also when a character + is received on an auxiliary channel. The "channel" parameter indicates the + channel on which reception occurred, "data" is the (full) character data + as received from the console or terminal multiplexer libraries, and + "loopback" is TRUE if the data should be looped back to the alternate channel + for diagnostic execution. + + On entry, the bits required to pad the character are obtained. If a BREAK + was detected, then break status is set, and the character is set to NUL, + reflecting the all-space reception. Otherwise, if a character is already + present in the receive buffer, "character lost" status is set to indicate + that it will be overwritten. + + If this is a loopback reception, and echo is enabled on the channel, the + character is sent back to the alternate channel. Otherwise, if this is a + main and not auxiliary channel reception, the character is upshifted if the + UNIT_CAPSLOCK flag is set. If echo is enabled, the character is written back + to the console or terminal multiplexer library line. Finally, the completion + flag is set if enabled. + + + Implementation notes: + + 1. The echo to a terminal multiplexer library line will return SCPE_LOST if + the line has been dropped on the remote end. We can ignore the error + here, as the line drop will be picked up when the next input poll is + performed. + + In addition, the SCPE_STALL returned for a full output buffer is also + ignored, as there's no way of queuing echoed characters while waiting for + the buffer to empty. +*/ + +static void receive (int32 channel, int32 data, t_bool loopback) +{ +int32 recv_data, char_data, char_echo, pad; + +recv_data = data & DDR_DATA_MASK; /* mask to just the character data */ +char_data = recv_data & ASCII_MASK; /* and to the equivalent ASCII character */ + +if (channel <= LAST_TERM) /* if this is a receive channel */ + pad = line_unit [channel].stop_bits; /* then set the stop-bit padding from the unit */ +else /* otherwise it's an auxiliary channel */ + pad = PAD_BITS (recv_param [channel]); /* so calculate the padding */ + + +if (data & SCPE_BREAK) { /* if a break was detected */ + recv_buffer [channel] = NUL; /* then return a NUL character */ + recv_status [channel] |= DST_BREAK; /* and set break reception status */ + + dprintf (atcd_dev, DEB_XFER, "Channel %d break detected\n", + channel); + } + +else { /* otherwise a normal character was received */ + if (recv_status [channel] & DST_COMPLETE) { /* if a character is already pending */ + recv_status [channel] |= DST_CHAR_LOST; /* then the previous character will be lost */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d receive data overrun\n", + channel); + } + + recv_buffer [channel] = (uint16) (recv_data | pad); /* save the character and padding in the buffer */ + + if (loopback) { /* if this channel has a loopback cable installed */ + if (recv_param [channel] & DPI_ENABLE_ECHO) { /* and the channel has echo enabled */ + recv_buffer [channel ^ 1] = (uint16) data; /* then send the data back to the other channel */ + + activate_unit (&line_unit [channel ^ 1], Loop); /* schedule the reception */ + + dprintf (atcd_dev, DEB_XFER, "Channel %d character %s echoed to channel %d\n", + channel, fmt_char (char_data), channel ^ 1); + } + } + + else if (channel <= LAST_TERM) { /* otherwise if it's a receive channel */ + if (line_unit [channel].flags & UNIT_CAPSLOCK) { /* then if caps lock is down */ + recv_data = toupper (recv_data); /* then convert to upper case if lower */ + recv_buffer [channel] = (uint16) (recv_data | pad); /* and replace the character in the buffer */ + } + + if (recv_param [channel] & DPI_ENABLE_ECHO) { /* if the channel has echo enabled */ + char_echo = sim_tt_outcvt (recv_data, /* then convert the character per the output mode */ + TT_GET_MODE (line_unit [channel].flags)); + + if (char_echo >= 0) { /* if the converted character is valid for the mode */ + if (channel == 0) /* then if this is for channel 0 */ + sim_putchar (char_echo); /* then write it back to the simulation console */ + + else { /* otherwise */ + tmxr_putc_ln (&atcd_ldsc [channel], char_echo); /* write it to the multiplexer output line */ + tmxr_poll_tx (&atcd_mdsc); /* and poll to transmit it now */ + } + + dprintf (atcd_dev, DEB_XFER, ("Channel %d character %s echoed\n"), + channel, fmt_char (char_echo)); + } + + else /* otherwise the echo character was discarded */ + dprintf (atcd_dev, DEB_XFER, "Channel %d character %s echo discarded by output filter\n", + channel, fmt_char (char_data)); + } + } + } + +if (recv_param [channel] & DPI_ENABLE_IRQ) /* if the channel is enabled to interrupt */ + recv_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + +dprintf (atcd_dev, DEB_XFER, "Channel %d character %s %s\n", + channel, fmt_char (char_data), + (data == GEN_ACK ? "generated internally" : "received")); + +return; +} + + +/* Check for a character received on an auxiliary channel. + + If a send or receive channel has its "diagnose" bit set, then this routine is + called to check if any of the auxiliary channels would receive the character + too. If one or more would, then the "receive" routine is called to store the + character in the appropriate buffer. + + The diagnosis mode is typically used to speed-sense a receive channel. + In hardware, reception on a given channel is simultaneously received on the + five auxiliary channels, with each channel set for a different baud rate. + When a specific character (e.g., CR) is sent, only the channel with the + correct baud rate setting will receive the intended character. By + determining which channel received the correct data, the baud rate of the + sending terminal may be obtained. + + In simulation, a main channel will receive a character regardless of the baud + rate configuration. Therefore, an auxiliary channel will receive the same + character only if it is configured for the same baud rate and character size. +*/ + +static void diagnose (uint16 control, int32 data) +{ +const uint16 config = control & DPI_CHAR_CONFIG; /* main channel character size and baud rate */ +int32 channel; + +for (channel = FIRST_AUX; channel <= LAST_AUX; channel++) /* scan the auxiliary channels */ + if ((recv_param [channel] & DPI_CHAR_CONFIG) == config) /* if the character configurations match */ + receive (channel, data, FALSE); /* then receive the data on this channel */ + +return; +} + + +/* Scan the channels for a transfer completion interrupt. + + If the multiplexer data flag is not set, this routine is called to scan the + channels for completion flags. If the "channel" parameter value is SCAN_ALL, + then all of the channels are checked. Otherwise, only the specified channel + is checked. + + If a channel has its completion flag set, the multiplexer data and status + words are set for return to the CPU, the data flag is set, and an interrupt + is requested. The channel requesting the interrupt is contained in the + status word. + + In hardware, the recirculating buffer consists of the sixteen receive + channels, then the sixteen send channels, and then the five auxiliary + channels. The completion flags are checked in this order during the + recirculation after a completion flag is set. If the scan has been inhibited + by the data flag, it will commence with the channel currently in the + recirculation window at the time the flag was cleared and then continue in + the order indicated. + + In simulation, the scan is always initiated as though at the beginning of a + recirculation. + + + Implementation notes: + + 1. After a send completion, the data word contains all ones (stop bits). +*/ + +static void scan_channels (int32 channel) +{ +int32 chan, first_chan, last_chan; + +if (channel == SCAN_ALL) { /* if all channels are to be scanned */ + first_chan = FIRST_TERM; /* then set the loop limits */ + last_chan = LAST_TERM; /* to the full range of channels */ + } + +else /* otherwise scan just the channel indicated */ + first_chan = last_chan = channel; /* plus the auxiliary channels if requested */ + +for (chan = first_chan; chan <= last_chan; chan++) { /* scan the receive channels */ + if (recv_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */ + tdi_read_word = DDR_DATA (recv_buffer [chan]) /* then form the input data word */ + | DDR_CHAN (chan) /* from the character, channel, and parity */ + | RECV_PARITY (recv_buffer [chan]); + + tdi_status_word = recv_status [chan] /* form the partial status word */ + | DST_CHAN (chan); + + recv_buffer [chan] = 0; /* clear the receive buffer */ + recv_status [chan] = 0; /* and the channel status */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d receive interrupt requested\n", + chan); + + tdi_set_interrupt (); /* set the data flag and request an interrupt */ + return; /* and terminate scanning */ + } + } + +for (chan = first_chan; chan <= last_chan; chan++) { /* scan the send channels */ + if (send_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */ + tdi_read_word = DDR_DATA_MASK /* then form the input data word from the */ + | DDR_CHAN (chan); /* data input buffer and the channel number */ + + tdi_status_word = send_status [chan] /* form the partial status word */ + | DST_CHAN (chan) + | DST_SEND_IRQ; + + send_status [chan] = 0; /* clear the channel status */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d send interrupt requested\n", + chan); + + tdi_set_interrupt (); /* set the data flag and request an interrupt */ + return; /* and terminate scanning */ + } + } + +if (channel == SCAN_ALL /* if we're scanning all channels */ + || send_param [channel] & DPI_DIAGNOSE /* or the indicated channel is diagnosing */ + || recv_param [channel] & DPI_DIAGNOSE) /* its transmission or reception */ + for (chan = FIRST_AUX; chan <= LAST_AUX; chan++) { /* then scan the auxiliary channels */ + if (recv_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */ + tdi_read_word = DDR_DATA (recv_buffer [chan]) /* then form the input data word */ + | DDR_CHAN (chan) /* from the character, channel, and parity */ + | RECV_PARITY (recv_buffer [chan]); + + tdi_status_word = recv_status [chan] /* form the partial status word */ + | DST_CHAN (chan) + | DST_DIAGNOSE; + + recv_buffer [chan] = 0; /* clear the receive buffer */ + recv_status [chan] = 0; /* and the channel status */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d receive interrupt requested\n", + chan); + + tdi_set_interrupt (); /* set the data flag and request an interrupt */ + return; /* and terminate scanning */ + } + } + +return; /* no channel has completed */ +} + + +/* Check for a control interrupt. + + If the scan flag is clear, then return the interrupt status bits for the + channel indicated by the current control counter value. Otherwise, scan all + of the control channels, starting with the current counter, to check for a + status mismatch. This occurs when either of the incoming status bits does + not match the stored status, and the corresponding mismatch detection is + enabled. If an enabled mismatch is found, request an interrupt from the CPU, + clear the scan flag, and return the interrupt status bits with the counter + pointing at the interrupting channel. +*/ + +static uint16 scan_status (void) +{ +uint32 chan_count; +uint16 interrupts; + +if (tci_scan) /* if the control interface is scanning */ + chan_count = TERM_COUNT; /* then look at all of the channels */ +else /* otherwise */ + chan_count = 1; /* look at only the current channel */ + +while (chan_count > 0) { /* scan the control channels */ + interrupts = CST_IX (CCN_ESX (cntl_param [tci_cntr]) /* check for an enabled status mismatch */ + & (cntl_param [tci_cntr] ^ cntl_status [tci_cntr])); + + if (tci_scan) { /* if the interface is scanning */ + if (interrupts) { /* and a mismatch was found */ + atcc_dib.interrupt_request = SET; /* then request an interrupt */ + + if (tci_interrupt_mask) /* if the interrupt mask is satisfied */ + iop_assert_INTREQ (&atcc_dib); /* then assert the INTREQ signal */ + + tci_scan = CLEAR; /* stop the scan at the current channel */ + + dprintf (atcc_dev, DEB_CSRW, "Channel %d interrupt requested\n", + tci_cntr); + break; + } + + tci_cntr = (tci_cntr + 1) % TERM_COUNT; /* set the counter to the next channel in sequence */ + } + + chan_count = chan_count - 1; /* drop the count of channels to check */ + } + +return interrupts; /* return the interrupt status bits */ +} diff --git a/HP3000/hp3000_clk.c b/HP3000/hp3000_clk.c new file mode 100644 index 00000000..83afa382 --- /dev/null +++ b/HP3000/hp3000_clk.c @@ -0,0 +1,929 @@ +/* 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 + + 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 (1 << UNIT_CALTIME_SHIFT) + + +/* Debug flags */ + +#define DEB_CSRW (1 << 0) /* trace commands received and status returned */ +#define DEB_PSERV (1 << 1) /* trace unit service scheduling calls */ +#define DEB_IOB (1 << 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 0100000 /* (M) master reset (if bit 3 = 0) */ +#define CN_RATE_MASK 0160000 /* clock rate selector mask (if bit 3 = 1) */ +#define CN_RESET_LOAD_SEL 0010000 /* (E) select reset/load rate (0/1) */ +#define CN_IRQ_RESET_MASK 0003400 /* interrupt request reset selector mask */ +#define CN_COUNT_RESET 0000200 /* (C) reset count register after LR=CR interrupt */ +#define CN_LIMIT_COUNT_SEL 0000100 /* (L) select limit/count (0/1) register */ +#define CN_IRQ_RESET_ALL 0000040 /* (A) reset all interrupt requests */ +#define CN_IRQ_ENABLE 0000001 /* (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 */ + NULL, /* 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 */ + NULL, /* 100 = unused */ + NULL, /* 101 = unused */ + NULL, /* 110 = unused */ + NULL, /* 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 0040000 /* (D) direct I/O OK to use */ +#define ST_RATE_MASK 0034000 /* clock rate mask */ +#define ST_LR_EQ_CR 0000040 /* (C) limit register = count register */ +#define ST_LR_EQ_CR_OVFL 0000020 /* (F) limit register = count register overflow */ +#define ST_SYSTEM_IRQ 0000004 /* (I) I/O system interrupt request */ +#define ST_LIMIT_COUNT_SEL 0000002 /* (L) limit/count (0/1) register selected */ +#define ST_COUNT_RESET 0000001 /* (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 uint32 control_word; /* control word */ +static uint32 status_word; /* status word */ +static uint32 count_register; /* counter register */ +static uint32 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 }, + { SRDATA (DIB, clk_dib), REG_HRO }, + { 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.wait /* the elapsed time is the original wait time */ + - sim_activate_time (&clk_unit); /* less the time remaining before the next service */ + + ticks = (elapsed * CLK_MULTIPLIER) / clk_unit.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, uint16 inbound_value) +{ +INBOUND_SIGNAL signal; +INBOUND_SET working_set = inbound_signals; +uint16 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.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); /* changing the rate restarts the timing divider */ + + if (rate > 0) { /* if the rate is valid */ + clk_unit.wait = delay [rate]; /* then set the initial service delay */ + sim_rtcn_init (clk_unit.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, (control_word & CN_RESET_LOAD_SEL + ? "Control is %s | %s rate\n" + : "Control is %s%.0s\n"), + fmt_bitset (inbound_value, control_format), + rate_name [CN_RATE (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 = (uint16) 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 %d 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 %d 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 */ + + dibptr->interrupt_request = system_irq /* recalculate the interrupt request signal */ + || control_word & CN_IRQ_ENABLE + && (limit_irq | lost_tick_irq); + + 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 %d increment %d limit %d\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.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.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.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.wait); + +sim_activate_abs (&clk_unit, clk_unit.wait); /* restart the clock */ + +return; +} diff --git a/HP3000/hp3000_cpu.c b/HP3000/hp3000_cpu.c new file mode 100644 index 00000000..f66c3a8b --- /dev/null +++ b/HP3000/hp3000_cpu.c @@ -0,0 +1,4033 @@ +/* hp3000_cpu.c: HP 3000 Central Processing Unit 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. + + CPU HP 3000 Series III Central Processing Unit + + 22-Dec-15 JDB First release version + 01-Apr-15 JDB First successful run of MPE-V/R through account login + 11-Dec-12 JDB Created + + References: + - HP 3000 Series II System Microprogram Listing + (30000-90023, August 1976) + - HP 3000 Series II/III System Reference Manual + (30000-90020, July 1978) + - Machine Instruction Set Reference Manual + (30000-90022, June 1984) + + + The HP 3000 is a family of general-purpose business computers that were sold + by Hewlett-Packard from 1972 through 2001. There are two major divisions + within this family: the "classic" 16-bit, stack-oriented CISC machines, and + the "Precision Architecture" 32-bit, register-oriented RISC machines that + succeeded them. All machines run versions of MPE, the "Multiprogramming + Executive" operating system. + + Within the "classic" division, there are two additional subdivisions, based + on the method used for peripheral connections: the original "SIO" machines, + and the later "HP-IB" machines. The I/O interfacing hardware differs between + the two types of machines, as do the privileged I/O machine instructions. + The user instruction sets are identical, as are the register sets visible to + the programmer. The I/O drivers are different to account for the hardware + differences, and therefore they run slightly different versions of MPE. + + This implementation is a simulator for the classic SIO machines. This group + consists of the 3000 CX, the Series I, Series II, and Series III; the last is + simulated here. The CX and Series I, which is a repackaged CX, are + essentially subsets of the Series II/III -- a smaller instruction set, + limited memory size, and lower-precision floating-point instructions. + Simulation of these machines may be added in the future. Future simulation + of the HP-IB machines (the Series 30 through 70) is desirable, as the latest + MPE versions run only on these machines, but documentation on the internals + of the HP-IB hardware controllers is nonexistent. + + The CX and Series I support 64K 16-bit words of memory. The Series II + supports up to 256K, divided into four banks of 64K words each, and the + Series III extends this to 1024K words using 16 banks. Memory is divided + into variable-length code and data segments, with the latter containing a + program's global data area and stack. + + Memory protection is accomplished by checking program, data, and stack + accesses against segment base and limit registers, which can be set only by + MPE. Bounds violations cause automatic hardware traps to a handler routine + within MPE. Some violations may be permitted; for example, a Stack Overflow + trap may cause MPE to allocate a larger stack and then restart the + interrupted instruction. Almost all memory references are position- + independent, so moving segments to accommodate expansion requires only + resetting of the segment registers to point at the new location. Code + segments are fully reentrant and shareable, and both code and data are + virtual, as the hardware supports absent code and data segment traps. + + The classic 3000s are stack machines. Most of the instructions operate on + the value on the top of the stack (TOS) or on the TOS and the next-to-the-top + of the stack (NOS). To improve execution speed, the 3000 has a set of + hardware registers that are accessed as the first four locations at the top + of the stack, while the remainder of the stack locations reside in main + memory. A hardware register renamer provides fast stack pushes and pops + without physically copying values between registers. + + In hardware, the stack registers are referenced internally as TR0-TR3 and + externally as RA-RD. An access to the RA (TOS) register is translated by the + renamer to access TR0, TR1, etc. depending on which internal register is + designated as the current top of the stack. For example, assume that RA + corresponds to TR0. To push a new value onto the top of the stack, the + renamer is adjusted so that RA corresponds to TR1, and the new value is + stored there. In this state, RB corresponds to TR0, the previous TOS (and + current NOS) value. Additional pushes rename RA as TR2 and then TR3, with RB + being renamed to TR1 and then TR2, and similarly for RC and RD. The number + of valid TOS registers is given by the value in the SR register, and the + first stack location in memory is given by the value in the SM register. + Pops reverse the sequence: a pop with RA corresponding to TR3 renames RA to + TR2, RB to TR1, etc. When all four stack registers are in use, a push will + copy the value in RD to the top of the memory stack (SM + 1) before the + registers are renamed and the new value stored into RA. Similarly, when all + four stack registers are empty, a pop simply decrements the SM register, + effectively deleting the top of the memory stack. + + Because of the renamer, the microcode can always use RA to refer to the top + of the stack, regardless of which internal TR register is being used, and + similarly for RB-RD. Execution of each stack instruction begins with a + preadjustment, if needed, that loads the TOS registers that will be used by + the instruction from the top of the memory stack. For example, if only RA + contains a value (i.e., the SR register value is 1), the ADD instruction, + which adds the values in RA and RB, will load RB with the value on the top of + the memory stack, the SR count will be incremented, and the SM register will + be decremented. On the other hand, if both RA and RB contained values (SR >= + 2), then no preadjustment would be required before the ADD instruction + microcode manipulated RA and RB. + + In simulation, the renamer is implemented by physically copying the values + between registers, as this is much faster than mapping via array index + values, as the hardware does. A set of functions provides the support to + implement the hardware stack operations: + + cpu_push - empty the TOS register (SR++, caller stores into RA) + cpu_pop - delete the TOS register (SR--) + cpu_queue_up - move from memory to the lowest TOS register (SR++, SM--) + cpu_queue_down - move from the lowest TOS register to memory (SR--, SM++) + cpu_flush - move all stack registers to memory (SR = 0 on return) + cpu_adjust_sr - queue up until the required SR count is reached + cpu_mark_stack - write a stack marker to memory + + The renamer is described in US Patent 3,737,871 (Katzman, "Stack Register + Renamer", June 1973). + + The MPE operating system is supported by special microcode features that + perform code and data segment mapping, segment access bounds checking, + privilege checking, etc. The layout of certain in-memory tables is known to + both the OS and the microcode and is used to validate execution of + instructions. For instance, every stack instruction is checked for a valid + access within the stack segment boundaries, which are set up by the OS + before program dispatch. For this reason, the 3000 cannot be operated as a + "bare" machine, because these tables would not have been initialized. + Similarly, the "cold load" process by which the OS is loaded from storage + media into memory is entirely in microcode, as machine instructions cannot be + executed until the required tables are loaded into memory. + + This OS/microcode integration means that the microcode may detect conditions + that make continued execution impossible. An example would be a not-present + segment fault for the segment containing the disc I/O driver. If such a + condition is detected, the CPU does a "system halt." This fatal microcode + error, distinct from a regular programmed halt, causes operation to cease + until the CPU is reset. + + The CPU hardware includes a free-running 16-bit process clock that increments + once per millisecond whenever a user program is executing. MPE uses the + process clock to charge CPU usage to programs, and thereby to users, groups, + and accounts. Instructions are provided to read (RCLK) and set (SCLK) the + process clock. + + + The data types supported by the instruction set are: + + - 8-bit unsigned byte + - 16-bit unsigned integer ("logical" format) + - 16-bit two's-complement integer + - 32-bit two's-complement integer + - 32-bit sign-magnitude floating point + - 64-bit sign-magnitude floating point + + Multi-word values are stored in memory with the most-significant words in the + lowest addresses. The stack is organized in memory from lower to higher + addresses, so a value on the stack has its least-significant word on the top + of the stack. + + Machine instructions are initially decoded by a sub-opcode contained in the + four highest bits, as follows (note that the HP 3000 numbers bits from + left-to-right, i.e., bit 0 is the MSB and bit 15 is the LSB): + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 0 0 | 1st stack opcode | 2nd stack opcode | Stack + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 0 1 | X | shift opcode | shift count | Shift + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 0 1 | I | branch opcode |+/-| P displacement | Branch + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 0 1 | X | bit test opcode | bit position | Bit Test + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 0 | move op | opts/S decrement | Move + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 0 | special op | 0 0 | sp op | Special + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | firmware option op | Firmware + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | imm opcode | immediate operand | Immediate + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | field opcode | J field | K field | Field + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | register op | SK| DB| DL| Z |STA| X | Q | S | Register + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | 0 0 0 0 | I/O opcode | K field | I/O + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | 0 0 0 0 | cntl opcode | 0 0 | cn op | Control + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | program op | N field | Program + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | immediate op | immediate operand | Immediate + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | memory op | P displacement | Memory + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + The memory, loop, and branch instructions occupy the remainder of the + sub-opcodes (04-17): + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | memory op | X | I | mode and displacement | Memory + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 0 | P+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | P- displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 0 | DB+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 0 | Q+ displacement 0-127 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | 0 | Q- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | 1 | S- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | memory op | X | I | s | mode and displacement | Memory + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | DB+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+ + | 1 | 0 | Q+ displacement 0-127 | + +---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 0 | Q- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | S- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 1 0 1 |loop op| 0 |+/-| P-relative displacement | Loop + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 1 1 0 0 | I | 0 1 | > | = | < | P+- displacement 0-31 | Branch + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Optional firmware extension instruction sets occupy instruction codes + 020400-020777, except for the DMUL (020570) and DDIV (020571) instructions + that are part of the base set, as follows: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | 0 1 1 1 1 0 0 | x | DMUL/DDIV + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | 0 0 0 0 1 | ext fp op | Extended FP + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | 1 | options | decimal op | Decimal + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Most instructions are defined by unique 16-bit codes that specify the + operations, operands, addressing modes, shift counts, etc. For each of these + instructions, there is only one canonical form that encodes a given + instruction (or instruction pair, in the case of stack instructions). For + example, the octal value 140003 is the only value that encodes the "BR P+3" + instruction. + + There are also instruction codes that contain one or more bits designated as + "reserved" in the Machine Instruction Set Reference Manual. For some of + these instructions, the reserved bits do not affect instruction decoding. + Each instruction of this type has a defined canonical form -- typically with + the reserved bits set to zero -- but will execute identically if one of the + undefined forms is used. For example, the "MOVE" instructions define bits + 12-13 as 00, but the bits are not decoded, so values of 01, 10, and 11 for + these bits will result in identical execution. Specifically, "MOVE 0" is + encoded canonically as octal value 020020, but the undefined codes 020024, + 020030, and 020034 execute "MOVE 0" as well. + + For the rest of these instructions, the reserved bits are decoded and will + affect the instruction interpretation. An example of this is the "IXIT" + instruction. It also defines bits 12-13 as 00 (canonical encoding 020360), + but in this case the bits must be 00 for the instruction to execute; any + other value (e.g., undefined codes 020364, 020370, or 020374) executes as a + "PCN" instruction, whose canonical encoding is 020362. + + Finally, some codes are not assigned to any instructions, or they are + assigned to instructions that are supplied by optional firmware that is not + present in the machine. These instruction codes are designated as + "unimplemented" and will cause Unimplemented Instruction traps if they are + executed. Examples are the stack operation code 072 in either the left-hand + or right-hand position (i.e., instruction codes 0072xx and 00xx72), codes + 020410-020415 if the Extended Instruction Set firmware option is not + installed, and codes 036000-036777. + + When the simulator examines the bit patterns of instructions to execute, each + will fall into one of four categories: + + 1. Defined (canonical) instruction encodings. + + 2. Undefined (non-canonical) instruction encodings, where reserved fields + are "don't care" bits (e.g., MOVE). + + 3. Undefined (non-canonical) instruction encodings, where reserved fields + are decoded (e.g., IXIT). + + 4. Unimplemented instruction encodings (e.g., stack opcode 072 or EADD with + no EIS installed). + + When examining memory or register values in instruction-mnemonic form, the + names of the canonical instructions in category 1 are displayed in uppercase, + as are the names of the non-canonical instructions in category 2. The + non-canonical instruction names in category 3 are displayed in lowercase. + This is to indicate to the user that the instructions that will be executed + may not be the ones expected from the decoding. Instruction names in + category 4 that correspond to supported firmware options are displayed in + uppercase, regardless of whether or not the option is enabled. Category 4 + encodings that do not correspond to instructions are displayed in octal. + + + The simulator provides four stop conditions related to instruction execution + that may be enabled with a SET CPU STOP= command: + + Action + ------ ------------------------------------ + LOOP stop on an infinite loop + PAUSE stop on a PAUS instruction + UNDEF stop on an undefined instruction + UNIMPL stop on an unimplemented instruction + + If an enabled stop condition is detected, execution ceases with the + instruction pending, and control returns to the SCP prompt. When simulation + stops, execution may be resumed in two ways. If the cause of the stop has + not been remedied and the stop has not been disabled, resuming execution with + CONTINUE, STEP, GO, or RUN will cause the stop to occur again. Alternately, + specifying the "-B" switch with any of the preceding commands will resume + execution while bypassing the stop for the current instruction. + + The LOOP option stops the simulator if it attempts to execute an instruction + that enters an infinite loop (e.g., BR P+0). The branch instructions TBA, + TBX, BCC, BR, BCY, BNCY, BOV, and BNOV result in an infinite loop if the + branch displacement is zero and the branch condition is true. The remaining + branch instructions cannot result in an infinite loop, as they all modify the + CPU state and so eventually reach a point where they drop out of the loop. + + The PAUSE option stops the simulator if execution of a PAUS instruction is + attempted. This instruction normally suspends instruction fetching until an + interrupt occurs. Clearing the stop and resuming execution suspends the + fetch/execute process until an external interrupt occurs. Resuming with the + stop bypassed continues execution with the instruction following the PAUS; + this is the same action that occurs when pressing the HALT button and then + the RUN button in hardware. + + The UNDEF option stops the simulator if execution of a non-canonical + instruction from decoding category 3 (i.e., an instruction containing a + decoded reserved bit pattern other than that defined in the Machine + Instruction Set manual) is attempted. The intent is to catch instructions + containing reserved fields with values that change the meaning of those + instructions. Bypassing the stop will decode and execute the instruction in + the same manner as the HP 3000 microcode. + + The UNIMPL option stops the simulator if execution of an instruction from + decoding category 4 is attempted. Bypassing the stop will cause an + Unimplemented Instruction trap. Instructions that depend on the presence of + firmware options are implemented if the option is present and unimplemented + if the option is absent. For example, instruction code 020410 executes as + the EADD instruction if the Extended Instruction Set is enabled but is + unimplemented if the EIS is disabled. It is displayed as EADD in either + case. + + The instructions in category 2 whose non-canonical forms do not cause an + UNDEF stop are: + + Canonical Reserved + Inst Encoding Bits Defined As Decoded As + ---- --------- -------- ----------- ----------- + SCAN 010600 10-15 0 0 0 0 0 0 x x x x x x + TNSL 011600 10-15 0 0 0 0 0 0 x x x x x x + MOVE 020000 12-13 0 0 x x + MVB 020040 12-13 0 0 x x + MVBL 020100 13 0 x + SCW 020120 13 0 x + MVLB 020140 13 0 x + SCU 020160 13 0 x + CMPB 020240 12-13 0 0 x x + RSW 020300 12-14 0 0 0 x x x + LLSH 020301 12-14 0 0 0 x x x + PLDA 020320 12-14 0 0 0 x x x + PSTA 020321 12-14 0 0 0 x x x + LSEA 020340 12-13 0 0 x x + SSEA 020341 12-13 0 0 x x + LDEA 020342 12-13 0 0 x x + SDEA 020343 12-13 0 0 x x + PAUS 030020 12-15 0 0 0 0 x x x x + + The instructions in category 3 whose non-canonical forms cause an UNDEF stop + are: + + Canonical Reserved + Inst Encoding Bits Defined As Decoded As + ---- --------- -------- ---------- ---------- + IXIT 020360 12-15 0 0 0 0 0 0 0 0 + LOCK 020361 12-15 0 0 0 1 n n 0 1 + PCN 020362 12-15 0 0 1 0 n n n 0 + UNLK 020363 12-15 0 0 1 1 n n 1 1 + + SED 030040 12-15 0 0 0 x n n n x + + XCHD 030060 12-15 0 0 0 0 0 0 0 0 + PSDB 030061 12-15 0 0 0 1 n n 0 1 + DISP 030062 12-15 0 0 1 0 n n n 0 + PSEB 030063 12-15 0 0 1 1 n n 1 1 + + SMSK 030100 12-15 0 0 0 0 0 0 0 0 + SCLK 030101 12-15 0 0 0 1 n n n n + RMSK 030120 12-15 0 0 0 0 0 0 0 0 + RCLK 030121 12-15 0 0 0 1 n n n n + + Where: + + x = 0 or 1 + n = any collective value other than 0 + + In hardware, the SED instruction works correctly only if opcodes 030040 and + 030041 are used. Opcodes 030042-030057 also decode as SED, but the status + register is set improperly (the I bit is cleared, bits 12-15 are rotated + right twice and then ORed into the status register). In simulation, opcodes + 030042-030057 work correctly but will cause an UNDEF simulation stop if + enabled. + + + The CPU simulator provides extensive tracing capabilities that may be enabled + with the SET CONSOLE DEBUG= and SET CPU DEBUG= commands. + The trace options that may be specified are: + + Trace Action + ----- ---------------------------------- + INSTR trace instruction executions + DATA trace memory data accesses + FETCH trace memory instruction fetches + REG trace registers + PSERV trace process clock service events + + A section of an example trace is: + + >>CPU fetch: 00.010342 020320 instruction fetch + >>CPU instr: 00.010341 000300 ZROX,NOP + >>CPU reg: 00.006500 000000 X 000000, M i t r o c CCG + >>CPU fetch: 00.010343 041100 instruction fetch + >>CPU instr: 00.010342 020320 PLDA + >>CPU data: 00.000000 001340 absolute read + >>CPU reg: 00.006500 000001 A 001340, X 000000, M i t r o c CCG + >>CPU fetch: 00.010344 037777 instruction fetch + >>CPU instr: 00.010343 041100 LOAD DB+100 + >>CPU data: 00.002100 123003 data read + >>CPU reg: 00.006500 000002 A 123003, B 001340, X 000000, M i t r o c CCL + >>CPU fetch: 00.010345 023404 instruction fetch + >>CPU instr: 00.010344 037777 ANDI 377 + >>CPU reg: 00.006500 000002 A 000003, B 001340, X 000000, M i t r o c CCG + >>CPU fetch: 00.010346 002043 instruction fetch + >>CPU instr: 00.010345 023404 MPYI 4 + >>CPU reg: 00.006500 000002 A 000014, B 001340, X 000000, M i t r o c CCG + >>CPU fetch: 00.010347 020320 instruction fetch + + The INSTR option traces instruction executions. Each instruction is printed + before it is executed. The two opcodes of a stack instruction are printed + together before the left-hand opcode is executed. If the right-hand opcode + is not NOP, it is reprinted before execution, with dashes replacing the + just-executed left-hand opcode. + + The DATA option traces reads from and writes to memory. Each access is + classified by the memory bank register that is paired with the specified + offset; they are: dma, absolute, program, data, and stack. DMA accesses + derive their bank addresses from the banks specified in Set Bank I/O program + orders. Absolute accesses always use bank 0. Program, data, and stack + accesses use the bank addresses in the PBANK, DBANK, and SBANK registers, + respectively. + + The FETCH option traces instruction fetches from memory. These accesses are + separated from those traced by the DATA option because fetches usually are of + little interest except when debugging the fetch/execute sequence. Because + the HP 3000 has a two-stage pipeline, fetches load the NIR (Next Instruction + Register) with the instruction after the instruction to be executed from the + CIR (Current Instruction Register). + + The REG option traces register values. Two sets of registers are printed. + After executing each instruction, the currently active TOS registers, the + index register, and the status register are printed. After executing an + instruction that may alter the base registers, the program, data, and stack + segment base registers are printed. + + The PSERV option traces process clock event service entries. Each trace + reports whether or not the CPU was executing on the Interrupt Control Stack + when the process clock ticked. Execution on the ICS implies that the + operating system is executing. As the process clock ticks every millisecond, + enabling PSERV tracing can quickly produce a large number of trace lines. + + The various trace formats are interpreted as follows: + + >>CPU instr: 00.010341 000300 ZROX,NOP + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~ + | | | | + | | | +-- instruction mnemonic(s) + | | +---------- octal data (instruction opcode) + | +------------------ octal address (P) + +----------------------- octal bank (PBANK) + + >>CPU instr: 00.001240 000006 external interrupt + >>CPU instr: 00.023736 000000 unimplemented instruction trap + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- interrupt classification + | | +---------- parameter + | +------------------ octal address (P) at interrupt + +----------------------- octal bank (PBANK) at interrupt + + + >>CPU data: 00.000000 001340 absolute read + >>CPU data: 00.002100 123003 data read + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~ + | | | | + | | | +-- memory access classification + | | +------------ octal data (memory contents) + | +-------------------- octal address (effective address) + +------------------------- octal bank (PBANK, DBANK, or SBANK) + + + >>CPU fetch: 00.010342 020320 instruction fetch + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- memory access classification + | | +------------ octal data (instruction opcode) + | +-------------------- octal address (P + 1) + +------------------------- octal bank (PBANK) + + + >>CPU reg: 00.006500 000002 A 123003, B 001340, X 000000, M i t r o c CCL + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- register values (from 0-4 TOS registers, X, STA) + | | +------------ octal stack register count (SR) + | +-------------------- octal stack memory address (SM) + +------------------------- octal bank (SBANK) + + >>CPU reg: 00.000000 000001 PB 010000, PL 025227, DL 001770, DB 002000, Q 006510, Z 007000 + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- base register values + | | +------------ current code segment number (from STA) + | +-------------------- zero + +------------------------- octal bank (DBANK) + + + >>CPU pserv: Process clock service entered not on the ICS + + + The process clock offers a user-selectable choice of calibrated or realistic + timing. Calibrated timing adjusts the clock to match actual elapsed time + (i.e., wall-clock time). Realistic timing bases the process-clock interval + on machine instructions executed, using a mean instruction time of 2.5 + microseconds. Running on a typical host platform, the simulator is one or + two orders of magnitude faster than a real HP 3000, so the number of machine + instructions executed for a given calibrated time interval will be + correspondingly greater. When the process clock is calibrated, the current + simulation speed, expressed as a multiple of the speed of a real HP 3000 + Series III, may be obtained with the SHOW CPU SPEED command. The speed + reported will not be representative if the machine was executing a PAUS + instruction when the simulator was stopped. + + When enabled by a SET CPU IDLE command, execution of a PAUS instruction will + idle the simulator. While idle, the simulator does not use any host system + processor time. Idle detection requires that the process clock and system + clock be set to calibrated timing. Idling is disabled by default. + + + Implementation notes: + + 1. Three timing sources in the simulator may be calibrated to wall-clock + time. These are the process clock, the system clock, and the ATC poll + timer. The process clock is always enabled and running, although the + PCLK register only increments if the CPU is not executing on the ICS. + The system clock and poll timer run continuously if their respective + devices are enabled. If the ATC is disabled, then the process clock + takes over polling for the simulation console. + + The three sources must be synchronized to allow efficient simulator + idling. This is accomplished by designating the process clock as the + master device, which calls the SCP timer calibration routines, and + setting the system clock and ATC poll timer to the process clock wait. + All three devices will then enter their respective service routines + concurrently. + + 2. In hardware, the process clock period is fixed at one millisecond, and + the system clock period, while potentially variable, is set by MPE to one + millisecond with an interrupt every 100 ticks. These periods are too + short to allow the simulator to idle, as the host OS clock resolution is + typically also one millisecond. + + Therefore, the process and system clock simulators are scheduled with + ten-millisecond service times, and the PCLK and counter registers are + incremented by ten for each event service. To present the correct values + when the registers are read, the counts are incremented by amounts + proportional to the fractions of the service intervals that have elapsed + when the reads occur. + + 3. In simulation, the TOS renamer is implemented by permanently assigning + the register names RA-RD to TR [0]-TR [3], respectively, and copying the + contents between registers to pop and push values. An alternate + implementation approach is to use a renaming register, RN, that tracks + the correspondence between registers and array entries, and to assign the + register names dynamically using modular indices, e.g., RA is TR [RN], RB + is TR [(RN + 1) & 3], etc. In lieu of copying, incrementing and + decrementing RN is done to pop and push values. + + Both implementations were mocked up and timing measurements made. The + results were that copying values is much faster than mapping via array + index values, so this is the implementation chosen. +*/ + + + +#include "hp3000_defs.h" +#include "hp3000_cpu.h" +#include "hp3000_cpu_ims.h" +#include "hp3000_io.h" + + + +/* External I/O data structures */ + +extern DEVICE iop_dev; /* I/O Processor */ +extern DEVICE sel_dev; /* Selector Channel */ + + +/* Program constants */ + +#define PCLK_PERIOD mS (1) /* 1 millisecond process clock period */ + +#define PCLK_MULTIPLIER 10 /* number of hardware process clock ticks per service */ +#define PCLK_RATE (1000 / PCLK_MULTIPLIER) /* process clock rate in ticks per second */ + +#define UNIT_OPTS (UNIT_EIS) /* the standard equipment feature set */ + + +/* CPU global SCP data definitions */ + +DEVICE cpu_dev; /* incomplete device structure */ + +REG *sim_PC; /* the pointer to the P register */ + + +/* CPU global data structures */ + + +/* CPU registers */ + +HP_WORD CIR = 0; /* current instruction register */ +HP_WORD NIR = 0; /* next instruction register */ +HP_WORD PB = 0; /* program base register */ +HP_WORD P = 0; /* program counter register */ +HP_WORD PL = 0; /* program limit register */ +HP_WORD PBANK = 0; /* program segment bank register */ +HP_WORD DL = 0; /* data limit register */ +HP_WORD DB = 0; /* data base register */ +HP_WORD DBANK = 0; /* data segment bank register */ +HP_WORD Q = 0; /* stack marker register */ +HP_WORD SM = 0; /* stack memory register */ +HP_WORD SR = 0; /* stack register counter */ +HP_WORD Z = 0; /* stack limit register */ +HP_WORD SBANK = 0; /* stack segment bank register */ +HP_WORD TR [4] = { 0, 0, 0, 0 }; /* top of stack registers */ +HP_WORD X = 0; /* index register */ +HP_WORD STA = 0; /* status register */ +HP_WORD SWCH = 0; /* switch register */ +HP_WORD CPX1 = 0; /* run-mode interrupt flags register */ +HP_WORD CPX2 = 0; /* halt-mode interrupt flags register */ +HP_WORD PCLK = 0; /* process clock register */ +HP_WORD CNTR = 0; /* microcode counter */ + + +/* Condition Code B lookup table. + + Byte-oriented instructions set the condition code in the status word using + Pattern B (CCB). For this encoding: + + CCG = ASCII numeric character + CCE = ASCII alphabetic character + CCL = ASCII special character + + The simplest implementation of this pattern is a 256-way lookup table using + disjoint condition code flags. The SET_CCB macro uses this table to set the + condition code appropriate for the supplied operand into the status word. +*/ + +const uint16 cpu_ccb_table [256] = { + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* NUL SOH STX ETX EOT ENQ ACK BEL */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* BS HT LF VT FF CR SO SI */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* DLE DC1 DC2 DC3 DC4 NAK SYN ETB */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* CAN EM SUB ESC FS GS RS US */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* spa ! " # $ % & ' */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* ( ) * + , - . / */ + CFG, CFG, CFG, CFG, CFG, CFG, CFG, CFG, /* 0 1 2 3 4 5 6 7 */ + CFG, CFG, CFL, CFL, CFL, CFL, CFL, CFL, /* 8 9 : ; < = > ? */ + CFL, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* @ A B C D E F G */ + CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* H I J K L M N O */ + CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* P Q R S T U V W */ + CFE, CFE, CFE, CFL, CFL, CFL, CFL, CFL, /* X Y Z [ \ ] ^ _ */ + CFL, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* ` a b c d e f g */ + CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* h i j k l m n o */ + CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* p q r s t u v w */ + CFE, CFE, CFE, CFL, CFL, CFL, CFL, CFL, /* x y z { | } ~ DEL */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 200 - 207 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 210 - 217 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 220 - 227 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 230 - 237 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 240 - 247 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 250 - 257 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 260 - 267 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 270 - 277 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 300 - 307 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 310 - 317 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 320 - 327 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 330 - 337 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 340 - 347 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 350 - 357 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 360 - 367 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL /* 370 - 377 */ + }; + + +/* CPU global state */ + +jmp_buf cpu_save_env; /* the saved environment for microcode aborts */ +uint32 cpu_stop_flags; /* the simulation stop flag set */ + +EXEC_STATE cpu_micro_state = halted; /* the microcode execution state */ +t_bool cpu_base_changed = FALSE; /* TRUE if any base register is changed */ +t_bool cpu_is_calibrated = TRUE; /* TRUE if the process clock is calibrated */ +UNIT *cpu_pclk_uptr = &cpu_unit; /* a pointer to the process clock unit */ + + +/* CPU local state */ + +static uint32 sim_stops = 0; /* the current simulation stop flag settings */ +static uint32 cpu_speed = 1; /* the CPU speed, expressed as a multiplier of a real machine */ +static uint32 pclk_increment = 1; /* the process clock increment per event service */ + + +/* CPU local data structures */ + + +/* Main memory */ + +#define MEMSIZE (cpu_unit.capac) /* the current memory size in 16-bit words */ + +static uint16 *M = NULL; /* the pointer to the main memory allocation */ + + +/* Interrupt classification names */ + +static const char *const interrupt_name [] = { /* class names, indexed by IRQ_CLASS */ + "integer overflow", /* 000 irq_Integer_Overflow */ + "bounds violation", /* 001 irq_Bounds_Violation */ + "illegal memory address error", /* 002 irq_Illegal_Address */ + "non-responding module error", /* 003 irq_Timeout */ + "system parity error", /* 004 irq_System_Parity */ + "address parity error", /* 005 irq_Address_Parity */ + "data parity error", /* 006 irq_Data_Parity */ + "module", /* 007 irq_Module */ + "external", /* 010 irq_External */ + "power fail", /* 011 irq_Power_Fail */ + "ICS trap", /* 012 irq_Trap */ + "dispatch", /* 013 irq_Dispatch */ + "exit" /* 014 irq_IXIT */ + }; + + +/* Trap classification names */ + +static const char *const trap_name [] = { /* trap names, indexed by TRAP_CLASS */ + "no", /* 000 trap_None */ + "bounds violation", /* 001 trap_Bounds_Violation */ + NULL, /* 002 (unused) */ + NULL, /* 003 (unused) */ + NULL, /* 004 (unused) */ + NULL, /* 005 (unused) */ + NULL, /* 006 (unused) */ + NULL, /* 007 (unused) */ + NULL, /* 010 (unused) */ + NULL, /* 011 (unused) */ + NULL, /* 012 (unused) */ + NULL, /* 013 (unused) */ + NULL, /* 014 (unused) */ + NULL, /* 015 (unused) */ + NULL, /* 016 (unused) */ + NULL, /* 017 (unused) */ + "unimplemented instruction", /* 020 trap_Unimplemented */ + "STT violation", /* 021 trap_STT_Violation */ + "CST violation", /* 022 trap_CST_Violation */ + "DST violation", /* 023 trap_DST_Violation */ + "stack underflow", /* 024 trap_Stack_Underflow */ + "privileged mode violation", /* 025 trap_Privilege_Violation */ + NULL, /* 026 (unused) */ + NULL, /* 027 (unused) */ + "stack overflow", /* 030 trap_Stack_Overflow */ + "user", /* 031 trap_User */ + NULL, /* 032 (unused) */ + NULL, /* 033 (unused) */ + NULL, /* 034 (unused) */ + NULL, /* 035 (unused) */ + NULL, /* 036 (unused) */ + "absent code segment", /* 037 trap_CS_Absent */ + "trace", /* 040 trap_Trace */ + "STT entry uncallable", /* 041 trap_Uncallable */ + "absent data segment", /* 042 trap_DS_Absent */ + "power on", /* 043 trap_Power_On */ + "cold load", /* 044 trap_Cold_Load */ + "system halt" /* 045 trap_System_Halt */ + }; + + +/* CPU features table. + + The feature table is used to validate CPU feature changes within the subset + of features supported by a given CPU. Features in the typical list are + enabled when the CPU model is selected. If a feature appears in the typical + list but NOT in the optional list, then it is standard equipment and cannot + be disabled. If a feature appears in the optional list, then it may be + enabled or disabled as desired by the user. + + + Implementation notes: + + 1. The EIS was standard equipment for the Series II and III, so UNIT_EIS + should appear in their "typ" fields. However, the EIS instructions are + not currently implemented, so the value is omitted below. +*/ + +struct FEATURE_TABLE { + uint32 typ; /* standard features plus typically configured options */ + uint32 opt; /* complete list of optional features */ + uint32 maxmem; /* maximum configurable memory in 16-bit words */ + }; + +static const struct FEATURE_TABLE cpu_features [] = { /* features indexed by CPU_MODEL */ + { 0, /* UNIT_SERIES_III */ + 0, + 1024 * 1024 }, + { 0, /* UNIT_SERIES_II */ + 0, + 256 * 1024 } + }; + + +/* Memory access classification table */ + +typedef struct { + HP_WORD *bank_ptr; /* a pointer to the bank register */ + DEVICE *device_ptr; /* a pointer to the accessing device */ + uint32 debug_flag; /* the debug flag for tracing */ + t_bool irq; /* TRUE if an interrupt is requested on error */ + char *const name; /* the classification name */ + } ACCESS_PROPERTIES; + + +static const ACCESS_PROPERTIES access [] = { /* indexed by ACCESS_CLASS */ +/* bank_ptr device_ptr debug_flag irq name */ +/* -------- ---------- ---------- ------ ------------------- */ + { NULL, & iop_dev, DEB_MDATA, FALSE, "absolute" }, /* absolute_iop */ + { NULL, & iop_dev, DEB_MDATA, FALSE, "dma" }, /* dma_iop */ + { NULL, & sel_dev, DEB_MDATA, FALSE, "absolute" }, /* absolute_sel */ + { NULL, & sel_dev, DEB_MDATA, FALSE, "dma" }, /* dma_sel */ + { NULL, & cpu_dev, DEB_MDATA, TRUE, "absolute" }, /* absolute */ + { NULL, & cpu_dev, DEB_MDATA, TRUE, "absolute" }, /* absolute_checked */ + { & PBANK, & cpu_dev, DEB_FETCH, TRUE, "instruction fetch" }, /* fetch */ + { & PBANK, & cpu_dev, DEB_FETCH, TRUE, "instruction fetch" }, /* fetch_checked */ + { & PBANK, & cpu_dev, DEB_MDATA, TRUE, "program" }, /* program */ + { & PBANK, & cpu_dev, DEB_MDATA, TRUE, "program" }, /* program_checked */ + { & DBANK, & cpu_dev, DEB_MDATA, TRUE, "data" }, /* data */ + { & DBANK, & cpu_dev, DEB_MDATA, TRUE, "data" }, /* data_checked */ + { & SBANK, & cpu_dev, DEB_MDATA, TRUE, "stack" }, /* stack */ + { & SBANK, & cpu_dev, DEB_MDATA, TRUE, "stack" } /* stack_checked */ + }; + + +/* CPU local SCP support routine declarations */ + +static t_stat cpu_service (UNIT *uptr); +static t_stat cpu_reset (DEVICE *dptr); +static t_stat cpu_examine (t_value *eval_array, t_addr address, UNIT *uptr, int32 switches); +static t_stat cpu_deposit (t_value value, t_addr address, UNIT *uptr, int32 switches); + +static t_stat set_stops (UNIT *uptr, int32 option, char *cptr, void *desc); +static t_stat set_size (UNIT *uptr, int32 new_size, char *cptr, void *desc); +static t_stat set_model (UNIT *uptr, int32 new_model, char *cptr, void *desc); +static t_stat set_option (UNIT *uptr, int32 new_option, char *cptr, void *desc); + +static t_stat show_stops (FILE *st, UNIT *uptr, int32 val, void *desc); +static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, void *desc); + + +/* CPU local utility routine declarations */ + +static t_stat halt_mode_interrupt (HP_WORD device_number); +static t_stat machine_instruction (void); + + +/* CPU SCP data structures */ + + +/* Unit list. + + The CPU unit holds the main memory capacity and is used to schedule the + process clock events. + + + Implementation notes: + + 1. The unit structure must be global for other modules to obtain the memory + size via the MEMSIZE macro, which references the "capac" field. +*/ + +UNIT cpu_unit = { + UDATA (&cpu_service, UNIT_FIX | UNIT_BINK | UNIT_IDLE | UNIT_CALTIME, 0), PCLK_PERIOD * PCLK_MULTIPLIER + }; + + +/* Register list. + + The CPU register list exposes the machine registers for user inspection and + modification. User flags describe the permitted and default display formats, + as follows: + + - REG_A permits any display + - REG_B permits binary display + - REG_M defaults to CPU instruction mnemonic display + - REG_S defaults to CPU status mnemonic display + + + Implementation notes: + + 1. All registers that reference variables of type HP_WORD must have the + REG_FIT flag for proper access if HP_WORD is a 16-bit type. + + 2. The CNTR register is set to the value of the SR register when the + micromachine halts or pauses. This allows the SR value to be accessed by + the diagnostics. The top-of-stack registers are flushed to main memory + when the machine halts or pauses, which alters SR. +*/ + +static REG cpu_reg [] = { +/* Macro Name Location Width Flags */ +/* ------ ------ ------------ ----- ------------------------ */ + { ORDATA (CIR, CIR, 16), REG_M | REG_RO | REG_FIT }, /* current instruction register */ + { ORDATA (NIR, NIR, 16), REG_M | REG_RO | REG_FIT }, /* next instruction register */ + { ORDATA (PB, PB, 16), REG_FIT }, /* program base register */ + { ORDATA (P, P, 16), REG_FIT }, /* program counter register */ + { ORDATA (PL, PL, 16), REG_FIT }, /* program limit register */ + { ORDATA (PBANK, PBANK, 4), REG_FIT }, /* program segment bank register */ + { ORDATA (DL, DL, 16), REG_FIT }, /* data limit register */ + { ORDATA (DB, DB, 16), REG_FIT }, /* data base register */ + { ORDATA (DBANK, DBANK, 4), REG_FIT }, /* data segment bank register */ + { ORDATA (Q, Q, 16), REG_FIT }, /* stack marker register */ + { ORDATA (SM, SM, 16), REG_FIT }, /* stack memory register */ + { ORDATA (SR, SR, 3), REG_FIT }, /* stack register counter */ + { ORDATA (Z, Z, 16), REG_FIT }, /* stack limit register */ + { ORDATA (SBANK, SBANK, 4), REG_FIT }, /* stack segment bank register */ + { ORDATA (RA, TR [0], 16), REG_A | REG_FIT }, /* top of stack register */ + { ORDATA (RB, TR [1], 16), REG_A | REG_FIT }, /* top of stack - 1 register */ + { ORDATA (RC, TR [2], 16), REG_A | REG_FIT }, /* top of stack - 2 register */ + { ORDATA (RD, TR [3], 16), REG_A | REG_FIT }, /* top of stack - 3 register */ + { ORDATA (X, X, 16), REG_A | REG_FIT }, /* index register */ + { ORDATA (STA, STA, 16), REG_S | REG_B | REG_FIT }, /* status register */ + { ORDATA (SWCH, SWCH, 16), REG_A | REG_FIT }, /* switch register */ + { ORDATA (CPX1, CPX1, 16), REG_B | REG_FIT }, /* run-mode interrupt flags */ + { ORDATA (CPX2, CPX2, 16), REG_B | REG_FIT }, /* halt-mode interrupt flags */ + { ORDATA (PCLK, PCLK, 16), REG_FIT }, /* process clock register */ + { ORDATA (CNTR, CNTR, 6), REG_HRO | REG_FIT }, /* microcode counter */ + + { ORDATA (WRU, sim_int_char, 8), REG_HRO }, /* SCP interrupt character */ + { ORDATA (BRK, sim_brk_char, 8), REG_HRO }, /* SCP break character */ + { ORDATA (DEL, sim_del_char, 8), REG_HRO }, /* SCP delete character */ + + { NULL } + }; + + +/* Modifier list */ + +static MTAB cpu_mod [] = { +/* Mask Value Match Value Print String Match String Validation Display Descriptor */ +/* ------------ --------------- ------------------- ------------ ----------- ------- ---------- */ + { UNIT_MODEL, UNIT_SERIES_II, "Series II", NULL, &set_model, NULL, NULL }, + { UNIT_MODEL, UNIT_SERIES_III, "Series III", "III", &set_model, NULL, NULL }, + + { UNIT_EIS, UNIT_EIS, "EIS", NULL, &set_option, NULL, NULL }, + { UNIT_EIS, 0, "no EIS", "NOEIS", NULL, NULL, NULL }, + + { 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, 128 * 1024, NULL, "128K", &set_size, NULL, NULL }, + { MTAB_XDV, 256 * 1024, NULL, "256K", &set_size, NULL, NULL }, + { MTAB_XDV, 384 * 1024, NULL, "384K", &set_size, NULL, NULL }, + { MTAB_XDV, 512 * 1024, NULL, "512K", &set_size, NULL, NULL }, + { MTAB_XDV, 768 * 1024, NULL, "768K", &set_size, NULL, NULL }, + { MTAB_XDV, 1024 * 1024, NULL, "1024K", &set_size, NULL, NULL }, + + { MTAB_XDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle, NULL }, + { MTAB_XDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL, NULL }, + { MTAB_XDV | MTAB_NMO, 1, "STOPS", "STOP", &set_stops, &show_stops, NULL }, + { MTAB_XDV, 0, NULL, "NOSTOP", &set_stops, NULL, NULL }, + { MTAB_XDV | MTAB_NMO, 0, "SPEED", NULL, NULL, &show_speed, NULL }, + + { 0 } + }; + + +/* Debugging trace list */ + +static DEBTAB cpu_deb [] = { + { "INSTR", DEB_INSTR }, /* instruction executions */ + { "DATA", DEB_MDATA }, /* data accesses */ + { "FETCH", DEB_FETCH }, /* instruction fetches */ + { "REG", DEB_REG }, /* register values */ + { "PSERV", DEB_PSERV }, /* process clock service events */ + { NULL, 0 } + }; + +/* Debugging stop list */ + +static DEBTAB cpu_stop [] = { + { "LOOP", SS_LOOP }, /* stop on an infinite loop */ + { "PAUSE", SS_PAUSE }, /* stop on a PAUS instruction */ + { "UNDEF", SS_UNDEF }, /* stop on an undefined instruction */ + { "UNIMPL", SS_UNIMPL }, /* stop on an unimplemented instruction */ + { NULL, 0 } + }; + + +/* Device descriptor */ + +DEVICE cpu_dev = { + "CPU", /* device name */ + &cpu_unit, /* unit array */ + cpu_reg, /* register array */ + cpu_mod, /* modifier array */ + 1, /* number of units */ + 8, /* address radix */ + PA_WIDTH, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 16, /* data width */ + &cpu_examine, /* examine routine */ + &cpu_deposit, /* deposit routine */ + &cpu_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + NULL, /* device information block pointer */ + DEV_DEBUG, /* device flags */ + 0, /* debug control flags */ + cpu_deb, /* debug flag name array */ + NULL, /* memory size change routine */ + NULL /* logical device name */ + }; + + + +/* CPU global SCP support routines */ + + +/* Execute CPU instructions. + + This is the instruction decode routine for the HP 3000. It is called from + the simulator control program to execute instructions in simulated memory, + starting at the simulated program counter. It runs until the status to be + returned is set to a value other than SCPE_OK. + + On entry, P points to the instruction to execute, and the "sim_switches" + global contains any command-line switches included with the run command. On + exit, P points at the next instruction to execute (or the current + instruction, in the case of a simulator stop during a PAUS instruction or + after the first of two stack operations). + + Execution is divided into four phases. + + First, the instruction prelude configures the simulation state to resume + execution. This involves verifying that there are no device conflicts (e.g., + two devices with the same device number), initializing the I/O processor and + channels, and setting the RUN switch if no other front panel switches are + pressed. These actions accommodate reconfiguration of the I/O device + settings and program counter while the simulator was stopped. The prelude + also responds to one command-line switch: if "-B" is specified, the current + set of simulation stop conditions is bypassed for the first instruction + executed. This allows, e.g., a PAUS instruction to be bypassed or an + unimplemented instruction trap to be taken. + + Second, the microcode abort mechanism is set up. Microcode aborts utilize + the "setjmp/longjmp" mechanism to transfer control out of the instruction + executors without returning through the call stack. This allows an + instruction to be aborted part-way through execution when continuation is + impossible, e.g., due to a memory access violation. It corresponds to direct + microcode jumps out of the execution sequence and to the appropriate trap + handlers. + + Third, the instruction execution loop decodes instructions and calls the + individual executors in turn until a condition occurs that prevents further + execution. Examples of such conditions includes execution of a HALT + instruction, a user stop request (CTRL+E) from the simulation console, a + recoverable device error (such as an improperly formatted tape image), a + user-specified breakpoint, and a simulation stop condition (such as execution + of an unimplemented instruction). The execution loop also polls for I/O + events and device interrupts, and runs I/O channel cycles. During + instruction execution, the CIR register contains the currently executing + instruction, the NIR register contains the next instruction to execute, and + the P register points to the memory location two instructions ahead of the + current instruction. + + Fourth, the instruction postlude updates the simulation state in preparation + for returning to the SCP command prompt. Devices that maintain an internal + state different from their external state, such as the CPU process clock, are + updated so that their internal and external states are fully consistent. + This ensures that the state visible to the user during the simulation stop is + correct. It also ensures that the program counter points correctly at the + next instruction to execute upon resumption. + + If enabled, the simulator is idled when a PAUS instruction has been executed + and no service requests for the multiplexer or selector channels are active. + Execution of a PAUS instruction suspends the fetch-and-execute process until + an interrupt occurs or the simulator is stopped and then resumed with a GO -B + or RUN -B command. + + The HP 3000 is a microcoded machine. In hardware, the micromachine is always + executing microinstructions, even when the CPU is "halted." The halt/run + state is simply a flip-flop setting, reflected in bit 15 of the CPX2 + register, that determines whether the "halt-mode" or "run-mode" microprogram + is currently executing. + + In simulation, the "cpu_micro_state" variable indicates the state of the + micromachine, i.e., which section of the microcode it is executing, while + CPX2 bit 15 indicates whether the macromachine is halted or running. The + micromachine may be in one of four states: + + - running : the run-mode fetch-and-execute microcode is executing + - paused : the run-mode PAUS instruction microcode is executing + - loading : the halt-mode COLD LOAD microcode is executing + - halted : the halt-mode front panel microcode is executing + + Simulation provides a variety of stop conditions that break instruction + execution and return to the SCP prompt with the CPU still in run mode. These + have no analog in hardware; the only way to stop the CPU is to press the HALT + button on the front panel, which shifts the micromachine into halt-mode + microcode execution. When any of these conditions occur, the micromachine + state is set to "halted," but the CPX2 run flag is remains set unless the + stop was caused by execution of a HALT instruction. Resuming execution with + a STEP, CONT, GO, or RUN command proceeds as though the hardware RUN switch + was pressed after a programmed halt. This provides the proper semantics for + seamlessly stopping and restarting instruction execution. + + A microcode abort is performed by executing a "longjmp" to the abort handler, + which is outside of and precedes the instruction execution loop. The value + passed to "longjmp" is a 32-bit integer containing the Segment Transfer Table + index of the trap handler in the lower word and an optional parameter in the + upper word. Aborts are invoked by the MICRO_ABORT macro, which takes as its + parameter a trap classification value, e.g.: + + MICRO_ABORT (trap_Privilege_Violation); + MICRO_ABORT (trap_Integer_Zero_Divide); + + Some aborts require an additional parameter and must be invoked by the + MICRO_ABORTP macro, which takes a trap classification value and a + trap-specific value as parameters. The traps that require additional + parameters are: + + MICRO_ABORTP (trap_CST_Violation, segment_number); + MICRO_ABORTP (trap_STT_Violation, segment_number); + MICRO_ABORTP (trap_CS_Absent, label/n/0); + MICRO_ABORTP (trap_DS_Absent, DST_number); + MICRO_ABORTP (trap_Uncallable, label); + MICRO_ABORTP (trap_Trace, label/n/0); + MICRO_ABORTP (trap_User, trap_number); + + trap_User is not usually called explicitly via MICRO_ABORTP; rather, + MICRO_ABORT is used with one of the specific user-trap identifiers, e.g., + trap_Integer_Overflow, trap_Float_Overflow, trap_Decimal_Overflow, etc., that + supplies both the trap classification and the trap parameter value. + + In addition, user traps must be enabled by setting the T-bit in the status + word. If the T bit is not set, a user trap sets the O-bit (overflow) in the + status word and resumes execution with the next instruction instead of + invoking the user trap handler. + + When an abort occurs, an equivalent PCAL to the appropriate STT entry is set + up. Then execution drops into the instruction loop to execute the first + instruction of the trap handler. + + When the instruction loop is exited, the CPU process clock and system clock + registers are updated, the micromachine is halted, and control returns to + SCP. Upon return, P points at the next instruction to execute, i.e., the + instruction that will execute when the instruction loop is reentered. + + If the micromachine is paused, then P is reset to point to the PAUS + instruction, which will be reexecuted when the routine is reentered. If it + is running, then P is reset to point to the current instruction if the stop + allows it to be rerun, or at the next instruction. + + + Implementation notes: + + 1. While the Microsoft VC++ "setjmp" documentation says, "All variables + (except register variables) accessible to the routine receiving control + contain the values they had when longjmp was called," the ISO C99 + standard says, "All accessible objects have values...as of the time the + longjmp function was called, except that the values of objects of + automatic storage duration that are local to the function containing the + invocation of the corresponding setjmp macro that do not have + volatile-qualified type and have been changed between the setjmp + invocation and longjmp call are indeterminate." + + Therefore, after a microcode abort, we cannot depend upon the values of + any local variables. + + 2. In hardware, the NEXT microcode order present at the end of each + instruction transfers the NIR content to the CIR, reads the memory word + at P into the NIR, and increments P. However, if an interrupt is + present, then this action is omitted, and a microcode jump is performed + to control store location 3, which then jumps to the microcoded interrupt + handler. In simulation, the CIR/NIR/P update is performed before the + next instruction is executed, rather than after the last instruction + completes, so that interrupts are handled before updating. + + In addition, the NEXT action is modified in hardware if the NIR contains + a stack instruction with a non-NOP B stackop. In this case, NEXT + transfers the NIR content to the CIR, reads the memory word at P into the + NIR, but does not increment P. Instead, the R bit of the status register + is set to indicate that a B stackop is pending. When the NEXT at the + completion of the A stackop is executed, the NIR and CIR are untouched, + but P is incremented, and the R bit is cleared. This ensures that if an + interrupt or trap occurs between the stackops, P will point correctly at + the next instruction to be executed. + + In simulation, following the hardware would require testing the NIR for a + non-NOP B stackop at every pass through the instruction execution loop. + To avoid this, the NEXT simulation unilaterally increments P, rather than + only when a B stackop is not present, and the stack instruction executor + tests for the B stackop and sets the R bit there. However, by that time, + P has already been incremented, so we decrement it there to return it to + the correct value. + + 3. The System Halt trap has no handler. Instead, the simulator is halted, + and control returns to the SCP prompt. + + 4. The trace display for a trap reports the parameter value supplied with + the microcode abort. This is not necessarily the same as the parameter + that is pushed on the stack for the trap handler. As some traps, e.g., + trap_CST_Violation, can cause a System Halt, the placement of the trace + call is dictated by the desire to report both the original trap and the + System Halt trap, even though the placement results in the display of the + incoming parameter value, rather than the stacked parameter value. +*/ + +t_stat sim_instr (void) +{ +static const char *const stack_formats [] = { /* stack register display formats, indexed by SR */ + BOV_FORMAT " ", /* SR = 0 format */ + BOV_FORMAT " A %06o, ", /* SR = 1 format */ + BOV_FORMAT " A %06o, B %06o, ", /* SR = 2 format */ + BOV_FORMAT " A %06o, B %06o, C %06o, ", /* SR = 3 format */ + BOV_FORMAT " A %06o, B %06o, C %06o, D %06o, " /* SR = 4 format */ + }; + +int abortval; +HP_WORD label, parameter, device; +TRAP_CLASS trap; +t_stat status = SCPE_OK; + + +/* Instruction prelude */ + +if (sim_switches & SWMASK ('B')) /* if a simulation stop bypass was requested */ + cpu_stop_flags = SS_BYPASSED; /* then clear the stop flags for the first instruction */ +else /* otherwise */ + cpu_stop_flags = sim_stops; /* set the stops as indicated */ + +if (hp_device_conflict ()) /* if the check for device assignment consistency fails */ + status = SCPE_STOP; /* then inhibit execution */ + +else { /* otherwise */ + device = iop_initialize (); /* initialize the IOP */ + mpx_initialize (); /* and the multiplexer channel */ + sel_initialize (); /* and the selector channel */ + + if ((CPX2 & CPX2_IRQ_SET) == 0) /* if no halt-mode interrupt is present */ + CPX2 |= cpx2_RUNSWCH; /* then assume a RUN command */ + } + + +/* Microcode abort processor */ + +abortval = setjmp (cpu_save_env); /* set the microcode abort handler */ + +if (abortval) { /* if a microcode abort occurred */ + trap = TRAP (abortval); /* then get the trap classification */ + parameter = PARAM (abortval); /* and the optional parameter */ + + label = TO_LABEL (LABEL_IRQ, trap); /* form the label from the STT number */ + + dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s trap%s\n", + PBANK, P - 1 & R_MASK, parameter, trap_name [trap], + (trap == trap_User && !(STA & STATUS_T) ? " (disabled)" : "")); + + switch (trap) { /* dispatch on the trap classification */ + + case trap_None: /* trap_None should never occur */ + case trap_System_Halt: + CNTR = SR; /* copy the stack register to the counter */ + cpu_flush (); /* and flush the TOS registers to memory */ + + RA = parameter; /* set RA to the parameter (system halt condition) */ + + CPX2 = CPX2 & ~cpx2_RUN | cpx2_SYSHALT; /* halt the CPU and set the system halt flag */ + status = STOP_SYSHALT; /* and report the system halt condition */ + + label = 0; /* there is no trap handler for a system halt */ + break; + + + case trap_CST_Violation: + if (STT_SEGMENT (parameter) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ + MICRO_ABORT (trap_SysHalt_CSTV_1); /* then the failure is fatal */ + + /* fall into the next trap handler */ + + case trap_STT_Violation: + if (STT_SEGMENT (parameter) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ + MICRO_ABORT (trap_SysHalt_STTV_1); /* then the failure is fatal */ + + /* fall into the next trap handler */ + + case trap_Unimplemented: + case trap_DST_Violation: + case trap_Stack_Underflow: + case trap_Privilege_Violation: + case trap_Bounds_Violation: + parameter = label; /* the label is the parameter for these traps */ + + /* fall into the next trap handler */ + + case trap_DS_Absent: + cpu_flush (); /* flush the TOS registers to memory */ + cpu_mark_stack (); /* and then write a stack marker */ + + /* fall into the next trap handler */ + + case trap_Uncallable: + break; /* set up the trap handler */ + + + case trap_User: + if (STA & STATUS_T) { /* if user traps are enabled */ + STA &= ~STATUS_O; /* then clear overflow status */ + cpu_flush (); /* and flush the TOS registers to memory */ + cpu_mark_stack (); /* and write a stack marker */ + } /* and set up the trap handler */ + + else { /* otherwise in lieu of trapping */ + STA |= STATUS_O; /* set overflow status */ + label = 0; /* and continue execution with the next instruction */ + } + break; + + + case trap_CS_Absent: + if (CPX1 & cpx1_ICSFLAG) /* if the trap occurred while on the ICS */ + MICRO_ABORT (trap_SysHalt_Absent_ICS); /* then the failure is fatal */ + + else if (STT_SEGMENT (STA) <= ISR_SEGMENT) /* otherwise if the trap occurred in segment 1 */ + MICRO_ABORT (trap_SysHalt_Absent_1); /* then the failure is fatal */ + break; /* otherwise set up the trap handler */ + + + case trap_Trace: + if (STT_SEGMENT (STA) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ + MICRO_ABORT (trap_SysHalt_Trace_1); /* then the failure is fatal */ + break; /* otherwise set up the trap handler */ + + + case trap_Cold_Load: /* this trap executes on the ICS */ + status = STOP_CLOAD; /* report that the cold load is complete */ + + /* fall into trap_Stack_Overflow */ + + case trap_Stack_Overflow: /* this trap executes on the ICS */ + if (CPX1 & cpx1_ICSFLAG) /* so if the trap occurred while on the ICS */ + MICRO_ABORT (trap_SysHalt_Overflow_ICS); /* then the failure is fatal */ + + cpu_setup_ics_irq (irq_Trap, trap); /* otherwise, set up the ICS */ + break; /* and then the trap handler */ + + + case trap_Power_On: /* this trap executes on the ICS */ + status = SCPE_INCOMP; /* but is not implemented yet */ + label = 0; /* the trap handler is not called */ + break; + } /* all cases are handled */ + + + if (label != 0) { /* if the trap handler is to be called */ + STA = STATUS_M; /* then clear the status and enter privileged mode */ + + SM = SM + 1 & R_MASK; /* increment the stack pointer */ + cpu_write_memory (stack, SM, parameter); /* and push the parameter on the stack */ + + X = CIR; /* save the current instruction for restarting */ + + cpu_call_procedure (label); /* set up PB, P, PL, and STA to call the procedure */ + + cpu_base_changed = TRUE; /* one or more base registers have changed */ + } + + sim_interval = sim_interval - 1; /* count the execution cycle that aborted */ + } + + +/* Instruction loop */ + +while (status == SCPE_OK) { /* execute until simulator status prevents continuation */ + + if (sim_interval <= 0) { /* if an event timeout has expired */ + status = sim_process_event (); /* then call the event service */ + + if (status != SCPE_OK) /* if the service failed */ + break; /* then abort execution and report the failure */ + } + + if (sel_request) /* if a selector channel request is pending */ + sel_service (1); /* then service it */ + + if (mpx_request_set) /* if a multiplexer channel request is pending */ + mpx_service (1); /* then service it */ + + if (iop_interrupt_request_set /* if a hardware interrupt request is pending */ + && STA & STATUS_I /* and interrupts are enabled */ + && CIR != SED_1) /* and not deferred by a SED 1 instruction */ + device = iop_poll (); /* then poll to acknowledge the request */ + + if (cpu_micro_state == running) /* if the micromachine is running */ + if (CPX1 & CPX1_IRQ_SET) /* then if a run-mode interrupt is pending */ + cpu_run_mode_interrupt (device); /* then service it */ + + else if (sim_brk_summ /* otherwise if a breakpoint exists */ + && sim_brk_test (TO_PA (PBANK, P - 1 & LA_MASK), /* at the next location */ + BP_EXEC)) { /* to execute */ + status = STOP_BRKPNT; /* then stop the simulation */ + sim_interval = sim_interval + 1; /* and don't count the cycle */ + } + + else { /* otherwise execute the next instruction */ + if (DPRINTING (cpu_dev, DEB_REG)) { /* if register tracing is enabled */ + hp_debug (&cpu_dev, DEB_REG, /* then output the active TOS registers */ + stack_formats [SR], + SBANK, SM, SR, RA, RB, RC, RD); + + fprintf (sim_deb, "X %06o, %s\n", /* output the index and status registers */ + X, fmt_status (STA)); + + if (cpu_base_changed) { /* if the base registers have changed since last time */ + hp_debug (&cpu_dev, DEB_REG, /* then output the base registers */ + BOV_FORMAT " PB %06o, PL %06o, DL %06o, DB %06o, Q %06o, Z %06o\n", + DBANK, 0, STA & STATUS_CS_MASK, + PB, PL, DL, DB, Q, Z); + + cpu_base_changed = FALSE; /* clear the base register change flag */ + } + } + + if (!(STA & STATUS_R)) { /* (NEXT) if the right-hand stack op is not pending */ + CIR = NIR; /* then update the current instruction */ + cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ + } + + P = P + 1 & R_MASK; /* point to the following instruction */ + + if (DEBUG_PRI (cpu_dev, DEB_INSTR)) { /* if instruction tracing is enabled */ + sim_eval [0] = CIR; /* then save the instruction that will be executed */ + sim_eval [1] = NIR; /* and the following word for evaluation */ + + hp_debug (&cpu_dev, DEB_INSTR, BOV_FORMAT, /* print the address and the instruction opcode */ + PBANK, P - 2 & R_MASK, CIR); /* as an octal value */ + + if (fprint_cpu (sim_deb, sim_eval, 0, SIM_SW_STOP) != SCPE_OK) /* print the mnemonic; if that fails */ + fprint_val (sim_deb, sim_eval [0], cpu_dev.dradix, /* then print the numeric */ + cpu_dev.dwidth, PV_RZRO); /* value again */ + + fputc ('\n', sim_deb); /* end the trace with a newline */ + } + + status = machine_instruction (); /* execute one machine instruction */ + + cpu_stop_flags = sim_stops; /* reset the stop flags as indicated */ + } + + else if (cpu_micro_state == paused) { /* otherwise if the micromachine is paused */ + if (CPX1 & CPX1_IRQ_SET) /* then if a run-mode interrupt is pending */ + cpu_run_mode_interrupt (device); /* then service it */ + + else if (sim_idle_enab /* otherwise if idling is enabled */ + && ! sel_request && mpx_request_set == 0) /* and there are no channel requests pending */ + sim_idle (TMR_PCLK, FALSE); /* then idle the simulator */ + } + + else if (CPX2 & CPX2_IRQ_SET) /* otherwise if a halt-mode interrupt is pending */ + status = halt_mode_interrupt (device); /* then service it */ + + sim_interval = sim_interval - 1; /* count the execution cycle */ + } /* and continue with the instruction loop */ + + +/* Instruction postlude */ + +cpu_update_pclk (); /* update the process clock */ +clk_update_counter (); /* and system clock counters */ + +if (cpu_micro_state == paused) /* if the micromachine is paused */ + P = P - 2 & R_MASK; /* then set P to point to the PAUS instruction */ + +else if (cpu_micro_state == running) /* otherwise if it is running */ + if (status <= STOP_RERUN) /* then if the instruction will be rerun when resumed */ + P = P - 2 & R_MASK; /* then set P to point to it */ + else /* otherwise */ + P = P - 1 & R_MASK; /* set P to point to the next instruction */ + +cpu_micro_state = halted; /* halt the micromachine */ + +dprintf (cpu_dev, cpu_dev.dctrl, BOV_FORMAT "simulation stop: %s\n", + PBANK, P, STA, sim_error_text (status)); + +return status; /* return the reason for the stop */ +} + + + +/* CPU global utility routines */ + + +/* Read a word from memory. + + Read and return a word from memory at the indicated offset and implied bank. + If the access succeeds, the routine returns TRUE. If the accessed word is + outside of physical memory, the Illegal Address interrupt flag is set for + CPU accesses, the value is set to 0, and the routine returns FALSE. If + access checking is requested, and the check fails, a Bounds Violation trap is + taken. + + On entry, "offset" is a logical offset into the memory bank implied by the + access classification, except for absolute and DMA accesses, for which + "offset" is a physical address. CPU access classifications other than fetch + may be checked or unchecked. Checked accesses must specify locations within + the corresponding segments (PB <= ea <= PL for program, or DL <= ea <= S for + data or stack) unless the CPU in is privileged mode, and those that reference + the TOS locations return values from the TOS registers instead of memory. + + For checked data and stack accesses, there are three cases, depending on the + effective address: + + - EA >= DL and EA <= SM : read from memory + + - EA > SM and EA <= SM + SR : read from a TOS register if bank = stack bank + + - EA < DL or EA > SM + SR : trap if not privileged, else read from memory + + + Implementation notes: + + 1. The physical address is formed by merging the bank and offset without + masking either value to their respective register sizes. Masking is not + necessary, as it was done when the bank registers were loaded, and it is + faster to avoid it. Primarily, though, it is not done so that an invalid + bank register value (e.g., loaded from a corrupted stack) will generate + an illegal address interrupt and so will pinpoint the problem for + debugging. + + 2. In hardware, bounds checking is performed explicitly by microcode. In + simulation, bounds checking is performed explicitly by employing the + "_checked" versions of the desired access classifications. + + 3. The "_iop" and "_sel" classifications serve only to select the correct + accessing device pointer and illegal address interrupt request state. +*/ + +t_bool cpu_read_memory (ACCESS_CLASS classification, uint32 offset, HP_WORD *value) +{ +uint32 bank, address; + +if (access [classification].bank_ptr == NULL) { /* if this is an absolute or DMA access */ + address = offset; /* then the "offset" is already a physical address */ + bank = TO_BANK (offset); /* separate the bank and offset */ + offset = TO_OFFSET (offset); /* in case tracing is active */ + } + +else { /* otherwise the bank register is implied */ + bank = *access [classification].bank_ptr; /* by the access classification */ + address = bank << LA_WIDTH | offset; /* form the physical address with the supplied offset */ + } + +if (address >= MEMSIZE) { /* if this access is beyond the memory size */ + if (access [classification].irq) /* then if an interrupt is requested */ + CPX1 |= cpx1_ILLADDR; /* then set the Illegal Address interrupt */ + + *value = 0; /* return a zero value */ + return FALSE; /* and indicate failure to the caller */ + } + +else { /* otherwise the access is within the memory range */ + switch (classification) { /* so dispatch on the access classification */ + + case absolute_iop: + case dma_iop: + case absolute_sel: + case dma_sel: + case absolute: + case fetch: + case program: + case data: + case stack: + *value = (HP_WORD) M [address]; /* unchecked access values comes from memory */ + break; + + + case absolute_checked: + if (offset > SM && offset <= SM + SR && bank == SBANK) /* if the offset is within the TOS */ + *value = TR [SM + SR - offset]; /* then the value comes from a TOS register */ + else /* otherwise */ + *value = (HP_WORD) M [address]; /* the value comes from memory */ + break; + + + case fetch_checked: + if (PB <= offset && offset <= PL) /* if the offset is within the program segment bounds */ + *value = (HP_WORD) M [address]; /* then the value comes from memory */ + else /* otherwise */ + MICRO_ABORT (trap_Bounds_Violation); /* trap for a bounds violation */ + break; + + + case program_checked: + if (PB <= offset && offset <= PL || PRIV) /* if the offset is within bounds or is privileged */ + *value = (HP_WORD) M [address]; /* then the value comes from memory */ + else /* otherwise */ + MICRO_ABORT (trap_Bounds_Violation); /* trap for a bounds violation */ + break; + + + case data_checked: + case stack_checked: + if (offset > SM && offset <= SM + SR && bank == SBANK) /* if the offset is within the TOS */ + *value = TR [SM + SR - offset]; /* then the value comes from a TOS register */ + else if (DL <= offset && offset <= SM + SR || PRIV) /* if the offset is within bounds or is privileged */ + *value = (HP_WORD) M [address]; /* then the value comes from memory */ + else /* otherwise */ + MICRO_ABORT (trap_Bounds_Violation); /* trap for a bounds violation */ + break; + } /* all cases are handled */ + + dpprintf (access [classification].device_ptr, access [classification].debug_flag, + BOV_FORMAT " %s%s\n", bank, offset, *value, + access [classification].name, + access [classification].debug_flag == DEB_MDATA ? " read" : ""); + + return TRUE; /* indicate success with the returned value stored */ + } +} + + +/* Write a word to memory. + + Write a word to memory at the indicated offset and implied bank. If the + write succeeds, the routine returns TRUE. If the accessed location is outside + of physical memory, the Illegal Address interrupt flag is set for CPU + accesses, the write is ignored, and the routine returns FALSE. If access + checking is requested, and the check fails, a Bounds Violation trap is taken. + + For checked data and stack accesses, there are three cases, depending on the + effective address: + + - EA >= DL and EA <= SM + SR : write to memory + + - EA > SM and EA <= SM + SR : write to a TOS register if bank = stack bank + + - EA < DL or EA > SM + SR : trap if not privileged, else write to memory + + Note that cases 1 and 2 together imply that a write to a TOS register also + writes through to the underlying memory. + + + Implementation notes: + + 1. The physical address is formed by merging the bank and offset without + masking either value to their respective register sizes. Masking is not + necessary, as it was done when the bank registers were loaded, and it is + faster to avoid it. Primarily, though, it is not done so that an invalid + bank register value (e.g., loaded from a corrupted stack) will generate + an illegal address interrupt and so will pinpoint the problem for + debugging. + + 2. In hardware, bounds checking is performed explicitly by microcode. In + simulation, bounds checking is performed explicitly by employing the + "_checked" versions of the desired access classifications. + + 3. The Series II microcode shows that only the STOR and STD instructions + write through to memory when the effective address is in a TOS register. + However, in simulation, all (checked) stack and data writes will write + through. + + 4. The "_iop" and "_sel" classifications serve only to select the correct + accessing device pointer and illegal address interrupt request state. +*/ + +t_bool cpu_write_memory (ACCESS_CLASS classification, uint32 offset, HP_WORD value) +{ +uint32 bank, address; + +if (access [classification].bank_ptr == NULL) { /* if this is an absolute or DMA access */ + address = offset; /* then "offset" is already a physical address */ + bank = TO_BANK (offset); /* separate the bank and offset */ + offset = TO_OFFSET (offset); /* in case tracing is active */ + } + +else { /* otherwise the bank register is implied */ + bank = *access [classification].bank_ptr; /* by the access classification */ + address = bank << LA_WIDTH | offset; /* form the physical address with the supplied offset */ + } + +if (address >= MEMSIZE) { /* if this access is beyond the memory size */ + if (access [classification].irq) /* then if an interrupt is requested */ + CPX1 |= cpx1_ILLADDR; /* then set the Illegal Address interrupt */ + + return FALSE; /* indicate failure to the caller */ + } + +else { /* otherwise the access is within the memory range */ + switch (classification) { /* so dispatch on the access classification */ + + case absolute_iop: + case dma_iop: + case absolute_sel: + case dma_sel: + case absolute: + case data: + case stack: + M [address] = (uint16) value; /* write the value to memory */ + break; + + + case absolute_checked: + if (offset > SM && offset <= SM + SR && bank == SBANK) /* if the offset is within the TOS */ + TR [SM + SR - offset] = value; /* then write the value to a TOS register */ + else /* otherwise */ + M [address] = (uint16) value; /* write the value to memory */ + break; + + + case data_checked: + case stack_checked: + if (offset > SM && offset <= SM + SR && bank == SBANK) /* if the offset is within the TOS */ + TR [SM + SR - offset] = value; /* then write the value to a TOS register */ + + if (DL <= offset && offset <= SM + SR || PRIV) /* if the offset is within bounds or is privileged */ + M [address] = (uint16) value; /* then write the value to memory */ + else /* otherwise */ + MICRO_ABORT (trap_Bounds_Violation); /* trap for a bounds violation */ + break; + + + case fetch: + case fetch_checked: + case program: + case program_checked: /* these classes cannot be used for writing */ + CPX1 |= cpx1_ADDRPAR; /* so set an Address Parity Error interrupt */ + return FALSE; /* and indicate failure to the caller */ + + } /* all cases are handled */ + + dpprintf (access [classification].device_ptr, access [classification].debug_flag, + BOV_FORMAT " %s write\n", bank, offset, value, + access [classification].name); + + return TRUE; /* indicate success with the value written */ + } +} + + +/* Process a run-mode interrupt. + + This routine is called when one or more of the interrupt request bits are set + in the CPX1 register. The highest-priority request is identified and + cleared, the interrupt classification and parameter are established, and the + associated interrupt handler is set up for execution. On return, the CPU has + been configured and is ready to execute the first instruction in the handler. + + On entry, the routine first checks for an external interrupt alone; this is + done to improve performance, as this is the most common case. If some other + interrupt is requested, or if multiple interrupts are requested, the CPX1 + register is scanned from MSB to LSB to identify the request. + + + Implementation notes: + + 1. In hardware, halting the CPU while a PAUS instruction is executing leaves + P pointing to the following instruction, which is executed when the RUN + button is pressed. In simulation, this action occurs only when execution + is resumed with the "-B" option to bypass the pause. Otherwise, resuming + (or stepping) continues with the PAUS instruction. + + If an interrupt occurs while PAUS is executing, the interrupt handler + will return to the instruction following the PAUS, as though HALT and RUN + had been pressed. This occurs because P normally points two instructions + beyond the current instruction, so stacking the value P - 1 during the + interrupt will return to the next instruction to be executed. When + resuming the PAUS instruction from a simulation stop with an interrupt + pending, though, P is set so that P - 1 points to the PAUS instruction, + which is (correctly) the next instruction to execute on resumption. + Stacking the usual P - 1 value, therefore, will cause the interrupt + handler to return to the PAUS instruction instead of to the instruction + following, as would have occurred had a simulation stop not been + involved. + + Therefore, this case is detected when a PAUS instruction is in the CIR + but the micromachine state is "running" instead of "paused", and P is + incremented so that the return will be to the instruction following PAUS. +*/ + +void cpu_run_mode_interrupt (HP_WORD device_number) +{ +HP_WORD request_set, request_count, parameter; +IRQ_CLASS class; + +if (cpu_micro_state == running /* if we are resuming from a sim stop */ + && (CIR & PAUS_MASK) == PAUS) /* into a PAUS instruction */ + P = P + 1 & R_MASK; /* then return is to the instruction following */ +else /* otherwise the micromachine may be paused */ + cpu_micro_state = running; /* but is no longer */ + +request_set = CPX1 & CPX1_IRQ_SET; /* get the set of active interrupt requests */ + +if (request_set == cpx1_EXTINTR) { /* if only an external request present */ + class = irq_External; /* (the most common case) then set the class */ + parameter = device_number; /* and set the parameter to the device number */ + } + +else { /* otherwise scan for the class */ + request_count = 0; /* where CPX1.1 through CPX1.9 */ + request_set = D16_SIGN; /* correspond to IRQ classes 1-9 */ + + while ((CPX1 & request_set) == 0) { /* scan from left to right for the first request */ + request_count = request_count + 1; /* while incrementing the request number */ + request_set = request_set >> 1; /* and shifting the current request bit */ + } + + class = (IRQ_CLASS) request_count; /* set the class from the request count */ + + if (class == irq_Integer_Overflow) { /* if an integer overflow occurred */ + parameter = 1; /* then set the parameter to 1 */ + STA &= ~STATUS_O; /* and clear the overflow flag */ + } + + else if (class == irq_External) /* otherwise if an external interrupt occurred */ + parameter = device_number; /* then set the parameter to the device number */ + + else if (class == irq_Module) /* otherwise if the class is a module interrupt */ + parameter = 0; /* then the parameter is the module number */ + + else /* otherwise the parameter */ + parameter = TO_LABEL (LABEL_IRQ, class); /* is the label */ + } + +CPX1 &= ~request_set; /* clear the associated CPX request bit */ + +dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s interrupt\n", + PBANK, P - 1 & R_MASK, parameter, interrupt_name [class]); + +cpu_setup_irq_handler (class, parameter); /* set up the entry into the interrupt handler */ + +return; +} + + +/* Set up a front panel operation. + + This routine sets the SWCH register to the supplied value and then sets the + appropriate bit in the CPX2 register. This will cause a halt-mode interrupt + when simulated execution is resumed. + + + Implementation notes: + + 1. We do this here to avoid having to export the registers and CPX values + globally. +*/ + +void cpu_front_panel (HP_WORD switch_reg, PANEL_TYPE request) +{ +SWCH = switch_reg; /* set the SWCH register value */ + +switch (request) { /* dispatch on the request type */ + + case Run: /* a run request */ + CPX2 |= cpx2_RUNSWCH; /* set the RUN switch */ + break; + + case Cold_Load: /* a cold load request */ + CPX2 |= cpx2_LOADSWCH; /* set the LOAD switch */ + break; + + case Cold_Dump: /* a cold dump request */ + CPX2 |= cpx2_DUMPSWCH; /* set the DUMP switch */ + break; + } /* all cases are handled */ + +return; +} + + +/* Update the process clock. + + If the process clock is currently calibrated, then the service interval is + actually ten times the hardware period of 1 millisecond. This provides + sufficient event service call spacing to allow idling to work. + + To present the correct value when the process clock 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 is called by the CPU + instruction postlude, so that the PCLK register will have the correct value + if it is examined from the SCP command prompt. +*/ + +void cpu_update_pclk (void) +{ +int32 elapsed, ticks; + +if (cpu_is_calibrated) { /* if the process clock is calibrated */ + elapsed = /* then the elapsed time is the original wait time */ + cpu_unit.wait - sim_activate_time (&cpu_unit); /* less the time remaining before the next service */ + + ticks = /* the adjustment is */ + (elapsed * PCLK_MULTIPLIER) / cpu_unit.wait /* the elapsed fraction of the multiplier */ + - (PCLK_MULTIPLIER - pclk_increment); /* less the amount of any adjustment already made */ + + PCLK = PCLK + ticks & R_MASK; /* update the process clock counter with rollover */ + pclk_increment = pclk_increment - ticks; /* and reduce the amount remaining to add at service */ + } + +return; +} + + + +/* CPU global instruction execution routines */ + + +/* Push the stack down. + + This routine implements the PUSH micro-order to create space on the stack for + a new value. On return, the new value may be stored in the RA register. + + If the SR register indicates that all of the TOS registers are in use, then + the RD register is freed by performing a queue down. Then the values in the + TOS registers are shifted down, freeing the RA register. Finally, SR is + incremented in preparation for the store. +*/ + +void cpu_push (void) +{ +if (SR == 4) /* if all TOS registers are full */ + cpu_queue_down (); /* then move the RD value to memory */ + +RD = RC; /* shift */ +RC = RB; /* the register */ +RB = RA; /* values down */ + +SR = SR + 1; /* increment the register-in-use count */ + +return; +} + + +/* Pop the stack up. + + This routine implements the POP micro-order to delete the top-of-stack value. + + On entry, if the SR register indicates that all of the TOS registers are + empty, then if decrementing the SM register would move it below the DB + register value, and the CPU is not in privileged mode, then a Stack Underflow + trap is taken. Otherwise, the stack memory pointer is decremented. + + If one or more values exist in the TOS registers, the values are shifted up, + deleting the previous value in the RA register, and SR is decremented. +*/ + +void cpu_pop (void) +{ +if (SR == 0) { /* if the TOS registers are empty */ + if (SM <= DB && NPRV) /* then if SM isn't above DB and the mode is non-privileged */ + MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ + + SM = SM - 1 & R_MASK; /* decrement the stack memory register */ + } + +else { /* otherwise at least one TOS register is occupied */ + RA = RB; /* so shift */ + RB = RC; /* the register */ + RC = RD; /* values up */ + + SR = SR - 1; /* decrement the register-in-use count */ + } + +return; +} + + +/* Queue a value from memory up to the register file. + + This routine implements the QUP micro-order to move the value at the top of + the memory stack into the bottom of the TOS register file. There must be a + free TOS register when this routine is called. + + On entry, if decrementing the SM register would move it below the DB register + value, and the CPU is not in privileged mode, then a Stack Underflow trap is + taken. Otherwise, the value pointed to by SM is read into the first unused + TOS register, SM is decremented to account for the removed value, and SR is + incremented to account for the new TOS register in use. + + + Implementation notes: + + 1. SR must be less than 4 on entry, so that TR [SR] is the first unused TOS + register. + + 2. SM and SR must not be modified within the call to cpu_read_memory. For + example, SR++ cannot be passed as a parameter. +*/ + +void cpu_queue_up (void) +{ +if (SM <= DB && NPRV) /* if SM isn't above DB and the mode is non-privileged */ + MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ + +else { /* otherwise */ + cpu_read_memory (stack, SM, &TR [SR]); /* read the value from memory into a TOS register */ + + SM = SM - 1 & R_MASK; /* decrement the stack memory register */ + SR = SR + 1; /* and increment the register-in-use count */ + } + +return; +} + + +/* Queue a value from the register file down to memory. + + This routine implements the QDWN micro-order to move the value at the bottom + of the TOS register file into the top of the memory stack. There must be a + TOS register in use when this routine is called. + + On entry, if incrementing the SM register would move it above the Z register + value, then a Stack Overflow trap is taken. Otherwise, SM is incremented to + account for the new value written, SR is decremented to account for the TOS + register removed from use, and the value in that TOS register is written into + memory at the top of the memory stack. + + + Implementation notes: + + 1. SR must be greater than 0 on entry, so that TR [SR - 1] is the last TOS + register in use. + + 2. SM and SR must not be modified within the call to cpu_write_memory. For + example, SR-- cannot be passed as a parameter. +*/ + +void cpu_queue_down (void) +{ +if (SM >= Z) /* if SM isn't below Z */ + MICRO_ABORT (trap_Stack_Overflow); /* then trap with a Stack Overflow */ + +SM = SM + 1 & R_MASK; /* increment the stack memory register */ +SR = SR - 1; /* and decrement the register-in-use count */ + +cpu_write_memory (stack, SM, TR [SR]); /* write the value from a TOS register to memory */ + +return; +} + + +/* Flush the register file. + + This routine implements the PSHA microcode subroutine that writes the values + of all TOS registers in use to the memory stack. As each value is written, + the SM register is incremented and the SR register is decremented. On + return, the SR register value will be zero. + + The routine does not check for stack overflow. +*/ + +void cpu_flush (void) +{ +while (SR > 0) { /* while one or more registers are in use */ + SM = SM + 1 & R_MASK; /* increment the stack memory register */ + SR = SR - 1; /* and decrement the register-in-use count */ + + cpu_write_memory (stack, SM, TR [SR]); /* write the value from a TOS register to memory */ + } + +return; +} + + +/* Adjust SR until it reaches a specified value. + + This routine implements the SRP1-SRP4 microcode subroutines that adjust the + stack until the prescribed number (1-4) of TOS registers are occupied. It + performs queue ups, i.e., moves values from the top of the memory stack to + the bottom of the register file, until the specified SR value is reached. + Stack underflow is checked after the all of the values have been moved. The + routine assumes that at least one value must be moved. + + + Implementation notes: + + 1. SR must be greater than 0 on entry, so that TR [SR - 1] is the last TOS + register in use. + + 2. SM and SR must not be modified within the call to cpu_read_memory. For + example, SR++ cannot be passed as a parameter. + + 3. The cpu_queue_up routine isn't used, as that routine checks for a stack + underflow after each word is moved rather than only after the last word. +*/ + +void cpu_adjust_sr (uint32 target) +{ +do { + cpu_read_memory (stack, SM, &TR [SR]); /* read the value from memory into a TOS register */ + + SM = SM - 1 & R_MASK; /* decrement the stack memory register */ + SR = SR + 1; /* and increment the register-in-use count */ + } +while (SR < target); /* queue up until the requested number of registers are in use */ + +if (SM <= DB && NPRV) /* if SM isn't above DB, or the mode is non-privileged */ + MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ + +return; +} + + +/* Write a stack marker to memory. + + This routine implements the STMK microcode subroutine that writes a four-word + marker to the stack. The format of the marker is as follows: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X register value | [Q - 3] X + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | PB-relative return address | [Q - 2] P + 1 - PB + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Status register value | [Q - 1] STA + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Delta Q value | [Q - 0] S - Q + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + After the values are written, the Q register is set to point to the marker. + + This routine is always entered with SR = 0, i.e., with the TOS registers + flushed to the memory stack. It does not check for stack overflow. + + + Implementation notes: + + 1. The PB-relative return address points to the instruction after the point + of the call. Conceptually, this is location P + 1, but because the CPU + uses a two-instruction prefetch, the location is actually P - 1. +*/ + +void cpu_mark_stack (void) +{ +SM = SM + 4 & R_MASK; /* adjust the stack pointer */ + +cpu_write_memory (stack, SM - 3, X); /* push the index register */ +cpu_write_memory (stack, SM - 2, P - 1 - PB & LA_MASK); /* and delta P */ +cpu_write_memory (stack, SM - 1, STA); /* and the status register */ +cpu_write_memory (stack, SM - 0, SM - Q & LA_MASK); /* and delta Q */ + +Q = SM; /* set Q to point to the new stack marker */ + +return; +} + + +/* Calculate an effective memory address. + + This routine calculates the effective address for a memory reference or + branch instruction. On entry, "mode_disp" contains the mode, displacement, + index, and indirect fields of the instruction, "classification" and "offset" + point to variables to receive the corresponding values, and "selector" points + to a variable to receive the byte selection ("upper" or "lower") for byte- + addressable instructions or is NULL for word-addressable instructions. On + exit, "classification" is set to the memory access classification, "offset" + is set to the address offset within the memory bank implied by the + classification, and "selector" is set to indicate the byte referenced if the + pointer is non-NULL. + + The mode and displacement fields of the instruction encode an address + relative to one of the base registers P, DB, Q, or S, as follows: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | memory op | X | I | mode and displacement | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 0 | P+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | P- displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 0 | DB+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 0 | Q+ displacement 0-127 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | 0 | Q- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | 1 | S- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+---+ + + The displacement encoded in the instruction is an unsigned value that is + added to or subtracted from the indicated base register. + + If the X and I fields are both 0, the addressing is direct. If the X field + is 1, the addressing is indexed. If the I field is 1, the addressing is + indirect. If both fields are 1, the addressing is indirect indexed, with + indirection performed before indexing. + + To improve execution speed in hardware, a preadder is implemented that sums + the offset contained in certain bits of the CIR with the index register (if + enabled). The primary use is to save a microinstruction cycle during memory + reference instructions, which must add a base register, the offset in the + CIR, and potentially the X register (either directly or shifted left or right + by one place for LDD/STD or LDB/STB, respectively). The preadder also serves + to hold other counts obtained from the CIR, e.g., shift counts, although in + these cases, the addition function is not used. + + This routine simulates the preadder as part of the effective address + calculation. The calculations employed for word addressing are: + + Direct word addressing: + ea = PBANK.(P + displacement) + ea = DBANK.(DB + displacement) + ea = SBANK.(Q,S + displacement) + + Direct indexed word addressing: + ea = PBANK.(P + displacement + X) + ea = DBANK.(DB + displacement + X) + ea = SBANK.(Q,S + displacement + X) + + Indirect word addressing: + ea = PBANK.(P + displacement + M [PBANK.(P + displacement)]) + ea = DBANK.(DB + M [DBANK.(DB + displacement)]) + ea = DBANK.(DB + M [SBANK.(Q,S + displacement)]) + + Indirect indexed word addressing: + ea = PBANK.(P + displacement + M [PBANK.(P + displacement)] + X) + ea = DBANK.(DB + M [DBANK.(DB + displacement)] + X) + ea = DBANK.(DB + M [SBANK.(Q,S + displacement)] + X) + + The indirect cell contains either a self-relative, P-relative address or a + DB-relative address, even for S or Q-relative modes. Indirect branches with + DB/Q/S-relative mode are offsets from PB, not DB. + + The effective address calculations employed for byte addressing are: + + Direct byte addressing: + ea = DBANK.(DB + displacement).byte [0] + ea = SBANK.(Q,S + displacement).byte [0] + + Direct indexed byte addressing: + ea = DBANK.(DB + displacement + X / 2).byte [X & 1] + ea = SBANK.(Q,S + displacement + X / 2).byte [X & 1] + + Indirect byte addressing: + ea,I = DBANK.(DB + M [DBANK.(DB + displacement)] / 2).byte [cell & 1] + ea,I = DBANK.(DB + M [SBANK.(Q,S + displacement)] / 2).byte [cell & 1] + + Indirect indexed byte addressing: + ea,I = DBANK.(DB + (M [DBANK.(DB + displacement)] + X) / 2).byte [cell + X & 1] + ea,I = DBANK.(DB + (M [SBANK.(Q,S + displacement)] + X) / 2).byte [cell + X & 1] + + For all modes, the displacement is a word address, whereas the indirect cell + and index register contain byte offsets. For direct addressing, the byte + selected is byte 0. For all other modes, the byte selected is the byte at + (offset & 1), where the offset is the index register value, the indirect cell + value, or the sum of the two. + + Byte offsets into data segments present problems, in that negative offsets + are permitted (to access the DL-to-DB area), but there are not enough bits to + represent all locations unambiguously in the potential -32K to +32K word + offset range. Therefore, a byte offset with bit 0 = 1 can represent either a + positive or negative word offset from DB, depending on the interpretation. + The HP 3000 adopts the convention that if the address resulting from a + positive-offset interpretation does not fall within the DL-to-S range, then + 32K is added to the address, effectively changing the interpretation from a + positive to a negative offset. If this new address does not fall within the + DL-to-S range, a Bounds Violation trap occurs if the mode is non-privileged. + + The reinterpretation as a negative offset is performed only if the CPU is not + in split-stack mode (where either DBANK is different from SBANK, or DB does + not lie between DL and Z), as extra data segments do not permit negative-DB + addressing. Reinterpretation is also not used for code segments, as negative + offsets from PB are not permitted. + + + Implementation notes: + + 1. On entry, the program counter points to the instruction following the + next instruction (i.e., the NIR location + 1). However, P-relative + offsets are calculated from the current instruction (CIR location). + Therefore, we decrement P by two before adding the offset. + + In hardware, P-relative addresses obtained from the preadder are offset + from P + 1, whereas P-relative addresses obtained by summing with + P-register values obtained directly from the S-Bus are offset from P + 2. + This is because the P-register increment that occurs as part of a NEXT + micro-order is coincident with the R-Bus and S-Bus register loads; both + operations occur when the NXT+1 signal asserts. Therefore, the microcode + handling P-relative memory reference address resolution subtracts one to + get the P value corresponding to the CIR, whereas branches on overflow, + carry, etc. subtract two. + + 2. If the mode is indirect, this routine handles bounds checks and TOS + register accesses on the initial address. + + 3. The System Reference Manual states that byte offsets are interpreted as + negative if the effective address does not lie between DL and Z. + However, the Series II microcode actually uses DL and S for the limits. +*/ + +void cpu_ea (HP_WORD mode_disp, ACCESS_CLASS *classification, HP_WORD *offset, BYTE_SELECTOR *selector) +{ +HP_WORD base, displacement; +ACCESS_CLASS class; + +switch ((mode_disp & MODE_MASK) >> MODE_SHIFT) { /* dispatch on the addressing mode */ + + case 000: + case 001: + case 002: + case 003: /* positive P-relative displacement */ + base = P - 2 + (mode_disp & DISPL_255_MASK); /* add the displacement to the base */ + class = program_checked; /* and classify as a program reference */ + break; + + case 004: + case 005: + case 006: + case 007: /* negative P-relative displacement */ + base = P - 2 - (mode_disp & DISPL_255_MASK); /* subtract the displacement from the base */ + class = program_checked; /* and classify as a program reference */ + break; + + case 010: + case 011: + case 012: + case 013: /* positive DB-relative displacement */ + base = DB + (mode_disp & DISPL_255_MASK); /* add the displacement to the base */ + class = data_checked; /* and classify as a data reference */ + break; + + case 014: + case 015: /* positive Q-relative displacement */ + base = Q + (mode_disp & DISPL_127_MASK); /* add the displacement to the base */ + class = stack_checked; /* and classify as a stack reference */ + break; + + case 016: /* negative Q-relative displacement */ + base = Q - (mode_disp & DISPL_63_MASK); /* subtract the displacement from the base */ + class = stack_checked; /* and classify as a stack reference */ + break; + + case 017: /* negative S-relative displacement */ + base = SM + SR - (mode_disp & DISPL_63_MASK); /* subtract the displacement from the base */ + class = stack_checked; /* and classify as a stack reference */ + break; + } /* all cases are handled */ + + +if (!(mode_disp & I_FLAG_BIT_5)) /* if the mode is direct */ + displacement = 0; /* then there's no displacement */ + +else { /* otherwise the mode is indirect */ + cpu_read_memory (class, base & LA_MASK, &displacement); /* so get the displacement value */ + + if ((CIR & BR_MASK) == BR_DBQS_I) { /* if this a DB/Q/S-relative indirect BR instruction */ + base = PB; /* then PB is the base for the displacement */ + class = program_checked; /* reclassify as a program reference */ + } + + else if (class != program_checked) { /* otherwise if it is a data or stack reference */ + base = DB; /* then DB is the base for the displacement */ + class = data_checked; /* reclassify as a data reference */ + } + /* otherwise, this is a program reference */ + } /* which is self-referential */ + +if ((CIR & LSDX_MASK) == LDD_X /* if the mode */ + || (CIR & LSDX_MASK) == STD_X) /* is double-word indexed */ + displacement = displacement + X * 2; /* then add the doubled index to the displacement */ + +else if (mode_disp & X_FLAG) /* otherwise if the mode is indexed */ + displacement = displacement + X; /* then add the index to the displacement */ + +if (selector == NULL) /* if a word address is requested */ + base = base + displacement; /* then add in the word displacement */ + +else if ((mode_disp & (X_FLAG | I_FLAG_BIT_5)) == 0) /* otherwise if a direct byte address is requested */ + *selector = upper; /* then it references the upper byte */ + +else { /* otherwise an indexed or indirect byte address is requested */ + if (displacement & 1) /* so if the byte displacement is odd */ + *selector = lower; /* then the lower byte was requested */ + else /* otherwise it is even */ + *selector = upper; /* and the upper byte was requested */ + + base = base + (displacement >> 1) & LA_MASK; /* convert the displacement from byte to word and add */ + + if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */ + && (base < DL || base > SM + SR)) /* and the word address is out of range */ + base = base ^ D16_SIGN; /* then add 32K to swap the offset polarity */ + } + +*offset = base & LA_MASK; /* set the */ +*classification = class; /* return values */ + +return; +} + + +/* Set up the entry into an interrupt handler. + + This routine prepares the CPU state to execute an interrupt handling + procedure. On entry, "class" is the classification of the current interrupt, + and "parameter" is the parameter associated with the interrupt. On exit, the + stack has been set up correctly, and the PB, P, PL, and status registers have + been set up for entry into the interrupt procedure. + + Run-mode interrupts are classified as external or internal and ICS or + non-ICS. External interrupts are those originating with the device + controllers, and internal interrupts are conditions detected by the microcode + (e.g., a bounds violation or arithmetic overflow). ICS interrupts execute + their handlers on the system's Interrupt Control Stack. Non-ICS interrupts + execute on the user's stack. + + Of the run-mode interrupts, the External, System Parity Error, Address + Parity Error, Data Parity Error, and Module interrupts execute on the ICS. + All other interrupts execute on the user's stack. The routine begins by + determining whether an ICS or non-ICS interrupt is indicated. The + appropriate stack is established, and the stack marker is written to preserve + the state of the interrupted routine. The label of the handler procedure is + obtained, and then the procedure designated by the label is set up. On + return, the first instruction of the handler is ready to execute. + + + Implementation notes: + + 1. This routine implements various execution paths through the microcode + labeled as INT0 through INT7. + + 2. This routine is also called directly by the IXIT instruction executor if + an external interrupt is pending. This is handled as an external + interrupt but is classified differently so that the teardown and rebuild + of the stack may be avoided to improve performance. + + 3. ICS interrupts other than external interrupts (e.g., parity errors) are + not currently generated or handled by the simulation. +*/ + +void cpu_setup_irq_handler (IRQ_CLASS class, HP_WORD parameter) +{ +HP_WORD label; + +if (class == irq_External || class == irq_IXIT) { /* if entry is for an external interrupt */ + if (class == irq_External) /* then if it was detected during normal execution */ + cpu_setup_ics_irq (class, 0); /* then set it up on the ICS */ + else /* otherwise it was detected during IXIT */ + SM = Q + 2 & R_MASK; /* so the ICS is already set up */ + + DBANK = 0; /* all handlers are in bank 0 */ + STA = STATUS_M | STATUS_I; /* enter privileged mode with interrupts enabled */ + + cpu_read_memory (stack, parameter * 4 + 2, &DB); /* read the DB value */ + cpu_read_memory (stack, parameter * 4 + 1, &label); /* and the procedure label from the DRT */ + } + +else if (class >= irq_System_Parity /* otherwise if entry is for */ + && class <= irq_Power_Fail) { /* another ICS interrupt */ + return; /* then.... [not handled yet] */ + } + +else { /* otherwise entry is for a non-ICS interrupt */ + if (class == irq_Integer_Overflow) /* if this is an integer overflow interrupt */ + label = TO_LABEL (LABEL_IRQ, trap_User); /* then form the label for a user trap */ + else /* otherwise form the label */ + label = TO_LABEL (LABEL_IRQ, class); /* for the specified classification */ + + cpu_flush (); /* flush the TOS registers to memory */ + cpu_mark_stack (); /* and write a stack marker */ + + STA = STATUS_M; /* clear status and enter privileged mode */ + } + +SM = SM + 1 & R_MASK; /* increment the stack pointer */ +cpu_write_memory (stack, SM, parameter); /* and push the parameter on the stack */ + +X = CIR; /* save the CIR in the index register */ + +cpu_call_procedure (label); /* set up to call the interrupt handling procedure */ + +return; +} + + +/* Set up an interrupt on the Interrupt Control Stack. + + This routine prepares the Interrupt Control Stack (ICS) to support interrupt + processing. It is called from the run-time interrupt routine for ICS + interrupts, the microcode abort routine for ICS traps, and from the DISP and + PSEB instruction executors before entering the dispatcher. On entry, "class" + is the interrupt classification, and, if the class is "irq_Trap", then "trap" + is the trap classification. The trap classification is ignored for + interrupts, including the dispatcher start interrupt. + + Unless entry is for a Cold Load trap, the routine begins by writing a + six-word stack marker. This special ICS marker extends the standard marker + by adding the DBANK and DB values as follows: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X register value | [Q - 3] X + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | PB-relative return address | [Q - 2] P + 1 - PB + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Status register value | [Q - 1] STA + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | D | Delta Q value | [Q - 0] S - Q + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | DB-Bank value | [Q + 1] DBANK + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | DB value | [Q + 2] DB + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + D = the dispatcher was interrupted + + After the values are written, the Q register is set to point to the marker. + The stack bank register is then cleared, as the ICS is always located in + memory bank 0. + + If the interrupt or trap occurred while executing on the ICS, and the + dispatcher was running at the time, the "dispatcher running" bit in the CPX1 + register is cleared, and the D-bit is set in the delta-Q value word of the + stack marker. This bit will be used during interrupt exit to decide whether + to restart the dispatcher. + + If the CPU was executing on the user's stack, the "ICS flag" bit in CPX1 is + set, the Q register is reset to point at the permanent dispatcher stack + marker established by the operating system, and the Z register is reset to + the stack limit established by the OS for the ICS; the values are obtained + from reserved memory locations 5 and 6, respectively. The ICS DB value is + read from the ICS global area that precedes the dispatcher stack marker and + is used to write the stack-DB-relative S value back to the global area. + + Finally, the stack pointer is set to point just above the stack marker. + + + Implementation notes: + + 1. This routine implements various execution paths through the microcode + labeled as INT1 through INT5. +*/ + +void cpu_setup_ics_irq (IRQ_CLASS class, TRAP_CLASS trap) +{ +HP_WORD delta_q, stack_db; + +if (class != irq_Trap || trap != trap_Cold_Load) { /* if this is not a cold load trap entry */ + cpu_flush (); /* then flush the TOS registers to memory */ + cpu_mark_stack (); /* and write a four-word stack marker */ + + cpu_write_memory (stack, SM + 1 & LA_MASK, DBANK); /* add DBANK and DB to the stack */ + cpu_write_memory (stack, SM + 2 & LA_MASK, DB); /* to form a six-word ICS marker */ + } + +SBANK = 0; /* the ICS is always located in bank 0 */ + +if (CPX1 & cpx1_ICSFLAG) { /* if execution is currently on the ICS */ + if (CPX1 & cpx1_DISPFLAG) { /* then if the dispatcher was interrupted */ + CPX1 &= ~cpx1_DISPFLAG; /* then clear the dispatcher flag */ + + cpu_read_memory (stack, Q, &delta_q); /* get the delta Q value from the stack marker */ + cpu_write_memory (stack, Q, delta_q | STMK_D); /* and set the dispatcher-interrupted flag */ + } + } + +else { /* otherwise execution is on the user's stack */ + CPX1 |= cpx1_ICSFLAG; /* so set the ICS flag */ + + cpu_read_memory (stack, ICS_Q, &Q); /* set Q = QI */ + cpu_read_memory (stack, ICS_Z, &Z); /* set Z = ZI */ + + cpu_read_memory (stack, Q - 4 & LA_MASK, /* read the stack DB value */ + &stack_db); + + cpu_write_memory (stack, Q - 6 & LA_MASK, /* write the stack-DB-relative S value */ + SM + 2 - stack_db & DV_MASK); /* which is meaningless if this is a cold load */ + + SR = 0; /* invalidate the stack registers for a cold load */ + DL = D16_UMAX; /* and set the data limit */ + } + +SM = Q + 2 & R_MASK; /* set S above the stack marker */ + +return; +} + + +/* Set up a code segment. + + This routine is called to set up a code segment in preparation for calling or + exiting a procedure located in a segment different from the currently + executing segment. On entry, "label" indicates the segment number containing + the procedure. On exit, the new status register value and the first word of + the Code Segment Table entry are returned to the variables pointed to by + "status" and "entry_0", respectively. + + The routine begins by reading the CST pointer. The CST is split into two + parts: a base table, and an extension table. The table to use is determined + by the requested segment number. Segment numbers 0 and 192, corresponding to + the first entries of the two tables, are reserved and cause a CST Violation + trap if specified. + + The CST entry corresponding to the segment number is examined to set the + program bank, base, and limit registers (the segment length stored in the + table is number of quad-words, which must be multiplied by four to get the + size in words). The new status register value is set up and returned, along + with the first word of the CST entry. + + + Implementation notes: + + 1. This routine implements the microcode SSEG subroutine. + + 2. Passing -1 as a parameter to trap_CST_Violation ensures that the segment + number >= 2 check will pass and the trap handler will be invoked. + + 3. The Series II microcode sets PBANK and PB unilaterally but sets PL only + if the code segment is not absent. An absent segment entry contains the + disc address in words 3 and 4 instead of the bank address and base + address, so PBANK and PB will contain invalid values in this case. It is + not clear why the microcode avoids setting PL; the microinstruction in + question also sets Flag 2, so conditioning PL may be just a side effect. + In any case, we duplicate the firmware behavior here. + + 4. This routine is only used locally, but we leave it as a global entry to + support future firmware extensions that may need to call it. +*/ + +void cpu_setup_code_segment (HP_WORD label, HP_WORD *status, HP_WORD *entry_0) +{ +HP_WORD cst_pointer, cst_size, cst_entry, cst_bank, segment_number, entry_number; + +segment_number = STT_SEGMENT (label); /* isolate the segment number from the label */ + +if (segment_number < CST_RESERVED) { /* if the target segment is in the base table */ + cpu_read_memory (absolute, CSTB_POINTER, &cst_pointer); /* then read the CST base pointer */ + entry_number = segment_number; /* and set the entry number */ + } + +else { /* otherwise it is in the extension table */ + cpu_read_memory (absolute, CSTX_POINTER, &cst_pointer); /* so read the CST extension pointer */ + entry_number = segment_number - CST_RESERVED; /* and set the entry number */ + } + +if (entry_number == 0) /* segment numbers 0 and 192 do not exist */ + MICRO_ABORTP (trap_CST_Violation, -1); /* so trap for a violation if either is specified */ + +cpu_read_memory (absolute, cst_pointer, &cst_size); /* read the table size */ + +if (entry_number > cst_size) /* if the entry is outside of the table */ + MICRO_ABORTP (trap_CST_Violation, entry_number); /* then trap for a violation */ + +cst_entry = cst_pointer + entry_number * 4; /* get the address of the target CST entry */ + +cpu_read_memory (absolute, cst_entry, entry_0); /* get the first word of the entry */ +cpu_write_memory (absolute, cst_entry, *entry_0 | CST_R_BIT); /* and set the segment reference bit */ + +cpu_read_memory (absolute, cst_entry + 2, &cst_bank); /* read the bank address word */ +PBANK = cst_bank & CST_BANK_MASK; /* and mask to just the bank number */ + +cpu_read_memory (absolute, cst_entry + 3, &PB); /* read the segment's base address */ + +PL = (*entry_0 & CST_SEGLEN_MASK) * 4 - 1; /* set PL to the segment length - 1 */ + +*status = STA & ~LABEL_SEGMENT_MASK | segment_number; /* set the segment number in the new status word */ + +if (*entry_0 & CST_M_BIT) /* if the segment executes in privileged mode */ + *status |= STATUS_M; /* then set up to enter privileged mode */ + +if (! (*entry_0 & CST_A_BIT)) /* if the segment is not absent */ + PL = PL + PB; /* then set the segment limit */ + +return; +} + + +/* Set up a data segment. + + This routine is called to set up a data segment for access. It is called by + the MDS, MFDS, and MTDS instruction executors to obtain the bank and offset + of specified segments from the Data Segment Table. On entry, + "segment_number" indicates the number of the desired data segment. On exit, + the memory bank number and offset of the data segment base are returned to + the variables pointed to by "bank" and "address", respectively. + + The routine begins by reading the DST pointer. Segment number 0, + corresponding to the first entry of the table, is reserved and causes a DST + Violation trap if specified. + + The DST entry corresponding to the segment number is examined to obtain the + bank and base address. If the segment is absent, a Data Segment Absent trap + is taken. Otherwise, the bank and address values are returned. + + + Implementation notes: + + 1. This routine implements the microcode DSEG subroutine. +*/ + +void cpu_setup_data_segment (HP_WORD segment_number, HP_WORD *bank, HP_WORD *address) +{ +HP_WORD dst_pointer, dst_size, dst_entry, entry_0; + +cpu_read_memory (absolute, DST_POINTER, &dst_pointer); /* read the DST base pointer */ + +if (segment_number == 0) /* segment number 0 does not exist */ + MICRO_ABORT (trap_DST_Violation); /* so trap for a violation if it is specified */ + +cpu_read_memory (absolute, dst_pointer, &dst_size); /* read the table size */ + +if (segment_number > dst_size) /* if the entry is outside of the table */ + MICRO_ABORT (trap_DST_Violation); /* then trap for a violation */ + +dst_entry = dst_pointer + segment_number * 4; /* get the address of the target DST entry */ + +cpu_read_memory (absolute, dst_entry, &entry_0); /* get the first word of the entry */ +cpu_write_memory (absolute, dst_entry, entry_0 | DST_R_BIT); /* and set the segment reference bit */ + +if (entry_0 & DST_A_BIT) /* if the segment is absent */ + MICRO_ABORTP (trap_DS_Absent, segment_number); /* then trap for an absentee violation */ + +cpu_read_memory (absolute, dst_entry + 2, bank); /* read the segment bank number */ +cpu_read_memory (absolute, dst_entry + 3, address); /* and base address */ + +*bank = *bank & DST_BANK_MASK; /* mask off the reserved bits */ + +return; /* before returning to the caller */ +} + + +/* Call a procedure. + + This routine sets up the PB, P, PL, and status registers to enter a + procedure. It is called by the PCAL instruction executor and by the + interrupt and trap routines to set up the handler procedures. On entry, + "label" contains an external program label indicating the segment number and + Segment Transfer Table entry number describing the procedure, or a local + program label indicating the starting address of the procedure. On exit, the + registers are set up for execution to resume with the first instruction of + the procedure. + + If the label is a local label, the PB-relative address is obtained from the + label and stored in the P register, and the Next Instruction Register is + loaded with the first instruction of the procedure. + + If the label is external, the code segment referenced by the label is set up. + If the "trace" or "absent" bits are set, the corresponding trap is taken. + Otherwise, the Segment Transfer Table length is read, and the STT entry + number is validated; if it references a location outside of the table, a STT + violation trap is taken. + + Otherwise, the valid STT entry is examined. If the target procedure is not + in the designated code segment or is uncallable if not in privileged mode, + the appropriate traps are taken. If the STT entry contains a local label, it + is used to set up the P register and NIR as above. + + + Implementation notes: + + 1. This routine implements the microcode PCL3 and PCL5 subroutines. +*/ + +void cpu_call_procedure (HP_WORD label) +{ +HP_WORD new_status, new_label, new_p, cst_entry, stt_size, stt_entry; + +new_status = STA; /* save the status for a local label */ + +if (label & LABEL_EXTERNAL) { /* if the label is non-local */ + cpu_setup_code_segment (label, &new_status, &cst_entry); /* then set up the corresponding code segment */ + + stt_entry = STT_NUMBER (label); /* get the STT entry number from the label */ + + if (cst_entry & (CST_A_BIT | CST_T_BIT)) { /* if the code segment is absent or being traced */ + STA = new_status; /* then set the new status before trapping */ + cpu_mark_stack (); /* and write a stack marker to memory */ + + if (cst_entry & CST_A_BIT) /* if the code segment is absent */ + MICRO_ABORTP (trap_CS_Absent, label); /* then trap to load it */ + else /* otherwise */ + MICRO_ABORTP (trap_Trace, label); /* trap to trace it */ + } + + cpu_read_memory (program_checked, PL, &stt_size); /* read the table size */ + + if (stt_entry > STT_LENGTH (stt_size)) /* if the entry is outside of the table */ + MICRO_ABORTP (trap_STT_Violation, new_status); /* then trap for a violation */ + + cpu_read_memory (program_checked, PL - stt_entry, &new_label); /* read the label from the STT */ + + if (new_label & LABEL_EXTERNAL) /* if the procedure is not in the target segment */ + MICRO_ABORTP (trap_STT_Violation, new_status); /* then trap for a violation */ + + if ((new_label & LABEL_UNCALLABLE) && NPRV) /* if the procedure is uncallable in the current mode */ + MICRO_ABORTP (trap_Uncallable, label); /* then trap for a violation */ + + if (stt_entry == 0) /* if the STT number is zero in an external label */ + label = 0; /* then the starting address is PB */ + else /* otherwise */ + label = new_label; /* the PB offset is contained in the new label */ + } + +new_p = PB + (label & LABEL_ADDRESS_MASK); /* get the procedure starting address */ + +cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the next instruction */ +P = new_p + 1 & R_MASK; /* the bounds are valid, so set the new P value */ + +STA = new_status; /* set the new status value */ + +cpu_base_changed = TRUE; /* one or more base registers have changed for tracing */ + +return; +} + + +/* Return from a procedure. + + This routine sets up the P, Q, SM, and status registers to return from a + procedure. It is called by the EXIT and IXIT instruction executors and by + the cpu_start_dispatcher routine to enter the dispatcher. On entry, "new_q" + and "new_sm" contain the new values for the Q and SM registers that unwind + the stack. The "parameter" value is used only if a Trace or Code Segment + Absent trap is taken. For EXIT, the parameter is the stack adjustment value + (the N field). For IXIT, the parameter is zero. On exit, the registers are + set up for execution to resume with the first instruction after the procedure + call or interrupt. + + The routine begins by reloading register values from the stack marker. The + stack marker format is: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X register value | [Q - 3] X + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | T | M | PB-relative return address | [Q - 2] P + 1 - PB + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Status register value | [Q - 1] STA + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Delta Q value | [Q - 0] S - Q + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + T = a trace or control-Y interrupt is pending + M = the code segment is physically mapped + + The T and M bits are set by the operating system, if applicable, after the + stack marker was originally written. + + Stack underflow and overflow are checked, and privilege changes are + validated. If the return will be to a different code segment, it is set up. + Finally, the new P, Q, SM, and status register values are loaded, and NIR is + loaded with the first instruction after the return. + + + Implementation notes: + + 1. This routine implements the microcode EXI1 subroutine. + + 2. We pass a temporary status to cpu_setup_code_segment because it forms the + returned new status from the current STA register value. But for EXIT + and IXIT, the new status comes from the stack marker, which already has + the segment number to which we're returning, and which must not be + altered. + + 3. The NEXT action is modified when the R-bit is set in the status word + being restored. This occurs when an interrupt occurred between the two + stackops of a stack instruction. The main instruction loop does not + alter CIR in this case, so we must set it up here. +*/ + +void cpu_exit_procedure (HP_WORD new_q, HP_WORD new_sm, HP_WORD parameter) +{ +HP_WORD temp_status, new_status, new_p, cst_entry; + +SM = Q; /* delete any local values from the stack */ + +if (new_q > Z || new_sm > Z) /* if either the new Q or SM exceed the stack limit */ + MICRO_ABORT (trap_Stack_Overflow); /* then trap with a stack overflow */ + +cpu_read_memory (stack, Q - 1, &new_status); /* read the new status value from the stack marker */ + +if ((CIR & EXIT_MASK) == EXIT /* if an EXIT instruction is executing */ + && (new_q < DB || new_sm < DB) /* and either new Q or new S are below the data base */ + && (new_status & STATUS_M) == 0) /* and the new mode is non-privileged */ + MICRO_ABORT (trap_Stack_Underflow); /* then trap with a stack underflow */ + +cpu_read_memory (stack, Q - 2, &new_p); /* read the PB-relative return value from the stack marker */ + +if (NPRV /* if currently in user mode */ + && ((new_status & STATUS_M) /* and returning to privileged mode */ + || (new_status & STATUS_I) != (STA & STATUS_I))) /* or attempting to change interrupt state */ + MICRO_ABORT (trap_Privilege_Violation); /* then trap with a privilege violation */ + +STA &= ~STATUS_I; /* turn off external interrupts */ + +cpu_read_memory (stack, Q - 3, &X); /* read the new X value from the stack marker */ + +if ((new_status & STATUS_CS_MASK) != (STA & STATUS_CS_MASK)) { /* if returning to a different segment */ + cpu_setup_code_segment (new_status, &temp_status, &cst_entry); /* then set up the new segment */ + + if (NPRV && (temp_status & STATUS_M)) /* if in user mode now and returning to a privileged segment */ + MICRO_ABORT (trap_Privilege_Violation); /* then trap with a privilege violation */ + + if (new_p & STMK_T) /* if the new code segment is being traced */ + MICRO_ABORTP (trap_Trace, parameter); /* then trap to trace it */ + + if (cst_entry & CST_A_BIT) /* if the code segment is absent */ + MICRO_ABORTP (trap_CS_Absent, parameter); /* then trap to load it */ + } + +new_p = PB + (new_p & STMK_RTN_ADDR); /* convert the relative address to absolute */ + +cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the next instruction */ +P = new_p + 1 & R_MASK; /* the bounds are valid, so set the new P value */ + +STA = new_status; /* set the new status value */ +Q = new_q; /* and the stack marker */ +SM = new_sm; /* and the stack pointer */ + +if (STA & STATUS_R) { /* if a right-hand stack op is pending */ + CIR = NIR; /* then set the current instruction */ + cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ + } + +cpu_base_changed = TRUE; /* one or more base registers have changed for tracing */ + +return; +} + + +/* Start the dispatcher. + + This routine is called by the DISP and PSEB instruction executors to start + the dispatcher and by the IXIT executor to restart the dispatcher if it was + interrupted. + + On entry, the ICS has been set up. The "dispatcher running" bit in the CPX1 + register is set, Q is set to point at the permanent dispatcher stack marker + on the ICS, the dispatcher's DBANK and DB registers are loaded, and an "exit + procedure" is performed to return to the dispatcher. +*/ + +void cpu_start_dispatcher (void) +{ +dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s interrupt\n", + PBANK, P - 1 & R_MASK, 0, interrupt_name [irq_Dispatch]); + +CPX1 |= cpx1_DISPFLAG; /* set the "dispatcher is running" flag */ + +cpu_read_memory (absolute, ICS_Q, &Q); /* set Q to point to the dispatcher's stack marker */ +cpu_write_memory (absolute, Q, 0); /* and clear the stack marker delta Q value */ + +cpu_read_memory (stack, Q + 1 & LA_MASK, &DBANK); /* load the dispatcher's data bank */ +cpu_read_memory (stack, Q + 2 & LA_MASK, &DB); /* and data base registers */ + +cpu_exit_procedure (Q, Q + 2, 0); /* return to the dispatcher */ + +return; +} + + + +/* CPU local SCP support routines */ + + +/* Service the CPU process clock. + + The process clock is used by the operating system to time per-process CPU + usage. It is always enabled and running, although the PCLK register only + increments if the CPU is not executing on the ICS. + + The process clock may be calibrated to wall-clock time or set to real time. + In hardware, the process clock has a one-millisecond period. Setting the + mode to real time schedules clock events based on the number of event ticks + equivalent to one millisecond. Because the simulator is an order of + magnitude faster than the hardware, this short period precludes idling. + + In the calibrated mode, the short period would still preclude idling. + Therefore, in this mode, the clock is scheduled with a ten-millisecond + service time, and the PCLK register is incremented by ten for each event + service. To present the correct value when PCLK is read, the + "cpu_update_pclk" routine is called by the RCLK instruction executor to + increment the count by an amount proportional to the fraction of the service + interval that has elapsed. In addition, that routine is called by the CPU + instruction postlude, so that PCLK will have the correct value if it is + examined from the SCP command prompt. + + The simulation console is normally hosted by, and therefore polled by, the + ATC on channel 0. If the console is not hosted by the ATC, due either to a + SET ATC NOCONSOLE or a SET ATC DISABLED command, the process clock assumes + polling control over the console. + + + Implementation notes: + + 1. If the process clock is calibrated, the system clock and ATC poll + services are synchronized with the process clock service to improve + idling. + + 2. The current CPU speed, expressed as a multiple of the hardware speed, is + calculated for each service entry. It may be displayed at the SCP prompt + with the SHOW CPU SPEED command. The speed is only representative when + the process clock is calibrated, and the CPU is not executing a PAUS + instruction (which suspends the normal fetch/execute instruction cycle). +*/ + +static t_stat cpu_service (UNIT *uptr) +{ +const t_bool ics_exec = (CPX1 & cpx1_ICSFLAG) != 0; /* TRUE if the CPU is executing on the ICS */ +t_stat status; + +dprintf (cpu_dev, DEB_PSERV, "Process clock service entered on the %s\n", + (ics_exec ? "ICS" : "user stack")); + +if (!ics_exec) /* if the CPU is not executing on the ICS */ + PCLK = PCLK + pclk_increment & R_MASK; /* then increment the process clock */ + +cpu_is_calibrated = (uptr->flags & UNIT_CALTIME) != 0; /* TRUE if the process clock is calibrated */ + +if (cpu_is_calibrated) { /* if the process clock is tracking wall-clock time */ + uptr->wait = sim_rtcn_calb (PCLK_RATE, TMR_PCLK); /* then calibrate it */ + pclk_increment = PCLK_MULTIPLIER; /* and set the increment to the multiplier */ + } + +else { /* otherwise */ + uptr->wait = PCLK_PERIOD; /* set the delay as an event tick count */ + pclk_increment = 1; /* and set the increment without multiplying */ + } + +sim_activate (uptr, uptr->wait); /* reschedule the timer */ + +cpu_speed = uptr->wait / (PCLK_PERIOD * pclk_increment); /* calculate the current CPU speed multiplier */ + +if (atc_is_polling == FALSE) { /* if the ATC is not polling for the simulation console */ + status = sim_poll_kbd (); /* then we must poll for a console interrupt */ + + if (status < SCPE_KFLAG) /* if the result is not a character */ + return status; /* then return the resulting status */ + } + +return SCPE_OK; /* return the success of the service */ +} + + +/* Reset the CPU. + + This routine is called for a RESET, RESET CPU, or BOOT CPU command. It is the + simulation equivalent of the CPURESET signal, which is asserted by the front + panel LOAD switch. In hardware, this causes a microcode restart in addition + to clearing certain registers. + + If this is the first call after simulator startup, the initial memory array + is allocated, the default CPU and memory size configuration is set, and the + SCP-required program counter pointer is set to point to the REG array element + corresponding to the P register. + + If this is a power-on reset ("RESET -P"), the process clock calibrated timer + is initialized, and any LOAD or DUMP request in progress is cleared. + + The micromachine is halted, the process clock is scheduled, and, if a DUMP is + not in progress, several registers are cleared. The register values are + preserved for a DUMP to record the state of the machine accurately. + + + Implementation notes: + + 1. Setting the sim_PC value at run time accommodates changes in the register + order automatically. A fixed setting runs the risk of it not being + updated if a change in the register order is made. +*/ + +static t_stat cpu_reset (DEVICE *dptr) +{ +if (M == NULL) { /* if this is the first call after simulator start */ + M = (uint16 *) calloc (PA_MAX, sizeof (uint16)); /* then allocate the maximum amount of memory needed */ + + if (M == NULL) /* if the allocation failed */ + return SCPE_MEM; /* then report the error and abort the simulation */ + + else /* otherwise the memory was allocated */ + set_model (&cpu_unit, UNIT_SERIES_III, /* so establish the initial CPU model */ + NULL, NULL); + + for (sim_PC = dptr->registers; /* find the P register entry */ + sim_PC->loc != &P && sim_PC->loc != NULL; /* in the register array */ + sim_PC++); /* for the SCP interface */ + + if (sim_PC == NULL) /* if the P register entry is not present */ + return SCPE_NXREG; /* then there is a serious problem! */ + } + +if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */ + sim_rtcn_init (cpu_unit.wait, TMR_PCLK); /* then initialize the process clock timer */ + CPX2 &= ~(cpx2_LOADSWCH | cpx2_DUMPSWCH); /* and clear any cold load or dump request */ + } + +cpu_micro_state = halted; /* halt the micromachine */ +sim_activate_abs (&cpu_unit, cpu_unit.wait); /* and schedule the process clock */ + +if (!(CPX2 & cpx2_DUMPSWCH)) { /* if the DUMP switch is inactive */ + PCLK = 0; /* then clear the process clock counter */ + CPX1 = 0; /* and all run-mode signals */ + CPX2 &= ~(cpx2_RUN | cpx2_SYSHALT); /* and the run and system halt flip-flops */ + + CNTR = SR; /* copy the stack register to the counter */ + cpu_flush (); /* and flush the TOS registers to memory */ + } + +return SCPE_OK; /* indicate that the reset succeeded */ +} + + +/* Examine a memory location. + + This routine is called by the SCP to examine memory. The routine retrieves + the memory location indicated by "address" as modified by any "switches" that + were specified on the command line and returns the value in the first element + of "eval_array". + + On entry, if "switches" includes SIM_SW_STOP, then "address" is an offset + from PBANK; otherwise, it is an absolute address. If the supplied address is + beyond the current memory limit, "non-existent memory" status is returned. + Otherwise, the value is obtained from memory and returned in "eval_array." +*/ + +static t_stat cpu_examine (t_value *eval_array, t_addr address, UNIT *uptr, int32 switches) +{ +if (switches & SIM_SW_STOP) /* if entry is for a simulator stop */ + address = TO_PA (PBANK, address); /* then form a PBANK-based physical address */ + +if (address >= MEMSIZE) /* if the address is beyond memory limits */ + return SCPE_NXM; /* then return non-existent memory status */ + +else if (eval_array == NULL) /* if the value pointer was not supplied */ + return SCPE_IERR; /* then return internal error status */ + +else { /* otherwise */ + eval_array [0] = (t_value) M [address]; /* store the return value */ + return SCPE_OK; /* and return success */ + } +} + + +/* Deposit to a memory location. + + This routine is called by the SCP to deposit to memory. The routine stores + the supplied "value" into memory at the "address" location. If the supplied + address is beyond the current memory limit, "non-existent memory" status is + returned. + + The presence of any "switches" supplied on the command line does not affect + the operation of the routine. +*/ + +static t_stat cpu_deposit (t_value value, t_addr address, UNIT *uptr, int32 switches) +{ +if (address >= MEMSIZE) /* if the address is beyond memory limits */ + return SCPE_NXM; /* then return non-existent memory status */ + +else { /* otherwise */ + M [address] = value & DV_MASK; /* store the supplied value into memory */ + return SCPE_OK; /* and return success */ + } +} + + +/* Set the CPU simulation stop conditions. + + This validation routine is called to configure the set of CPU stop + conditions. The "option" parameter is 0 to clear the stops and 1 to set + them, and "cptr" points to the first character of the name of the stop to be + cleared or set. The unit and description pointers are not used. + + The routine processes commands of the form: + + SET CPU STOP + SET CPU STOP=[;...] + SET CPU NOSTOP + SET CPU NOSTOP=[;...] + + The valid s are contained in the debug table "cpu_stop". If names + are not specified, all stop conditions are enabled or disabled. + + + Implementation notes: + + 1. The CPU simulator maintains a private and a public set of simulator + stops. This routine sets the private set. The private set is copied to + the public set as part of the instruction execution prelude, unless the + "-B" ("bypass") command-line switch is used with the run command. This + allows the stops to be bypassed conveniently for the first instruction + execution only. +*/ + +static t_stat set_stops (UNIT *uptr, int32 option, char *cptr, void *desc) +{ +char gbuf [CBUFSIZE]; +uint32 stop; + +if (cptr == NULL) { /* if there are no arguments */ + sim_stops = 0; /* then clear all of the stop flags */ + + if (option == 1) /* if we're setting the stops */ + for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* then loop through the flags */ + sim_stops |= cpu_stop [stop].mask; /* and add each one to the set */ + } + +else if (*cptr == '\0') /* otherwise if the argument is empty */ + return SCPE_MISVAL; /* then report the missing value */ + +else /* otherwise at least one argument is present */ + while (*cptr) { /* loop through the arguments */ + cptr = get_glyph (cptr, gbuf, ';'); /* get the next argument */ + + for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* loop through the flags */ + if (strcmp (cpu_stop [stop].name, gbuf) == 0) { /* and if the argument matches */ + if (option == 1) /* then if it's a STOP argument */ + sim_stops |= cpu_stop [stop].mask; /* then add the stop flag */ + else /* otherwise it's a NOSTOP argument */ + sim_stops &= ~cpu_stop [stop].mask; /* so remove the flag */ + + break; /* this argument has been processed */ + } + + if (cpu_stop [stop].name == NULL) /* if the argument was not found */ + return SCPE_ARG; /* then report it */ + } + +return SCPE_OK; /* the stops were successfully processed */ +} + + +/* Change the CPU memory size. + + This validation routine is called to configure the CPU memory size. The + "new_size" parameter is set to the size desired and will be one of the + discrete sizes supported by the machine. The "uptr" parameter points to the + CPU unit and is used to obtain the CPU model. The other parameters are not + used. + + The routine processes commands of the form: + + SET [-F] CPU + + If the new memory size is larger than the supported size for the CPU model + currently selected, the routine returns an error. If the new size is smaller + than the previous size, and if the area that would be lost contains non-zero + data, the user is prompted to confirm that memory should be truncated. If + the user denies the request, the change is rejected. Otherwise, the new size + is set. The user may omit the confirmation request and force truncation by + specifying the "-F" switch on the command line. + + + Implementation notes: + + 1. The memory access routines return a zero value for locations beyond the + currently defined memory size. Therefore, the unused area need not be + explicitly zeroed. +*/ + +static t_stat set_size (UNIT *uptr, int32 new_size, char *cptr, void *desc) +{ +static const char confirm [] = "Really truncate memory [N]?"; + +const uint32 model = CPU_MODEL (uptr->flags); /* the current CPU model index */ +uint32 address; + +if ((uint32) new_size > cpu_features [model].maxmem) /* if the new memory size is not supported on current model */ + return SCPE_NOFNC; /* then report the error */ + +if (!(sim_switches & SWMASK ('F'))) /* if truncation is not explicitly forced */ + for (address = new_size; address < MEMSIZE; address++) /* then check the values in truncated memory, if any */ + if (M [address] != 0) /* if this location is non-zero */ + if (get_yn (confirm, FALSE) == FALSE) /* then if the user denies confirmation */ + return SCPE_INCOMP; /* then abort the command */ + else /* otherwise we have explicit confirmation to proceed */ + break; /* so checking is no longer necessary */ + +MEMSIZE = new_size; /* set the new memory size */ + +return SCPE_OK; /* confirm that the change is OK */ +} + + +/* Change the CPU model. + + This validation routine is called to configure the CPU model. The + "new_model" parameter is set to the model desired and will be one of the unit + model flags. The other parameters are not used. + + The routine processes commands of the form: + + SET [-F] CPU + + Setting the model establishes a set of typical hardware features. It also + verifies that the current memory size is supported by the new model. If it + is not, the size is reduced to the maximum supported memory configuration. + If the area that would be lost contains non-zero data, the user is prompted + to confirm that memory should be truncated. If the user denies the request, + the change is rejected. Otherwise, the new size is set. The user may omit + the confirmation request and force truncation by specifying the "-F" switch + on the command line. + + This routine is also called once from the CPU reset routine to establish the + initial CPU model. The current memory size will be 0 when this call is made. +*/ + +static t_stat set_model (UNIT *uptr, int32 new_model, char *cptr, void *desc) +{ +const uint32 new_index = CPU_MODEL (new_model); /* the new index into the CPU features table */ +uint32 new_memsize; +t_stat status; + +if (MEMSIZE == 0 /* if this is the initial establishing call */ + || MEMSIZE > cpu_features [new_index].maxmem) /* or if the current memory size is unsupported */ + new_memsize = cpu_features [new_index].maxmem; /* then set the new size to the maximum supported size */ +else /* otherwise the current size is valid for the new model */ + new_memsize = MEMSIZE; /* so leave it unchanged */ + +status = set_size (uptr, new_memsize, NULL, NULL); /* set the new memory size */ + +if (status == SCPE_OK) /* if the change succeeded */ + uptr->flags = uptr->flags & ~UNIT_OPTS /* then set the typical features */ + | cpu_features [new_index].typ; /* for the new model */ + +return status; /* return the validation result */ +} + + +/* Change a CPU option. + + This validation routine is called to configure the option set for the current + CPU model. The "new_option" parameter is set to the option desired and will + be one of the unit option flags. The "uptr" parameter points to the CPU unit + and is used to obtain the CPU model. The other parameters are not used. + + The routine processes commands of the form: + + SET CPU