1338 lines
65 KiB
C
1338 lines
65 KiB
C
/* hp3000_sel.c: HP 3000 30030C Selector Channel simulator
|
|
|
|
Copyright (c) 2016-2017, 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.
|
|
|
|
SEL HP 3000 Series III Selector Channel
|
|
|
|
05-Sep-17 JDB Changed REG_A (permit any symbolic override) to REG_X
|
|
10-Oct-16 JDB Renumbered debug flags to start at 0
|
|
Added port_read_memory, port_write_memory macros
|
|
11-Jul-16 JDB Change "sel_unit" from a UNIT to an array of one UNIT
|
|
30-Jun-16 JDB Reestablish active_dib pointer during sel_initialize
|
|
08-Jun-16 JDB Corrected %d format to %u for unsigned values
|
|
16-May-16 JDB abort_channel parameter is now a pointer-to-constant
|
|
21-Mar-16 JDB Changed uint16 types to HP_WORD
|
|
23-Sep-15 JDB First release version
|
|
27-Jan-15 JDB Passes the selector channel diagnostic (D429A)
|
|
10-Feb-13 JDB Created
|
|
|
|
References:
|
|
- HP 3000 Series II/III System Reference Manual
|
|
(30000-90020, July 1978)
|
|
- Stand-Alone HP 30030B/C Selector Channel Diagnostic
|
|
(30030-90011, July 1978)
|
|
- HP 3000 Series III Engineering Diagrams Set
|
|
(30000-90141, Apr-1980)
|
|
|
|
|
|
The HP 30030C Selector Channel provides high-speed data transfer between a
|
|
device and main memory. While several interfaces may be connected to the
|
|
selector channel bus, only one transfer is active at a time, and the channel
|
|
remains dedicated to that interface until the transfer is complete. The
|
|
channel contains its own memory port controller, so transfers to and from
|
|
memory bypass the I/O Processor.
|
|
|
|
Interfaces must have additional hardware to be channel-capable, as the
|
|
channel uses separate control and data signals from those used for direct
|
|
I/O. In addition, the multiplexer and selector channels differ somewhat in
|
|
their use of the signals, so interfaces are generally designed for use with
|
|
one or the other (the Selector Channel Maintenance Board is a notable
|
|
exception that uses jumpers to indicate which channel to use).
|
|
|
|
The transfer rate of the Series III selector channel is poorly documented.
|
|
Various rates are quoted in different publications: a 1.9 MB/second rate in
|
|
one, a 2.86 MB/second rate in another. Main memory access time is given as
|
|
300 nanoseconds, and the cycle time is 700 nanoseconds.
|
|
|
|
Once started by an SIO instruction, the channel executes I/O programs
|
|
independently of the CPU. Program words are read, and device status is
|
|
written back, directly via the port controller.
|
|
|
|
32-bit I/O program words are formed from a 16-bit I/O control word (IOCW) and
|
|
a 16-bit I/O address word (IOAW) in this general format:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| C | order | X | control word 1/word count | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| control word 2/status/address | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Most orders are fully decoded by bits 1-3, but a few use bit 4 to extend the
|
|
definition where bits 4-15 are not otherwise used. I/O programs always
|
|
reside in memory bank 0. The current I/O program pointer resides in word 0
|
|
of the Device Reference Table entry for the active interface.
|
|
|
|
The Jump and Jump Conditional orders use this format:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - | 0 0 0 | C | - - - - - - - - - - - | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| jump target address | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
...where C is 0 for an unconditional jump and 1 for a conditional jump. An
|
|
unconditional jump is handled entirely within the channel. A conditional
|
|
jump asserts the SETJMP signal to the interface. If the interface returns
|
|
JMPMET, the jump will occur; otherwise, execution continues with the next
|
|
program word.
|
|
|
|
The Return Residue order uses this format:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - | 0 0 1 0 | - - - - - - - - - - - | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| residue of word count | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
The remaining word count from the last transfer will be returned in the IOAW
|
|
as a two's-complement value. If the transfer completed normally, the
|
|
returned value will be zero.
|
|
|
|
The Set Bank order uses this format:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - | 0 0 1 1 | - - - - - - - - - - - | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - - - - - - - - - - - - | bank | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
This establishes the memory bank to be used for subsequent Write or Read
|
|
orders. Program addresses always use bank 0.
|
|
|
|
The Interrupt order uses this format:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - | 0 1 0 | - - - - - - - - - - - - | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - - - - - - - - - - - - - - - - | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
The SETINT signal is asserted to the interface for this order.
|
|
|
|
The End and End with Interrupt orders use this format:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - | 0 1 1 | I | - - - - - - - - - - - | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| device status | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
...where I is 0 for an End and 1 for an End with Interrupt. The PSTATSTB
|
|
signal is asserted to the interface to obtain the device status, which is
|
|
stored in the IOAW location. If the I bit is set, SETINT will also be
|
|
asserted,
|
|
|
|
The Control order uses this format:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - | 1 0 0 | control word 1 | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| control word 2 | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Both control words are sent to the interface. The full IOCW containing
|
|
control word 1 is sent with the PCMD1 signal asserted. It is followed by the
|
|
IOAW with PCONTSTB asserted.
|
|
|
|
The Sense order uses this format:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| - | 1 0 1 | - - - - - - - - - - - - | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| device status | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
The PSTATSTB signal is asserted to the interface to obtain the device status,
|
|
which is stored in the IOAW location.
|
|
|
|
The Write and Read orders use these formats:
|
|
|
|
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| C | 1 1 0 | negative word count to write | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| C | 1 1 1 | negative word count to read | IOCW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| transfer address | IOAW
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
The C bit is the "data chain" flag. If it is set, then this transfer is a
|
|
continuation of a previous Write or Read transfer. This is used to
|
|
circumvent the transfer size limitation inherent in the 12-bit word count
|
|
allocated in the IOCW. For single transfers larger than 4K words, multiple
|
|
contiguous Write or Read orders are used, with all but the last order having
|
|
their data chain bits set.
|
|
|
|
In simulation, IOCW bits 1-4 are used to index into a 16-element lookup table
|
|
to produce the final I/O order (because some of the orders define IOCW bit 4
|
|
as "don't care", there are only thirteen distinct orders).
|
|
|
|
Channel-capable interfaces connect via the selector channel bus and request
|
|
channel service by asserting the CHANSR signal. An interface may initiate an
|
|
I/O program on the channel only if the channel is not busy with a transfer
|
|
involving another interface. The SIO instruction microcode tests status bit
|
|
0 ("SIO OK") from the interface before starting the I/O program. This bit
|
|
reflects the SIOENABLE signal from the channel, which is true only when the
|
|
channel is idle. If the channel is busy, the SIO instruction sets CCG and
|
|
returns without disturbing the channel.
|
|
|
|
The channel uses double-buffering for both data and I/O program words,
|
|
allowing concurrent channel/interface and channel/memory transfers.
|
|
Prefetching of the next I/O program word increases channel speed by reducing
|
|
the wait on memory transfers.
|
|
|
|
In simulation, an interface is implicitly connected to the selector channel
|
|
bus by calling the "sel_assert_REQ" routine. This initiates the transfer
|
|
between the device number of the interface and the channel. The
|
|
"service_request" field in the DIB is not used (it must be set to the
|
|
SRNO_UNUSED value).
|
|
|
|
The channel simulator provides these global objects:
|
|
|
|
t_bool sel_is_idle
|
|
|
|
TRUE if the selector channel is idle; FALSE otherwise. Corresponds to
|
|
the hardware SIOENABLE signal and reflects the value of the Selector
|
|
Active flip-flop. Used by device interfaces to qualify their SIO OK
|
|
status bits.
|
|
|
|
sel_assert_REQ (DIB *)
|
|
|
|
Called by the device interface while processing a DSTARTIO signal to
|
|
request that the selector channel begin an SIO operation, or called at
|
|
any time while the channel is active to abort the operation. Corresponds
|
|
to asserting the REQ signal. If the channel is idle, it initializes the
|
|
channel and starts the sequencer. If the channel is active, it aborts
|
|
the I/O program execution and idles the channel.
|
|
|
|
sel_assert_CHANSR (DIB *)
|
|
|
|
Called by the device controller to request service from the selector
|
|
channel asynchronously. Corresponds to asserting the CHANSR signal.
|
|
Typically called from a device service routine; device controller
|
|
interface routines return the CHANSR signal to request service
|
|
synchronously. Sets the service_request flag in the DIB and sets
|
|
sel_request.
|
|
|
|
t_bool sel_request
|
|
|
|
TRUE if an interface is requesting service from the selector channel or
|
|
the channel is servicing an internal request; FALSE otherwise.
|
|
Corresponds to the CHANSR signal received by the channel. Used by the
|
|
CPU to determine if a request is pending and the selector channel service
|
|
routine should be called.
|
|
|
|
sel_initialize (void)
|
|
|
|
Called in the instruction execution prelude to allow devices to be
|
|
reassigned or reset. If a device is under channel control, the routine
|
|
reestablishes the device number for the channel. It also sets
|
|
sel_request TRUE if the device has a service request pending or FALSE
|
|
otherwise.
|
|
|
|
sel_service (uint32)
|
|
|
|
Called to service a request from the device interface or an internal
|
|
request from the selector channel. Executes one or more channel cycles
|
|
for the associated interface. Used by the CPU to run the selector
|
|
channel.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. In hardware, the installed memory size must be set with jumper S3 on the
|
|
Selector Channel Register PCA. In simulation, the size is determined by
|
|
using the MEMSIZE macro that tests the "capac" field of the CPU unit.
|
|
|
|
2. Multiple channels and interleaved memory operation and its associated
|
|
configuration jumpers are not supported.
|
|
|
|
3. The selectable trigger/freeze and error logging register features of the
|
|
Selector Channel Control PCA are not supported. Debug tracing may be
|
|
enabled to determine why an I/O program aborted.
|
|
|
|
3. The selector channel must execute more than one I/O order per CPU
|
|
instruction in order to meet the timing requirements of the diagnostic.
|
|
The timing is modeled by establishing a count of channel clock pulses at
|
|
poll entry and then executing orders until the count is exhausted. If
|
|
the clock count was exceeded, the excess count is saved and then
|
|
subtracted from the next entry's count, so that the typical execution
|
|
time is preserved over a number of entries.
|
|
*/
|
|
|
|
|
|
|
|
#include "hp3000_defs.h"
|
|
#include "hp3000_cpu_ims.h"
|
|
#include "hp3000_io.h"
|
|
#include "hp3000_mem.h"
|
|
|
|
|
|
|
|
/* Program constants.
|
|
|
|
The selector channel clock period is 175 nanoseconds. The channel runs
|
|
concurrently with the CPU, which executes instructions in an average of 2.57
|
|
microseconds, so multiple cycles are executed per CPU instruction.
|
|
|
|
In simulation, the channel is called from the instruction execution loop
|
|
after every instruction, and sometimes additionally within instructions that
|
|
have long execution times (e.g., MOVE). The number of event ticks that have
|
|
elapsed since the last call are passed to the channel; this determines the
|
|
number of channel cycles to execute.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The number of cycles consumed by the channel for various operations are
|
|
educated guesses. There is no documentation available that details the
|
|
cycle timing.
|
|
|
|
2. In simulation, the Wait Sequence exists separately from the Transfer
|
|
Sequence only to avoid cancelling the SR wait timer for each word
|
|
transferred. It is reported as a Transfer Sequence cycle.
|
|
*/
|
|
|
|
#define SR_WAIT_TIMER mS (1000) /* 1000 millisecond SR wait timer */
|
|
|
|
#define NS_PER_CYCLE 175 /* each clock cycle is 175 nanoseconds */
|
|
|
|
#define CYCLES_PER_FETCH 6
|
|
#define CYCLES_PER_PREFETCH 1
|
|
#define CYCLES_PER_EXECUTE 1
|
|
#define CYCLES_PER_RELOAD 3
|
|
#define CYCLES_PER_READ 4
|
|
#define CYCLES_PER_WRITE 4
|
|
|
|
#define CYCLES_PER_EVENT (uint32) (USEC_PER_EVENT * 1000 / NS_PER_CYCLE)
|
|
|
|
#define CNTR_MASK 0007777u /* word counter count mask */
|
|
#define CNTR_MAX 0007777u /* word counter maximum value */
|
|
|
|
|
|
typedef enum { /* selector channel sequencer state */
|
|
Idle_Sequence,
|
|
Fetch_Sequence,
|
|
Execute_Sequence,
|
|
Wait_Sequence,
|
|
Transfer_Sequence,
|
|
Reload_Sequence
|
|
} SEQ_STATE;
|
|
|
|
static const char *const seq_name [] = { /* indexed by SEQ_STATE */
|
|
"Idle",
|
|
"Fetch",
|
|
"Execute",
|
|
"Transfer", /* the wait sequence is reported as a transfer sequence */
|
|
"Transfer",
|
|
"Reload"
|
|
};
|
|
|
|
static const char *const action_name [] = { /* indexed by SEQ_STATE */
|
|
NULL, /* no loading occurs in the Idle_Sequence */
|
|
"loaded", /* loads occur in the Fetch_Sequence */
|
|
"prefetched", /* prefetches occur in the Execute_Sequence */
|
|
NULL, /* no loading occurs in the Wait_Sequence */
|
|
"prefetched", /* prefetches occur in the Transfer_Sequence */
|
|
"loaded" /* loads occur in the Reload_Sequence */
|
|
};
|
|
|
|
|
|
/* Debug flags */
|
|
|
|
#define DEB_CSRW (1u << 0) /* trace channel command initiations and completions */
|
|
#define DEB_PIO (1u << 1) /* trace programmed I/O commands */
|
|
#define DEB_STATE (1u << 2) /* trace state changes */
|
|
#define DEB_SR (1u << 3) /* trace service requests */
|
|
|
|
|
|
/* Memory access macros */
|
|
|
|
#define port_read_memory(c,o,v) mem_read (&sel_dev, c, o, v)
|
|
#define port_write_memory(c,o,v) mem_write (&sel_dev, c, o, v)
|
|
|
|
|
|
/* Channel global state */
|
|
|
|
t_bool sel_is_idle = TRUE; /* TRUE if the channel is idle */
|
|
t_bool sel_request = FALSE; /* TRUE if the channel sequencer is to be invoked */
|
|
|
|
|
|
/* Channel local state */
|
|
|
|
static SEQ_STATE sequencer = Idle_Sequence; /* the current sequencer execution state */
|
|
static SIO_ORDER order; /* the current SIO order */
|
|
static DIB *active_dib; /* a pointer to the participating interface's DIB */
|
|
static uint32 device_index; /* the index into the device table */
|
|
static t_bool prefetch_control; /* TRUE if the IOCW should be prefetched */
|
|
static t_bool prefetch_address; /* TRUE if the IOAW should be prefetched */
|
|
|
|
static uint32 device_number; /* the participating interface's device number */
|
|
static uint32 bank; /* the transfer bank register */
|
|
static uint32 word_count; /* the transfer word count register */
|
|
|
|
static HP_WORD program_counter; /* the I/O program counter */
|
|
static HP_WORD control_word; /* the current IOCW */
|
|
static HP_WORD control_buffer; /* the prefetched IOCW */
|
|
static HP_WORD address_word; /* the current IOAW */
|
|
static HP_WORD address_buffer; /* the prefetched IOAW */
|
|
static HP_WORD input_buffer; /* the input data word buffer */
|
|
static HP_WORD output_buffer; /* the output data word buffer */
|
|
|
|
static FLIP_FLOP rollover; /* SET if the transfer word count rolls over */
|
|
static int32 excess_cycles; /* the count of cycles in excess of allocation */
|
|
|
|
|
|
/* Channel local SCP support routines */
|
|
|
|
static t_stat sel_timer (UNIT *uptr);
|
|
static t_stat sel_reset (DEVICE *dptr);
|
|
|
|
/* Channel local utility routines */
|
|
|
|
static void end_channel (DIB *dibptr);
|
|
static SIGNALS_DATA abort_channel (const char *reason);
|
|
static void load_control (HP_WORD *value);
|
|
static void load_address (HP_WORD *value);
|
|
|
|
|
|
/* Channel SCP data structures */
|
|
|
|
|
|
/* Unit list */
|
|
|
|
static UNIT sel_unit [] = {
|
|
{ UDATA (&sel_timer, 0, 0), SR_WAIT_TIMER }
|
|
};
|
|
|
|
/* Register list */
|
|
|
|
static REG sel_reg [] = {
|
|
/* Macro Name Location Width Offset Flags */
|
|
/* ------ ------ --------------- ----- ------ ----------------- */
|
|
{ FLDATA (IDLE, sel_is_idle, 0) },
|
|
{ FLDATA (SREQ, sel_request, 0) },
|
|
{ DRDATA (DEVNO, device_number, 8), PV_LEFT },
|
|
{ DRDATA (EXCESS, excess_cycles, 32), PV_LEFT },
|
|
{ DRDATA (INDEX, device_index, 32), PV_LEFT | REG_HRO },
|
|
|
|
{ DRDATA (SEQ, sequencer, 3) },
|
|
{ ORDATA (ORDER, order, 4) },
|
|
{ FLDATA (ROLOVR, rollover, 0) },
|
|
{ FLDATA (PFCNTL, prefetch_control, 0) },
|
|
{ FLDATA (PFADDR, prefetch_address, 0) },
|
|
|
|
{ ORDATA (BANK, bank, 4), PV_LEFT },
|
|
{ DRDATA (WCOUNT, word_count, 12) },
|
|
|
|
{ ORDATA (PCNTR, program_counter, 16), REG_FIT },
|
|
{ ORDATA (CNTL, control_word, 16), REG_FIT },
|
|
{ ORDATA (CNBUF, control_buffer, 16), REG_FIT },
|
|
{ ORDATA (ADDR, address_word, 16), REG_FIT },
|
|
{ ORDATA (ADBUF, address_buffer, 16), REG_FIT },
|
|
{ ORDATA (INBUF, input_buffer, 16), REG_X | REG_FIT },
|
|
{ ORDATA (OUTBUF, output_buffer, 16), REG_X | REG_FIT },
|
|
|
|
{ NULL }
|
|
};
|
|
|
|
/* Debugging trace list */
|
|
|
|
static DEBTAB sel_deb [] = {
|
|
{ "CSRW", DEB_CSRW }, /* channel command initiations and completions */
|
|
{ "PIO", DEB_PIO }, /* programmed I/O commands executed */
|
|
{ "STATE", DEB_STATE }, /* channel state changes executed */
|
|
{ "SR", DEB_SR }, /* service requests received */
|
|
{ "DATA", DEB_MDATA }, /* I/O data accesses to memory */
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
/* Device descriptor */
|
|
|
|
DEVICE sel_dev = {
|
|
"SEL", /* device name */
|
|
sel_unit, /* unit array */
|
|
sel_reg, /* register array */
|
|
NULL, /* modifier array */
|
|
1, /* number of units */
|
|
8, /* address radix */
|
|
PA_WIDTH, /* address width */
|
|
1, /* address increment */
|
|
8, /* data radix */
|
|
16, /* data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* deposit routine */
|
|
&sel_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 */
|
|
sel_deb, /* debug flag name array */
|
|
NULL, /* memory size change routine */
|
|
NULL /* logical device name */
|
|
};
|
|
|
|
|
|
|
|
/* Channel global routines */
|
|
|
|
|
|
|
|
/* Initialize the channel.
|
|
|
|
This routine is called in the CPU instruction execution prelude to allow the
|
|
device number of the participating interface to be reassigned. It also sets
|
|
up the service request value from the device DIB. This allows the device
|
|
state to be changed during a simulation stop.
|
|
|
|
Implementation notes:
|
|
|
|
1. The active DIB pointer is restored from the device context to support
|
|
resuming after a SAVE and RESTORE is performed.
|
|
|
|
2. In simulation, we allow the device number to be changed during a
|
|
simulation stop, so this routine must recover it from the device.
|
|
Normally, the device number register would be reset from the device
|
|
number field in the DIB. However, the SCMB may be spoofing the device
|
|
number, and it is this spoofed number that must be restored. To do this,
|
|
we first assert the DEVNODB signal to the interface. The SCMB will
|
|
respond to the DEVNODB signal, as it supports connection to the
|
|
multiplexer channel. Devices that connect only to the selector channel
|
|
will not respond to DEVNODB, returning an outbound value of zero. In
|
|
this case, we use the DIB field to obtain the device number.
|
|
*/
|
|
|
|
void sel_initialize (void)
|
|
{
|
|
SIGNALS_DATA outbound;
|
|
|
|
if (sel_is_idle == FALSE) { /* if the channel is controlling a device */
|
|
active_dib = (DIB *) sim_devices [device_index]->ctxt; /* then restore the active DIB pointer */
|
|
|
|
outbound = active_dib->io_interface (active_dib, DEVNODB, 0); /* see if the device responds to DEVNODB */
|
|
|
|
if (IODATA (outbound) > 0) /* if it does (e.g., the SCMB) */
|
|
device_number = IODATA (outbound) / 4; /* then use the returned device number */
|
|
else /* otherwise */
|
|
device_number = active_dib->device_number; /* use the device number from the DIB */
|
|
|
|
sel_request = active_dib->service_request; /* restore the service request state */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Start an I/O program.
|
|
|
|
This routine is called by a device interface in response to a Start I/O (SIO)
|
|
instruction to request that the selector channel begin an I/O program. It
|
|
corresponds in hardware to asserting the REQ signal.
|
|
|
|
If REQ is asserted while the channel is servicing the interface, the channel
|
|
aborts the transfer. This occurs when an interface decides to terminate a
|
|
transfer, for example when an error retry count has expired or a device has
|
|
become not ready.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Both the HP 3000 Series II/III System Reference Manual and the HP 3000
|
|
Series III Reference/Training manuals say that the selector channel
|
|
asserts a DEVNODB signal as part of the REQ processing. This is
|
|
incorrect; the DEVNODB line is tied inactive by the channel, per the
|
|
Selector Channel Control PCA schematic. Instead, the channel expects the
|
|
device number, multiplied by four, to be present on the SRn bus during
|
|
REQ signal assertion, when it is loaded into the device number register.
|
|
Selector channel devices gate their device numbers onto SR6-13 when an
|
|
SIO instruction is decoded.
|
|
*/
|
|
|
|
void sel_assert_REQ (DIB *dibptr)
|
|
{
|
|
if (sel_is_idle) { /* if the channel is idle then set it up */
|
|
dprintf (sel_dev, DEB_CSRW, "Device number %u asserted REQ for channel initialization\n",
|
|
dibptr->device_number);
|
|
|
|
sel_is_idle = FALSE; /* the channel is now busy */
|
|
sel_request = TRUE; /* set the request flag */
|
|
|
|
sequencer = Fetch_Sequence; /* initialize the sequencer */
|
|
bank = 0; /* set the bank to bank 0 */
|
|
|
|
word_count = 0; /* clear the word counter */
|
|
rollover = CLEAR; /* and the word count rollover flip-flop */
|
|
excess_cycles = 0; /* clear the excess cycle count */
|
|
|
|
device_index = 0; /* find the device index */
|
|
/* corresponding to */
|
|
while ((DIB *) sim_devices [device_index]->ctxt != dibptr) /* the active DIB pointer */
|
|
device_index = device_index + 1; /* to aid later restoration */
|
|
|
|
active_dib = dibptr; /* save the interface's DIB pointer */
|
|
device_number = dibptr->device_number; /* and set the device number register */
|
|
|
|
port_read_memory (absolute, device_number * 4, /* read the initial program counter from the DRT */
|
|
&program_counter);
|
|
}
|
|
|
|
else { /* otherwise abort the transfer in progress */
|
|
dprintf (sel_dev, DEB_CSRW, "Device number %u asserted REQ for channel abort\n",
|
|
device_number);
|
|
|
|
end_channel (dibptr); /* idle the channel */
|
|
sim_cancel (&sel_unit [0]); /* and cancel the CHANSR timer */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Request channel service.
|
|
|
|
This routine is called by a device interface to request service from the
|
|
channel. It is called either directly by the interface or indirectly by the
|
|
channel in response to a CHANSR signal returned by the interface. A direct
|
|
call is needed for asynchronous assertion, e.g., in response to an event
|
|
service call. Synchronous assertion, i.e., in response to an interface call,
|
|
is made by returning the CHANSR to the channel. The routine corresponds in
|
|
hardware to asserting the CHANSR signal on the selector channel bus.
|
|
|
|
Sets the service_request flag in the DIB and sets the sel_request flag to
|
|
cause the channel sequencer to be invoked.
|
|
*/
|
|
|
|
void sel_assert_CHANSR (DIB *dibptr)
|
|
{
|
|
dprintf (sel_dev, DEB_SR, "Device number %u asserted CHANSR\n",
|
|
device_number);
|
|
|
|
dibptr->service_request = TRUE; /* set the service request flag in the interface */
|
|
sel_request = TRUE; /* and the selector request flag */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Invoke the channel sequencer in response to a service request.
|
|
|
|
This routine is called in the CPU instruction execution loop to service a
|
|
channel request, asserted either by the participating interface, or
|
|
generated internally by the channel. It executes one or more channel cycles
|
|
for the associated device interface and resets the service request flag in
|
|
the DIB before exiting. The routine is called after every instruction, and
|
|
sometimes additionally within instructions that have long execution times
|
|
(e.g., MOVE). The number of event ticks that have elapsed since the last
|
|
call are passed in; this determines the number of channel cycles available to
|
|
execute.
|
|
|
|
The selector channel clock period is 175 nanoseconds. The channel runs
|
|
concurrently with the CPU, which executes instructions in an average of 2.57
|
|
microseconds, so multiple cycles are executed per CPU instruction.
|
|
|
|
On entry, the routine executes the next state in the transfer for the
|
|
currently participating interface. The number of channel clock counts
|
|
consumed for the specified state execution is subtracted from the number of
|
|
clock counts available. If more time remains, and the service request is
|
|
still active, another channel cycle is run for the interface.
|
|
|
|
The actions for the orders are:
|
|
|
|
Transfer Last
|
|
SIO Order State Type Word Signals Asserted
|
|
--------- -------- -------- ---- -------------------------------
|
|
sioJUMP Execute -- -- (none)
|
|
|
|
sioJUMPC Execute -- -- SETJMP
|
|
|
|
sioRTRES Execute -- -- (none)
|
|
|
|
sioSBANK Execute -- -- (none)
|
|
|
|
sioINTRP Execute -- -- SETINT
|
|
|
|
sioEND Execute -- -- TOGGLESIOOK | PSTATSTB
|
|
|
|
sioENDIN Execute -- -- TOGGLESIOOK | PSTATSTB | SETINT
|
|
|
|
sioCNTL Execute -- -- PCMD1 | CHANSO
|
|
" Transfer -- -- PCONTSTB
|
|
|
|
sioSENSE Execute -- -- PSTATSTB
|
|
|
|
sioWRITE Execute -- -- TOGGLEOUTXFER
|
|
Transfer Normal No PWRITESTB
|
|
Transfer Normal Yes PWRITESTB | EOT
|
|
Transfer DEVEND No EOT | TOGGLEOUTXFER
|
|
Transfer DEVEND Yes (none)
|
|
|
|
sioWRITEC Execute -- -- TOGGLEOUTXFER
|
|
Transfer Normal No PWRITESTB
|
|
Transfer Normal Yes PWRITESTB | EOT | TOGGLEOUTXFER
|
|
Transfer DEVEND No EOT
|
|
Transfer DEVEND Yes (none)
|
|
|
|
sioREAD Execute -- -- TOGGLEINXFER | READNEXTWD
|
|
Transfer Normal No PREADSTB | READNEXTWD
|
|
Transfer Normal Yes PREADSTB | EOT | READNEXTWD
|
|
Transfer Devend No EOT | TOGGLEINXFER
|
|
Transfer DEVEND Yes (none)
|
|
|
|
sioREADC Execute -- -- TOGGLEINXFER | READNEXTWD
|
|
Transfer Normal No PREADSTB | READNEXTWD
|
|
Transfer Normal Yes PREADSTB | EOT | TOGGLEINXFER
|
|
Transfer Devend No EOT
|
|
Transfer DEVEND Yes (none)
|
|
|
|
A fundamental difference between the multiplexer and selector channels is
|
|
that the latter needs an external service request (i.e., CHANSR assertion)
|
|
only for operations on the interface. All other channel operations apply an
|
|
internal service request and so occur automatically without CHANSR being
|
|
asserted. In simulation, sel_request is set TRUE during channel
|
|
initialization and is only cleared when the channel is waiting for a response
|
|
from the interface during a control, read, or write operation. The following
|
|
signal assertions to the interface must return CHANSR to permit the channel
|
|
sequencer to advance:
|
|
|
|
- PCMD1
|
|
- PCONTSTB
|
|
- TOGGLEINXFER | READNEXTWD
|
|
- PREADSTB
|
|
- TOGGLEOUTXFER
|
|
- PWRITESTB
|
|
|
|
Because the channel is dedicated to an interface for the duration of a
|
|
transfer, a non-responding interface would tie up the channel forever. To
|
|
prevent this, the channel starts a one-millisecond timer whenever it is
|
|
waiting for the interface to assert CHANSR. If the timer expires, the
|
|
transfer is aborted, and the channel is freed. The channel also checks for
|
|
CHANACK in response to CHANSO assertion to the interface and will terminate
|
|
(but not abort) the transfer if the interface fails to return it.
|
|
|
|
To maintain the maximum transfer rate across chained read or write transfers,
|
|
the channel will attempt to prefetch the next set of I/O Control and Address
|
|
words during the current data transfer. The two memory reads are interleaved
|
|
between successive channel data transfers, but only if the input or output
|
|
data buffers are both empty (read) or full (write), respectively. This will
|
|
normally occur unless the device is using all available channel bandwidth;
|
|
the SCMB in high speed mode is an example of a device that asserts SR
|
|
continuously.
|
|
|
|
The selector channel diagnostic checks for the expected prefetching. With
|
|
the SCMB in slow mode, both the control and address prefetches will occur
|
|
within three (read) or five (write) I/O cycles. In fast mode, prefetches are
|
|
locked out during a write operation, and only the control word prefetch
|
|
occurs during a read operation.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Conceptually, the selector channel has three internal states: initiate,
|
|
fetch, and execute. The initiator sequence begins with a REQ
|
|
assertion from the interface and sets up the I/O program for execution.
|
|
The fetch sequence obtains the IOCW and IOAW. The execute sequence
|
|
generates the signals needed for each I/O order.
|
|
|
|
In hardware, the Selector Channel Sequencer PCA contains several state
|
|
machines (initiator, fetch, prefetch, buffer transfer, etc.) that provide
|
|
substates. In simulation, the prefetch, transfer, and reload actions are
|
|
given separate states, rather than making them substates of the execute
|
|
state.
|
|
|
|
2. In hardware, a ten-microsecond CHANACK timer is started when CHANSO is
|
|
asserted to the interface. In simulation, a timer is not needed;
|
|
instead, the interface must return CHANACK to avoid a CHANACK timeout
|
|
error.
|
|
|
|
3. In hardware, the CHANACK and CHANSR timers run on every channel cycle.
|
|
In simulation, CHANACK is tested only during a read or write transfer,
|
|
and CHANSR is tested only at the beginning of a read or write transfer,
|
|
and at the beginning and end of a control transfer. This limits overhead
|
|
while passing the diagnostic and providing some measure of protection.
|
|
|
|
4. In hardware, there are two input and two output buffers. This enables
|
|
memory transfers to overlap with interface data transfers. In
|
|
simulation, only one of each buffer is implemented, and memory transfers
|
|
occur synchronously with interface transfers.
|
|
|
|
5. In hardware, prefetches of the IOCW and IOAW are interleaved with channel
|
|
memory accesses and only occur when the port controller is idle. In
|
|
simulation, prefetches are not counted against the sequencer execution
|
|
time. Normal fetches, by contrast, wait for memory access and so the
|
|
time taken is counted.
|
|
|
|
6. The default label in the Execute Sequence switch statement is necessary
|
|
to quiet a warning that inbound_signals may be used uninitialized, even
|
|
though all cases are covered.
|
|
*/
|
|
|
|
void sel_service (uint32 ticks_elapsed)
|
|
{
|
|
HP_WORD inbound_data, outbound_data;
|
|
INBOUND_SET inbound_signals;
|
|
SIGNALS_DATA outbound;
|
|
int32 cycles;
|
|
uint32 return_address;
|
|
|
|
cycles = CYCLES_PER_EVENT - excess_cycles; /* decrease the cycles available by any left over */
|
|
|
|
while (sel_request && cycles > 0) { /* execute as long as a request and cycles remain */
|
|
outbound = IORETURN (NO_SIGNALS, 0); /* initialize in case we don't call the interface */
|
|
|
|
dprintf (sel_dev, DEB_STATE, "Channel entered the %s sequence with %d clock cycles remaining\n",
|
|
seq_name [sequencer], cycles);
|
|
|
|
switch (sequencer) { /* dispatch based on the selector state */
|
|
|
|
case Idle_Sequence: /* if the selector is idle */
|
|
sel_request = FALSE; /* then the request is invalid */
|
|
break;
|
|
|
|
|
|
case Fetch_Sequence:
|
|
sim_cancel (&sel_unit [0]); /* cancel the CHANSR timer */
|
|
|
|
load_control (&control_word); /* load the IOCW */
|
|
load_address (&address_word); /* and the IOAW */
|
|
cycles = cycles - 2 * CYCLES_PER_READ /* and count the accesses */
|
|
- CYCLES_PER_FETCH; /* and the fetch sequence */
|
|
|
|
order = IOCW_ORDER (control_word); /* save the current order */
|
|
|
|
if (control_word & IOCW_DC /* if the data chain bit is set */
|
|
&& order != sioREADC && order != sioWRITEC) /* but the order isn't a chained order */
|
|
outbound = abort_channel ("an illegal order"); /* then abort the channel program */
|
|
|
|
else /* otherwise the order is valid */
|
|
sequencer = Execute_Sequence; /* and execution is next */
|
|
break;
|
|
|
|
|
|
case Execute_Sequence:
|
|
switch (order) { /* dispatch based on the I/O order */
|
|
|
|
case sioJUMPC:
|
|
inbound_signals = SETJMP | CHANSO;
|
|
break;
|
|
|
|
case sioRTRES:
|
|
inbound_signals = NO_SIGNALS; /* no interface call is needed */
|
|
|
|
if (rollover == SET) /* if the count terminated */
|
|
outbound = IORETURN (NO_SIGNALS, 0); /* then return a zero count */
|
|
else /* otherwise return the two's-complement remainder */
|
|
outbound = IORETURN (NO_SIGNALS,
|
|
IOCW_COUNT (word_count));
|
|
break;
|
|
|
|
case sioINTRP:
|
|
inbound_signals = SETINT | CHANSO;
|
|
break;
|
|
|
|
case sioEND:
|
|
inbound_signals = TOGGLESIOOK | PSTATSTB | CHANSO;
|
|
break;
|
|
|
|
case sioENDIN:
|
|
inbound_signals = TOGGLESIOOK | PSTATSTB | SETINT | CHANSO;
|
|
break;
|
|
|
|
case sioCNTL:
|
|
inbound_signals = PCMD1 | CHANSO;
|
|
|
|
sel_request = FALSE; /* wait until the interface requests the next word */
|
|
break;
|
|
|
|
case sioSENSE:
|
|
inbound_signals = PSTATSTB | CHANSO;
|
|
break;
|
|
|
|
case sioWRITE:
|
|
case sioWRITEC:
|
|
inbound_signals = TOGGLEOUTXFER | CHANSO;
|
|
|
|
word_count = IOCW_WCNT (control_word); /* load the word count */
|
|
sel_request = FALSE; /* wait until the interface requests the next word */
|
|
break;
|
|
|
|
case sioREAD:
|
|
case sioREADC:
|
|
inbound_signals = TOGGLEINXFER | READNEXTWD | CHANSO;
|
|
|
|
word_count = IOCW_WCNT (control_word); /* load the word count */
|
|
sel_request = FALSE; /* wait until the interface requests the next word */
|
|
break;
|
|
|
|
default: /* needed to quiet warning about inbound_signals */
|
|
case sioJUMP: /* these orders do not need */
|
|
case sioSBANK: /* to call the interface */
|
|
inbound_signals = NO_SIGNALS;
|
|
break;
|
|
} /* end switch */
|
|
|
|
if (inbound_signals) { /* if there are signals to assert */
|
|
outbound = active_dib->io_interface (active_dib, /* then pass them to the interface */
|
|
inbound_signals,
|
|
control_word);
|
|
|
|
if ((outbound & CHANACK) == NO_SIGNALS) { /* if CHANACK was not returned */
|
|
dprintf (sel_dev, DEB_SR, "Device number %u CHANACK timeout\n",
|
|
device_number);
|
|
|
|
end_channel (active_dib); /* terminate the channel program */
|
|
|
|
dprintf (sel_dev, DEB_CSRW, "Channel program ended\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (order) { /* dispatch based on the I/O order */
|
|
|
|
case sioJUMP:
|
|
program_counter = address_word; /* load the program counter with the new address */
|
|
sequencer = Fetch_Sequence; /* next state is Fetch */
|
|
break;
|
|
|
|
case sioJUMPC:
|
|
if (outbound & JMPMET) /* if the jump condition is true */
|
|
program_counter = address_word; /* then load the program counter with the new address */
|
|
|
|
sequencer = Fetch_Sequence; /* the next state is Fetch */
|
|
break;
|
|
|
|
case sioRTRES:
|
|
case sioEND:
|
|
case sioENDIN:
|
|
case sioSENSE:
|
|
outbound_data = IODATA (outbound); /* get the status or residue to return */
|
|
return_address = program_counter - 1 & LA_MASK; /* point at the second of the program words */
|
|
|
|
port_write_memory (absolute, return_address, outbound_data); /* save the word */
|
|
cycles = cycles - CYCLES_PER_WRITE; /* and count the access */
|
|
|
|
dprintf (sel_dev, DEB_PIO, "Channel stored IOAW %06o to address %06o\n",
|
|
outbound_data, return_address);
|
|
|
|
if (order == sioEND || order == sioENDIN) { /* if it's an End or End with Interrupt order */
|
|
end_channel (active_dib); /* then terminate the program */
|
|
|
|
dprintf (sel_dev, DEB_CSRW, "Channel program ended\n");
|
|
}
|
|
|
|
else /* otherwise the program continues */
|
|
sequencer = Fetch_Sequence; /* with the fetch state */
|
|
break;
|
|
|
|
case sioSBANK:
|
|
bank = IOAW_BANK (address_word); /* set the bank number register */
|
|
sequencer = Fetch_Sequence; /* the next state is Fetch */
|
|
break;
|
|
|
|
case sioINTRP:
|
|
sequencer = Fetch_Sequence; /* the next state is Fetch */
|
|
break;
|
|
|
|
case sioCNTL:
|
|
prefetch_control = FALSE; /* prefetching is not used */
|
|
prefetch_address = FALSE; /* for the Control order */
|
|
|
|
sim_activate (&sel_unit [0], sel_unit [0].wait); /* start the SR timer */
|
|
sequencer = Wait_Sequence; /* and check for a timeout */
|
|
break;
|
|
|
|
case sioWRITE:
|
|
case sioWRITEC:
|
|
prefetch_control = (order == sioWRITEC); /* enable prefetching */
|
|
prefetch_address = (order == sioWRITEC); /* if the order is chained */
|
|
|
|
sim_activate (&sel_unit [0], sel_unit [0].wait); /* start the SR timer */
|
|
sequencer = Wait_Sequence; /* and check for a timeout */
|
|
break;
|
|
|
|
case sioREAD:
|
|
case sioREADC:
|
|
prefetch_control = (order == sioREADC); /* enable prefetching */
|
|
prefetch_address = (order == sioREADC); /* if the order is chained */
|
|
|
|
if (prefetch_control) { /* if control word prefetching is enabled */
|
|
load_control (&control_buffer); /* then prefetch the next IOCW into the buffer */
|
|
cycles = cycles - CYCLES_PER_PREFETCH; /* and count the sequencer time */
|
|
prefetch_control = FALSE; /* mark the job done */
|
|
}
|
|
|
|
sim_activate (&sel_unit [0], sel_unit [0].wait); /* start the SR timer */
|
|
sequencer = Wait_Sequence; /* and check for a timeout */
|
|
break;
|
|
} /* end switch */
|
|
|
|
cycles = cycles - CYCLES_PER_EXECUTE; /* count the sequencer time */
|
|
break;
|
|
|
|
|
|
case Wait_Sequence:
|
|
sim_cancel (&sel_unit [0]); /* cancel the SR timer */
|
|
|
|
sequencer = Transfer_Sequence; /* continue with the transfer sequence */
|
|
|
|
/* fall into the Transfer_Sequence */
|
|
|
|
|
|
case Transfer_Sequence:
|
|
if (order == sioCNTL) { /* if this is a Control order */
|
|
inbound_data = address_word; /* then supply the control word */
|
|
inbound_signals = PCONTSTB | CHANSO; /* to the interface */
|
|
sel_request = FALSE; /* wait until the interface confirms receipt */
|
|
}
|
|
|
|
else if (order == sioREAD || order == sioREADC) { /* otherwise if this is a Read or Read Chained order */
|
|
inbound_data = 0; /* then no value is needed */
|
|
inbound_signals = PREADSTB | CHANSO; /* by the interface */
|
|
|
|
if (word_count == CNTR_MAX) /* if the word count is exhausted */
|
|
if (order == sioREADC) /* then if the order is chained */
|
|
inbound_signals |= EOT | READNEXTWD; /* then continue the transfer block */
|
|
else /* otherwise */
|
|
inbound_signals |= EOT | TOGGLEINXFER; /* end the transfer block */
|
|
|
|
else /* otherwise the transfer continues */
|
|
inbound_signals |= READNEXTWD; /* with the next word */
|
|
|
|
sel_request = FALSE; /* wait until the interface confirms receipt */
|
|
}
|
|
|
|
else { /* otherwise it's a Write or Write Chained order */
|
|
if (port_read_memory (dma, /* if the memory read */
|
|
TO_PA (bank, address_word), /* from the specified bank and offset */
|
|
&input_buffer)) { /* succeeds */
|
|
cycles = cycles - CYCLES_PER_READ; /* then count the access */
|
|
|
|
inbound_data = input_buffer; /* get the word to supply */
|
|
inbound_signals = PWRITESTB | CHANSO; /* to the interface */
|
|
|
|
if (word_count == CNTR_MAX) /* if the word count is exhausted */
|
|
if (order == sioWRITEC) /* then if the order is chained */
|
|
inbound_signals |= EOT; /* then continue the transfer block */
|
|
else /* otherwise */
|
|
inbound_signals |= EOT | TOGGLEOUTXFER; /* end the transfer block */
|
|
|
|
sel_request = FALSE; /* wait until the interface confirms receipt */
|
|
}
|
|
|
|
else { /* otherwise the memory read failed */
|
|
outbound = abort_channel ("a memory read error"); /* so abort the transfer */
|
|
break; /* and skip the interface call */
|
|
}
|
|
}
|
|
|
|
cycles = cycles - CYCLES_PER_EXECUTE; /* count the sequencer time */
|
|
|
|
outbound = active_dib->io_interface (active_dib, inbound_signals, inbound_data); /* call the interface */
|
|
|
|
if (sel_is_idle) /* if the interface aborted the transfer */
|
|
break; /* then terminate processing now */
|
|
|
|
if ((outbound & CHANSR) == NO_SIGNALS) /* if the interface did not assert a service request */
|
|
if (prefetch_control) { /* then if control word prefetching is enabled */
|
|
load_control (&control_buffer); /* then prefetch the next IOCW into the buffer */
|
|
cycles = cycles - CYCLES_PER_PREFETCH; /* and count the sequencer time */
|
|
prefetch_control = FALSE; /* mark the job done */
|
|
}
|
|
|
|
else if (prefetch_address) { /* otherwise if address word prefetching is enabled */
|
|
load_address (&address_buffer); /* then prefetch the next IOAW into the buffer */
|
|
cycles = cycles - CYCLES_PER_PREFETCH; /* and count the sequencer time */
|
|
prefetch_address = FALSE; /* mark the job done */
|
|
}
|
|
|
|
if (order == sioCNTL) { /* if this is a Control order */
|
|
sim_activate (&sel_unit [0], sel_unit [0].wait); /* then start the SR timer */
|
|
sequencer = Fetch_Sequence; /* and the next state is Fetch */
|
|
}
|
|
|
|
else { /* otherwise it's a Write or Read (Chained) order */
|
|
if (outbound & DEVEND) { /* if the device ended the transfer */
|
|
if (word_count < CNTR_MAX) { /* then if the transfer is incomplete */
|
|
inbound_signals = EOT | CHANSO; /* then assert EOT to end the transfer */
|
|
|
|
if (order == sioREAD) /* if the order is Read and not chained */
|
|
inbound_signals |= TOGGLEINXFER; /* then terminate the input block */
|
|
|
|
else if (order == sioWRITE) /* otherwise if the order is Write and not chained */
|
|
inbound_signals |= TOGGLEOUTXFER; /* then terminate the output block */
|
|
|
|
active_dib->io_interface (active_dib, inbound_signals, 0); /* tell the interface */
|
|
}
|
|
|
|
sequencer = Reload_Sequence; /* the next state is Reload */
|
|
}
|
|
|
|
else if (order == sioREAD /* otherwise the transfer was successful */
|
|
|| order == sioREADC) { /* and if this is a Read or Read Chained order */
|
|
output_buffer = IODATA (outbound); /* then pick up the returned data word */
|
|
|
|
if (port_write_memory (dma, /* if the memory write */
|
|
TO_PA (bank, address_word), /* to the specified bank and offset */
|
|
output_buffer)) /* succeeds */
|
|
cycles = cycles - CYCLES_PER_WRITE; /* then count the access */
|
|
|
|
else { /* otherwise the memory write failed */
|
|
outbound = abort_channel ("a memory write error"); /* so abort the transfer */
|
|
break; /* and skip the address and count update */
|
|
}
|
|
}
|
|
|
|
address_word = address_word + 1 & LA_MASK; /* increment the transfer address */
|
|
word_count = word_count + 1 & CNTR_MASK; /* and the word count */
|
|
|
|
if (word_count == 0) { /* if the word count is exhausted */
|
|
rollover = SET; /* then set the rollover flip-flop */
|
|
sequencer = Reload_Sequence; /* and load the next I/O program word */
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
case Reload_Sequence:
|
|
if (order == sioWRITEC || order == sioREADC) { /* if the current order is chained */
|
|
if (prefetch_control) { /* and the IOCW has not been prefetched yet */
|
|
load_control (&control_buffer); /* then load it now */
|
|
cycles = cycles - CYCLES_PER_READ; /* and count the memory access */
|
|
}
|
|
|
|
if (prefetch_address) { /* if the IOAW has not be prefetched yet */
|
|
load_address (&address_buffer); /* then load it now */
|
|
cycles = cycles - CYCLES_PER_READ; /* and count the memory access */
|
|
}
|
|
|
|
if (prefetch_control || prefetch_address) /* if both words had not been prefetched */
|
|
cycles = cycles - CYCLES_PER_FETCH; /* then count as a fetch sequence */
|
|
else /* otherwise */
|
|
cycles = cycles - CYCLES_PER_RELOAD; /* count as a reload sequence */
|
|
|
|
if ((control_word ^ control_buffer) & IOCW_SIO_MASK) /* if the next order isn't the same type */
|
|
outbound = abort_channel ("an invalid chained order"); /* then an invalid order abort occurs */
|
|
|
|
else { /* otherwise the next order is OK */
|
|
control_word = control_buffer; /* so copy the control and address values */
|
|
address_word = address_buffer; /* from the buffers */
|
|
|
|
order = IOCW_ORDER (control_word); /* get the new order */
|
|
word_count = IOCW_WCNT (control_word); /* and word count */
|
|
|
|
rollover = CLEAR; /* clear the word count rollover flip-flop */
|
|
|
|
prefetch_control = (control_word & IOCW_DC) != 0; /* enable prefetching */
|
|
prefetch_address = (control_word & IOCW_DC) != 0; /* if the new order is chained */
|
|
|
|
sequencer = Transfer_Sequence; /* the next state is Transfer */
|
|
}
|
|
}
|
|
|
|
else /* otherwise an unchained order ends the transfer */
|
|
sequencer = Fetch_Sequence; /* so proceed directly to Fetch */
|
|
|
|
break;
|
|
} /* end switch */
|
|
|
|
|
|
if (outbound & INTREQ) /* if an interrupt request was asserted */
|
|
iop_assert_INTREQ (active_dib); /* then set it up */
|
|
|
|
if (sel_is_idle == FALSE) /* if the channel is still running */
|
|
if (outbound & CHANSR) /* then if the interface requested service */
|
|
sel_assert_CHANSR (active_dib); /* then set it up */
|
|
else /* otherwise */
|
|
active_dib->service_request = FALSE; /* clear the current service request */
|
|
|
|
else /* otherwise the channel has stopped */
|
|
sim_cancel (&sel_unit [0]); /* so cancel the CHANSR timer */
|
|
|
|
} /* end while */
|
|
|
|
|
|
if (cycles > 0) /* if we exited to wait for a service request */
|
|
excess_cycles = 0; /* then do a full set of cycles next time */
|
|
else /* otherwise we ran over our allotment */
|
|
excess_cycles = - cycles; /* so reduce the next poll by the overage */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* Channel local SCP support routines */
|
|
|
|
|
|
|
|
/* Service the channel service request timer.
|
|
|
|
The CHANSR timer is started whenever the channel is waiting for a service
|
|
request from the participating interface. Because the selector channel is
|
|
dedicated to a single interface until the end of the I/O program, if that
|
|
interface were to malfunction and not respond, the channel would be tied up
|
|
forever.
|
|
|
|
Normally, the timer is cancelled as soon as CHANSR is returned from the
|
|
interface. If this service routine is entered, it means that CHANSR is
|
|
taking too long, so the I/O program is aborted, and the channel is idled, so
|
|
that it is available for other devices.
|
|
*/
|
|
|
|
static t_stat sel_timer (UNIT *uptr)
|
|
{
|
|
SIGNALS_DATA outbound;
|
|
|
|
outbound = abort_channel ("a CHANSR timeout"); /* abort the transfer in progress */
|
|
|
|
if (outbound & INTREQ) /* if an interrupt request was asserted */
|
|
iop_assert_INTREQ (active_dib); /* then set it up */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Device reset.
|
|
|
|
This routine is called for a RESET or RESET SEL 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 the internal Clear Logic signal.
|
|
|
|
A reset does not clear any of the registers.
|
|
*/
|
|
|
|
static t_stat sel_reset (DEVICE *dptr)
|
|
{
|
|
rollover = CLEAR; /* clear the word count rollover flip-flop */
|
|
|
|
sel_is_idle = TRUE; /* the channel is now inactive */
|
|
sel_request = FALSE; /* clear the request flag */
|
|
|
|
sequencer = Idle_Sequence; /* stop the sequencer */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
|
|
/* Channel local utility routines */
|
|
|
|
|
|
|
|
/* End the channel I/O program.
|
|
|
|
The channel program ends, either normally via an sioEND or sioENDIN order, or
|
|
abnormally via a REQ or timeout abort. The program counter is written back
|
|
to the DRT, and the channel is idled by performing a Clear Logic operation.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The memory write cycle time need not be counted, as the channel will be
|
|
terminating unconditionally.
|
|
*/
|
|
|
|
static void end_channel (DIB *dibptr)
|
|
{
|
|
port_write_memory (absolute, device_number * 4, /* write the program counter back to the DRT */
|
|
program_counter);
|
|
|
|
dibptr->service_request = FALSE; /* clear any outstanding device service request */
|
|
|
|
sel_reset (&sel_dev); /* perform a Clear Logic operation */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Abort the transfer in progress.
|
|
|
|
If an internal channel error occurs (e.g., a memory read or write failure,
|
|
due to an invalid address), the channel asserts the XFERERROR signal to the
|
|
interface. The interface will clear its logic and assert REQ to the channel
|
|
to complete the abort.
|
|
*/
|
|
|
|
static SIGNALS_DATA abort_channel (const char *reason)
|
|
{
|
|
dprintf (sel_dev, DEB_CSRW, "Channel asserted XFERERROR for %s\n",
|
|
reason);
|
|
|
|
return active_dib->io_interface (active_dib, XFERERROR | CHANSO, 0); /* tell the interface that the channel has aborted */
|
|
}
|
|
|
|
|
|
/* Load the I/O Control Word.
|
|
|
|
The first of the two I/O program words is loaded into the channel register
|
|
pointed to by "value". The program counter points at the location to read
|
|
and is incremented after retrieving the value. This routine is called both
|
|
for a normal fetch and for a prefetch.
|
|
*/
|
|
|
|
static void load_control (HP_WORD *value)
|
|
{
|
|
port_read_memory (absolute, program_counter, value); /* read the IOCW from memory */
|
|
|
|
dprintf (sel_dev, DEB_PIO, "Channel %s IOCW %06o (%s) from address %06o\n",
|
|
action_name [sequencer], *value,
|
|
sio_order_name [IOCW_ORDER (*value)], program_counter);
|
|
|
|
program_counter = program_counter + 1 & LA_MASK; /* increment the program counter */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Load the I/O Address Word.
|
|
|
|
The second of the two I/O program words is loaded into the channel register
|
|
pointed to by "value". The program counter points at the location to read
|
|
and is incremented after retrieving the value. This routine is called both
|
|
for a normal fetch and for a prefetch.
|
|
*/
|
|
|
|
static void load_address (HP_WORD *value)
|
|
{
|
|
port_read_memory (absolute, program_counter, value); /* read the IOAW from memory */
|
|
|
|
dprintf (sel_dev, DEB_PIO, "Channel %s IOAW %06o from address %06o\n",
|
|
action_name [sequencer], *value, program_counter);
|
|
|
|
program_counter = program_counter + 1 & LA_MASK; /* increment the program counter */
|
|
|
|
return;
|
|
}
|