HP 21xx/1000 Simulation Notes ============================= ------------------ Instruction Timing ------------------ Currently, the simulator treats all instructions as having the same execution time: one event tick. The interval timer is decremented once per instruction just prior to calling the instruction executor. DMA cycles are not counted toward event time. That means that interfaces that can assert SRQ continuously, e.g., the 13175 Disc Interface, perform block transfers in zero time. At some point, it may be desirable to recast event ticks as microseconds and add variable consumption for certain instructions. For that, the interval timer would have to be adjusted AFTER the instruction execution, not before. It would appear that adjusting the time before, as is currently done, would result in "sim_activate" calls for DMA and CPU cycles producing different delays. Consider the current execution loop: loop if sim_interval <= 0 then do_event_service if dma_request then do_dma_cycle sim_interval := sim_interval - 1 do_machine_instruction end loop If "sim_activate" is called during STC processing for a DMA cycle, the interval timer is decremented before the next interval test is done. However, if it is called for a CPU cycle, the timer is not decremented before the next test. This would seem to imply that the event would be serviced one tick later for the CPU cycle than for the DMA cycle. But this doesn't happen. Here is the DMA cycle trace (assuming a preceding STF 44) for a device that does a "sim_activate (uptr, 2)" in response to STC: >>CPU instr: - 0001 03361 103706 STC 6,C >>DCPC1 sr: Select code 44 asserted SRQ >>MC iobus: Received data 000000 with signals IOI | STC | CLF | SIR >>MC iobus: Returned data 125252 with signals (none) >>DCPC1 data: - 0000 00172 125252 dma write >>CPU fetch: - 0001 03362 060172 instruction fetch >>CPU reg: - **** 00000 000000 A 052525, B 177776, X 000000, Y 000000, E O i >>CPU instr: - 0001 03362 060172 LDA 172 >>CPU data: - 0000 00172 125252 data read >>CPU fetch: - 0001 03363 064242 instruction fetch >>CPU reg: - **** 00000 000000 A 125252, B 177776, X 000000, Y 000000, E O i >>CPU instr: - 0001 03363 064242 LDB 242 >>CPU data: - 0000 00242 125252 data read >>MC iobus: Received data 000000 with signals ENF | SIR >>MC iobus: Returned data 000000 with signals SRQ >>DCPC1 sr: Select code 44 asserted SRQ And here is the CPU cycle trace: >>CPU instr: - 0000 00100 103744 STC 44,C >>MC iobus: Received data 000000 with signals STC | CLF | SIR >>MC iobus: Returned data 000000 with signals (none) >>CPU fetch: - 0000 00101 000000 instruction fetch >>CPU reg: - **** 00000 000000 A 000000, B 000000, X 000000, Y 000000, E o i >>CPU instr: - 0000 00101 000000 NOP >>CPU fetch: - 0000 00102 000000 instruction fetch >>CPU reg: - **** 00000 000000 A 000000, B 000000, X 000000, Y 000000, E o i >>CPU instr: - 0000 00102 000000 NOP >>MC iobus: Received data 000000 with signals ENF | SIR >>MC iobus: Returned data 000000 with signals SRQ In both cases, two instructions execute before the event service is entered and the flag is set. If the decrement occurs after the instruction execution: loop if sim_interval <= 0 then do_event_service if dma_request then do_dma_cycle do_machine_instruction sim_interval := sim_interval - 1 end loop ...then a DMA cycle STC would execute two instructions before calling the event service routine, but a CPU cycle STC would execute only one instruction. ---------------- Simulation Stops ---------------- STOP_UNIMPL = Unimplemented instruction STOP_UNSC = I/O to an unassigned select code STOP_UNDEF = Undefined instruction STOP_INDIR = Indirect address loop STOP_HALT = Programmed halt STOP_BRKPNT = Breakpoint STOP_NOCONN = No connection on interprocessor link STOP_NOTAPE = No tape in paper tape reader STOP_EOT = End of tape in paper tape reader NOTE_IOG = I/O instruction executed NOTE_INDINT = Interrupt occurred while resolving indirects I/O error returns from device service routines are supplied by: - DR (SCPE_UNATT) - LPS (SCPE_UNATT, STOP_OFFLINE, STOP_PWROFF) - MS (SCPE_UNATT, SCPE_IOERR) - MT (SCPE_UNATT, SCPE_IOERR) - PTR (SCPE_UNATT, SCPE_IOERR) - PTP (SCPE_UNATT, SCPE_IOERR) - TTP (SCPE_IOERR) DR is probably the only one that returning SCPE_UNATT makes sense, as it's a fixed-disc drive and so is always "attached" (although it could report "not ready" instead). SCPE_IOERR is indicated for MTSE_IOERR, which is returned if ferror() returns TRUE after a stream file I/O call. This is translated to parity error status if stop_ioe is not set. SCPE_IOERR is returned by PTR for stream I/O errors as well as reading more than the trailer limit past the physical EOF. The latter is not conditional on stop_ioe. SCPE_IOERR is unconditionally returned by PTP and TTP for stream I/O errors. ----------- CPU Tracing ----------- The CPU should implement the following traces: INSTR -- Machine instructions executed DATA -- Memory data accesses FETCH -- Memory instruction fetches REG -- Register values OPND -- Memory operand values EXEC -- Matching instruction execution states FETCH, INSTR, and REG tracing is implemented in the "sim_instr" routine. There are two places where an instruction fetch is performed: in the "normal" instruction path, and in the interrupt path. Both are calls to ReadW. The paths merge immediately thereafter, and the REG and INSTR trace calls are made there. The forms are: >>CPU fetch: - ---- 10341 016200 instruction fetch >>CPU reg: P 0000 00000 000000 A 123003, B 001340, X 000000, Y 000000, e O I >>CPU instr: U 0045 10341 016200 LDA 11200 >>CPU instr: U 0045 10341 000011 interrupt >>CPU reg: P 0000 00000 000000 MPF 00000, MPV 000000, MES 000000, MEV 000000 The REG trace prints A/B/E/O/I for 21xx machines and A/B/X/Y/E/O/I for 1000 machines. EXEC replaces, and is a more general than, the existing OS, OSTBG, VMA, EMA, VIS, and SIG traces. OSTBG might remain a separate option (renamed as PTBG) but would require that .TICK (105342) be filtered out of the rest of the OS instruction range. DATA traces requires a rewrite of the memory access routines to capture all memory accesses. Currently, there are individual routines for each type of access: - ReadPW : Read a word using a physical address - ReadB : Read a byte using the current map - ReadBA : Read a byte using the alternate map - ReadW : Read a word using the current map - ReadWA : Read a word using the alternate map - ReadIO : Read a word using the specified map without protection - ReadTAB : Read a word using the current map without protection ...plus corresponding Writexx routines. The routines are used for these types of access: - ReadPW : drc_boot (loader area), Readxx, Writexx routines - ReadB : MBI, LBT, MBT, CBT, SFB, TRSLT, .TNAM - ReadBA : MBF, MBW - ReadW : memory reference instructions - ReadWA : MWF, MWW, XLn, XCn, EMA/VMA, .IRT - ReadIO : dma_cycle (portmaps), sim_stop (smap), EMA/VMA (smap and umap) - ReadTAB : sim_instr postlude (current map), hp_post_cmd (current map) DMA accesses via ReadIO and WriteIO access memory locations 0 and 1 and not the A and B registers. sim_stop reads the instruction in the trap cell if an interrupt is pending; the current map is indeterminate at this point, although trap cells reside in page 0 locations 2-77 octal, and the system map base page always has these locations mapped to page 0. EMA/VMA instructions require both explicit and current map accesses. ReadTAB is used only to set the T register when simulation stops and when any command completes that changes the M register. It's identical to ReadW, except that a DMS violation cannot occur. Note: drc_boot can use ReadW instead of ReadPW, as DMS is guaranteed to be off when it is called due to the RESET ALL that is done prior to calling. The following actions do NOT want to trace the memory accesses: - sim stop with an interrupt pending - T register set - ibl_copy ibl_copy could be moved to the memory module and access M [] directly. The others can call mem_examine with the switches parameter set to the value appropriate to the mapping desired (the EXAMINE and DEPOSIT commands do A/B mapping; there is no way to access page 0 locations 0/1). The calling sequences for memory access are: uint16 ReadPW (uint32 pa); uint8 ReadB (uint32 va); uint8 ReadBA (uint32 va); uint16 ReadW (uint32 va); uint16 ReadWA (uint32 va); uint16 ReadIO (uint32 va, uint32 map); uint16 ReadTAB (uint32 va); void WritePW (uint32 pa, uint32 dat); void WriteB (uint32 va, uint32 dat); void WriteBA (uint32 va, uint32 dat); void WriteW (uint32 va, uint32 dat); void WriteWA (uint32 va, uint32 dat); void WriteIO (uint32 va, uint32 dat, uint32 map); The new common memory access routines are declared as: t_stat mem_examine (t_value *eval_array, t_addr address, UNIT *uptr, int32 switches); t_stat mem_deposit (t_value value, t_addr address, UNIT *uptr, int32 switches); HP_WORD mem_read (DEVICE *dptr, ACCESS_CLASS classification, uint32 logical_address); void mem_write (DEVICE *dptr, ACCESS_CLASS classification, uint32 logical_address, HP_WORD value); The following memory access classifications and DMS mappings are used: fetch : current map data : current map data_alt : alternate map abs_sys : system map (unprotected) abs_user : user map (unprotected) dma_1 : port a map (unprotected, no A/B access) dma_2 : port b map (unprotected, no A/B access) ------------- Trace Options ------------- Option Flag Trace Description ----------- ----------------------------------------------------------- TRACE_CMD Interface or controller commands TRACE_INCO Interface or controller command initiations and completions TRACE_CSRW Interface control, status, read, and write actions TRACE_STATE Command execution state changes TRACE_SERV Unit service scheduling calls and entries TRACE_PSERV Periodic unit service scheduling calls and entries TRACE_XFER Data receptions and transmissions TRACE_FIFO FIFO data buffer reads and writes TRACE_IOBUS I/O bus signals and data words received and returned TRACE_INSTR Instruction executions TRACE_DATA Memory data accesses TRACE_FETCH Memory instruction fetches TRACE_REG Register values TRACE_OPND Instruction operands TRACE_EXEC Matching instruction execution states TRACE_SR DMA service requests received DEB_CMDS Command initiations and completions DEB_CPU Words received from and sent to the CPU DEB_BUF Data read from and written to the FIFO DEB_XFER Data receptions and transmissions DEB_RWS Tape reads, writes, and status returns DEB_RWSC Disc read/write/status/control commands DEB_SERV Unit service scheduling calls and entries ----------- Trace Flags ----------- Note that the CPU does not use the IOBUS flag. One problem is that the CPU device hosts SC 00 (interrupt system), SC 01 (overflow) and SC 04 (power fail), so IOBUS would trace all of these together. Even so, it might seem helpful to trace operations. However, there is no information in the received data or signals to indicate which select code is being shown. Also, each TBG interrupt results in a half-dozen IOBUS transactions on the interrupt system (SC 00), overflow (SC 01) and CIR (SC 04), so the unidentified traces quickly pile up. A trace of this activity that yields more useful information is an EXEC trace for the I/O instructions refererncing the lower select codes: SET CPU EXEC=102000;172070,DEBUG=EXEC This traces I/O instructions targeted to select codes 00-07. In the tables below, the values of global flags start at bit 0 and increase, while private flags start at bit 30 and decrease. Flag Name P/G Trace Decription ----------- --- ----------------------------------------------------------- TRACE_CMD G Interface or controller commands TRACE_INCO G Interface or controller command initiations and completions TRACE_CSRW G Interface control, status, read, and write actions TRACE_STATE G State changes TRACE_SERV G Unit service scheduling calls and entries TRACE_PSERV G Periodic unit service scheduling calls and entries TRACE_XFER G Data receptions and transmissions TRACE_FIFO G Data buffer reads and writes TRACE_IOBUS G I/O bus signals and data words received and returned DEB_CMDS G (old) command initiations and completions DEB_CPU G (old) words received from and sent to the CPU DEB_BUF G (old) data read from and written to the FIFO DEB_RWSC G (old) disc read/write/status/control commands TRACE_INSTR P Instruction executions TRACE_DATA P Memory data accesses TRACE_FETCH P Memory instruction fetches TRACE_REG P Register values TRACE_OPND P Instruction operands TRACE_EXEC P Matching instruction execution states TRACE_SR P DMA service requests received ----------------------- Global ----------------------------- | ------------------- Private ------------------- Device CMD INCO CSRW STATE SERV PSERV FIFO XFER IOBUS | INSTR DATA FETCH REG OPND EXEC SR ------ ----- ----- ----- ----- ----- ----- ---- ----- ----- | ----- ----- ----- ----- ----- ----- ----- CPU - - - - x - - - - | x x x x x x - DMA1 x - x - - - - - x | - x - - - - x DMA2 x - x - - - - - x | - x - - - - x MP - - - - - - - - x | - - - - - - - MEM - - - - - - - - - | - - - - - - - ----------------------- Global ----------------------------- | ------- Deprecated ------- Device CMD INCO CSRW STATE SERV PSERV FIFO XFER IOBUS | CMDS CPU BUF RWSC ------ ----- ----- ----- ----- ----- ----- ---- ----- ----- | ----- ----- ----- ----- BACI - - x - x x x x x | - - - - DA x x x x x - x x x | - - - - DC x x x x x - x x x | - - - - DPC - - - - - - - - x | - - - - DPD - - - - - - - - x | - - - - DQC - - - - - - - - x | - - - - DQD - - - - - - - - x | - - - - DRC - - - - - - - - x | - - - - DRD - - - - - - - - x | - - - - DS - - - - x - - - x | x x x x IPLI x - x - - x - x x | - - - - IPLO x - x x - x - x x | - - - - LPS x - x x x - - x x | - - - - LPT x - x - x - - x x | - - - - MC - - - - - - - x x | - - - - MPX x x x x x x x x x | - - - - MSC - - - - - - - - x | x x - x MSD - - - - - - - - x | - - - - MTC - - - - - - - - x | - - - - MTD - - - - - - - - x | - - - - MUX - - x - - x - - x | - - - - MUXL - - x - x - - x x | - - - - MUXC - - x - - - - x x | - - - - PIF x - - - - - - - x | - - - - PTP - - x - x - - x x | - - - - PTR - - - - x - - x x | - - - - TBG - - x - - x - - x | - - - - TTY - - x - x x - x x | - - - - ----------------------- Symbolic Interpretation ----------------------- SCP directly supports displaying or accepting data via the EXAMINE and DEPOSIT commands in numeric format with a specified radix. Symbolic interpretation extends this to include printing and parsing in character or mnemonic formats and is controlled by default and user-specified modes. The following symbolic modes for printing and parsing are supported by including one of the switches: Switch Interpretation ------ ----------------------------------------- -A a single character in the right-hand byte -C a two-character packed string -M a CPU instruction mnemonic When printing and parsing instruction mnemonics, an additional format switch may be specified to indicate the radix of any instruction operands: Switch Interpretation ------ ----------------------------------------- -A a single character in the right-hand byte -B a binary value -O an octal value -D a decimal value -H a hexadecimal value The -A switch thus has two meanings. In combination with -M, printing and parsing of instruction mnemonics assumes single characters for any data operands present. By itself, printing and parsing of single characters occur. The numeric switches may also be used by themselves to print or parse a value in the specified radix. The -M and other mnemonic mode switches are mutually exclusive. However, the -M mode switch can be combined with one format switch to specify the display or entry format for instruction parameters. The format overrides are also mutually exclusive. However, this is not enforced by SCP and cannot be enforced by the HP simulators, as there is no means to communicate errors back to the user. Symbolic parsing of the command parameters is used when evaluating an expression (EVAL command) or depositing to memory or registers (DEPOSIT command). Before parsing, the "get_radix" routine is called to obtain the radix to use for numeric operands. This routine is hooked by the "ex_get_radix" routine to override the default radix with the -B (binary), -O (octal), -D (decimal), and -H (hexadecimal) switches. In the absence of any of these switches, the default radix specified by the target is used. The HP simulators provide explicit support the -A (single character), -C (two-character string), and -M (instruction mnemonic) override switches. In the absence of any of these switches, a leading apostrophe (') implies -A, a leading quotation mark (") implies -C, and a leading alphabetic character implies -M. The 3000 simulator supports several additional types of mnemonic mode switches (-E, -ER, -I, and -T) that are variants of the -M switch. For evaluation and depositing to memory, the "parse_sym" routine is called unconditionally. For depositing to registers, "parse_sym" is called if the register has the REG_VMIO or any user REG flags set. If the flags are not set, or if the routine is called and returns an SCPE error, then the "get_uint" routine is called to parse the numeric value. The parameters to "parse_sym" vary, depending on the calling context: Parameter EVAL DEPOSIT address DEPOSIT register --------- -------------- --------------- ---------------------------- char * argument argument argument t_addr 0 address user flags (H) and radix (L) UNIT * CPU unit specified unit NULL t_value * returned value returned value returned value int32 switches switches switches and SIM_SW_REG Note that while symbolic parsing is always used for address values, parsing for register values requires the presence of a user flag on the REG entry. If no user flag is present, only numeric parsing is supported. The user flags currently employed are: Flag Meaning -------- --------------------------------------------------- REG_A default display format is -A (one ASCII character) REG_C default display format is -C (two ASCII characters) REG_M default display format is -M (instruction mnemonic) REG_T default display format is -T (status mnemonic) REG_X allow symbolic overrides REG_X does not imply a default parse or display format; it is used only to trigger the call to "parse_sym" to permit symbolic parsing. REG flags specify symbolic defaults for display. They do not specify parsing formats. This is because the user has no way of determining the applied flags when entering arguments; the user manuals merely state that specified registers support symbolic entry and display. Consider two registers, only one of which has the REG_A flag. The "DEPOSIT REG1 0" and "DEPOSIT REG2 0" commands would store different values for the two registers (ASCII "0" and numeric 0, respectively) that would not be apparent to the user. So instead, REG_A would cause EXAMINE REG to display in ASCII, but DEPOSIT REG would accept the argument in the default numeric radix specified by the REG entry, unless the -A or ' override was used. ---- Symbolic printing is used when evaluating an expression (EVAL command), examining memory or registers (EXAMINE command), and to print the next instruction when the simulator stops. For all cases but the last, the "get_radix" routine is called before printing to obtain the radix to use for numeric operands. This routine is hooked by the "ex_get_radix" routine to override the default radix with the -B (binary), -O (octal), -D (decimal), and -H (hexadecimal) switches. In the absence of any of these switches, the default radix specified by the source is used. For examining registers, "fprint_sym" is called if the register has the REG_VMIO or any user REG flags set; otherwise, it is called unconditionally. If the flags are not set, or if the routine is called and returns an SCPE error, then the "fprint_val" routine is called to print a numeric value. The parameters to "fprint_sym" vary, depending on the calling context: Parameter EVAL EXAMINE address EXAMINE register Simulation stop --------- -------------- --------------- ---------------------- ---------------- FILE * output stream output stream output stream output stream t_addr 0 address user flags | radix program counter t_value * supplied value memory value register value memory value UNIT * CPU unit specified unit NULL NULL int32 switches switches switches | SIM_SW_REG -M | SIM_SW_STOP ----- NOTE: The -B switch is already used by several simulators with the EXAMINE command. For example, the PDP11 uses it to indicate byte display instead of word display. So we cannot use -B as a global switch to display in binary. Moreover, when formatting a register, there is no way to obtain the register descriptor's "width" and "flags" field. So, for example, EX -B will use the CPU's data width (16 bits) rather than the register's width. More distressing, EX -H gives different results, depending on whether or not it is passed to "fprint_sym". This means that adding REG_VMIO to all registers is undesirable. For instance, if the register width is 4 and the flags include PV_LEFT, then the display will be "000F" if REG_VMIO is present and "F" if not. Things don't work much better for memory accesses. Entering "D -B 0 10" stores 2 in location 0, but "D -B 0 12" stores 12 (octal) with no error. The "get_uint" call fails as expected and returns SCPE_ARG, but returning that from "parse_sym" causes "dep_addr" to call "get_uint" with the default radix, which succeeds. Conclusion: -B cannot be added at the "fprint_sym" and "parse_sym" level, and only symbolic interpretations that don't require register information and will fail on fallback can be supported. Probably want to eliminate -B as a user option and add it to SCP as a local patch if it is not accepted upstream. mnemonic overrides: shift (10) ASL, RRR override iopon (10) LAI, SAI override iopop (10) LAI, SAI override iopo (10) LAI, SAI override addrs (addr) memoryops no override datas (data) dataops override CRC, TRSLT, ILIST, PRFEI, PRFIO, and DPOLY The 1000 has only a very few operands, and they're all numeric. The 3000 has many operands, and they're all numeric except possibly immediate operands, which can be one alpha character (0-255). So -C with -M is useless in all cases. for 1000: EX [ -A | -B | -O | -D | -H ] as character or numeric with radix override EX -C as string EX -M [ -A | -B | -O | -D | -H ] as mnemonic with character or numeric operand radix override modes: -C, -M, (numeric) formats: -A, -B, -O, -D, -H for 3000: EX [ -A | -B | -O | -D | -H ] as character or numeric with radix override EX -C as string EX -E [ -A | -B | -O | -D | -H ] as I/O channel order EX -I [ -A | -B | -O | -D | -H ] as EDIT subop EX -M [ -A | -B | -O | -D | -H ] as mnemonic with character or numeric operand radix override EX -T [ -A | -B | -O | -D | -H ] as status modes: -C, -E, -I, -M, -T, (numeric) formats: -A, -B, -O, -D, -H YES: In the absence of any command-line switches, the EXAMINE command displays a numeric value in the CPU's data radix, which defaults to octal but may be set to a different radix with the SET CPU [ OCT | DEC | HEX ] command. If one of the mutually exclusive mode switches is given, the value is displayed in the specified form. Any numeric operands present are displayed in a default radix that depends on the operand unless overridden by one of the mutually exclusive format switches. If the -C switch is specified, the value is displayed as two characters separated by a comma. Alphanumeric and symbol characters are displayed in single quotation marks, control characters are displayed in symbolic form, and characters above 128 decimal are displayed in escaped numeric form. If the -M switch is specified, either alone or combined with a format switch, the value is displayed as a CPU machine instruction mnemonic (if it is defined and implemented in the current firmware set) or as a numeric value in the CPU's data radix (if not). Instruction operands are displayed as follows: - Operand memory addresses and IOG select codes use the CPU’s address radix, which is octal. - EAU shift and rotate counts and IOP index displacements use decimal unless overridden by a switch on the command line. - IOP and SIS operand data values use the CPU's data radix, which defaults to octal but may be set to a different radix or overridden by a switch on the command line. When entering values to deposit in the absence of switches, a leading ' (apostrophe) implies -A, a leading " (quotation mark) implies -C, a leading alphabetic character implies -M, and a leading digit implies a numeric value in the CPU's data radix. If one of the format switches is given, a value in the specified form is expected. If a single character is supplied with -C, the low byte of the resulting value will be zero; follow the character with a space to pad the low byte with a blank. If the -M switch is specified, either alone or combined with a format switch, a defined and implemented machine instruction mnemonic is expected. Instruction operands are parsed using the same rules as above. --> Need to define meaning of "DEP -H CCE" vs. "DEP -M CCE" vs. "DEP -M -H CCE". Should DEPOSIT -H 0 CCE deposit 0x0cce [YES], even though CCE is a valid mnemonic, or does the -H apply to numeric operands (e.g., RRR F vs. RRR 17 vs. RRR 15)? Seems as though -D/-O/-H should apply to any /numbers/ specified, or to the numeric operands of mnemonic instructions if -M is included. ------------- Power Control ------------- New meanings: - RESET -P CPU --> power-on PRESET; sends ~PON | POPIO | CRS to all interfaces (ioa_PONPRESET) - RESET CPU --> operator PRESET; sends PON | POPIO | CRS to all interfaces (ioa_PRESET) - RESET -P --> power-on reset of peripheral (not interface) - RESET --> reset of peripheral (not interface) - RESET -P --> power-on reset of all interfaces and peripherals - RESET --> reset of all interfaces and peripherals New commands: - POWER CPU FAIL --> CPU power off, IRQ4 if ARS - POWER CPU RESTORE --> CPU power on, power-on PRESET to interfaces, IRQ4 if ARS - POWER FAIL --> peripheral (not interface) power off - POWER RESTORE --> peripheral (not interface) power on, power-on peripheral reset (RESET -P) - POWER FAIL --> all devices power off, IRQ4 if ARS - POWER RESTORE --> all devices power on, power-on PRESET to interfaces, IRQ4 if ARS Do we want to differentiate between POWER FAIL (power_failing) and POWER OFF (power_off) and similarly between POWER RESTORE and POWER ON? Currently, there is no method analogous to holding PRESET down while powering up with ARS enabled. Maybe POWER ON CPU does that? (POWER ON for other devices might be identical to POWER RESTORE.) Resets occur as follows: - sim startup: reset_all_p (0) - SET ENABLE/DISABLE: dptr->reset - RESET [ALL]: reset_all (0) - RESET : dptr->reset - RUN/BOOT: reset_all (0) Problem: power on reset to CPU currently zeros memory, so POWER RESTORE cannot call reset_all_p. The 1000 zeros memory only if -MLOST, but there's no analog to this, unless RESET -P is it. If so, then POWER RESTORE needs another way to do power-up initialization of the CPU but leave memory alone. Problem: turning power off is really a per-unit operation. For example, one might turn power off on one of the four connected 7970 tape drives. This would not be the same as disabling the unit, which is equivalent to disconnecting the cable. In hardware, it is possible to differentiate between unit disconnected, unit connected but powered off, and unit connected and powered on but offline. Consider one interface line that is pulled up on the interface and grounded in the device and another that is pulled down on the interface and pulled up by the device. The first line detects device connected; the second detects device powered up. We cannot provide this detection if the device reset routine is reused for power off/on. Solution: provide a power routine and a pointer to the routine in the DIB: t_stat (*io_power) (UNIT *uptr, POWER_STATE new_state); Change POWER command to or . If , then uptr is NULL; otherwise, uptr points at unit. If io_power is NULL (i.e., not defined), device does not handle power state changes. In that case, only POWER is accepted, POWER OFF does nothing, and POWER ON does RESET -P. The return status is SCPE_OK if the operation succeeded. It could be SCPE_NOFNC if the command is not allowed due to the current power state (e.g., POWER OFF while power is already off). The CPU routine returns SCPE_INCOMP if the routine succeeds but the CPU must be run to complete the command (i.e., POWER RESTORE with ARS enabled). When all devices are failed or restored, individual routine errors must not stop the full sequence from being run. For instance, doing POWER OFF LPT followed by POWER OFF should succeed, even though the LPT handler would return SCPE_NOFNC. An error in this case would be reported only if none of the handlers succeeded. ~ARS/ARS is not identical to ~PFAR/PFAR. With ~ARS, no IRQ4 occurs on power down and CPU remains halted on power up, regardless of PFAR. With ARS and ~PFAR, IRQ4 occurs on power down but CPU halts on power up. With ARS and PFAR, IRQ4 occurs on power down and power up, and CPU runs on power up. However, there's little point to the ARS * ~PFAR case, as recovery from a power failure is not possible once memory contents are lost. This case might have been useful with the 1000 "A"-model power supplies that had a "standby" position that retained memory contents while CPU and I/O power was off. Therefore, it should suffice to have ARS imply PFAR and do IRQ4 on power off and on, and ~ARS imply ~PFAR and do neither. ----- To permit exercise of the power-fail capabilities, and to permit removing power from those devices that have visible state changes, new POWER FAIL (DOWN, OFF) and POWER RESTORE (UP, ON) commands are needed. Power can be manipulated on individual devices or on all devices. Consideration must be given to changing the power state of the device interface versus changing the state of the device peripheral. For example, cycling power on a line printer changes the status return and clears any partial print buffer received. Cycling power on the interface (by cycling power on the CPU) changes the control and flag flip-flop states without disturbing the printer. Currently, the LPS and LPT devices support cycling power on the device (via local SET LPx commands), while the CPU does not. Power-on and operator PRESETs may be asserted for individual devices, which has no hardware analog, or for all devices. A power-on PRESET is received via the device reset routine with the "P" switch set. In response, the routine calls the device interface routine by asserting PON and POPIO. Power-on initialization of the device occurs in the reset routine, while that of the interface occurs in the interface routine. In hardware, individual device peripherals may be power cycled via their power switches, or all device interfaces may be power cycled together by cycling CPU power via its power switch, or both actions may occur together via a main power loss. In simulation, these actions correspond to POWER FAIL, POWER CPU FAIL, and POWER FAIL, respectively. The CPU power state is communicated to the interfaces via the PON and POPIO signals (HP 1000) and the PWR ON and PFWARN signals (HP 3000). PON and PWR ON both indicate that DC power is stable. PFWARN indicates that power is failing. POPIO with PON denied indicates that power is coming up (on the 3000, there is no explicit I/O indication that power is restoring, although CPURST and IORESET are pulsed at power-up as well as when the LOAD button is pressed). PON and PWR ON are asserted while power is on. Notifying a device of a power state change can be modeled in three ways: * Calling a new device-specified power-notification routine. * Calling the existing device interface routine with the PON signal indicating the power state. * Calling the existing device reset routine with switches to indicate the power state. In the first model, the DIB is extended by adding a new function pointer that points at a device's power routine. If the field is not set, or is set to NULL, the device does not respond to power changes. The POWER command would call the appropriate routines via the DIB pointer, passing FALSE for power-down and TRUE for power-up. Calling the power routine would be equivalent to toggling the main power switch on the device. For the CPU, the routine would set up the power fail interrupt if ARS is enabled. For peripherals, the routine would simply set the power state that is reflected in the status bits. The second model communicates the power state via the existing IO_INTERFACE routine. The hardware PON and POPIO signal states represent four conditions, as follows: PON POPIO State --- ----- --------------- 0 0 Power is off 0 1 Power-on PRESET 1 0 Power is on 1 1 Operator PRESET The POWER FAIL command sends ~PON and ~POPIO to all or specified devices. A device responds by setting its internal state to "power off" (or, in the case of the CPU, to "power failing" if the CPU is running and ARS is enabled). The POWER RESTORE command sends ~PON and POPIO; this is the same as sending RESET -P. The device responds by setting its state to "power on" (or "power restoring"). Note that an unpowered device may get subsequent calls with PON asserted as a result of the CPU continuing to run. So PON by itself cannot be used to indicate the power state. This requires redefinition of PON. Currently, PON is dispatched only once during power-on reset, rather than being included in every I/O cycle. This has to be changed to dispatch PON with every cycle. PON is omitted in only two cases: for a power-on PRESET (or RESET -P), and for a POWER FAIL. Actually, the existing simulation of power-on PRESET is wrong. RESET -P calls "io_assert" with "ioa_PON", which sends PON | POPIO | CRS to the device, while RESET uses "ioa_POPIO" which sends POPIO | CRS. This is exactly backward from the hardware action, which has POPIO without PON indicating a power-on PRESET. So implementing this second model would move the simulation closer to the hardware, although in hardware PON remains asserted while power is failing and denies when power has failed. It is the PWU signal from the power supply to the CPU denying that indicates that power is failing, but PWU is not broadcast on the I/O bus. Because the absence of PON would indicate a power fail, detection in the IO_INTERFACE routine must be indirect, e.g.: POWER_STATE state = power_off; /* or power_failing */ while (working_set) { signal = IONEXTSIG (working_set); /* isolate the next signal */ switch (signal) { /* dispatch an I/O signal */ case ioPOPIO: if ((inbound_signals & ioPON) == 0) /* power restoring? */ /* power-on PRESET actions */ /* other PRESET actions */ state = power_on; /* power is on */ break; case ioPON: state = power_on; /* powwer is on */ break; } IOCLEARSIG (working_set, signal); /* remove the current signal from the set */ } if (state == power_off) /* power fail actions */ In this model, the POWER command would call the designated IO_INTERFACE routine (or all routines) and pass ~PON | ~POPIO for a FAIL and ~PON | POPIO | CRS for a RESTORE. The third model uses switches to indicate power failing and power restoring to the device reset routine. The switches should be "internal" (i.e., not corresponding to a user-specified letter), so that the user cannot initiate power recovery via RESET -P. Candidates are SIM_SW_SHUT and SIM_SW_REST for power fail and restore, respectively. All three models fail to differentiate between interface power cycling and device power cycling. Model 2 really only handles interface power notifications, and model 3 handles only device power notifications (unless additional hidden switches are defined). Model 1 could be adapted to handle both cases, but it would need an additional parameter to indicate interface/device power state. Power failing the entire system would require all power-notification routines to receive interface power notifications (from the CPU power loss) followed by device power notifications (from peripheral power loss). The first model is somewhat closer to the hardware, in that powering a device off does not generate any I/O backplane activity (as would occur with model 2), and powering the CPU off denies PON to stop the CPU when DC power is about to drop (rather than being the initiating event). Even if model 1 is selected, the PON definition should be changed to match the hardware, i.e., to differentiate between power-on PRESET and operator PRESET. ===================== I/O System Simulation ===================== --------------------- Device User Interface --------------------- Each device interface has a settable SC (select code) from 10-77 octal. Select codes 0-7 are assigned to the CPU, DCPC, Power Fail, and Memory Protect devices; the assignments cannot be changed. The select code of the highest-priority interrupting device is loaded into the CPU's CIR register (Central Interrupt Register) and is available to the CPU during interrupt processing. The select code is also addressed by I/O instructions and is used as an index into an array of I/O signal handlers. --------------- Data Structures --------------- Device Information Block: INTERFACE *interface() uint32 select_code uint32 card_index Signals and Data Block: OUTBOUND_SET signals HP_WORD data Interface Routine: function (DIB pointer, INBOUND_SET inbound_signals, HP_WORD inbound_value) returns outbound-signals-and-data --------------- Device Conflict --------------- Before executing instructions, the simulator must check for device conflicts. Each device's select code must be unique. This is checked by the hp_device_conflict() routine prior to execution. --------------- Dispatch Arrays --------------- Arrays are used for dispatching I/O signals to a selected device interface. The elements of each array contain pointers to specific interface DIBs, or NULL if no interface is assigned. The arrays used are: - devs [64] (indexed by select code for I/O instruction dispatch) Each of the arrays is set up during the device interface conflict check that is performed in the instruction prelude. -------------------- Priority Bit Vectors -------------------- External interrupts are initiated by device interfacees asserting the FLG and IRQ signals to the CPU. Priority is established by the position of a given interface in the select code order. To avoid interrupt polling and service request array scanning, the CPU maintains bit vectors that store the state of the requesting interfaces. The vectors used are: - service_request_set (64 bits) Indexed by select code for DCPC service requests; initial state is all bits clear. - interrupt_request_set (64 bits) Indexed by select code for pending interrupt requests; initial state is all bits clear. - priority_holdoff_set (64 bits) Indexed by select code for priority chain interruption; initial state is all bits clear. To allow interfaces to be relocated, and to allow user changes in interface state to be reflected correctly in the vectors, the vectors are set up in the instruction prelude each time that execution begins. The interrupt_state and service_state of each device is examined, and the bit vectors are set appropriately. When a device interfaces requests an interrupt or DMA service, it must call iop_interrupt() or multiplexer_service() to set the bit vectors appropriately. ---------------------- I/O Signal Definitions ---------------------- The CPU communicates with device interfaces by calling the interface's signal handler and passing a signal set. Set members correspond to individual bits in a 32-bit word, with assertion represented by a one-bit and denial represented by a zero-bit. Many of the hardware signals have direct counterparts in the signal set. Others are implied by the function calls and returns (e.g., some requests and acknowledgements). For efficiency, the simulator does not implement signal generation exactly as in the hardware. In hardware, ENF and SIR are periodic, PON is asserted continuously, and most signals are common to all interfaces and are qualified at the interface by the select code. Under simulation, signals are sent only when actions are to be taken and then only to the specific target device. For instance, PON is dispatched only once during power-on reset, rather than being included in every I/O cycle. SIR is dispatched only when flip-flops affecting the PRL, IRQ, or SRQ signals are changed, rather than with every instruction. ENF is sent only when the flag buffer is set or cleared, whereas in hardware, ENF samples the flag buffer value at every T2 and sets the flag accordingly. In one case, there is an intentional deviation. In hardware, the FLG and IRQ signals are always asserted together, and the interrupt source decoder uses FLGn to establish the MSD of the select code and IRQn to establish the LSD. In simulation, FLG is asserted when an interrupt condition exists, and IRQ is asserted if the interrupt flip-flop sets. If FLG asserts without IRQ, then either PRH or IEN is denied, which holds off the interrupt request. In this case, a bit is set in the pending interrupt vector; this avoids polling every interface for an interrupt condition when PRH and IEN are both asserted again. Signals are listed below as inbound (passed to the signal handler), outbound (returned from the signal handler), or implied. Inbound Signals: Name Meaning ----- -------------------------------- CLC Clear the control flip-flop STC Set the control flip-flop CLF Clear the flag flip-flop STF Set the flag flip-flop SFC Skip if the flag is clear SFS Skip if the flag is set IOI I/O data input IOO I/O data output EDT End of data transfer ENF (periodic) Enable flag SIR (periodic) Set interrupt request IEN Interrupt system enabled IAK Interrupt acknowledged PRH Priority high CRS Control reset POPIO Power-on preset to I/O PON Power on normal Outbound Signals: Name Meaning ----- ----------------- PRL Priority low IRQ* Interrupt request FLG* Flag SRQ Service request SKF Skip on flag Implied Signals: Name Meaning Implied by ----- ----------------------------------- --------------------- SCL* Select code least-significant digit SCM* Select code most-significant digit IOG I/O group instruction RUN Run flip-flop Unused Signals: Name Meaning Reason for Omission ---- ---------------- ----------------------------- BIOI Block I/O Input Block input is not supported BIOO Block I/O Output Block output is not supported BIOS Block I/O Strobe Block I/O is not supported When the "IOBUS" trace flag is set on a device, the signals and data word sent to the device interface are printed on entry, and the signals and data word returned, if non-null, are printed before exit. The signals and data are formatted into a symbolic representation via the fprint_signals() function to produce a view into the information exchanged on the I/O bus. -------------------- I/O System Interface -------------------- bool io_initialize (& interrupt_request_set, & interrupt_poll_set) Called in the instruction prelude. Initializes the devs and irqs DIB pointer arrays and sets the interrupt_request_set and interrupt_poll_set bit vectors from the interrupt_state values in the device DIBs. Returns True if successful and False if a device number or interrupt priority conflict exists. ///////////// DIB structure: INTERFACE *interface() uint32 select_code uint32 card_index STATUS_VALUE structure: t_stat status HP_WORD value SIGNALS_VALUE structure: OUTBOUND_SET signals HP_WORD value Device Interface: SIGNALS_VALUE dev_interface (DIB *, INBOUND_SET, HP_WORD) - processes inbound_signals and inbound_value - sets outbound.signals and outbound.value - calls io_dispatch (dev_sc, ioENF, 0) from event service to set flag I/O Dispatcher: STATUS_VALUE io_dispatch (uint32 select_code, INBOUND_SET, HP_WORD) - if select code unoccupied, returns floating bus and CPU stop status - provides IOBUS tracing via devs [select_code]->flags - generates PRH, IEN signals - calls dibs [select_code]->interface - sets bit vectors based on PRL, IRQ, FLG, and SRQ outbound signals - increments P if SKF outbound I/O Instruction Executor: t_stat cpu_iog (HP_WORD io_instruction, t_bool in_trap_cell) - called to execute an I/O Group instruction (e.g., STC 12,C) - also called by IOP and RTE OS instruction execution - does MP check - gets A/B register value for IOO - determines signals to use - calls io_dispatch with select code from instruction - sets A/B register value for IOI - returns status (e.g., STOP_UNSC) ---------------- Device Interface ---------------- Each device interface must provide an I/O signal handler and place a pointer to the handler in the DIB. This is the main interface between the CPU and the device. Each interface must also provide a reset routine. The "dev" prefixes below are typically the same as the device name (e.g., ms_interface, ds_reset, etc.). SIGNALS_DATA dev_interface (DIB *, INBOUND_SET, HP_WORD) // Called by the IOP for Direct I/O and by the channels for Programmed I/O. Corresponds to asserting the SO or the CHANSO and CHANACK signals. Processes the supplied signals in order and modifies the device state appropriately. The inbound_value is ignored if it is not indicated by the inbound_signals. The return value is a restricted outbound signal set in the high word and an outbound value in the low word. If the outbound value is not indicated by inbound_signals, the outbound value is zero. t_stat dev_reset (device_pointer) Called by the SCP for the RESET and RUN commands. Corresponds to asserting the IORESET signal. Clears the device state. ---------- Direct I/O ---------- I/O Group instructions are decoded and executed by the CPU and call the I/O signal dispatcher with select code and signal set. The dispatcher then calls the indicated device interface. The process is: // 1. The CPU calls iop_direct_io(), passing the device number obtained from the stack (or zero for SMSK), the I/O order determined by the instruction, and a value to be written for the CIO, WIO, and SMSK instructions. 2. The IOP translates the I/O order into the corresponding signal. For all instructions except SMSK, the IOP sets dibptr = devs[device_number]. If dibptr is NULL, the IOP sets the I/O Timer bit in CPX and returns a zero value. If dibptr is valid, the IOP calls dibptr->dev_controller(), passing the dibptr, the translated signal, and the inbound value. For SMSK, the IOP loops through the device list and calls each defined controller signal handler in turn with the DSETMASK signal and the mask value. 3. The device controller processes the signal, updates the device state as indicated, and returns the requested outbound value for the TIO and RIO instructions. The outbound signal set is ignored. 4. The IOP returns the outbound value to the CPU. 5. The CPU tests the I/O Timer bit in the CPX register. If set, the CPU clears it and aborts the instruction by setting CCL in the Status register. If clear, the instruction is completed normally. ------- Details ------- The IOG instruction executor calls the signal dispatcher with the following signals: Instr Signal Set ----- --------------------- HLT (none) HLT,C ioCLF | ioSIR STF ioSTF | ioSIR CLF ioCLF | ioSIR SFC ioSFC | ioSIR SFC,C ioSFC | ioCLF | ioSIR SFS ioSFS | ioSIR SFS,C ioSFS | ioCLF | ioSIR MI* ioIOI MI*,C ioIOI | ioCLF LI* ioIOI LI*,C ioIOI | ioCLF OT* ioIOO OT*,C ioIOO | ioCLF STC ioSTC | ioSIR STC,C ioSTC | ioCLF | ioSIR CLC ioCLC | ioSIR CLC,C ioCLC | ioCLF | ioSIR In addition, the dispatcher adds the ioIEN signal if the interrupt system is on and the ioPRH signal if priority is not inhibited to the selected interface. The SFC and SFS instructions test the returned signal set for ioSKF and skip if the signal is present. ------------------- DMA Service Request ------------------- - Virually all interfaces assert SRQ. - Typically, SRQ is the Q-side of the Flag flip-flop, but not always. - Asserting SRQ does nothing if the DMA channel is not configured for the select code and the DMA's Transfer Enable flip-flop is set (by STC). - SRQ may be asserted before the DMA trasfer enable flip-flop is set, or the flip-flop may be set before SRQ is asserted. - A DMA cycle is requested at the top of the instruction loop only if (a) the channel transfer is enabled and (b) the associated device's SRQ line is asserted. Therefore, channel requests need only be calculated (a) in the DMA interface's STC handler, and (b) in the I/O dispatcher when SRQ is asserted. This implies that SRQs that result from event service routines must be passed through the I/O dispatcher by sending ENF | SIR. - A DMA cycle may performed on channel 1, on channel 2, or on channel 1 and then channel 2. If SRQ is asserted continuously, then cycles occur contiguously without intervening CPU cycles. - Setting the DMA channel flag, either implicitly when the count expires or explicitly by a STF instruction, clears the transfer enable flip-flop. - Even though there are at most two channels, a bit vector is required because multiple SRQs may be set before one is associated with a DMA channel. For example, STF may be done on five interfaces, and then DMA configured to use two of the five. At completion, DMA may be configured to use two of the remaining three devices, and the SRQs must assert automatically to start the transfers. - The bit vectors must be reconfigured at DMA initialization time (in the instruction prelude), as the select codes may have been changed by the user. - Because DMA requests must be checked for each instruction, it is desirable to test for the presence of a request rather than calculate requests at the top of the instruction loop. DMA STC does: uint32 dma_request_set; uint32 dma_device [2] [2] = { // bit is set ONLY when xferen FF set { 0, 0 } { 0, 0 } }; dma_device [ch] [rank] = 1u << channel_select_code % 32; if (dma_device [ch] [rank] & service_request_set [rank]) dma_request_set |= DMA_chan_REQUEST; DMA STF does: dma_device [ch] [rank] = 0; I/O dispatcher does: uint32 service_request_set [2] = { 0, 0 }; if (outbound.signals & ioSRQ) { service_request_set [sc_rank] |= sc_bit; if (sc_bit & dma_device [ch1] [sc_rank]) dma_request_set |= DMA_CHAN1_REQUEST; if (sc_bit & dma_device [ch2] [sc_rank]) dma_request_set |= DMA_CHAN2_REQUEST; } else service_request_set [sc_rank] &= ~sc_bit; Instruction loop does: if (dma_request_set) dma_service (); --------------------------- IPL Process Synchronization --------------------------- Provide process synchronization via events. One instance would wait for the event, and the other would signal the event to release the first. Events should be allocated and released automatically as part of simulator or IPL device configuration. Event names should be unique to a simulator pair configuration, so that more than one pair of instances can use synchronization concurrently. * One or two events? A simple approach is to use separate events to wait for the SP and the IOP. The difficulty with using a single event for both SP and IOP waits is that an instance may signal the event to release the wait and then wait itself for a signal from the other instance. i.e.: SP IOP ====== ====== wait ... ... signal ... ... ... wait signal ... The danger is if the SP does not issue its wait before the IOP issues the signal, e.g., due to preemption. In this case, the IOP wait will not wait because it has signaled itself. That is: SP IOP ====== ====== ... signal <-- this signal beats the SP wait due to SP preemption ... ... ... wait <-- this wait is already signaled and so does not wait! ... ... wait ... <-- this wait waits forever, because the event is reset! ... ... signal ... With separate SP and IOP events, the IOP signals the IOP event and then waits on the SP event. The SP does not wait on the IOP event because it is already signaled, and the SP proceeds to signal the SP event, which releases the IOP's wait. Synchronization is maintained regardless of process execution time. However, the pattern above arises only in connection with the IOP program generation, and the IOP runs its loader after signalling the IOP event. The IOP cannot reach the SP wait until after it has received the data transfer, which does not start until the SP's wait receives the IOP event signal. So if the first case above is not satisfied, due to SP preemption, the situation will be: SP IOP ====== ====== ... signal ... ... <-- the IOP pauses here for the data transfer wait ... ... ... <-- the data transfer occurs here ... wait signal ... Therefore, only a single event is necessary. * Wait/signal syntax? Process synchronization provides a "wait" command that waits for an event to be signaled and a "signal" command that signals the event. These commands may be either top-level commands or IPL device modifiers. Top-level commands might be: WAIT SIGNAL Device modifiers might be: SET IPL WAIT SET IPL SIGNAL The above is nicer than SET IPLI WAIT, etc., and may be implemented either by creating a new IPL device to handle just these two commands, or by renaming the IPLI device to IPL and defining IPLI as the logical name. This permits SET IPL while externally presenting the device as IPLI. NOTE: if two events are used, then the wait and signal commands must either specify the events explicitly or determine the events implicitly. For the former, "wait sp", "wait iop", "signal sp", and "signal iop" commands would be needed. For the latter, the event can be determined by testing the CPU's UNIT_IOP flag. If it is not set (implying the SP), "wait" waits for the IOP event, and "signal" signals the SP event. If it is set (implying the IOP), "wait" waits for the SP event, and "signal" signals the IOP event. This would require SET CPU IOP to be specified for the IOP, even when IOP firmware is not needed (e.g., for 2000 F), but this is not a hardship. * Wait/signal semantics? generate name and CreateEvent on 2nd IPL attach CloseHandle on 1st IPL detach SetEvent on SET IPL SIGNAL WaitForSingleObject on SET IPL WAIT loops while waiting; CTRL+C to exit loop before wait completes * Event naming? To permit the two instances to share events, either the event names must be known (e.g., "HP2100-IPL"), or the first instance must pass the event handle to the second instance. Known naming is simplest, but it is desirable to allow more than one pair of instances to be active. This requires that the name be different for each pair but still be known to each pair independently. A simple approach is to incorporate the lower of the two IP port numbers in the name, e.g., "HP2100-IPLI-4020". The port numbers attached to the IPLI and IPLO devices are known to both instances of a pair but must be unique to the pair. The IPL devices may be attached in any order, so the lower number is known only when the second of the two devices is attached. Naming and allocation would take place then, and deallocation would be needed when the first of the two devices is detached (because that device could be reattached with a lower number). Passing event handles might be easy when the IPL device is changed to use shared memory spaces rather than sockets. Currently, though, the handle would have to be passed as an environment variable, which would need per-pair naming as above. So the latter approach offers no (current) advantages over the former and has some disadvanatges (e.g., checking for environment overflow). 4.x port = "port number", ok if host = "localhost" or "127.0.0.1" 3.x ipp = port number, OK if ipa = 0 or 0x7f000001 (localhost) --------------------- CS/80 Disc Simulation --------------------- CS/80 drives were supported on RTE-6/VM only. RTE-IVB provided MAC and ICD support. Initial implementation will be the subset of CS/80 that is used by the 12992J Boot Loader ROM, the RTE-6VM boot extensions, the DVM33 disc driver, and the direct control operations performed by SWTCH. The boot loader does: - Unlisten - Untalk - Universal Device Clear - Parallel Poll (wait for drive 0 ready) - Universal Device Clear - Listen 0 - Command: Set Unit 0, Locate and Read (defaults: address 0, length -1) - Unlisten - Talk 0 - Execution: DCPC accepts 620 words (1240 bytes) When DCPC completes, boot loader does JSB to 2055,I. The boot extension does: - master reset (terminates read) - Untalk - Unlisten - Listen 0 - Transparent: Cancel (no optional unit; terminates read) - Untalk - Unlisten - Parallel Poll (wait for drive 0 cancel) - Untalk - Unlisten - Talk 0 - Reporting: QSTAT (0) - Untalk - Unlisten - Listen 0 - Command: Set Unit 0, Set Volume 0, Set Length 65024 bytes, Set Address 2, Locate and Read - Untalk - Unlisten - Talk 0 - Execution: DCPC accepts accepts 32512 words (65024 bytes) - Untalk - Unlisten - Talk 0 - Reporting: QSTAT (0) - Untalk - Unlisten After completion, the boot extension does JMP 3,I at 77460 to $STRT in SCHD6. The RTE startup uses the DVM33 driver; it does: - Command: Set Unit 0, Set Volume 0, NOP, Set Length 2560 bytes, NOP, Set Address 288, NOP, Locate and Read - Execution: accepts 1280 words (2560 bytes) - Reporting: read QSTAT (0) - Command: Set Unit 0, Set Volume 0, NOP, Set Length 12288 bytes, NOP, Set Address 240, NOP, Locate and Read - Execution: accepts 6144 words (12288 bytes) - Reporting: read QSTAT (0) - Command: Set Unit 0, Set Volume 0, NOP, Set Length 7680 bytes, NOP, Set Address 162, NOP, Locate and Read - Execution: accepts 3840 words (7680 bytes) - Reporting: read QSTAT (0) - Command: Set Unit 0, Set Volume 0, NOP, Set Length 2044 bytes, NOP, Set Address 347, NOP, Locate and Read - Execution: accepts 1022 words (2044 bytes) - Reporting: read QSTAT (0) [...] - Command: Set Unit 0, Set Volume 0, NOP, Set Length 512 bytes, NOP, Set Address 4414, NOP, Locate and Write - Execution: sources 256 words (512 bytes) - Reporting: read QSTAT (0) [...] The RTE-6/VM System Manager's Manual says, "For all CS/80 discs, set the unit and volume number to 0. For the CTD, set the unit number to 1 and volume to 0." The Online Generator Reference Manual says, "The unit number is always 0 except unit 2 for the HP 7907 removable platter and unit 1 for integrated CTDs. For the volume, always enter 0." DVM33 issues the following commands internally: - Describe - Write File Mark - Locate and Read - Locate and Write - Unload - Set Options (auto sparing enabled, skip sparing, immediate reporting) - Read Run Log - Copy Data - Request Status - Release - Identify Read Run Log is sent only for a tape drive and then only to examine the fourth byte to see if the cartridge has been initialized. Release is sent only if a release request appears in the status bytes. Identify is sent only if an operation times out. It is used to determine if the device is busy or dead. SWTCH calls the following subroutines in the CS/80 disc library: - XLCRD Locate and read - XLCWR Locate and write - XLCVF Locate and verify - XRELS Release - XXSPR Run error rate test and spare Locate and Read/Write/Verify are used for normal system installation operations. A Release is performed only if an error is reported and one of the "release requested" status bits is set. An ERT and Spare is performed only if an error reports Unrecoverable Data Error, Unrecoverable Data Overflow, or Marginal Data status. The complementary command array requests the following commands: - Set unit - Set volume - Set address (single-vector) - Set length Complementary commands are applicable to a wide range of real-time, general purpose, and diagnostic commands, but not all combinations of complementaries are valid. Table 2-1 on page 2-2 of the CS/80 Instruction Set manual details the choices. Other notes from the manual: - Except for CLEAR and CANCEL, a command message is valid only if it occurs during the command phase of a transaction (1-4). - Execution messages are valid only during the execution phase of a transaction which started with a command that calls for an execution message (1-4). - The device initiates reporting messages during the reporting phase of each transaction, during special reporting phases entered for power recovery, or to service internal requests (1-4). - The only means of clearing the QSTAT byte is by issuing the Request Status command or the Clear command (1-4). - In its idle state, the device is in the command-ready state. When a command message is received, it is buffered, parsed, and validated as defined by the device firmware. If the command and its parameters are valid, the device enters the execution state and begins to carry out the command. If not, the device enters the reporting state and prepares an error status report (1-6). - Each unit within a device has its own set of programmable operating parameters. The parameters are initially assigned power-on values. New parameter values for a unit are established by a command message containing ONLY Complementary commands. A single command may be used to update multiple parameter values (2-1). - There are two Complementary commands that do not produce the temporary override. These are the Set Unit and Set Volume commands. The values specified in these commands remain in effect even if the commands are included in a real-time, general purpose, or diagnostic command (2-1). - Set Unit must be the first byte of the command sequence. If the opcode appears elsewhere in the message, an Illegal Opcode error will be reported. - If an illegal volume number is specifed with Set Volume, a Module Addressing status error is generated. - Set Length of 0 with a Locate and Read or Write specifes a seek only; it does not require an execution phase. The manual suggests that the command bytes are all accepted, then they are parsed and validated, and then they are executed. That implies that an error anywhere in the sequence inhibits the entire sequence, rather than commands being executed from left to right and stopping at the first bad command. However, SS/80 says, "When the peripheral's command decoder is working its way through the complementary commands, it will stop at the first one which is erroneous, or on the first illegal opcode it detects. The remaining commands following the error or illegal opcode will not be executed." That seems to imply that the ones before the error WILL be executed. The fact that Set Unit is required to appear first, but Set Volume is not, suggests that one may issue complementaries for several volumes of one unit. And tha implies left-to-right execution. Because complementaries only affect the current transaction unless the command sequence ends with a complementary, another implication is that none of the unit settings are altered if an error occurs in the sequence. By the same token, a bad opcode at the end of a sequence of complementaries must discard the complementaries, as they would only be set if the command ended with a valid complementary. So, effectively, a bad sequence is ignored, except, perhaps the initial Set Unit and Set Volume, which persist between commands. But the latter is not clear. It also suggests that the three phases are per-volume, so the controller could initiate command phases on each of the volumes of each of the units. For instance, a disc seek and a tape seek could begin concurrently. SS/80 devices explicitly do not support overlapping commands on units or volumes within a single device. Overlapping commands between devices is allowed. Utility Commands ---------------- The Initiate Utility command directs the addressed device to execute an internal utility. The command has three forms: Opcode Meaning ------ ------------------------------------- 30H Utility has no execution phase 31H Utility has a receive execution phase 32H Utility has a send execution phase Utilities are device-specific. Unfortunately, some utilities are required by drivers. For example, the RTE driver initiates utility C5H (Read Error Log) on the 9144 to determine if the installed cartridge is certified. Also, the EXER and TAPE programs initiate utility C3H (Read Revision Numbers) on startup. So we must provide at least these. The 9144 utilities are: Opcode Param Description ------ ----- ------------------------ 32H 30H Read Memory 32H 36H Return Servo Status 30H 37H Set Amplifier Gain 32H 38H Return Amplifier Gain 31H 3AH Send Servo Command 30H 3BH Set Retry Mode 32H C1H Read Error Summary 32H C3H Read Revision Numbers 32H C4H Read Drive Tables 32H C5H Read [Runtime] Error Log 32H C6H Read Error Rate Test Log 32H C7H Read Use Log 30H C8H Pattern Error Rate Test 30H CDH Clear Logs 30H CEH Preset Drive 31H D1H Receive User Pattern The TAPE exerciser program CERT command issues the Pattern Error Rate Test utility command (30 C8 01 02 02 00 = loop 1, type 2, area 2, data source 0). These utilities are defined for the 795x and 796x disc drives: Opcode Params Description ------ -------- ----------------------------- 30H C8H Pattern Error Rate Test (ERT) 30H C9H Read-Only ERT 30H CBH Random Pattern ERT 30H CCH Random Read-Only ERT 30H CDH Clear Logs 30H CEH Preset 32H BFH Servo Test 32H C0H Locate and Read Full Sector 32H C3H Read ROM Revision Number 32H C4H, 01H Read Spare Table 32H C5H Read Run Time Log 32H C6H Read Error Log 32H C7H Read Fault Log 32H C8H Pattern ERT 32H C9H Read Only Test 32H CBH Random Pattern ERT 32H CCH Random Read Only ERT 32H D4H Read Defect ESDI List The Read Spare Table command is supported on the 791x, 794x, and 795x drives. The format of the returned data block consists of a series of lists, one per defined drive head, as follows: Header Bytes Item (one set per head) ----- --------------------------------- 1 Head number 2 Number of sectors spared 1 Number of spare tracks used 1 Number of defective tracks spared Item 3 is the total number of factory and field tracks spared, while item 4 is just the number of field tracks spared. Bytes Item (one set per track) ----- --------------------------------- 1 Cylinder address (high) 1 Cylinder address (low) 1 Spare track used (ordinal) bit 7 = factory spare As simulated disc drives will not have any defective sectors, the Read Spare Table command will consist of one five-byte header for each head defined in the drive, with all of the sparing fields set to zero. As such, there will not be any spared-track entries. Terms ----- One 12821 Disc Interface has from 1-4 devices connected to the bus, each of which has its own bus address between 0-7. Each device contains a controller and from 1-7 units, each of which may be operated in parallel. Each unit may contain from 1-8 volumes, which are not capable of parallel operation. Our implementation supports only a controller and 1-2 units per device. Each unit supports only one volume. CS/80 Reference SIMH Reference --------------- ---------------------------------- Interface Card DC device Device DCn MODEL= Address DCn BUS= Unit DCn unit Volume DCn unit (only Volume 0 supported) Multiple CS/80 units may reside at the same bus address, e.g., 7946 unit 0 is the disc and unit 1 is the tape. Each CS/80 unit needs its own image file, but SCP has no way to attach multiple files to a single SIMH unit. So we must use one SIMH unit per CS/80 unit (actually, one would be needed per-volume, but all of the CS/80 drives we want to support have only one volume per unit). There are a couple of ways of mapping this: 1. DCn is unit 0 and DCn+4 is unit 1 This pairs pair SIMH units, e.g., DC0 is address 0 unit 0, while DC4 is address 0 unit 1. The 12821A only allows four CS/80 devices to be connected, so DC0-DC3 could be paired with DC4-DC7, respectively. Tape units have a WRITE PROTECT mechanism (actually, a cam on the cartridge), although discs do not. Model numbers must be kept consistent between pairs. One downside is that most devices have only one unit, so e.g. setting DC0 to 7945 would have to disable DC4, while setting it to 7946 would have to enable DC4. Another is the user would have to remember the stride between SIMH units to get the proper tape images attached. 2. DCn/2 is unit 0 and DCn/2+1 is unit 1 This pairs DC0/1, DC2/3, etc. rather than 0/4 and 1/5. This might be slightly easier to remember for tape units, but connecting for discs would use DC0/2/4/6. 3. DCx BUS=0 is unit 0, DCy BUS=0 is unit 1 This assigns units in increasing order to any DC units with the same address, e.g.: - DC0 BUS=0 is unit 0 - DC1 BUS=0 is unit 1 - DC2 BUS=1 is unit 0 - DC3 BUS=0 us unit 2 This removes the pairing, so the user is in charge of assigning unit numbers, albeit indirectly. It also allows for more than two units, though I don't think this matches any real devices (when it exists, unit 1 is either an integrated CTD or the removable platter of a 7907 drive). It also conceivably allows eight single-unit drives, although again the 12821A hardware doesn't permit this, probably because of bus loading. Still, if the module is reused for the HP 3000, this restriction may not apply (i.e., a GIC may allow more than 4 HP-IB drives). To avoid conflicts, setting any DCx model would set the models of all units on same bus address. This would require setting the bus address before the model, e.g.: SET DC0 BUS=0;7946 -- sets address 0 unit 0 model 7946 SET DC1 BUS=0 -- sets address 0 unit 1 model 7946 SET DC0 7945 -- resets address 0 units 0/1 model 7945 SET DC1 BUS=1;9144 -- resets address 0 unit 1 model 9144 After the last pair of settings, DC0 is 7945 bus 0 unit 0, while DC1 is 9144 bus 1 unit 0. One rather serious problem is that an attached unit might be renumbered. Consider the DC1 BUS=0 unit with an attached file. If DC0 BUS=0 is subsequently assigned, then the attached file silently moves from unit 0 to unit 1. More troubling, the parameters associated with unit 0 (last address, status, etc.) now become the property of the new unit 0. Similarly, if DC0 is then disabled or reassigned to a new address, the file associated with DC1 unit 1 now becomes unit 0. There could be a check to determine if any assignment causes a reassignment of another unit that has an attached file, but then the rejection error, e.g., "Command not allowed", would be confusing because it would be caused by a different unit than the one being assigned. 4. DC0 BUS=0;UNIT= is unit . This requires an explicit change by the user and so is better than assigning units indirectly. Changing one unit cannot affect another. Moreover, because an explicit change is required, changing an attached unit can be rejected, and the rejection applies to the unit specified. This would require a check to prevent confllicting assignments. The check could be at SET DCn time, but then swapping units would require an intermediate assignment. Also, enabling a unit with a conflicting bus assignment would have to be rejected, but then the unit could not be reassigned because one cannot SET the parameters of a disabled unit. Alternatively, a check could be made at I/O initialization time, and conflicts could be reported then (as with I/O conflicts). Just before execution begins, PON is dispatched to all devices, and the check could be scheduled then. An error code from the check cannot be reported back to the I/O dispatcher, but a unit could be scheduled at zero time with a special code to indicate a check, and the unit service (which will run before the first instruction is executed) could return the error code to stop execution. (The "io_initialize" routine currently ignores the return value from "io_dispach". However, it could test "result.skip" and abort initialization if anyone returns ioSKF. This would avoid the need for a zero-time service activation.) Addressing ---------- The current DI implementation works with unit numbers, rather than bus addresses. So, for example, the "acceptor" bit reflects a unit number rather than an address, as does a parallel poll bit. This worked OK for ICD discs, as units map 1:1 to devices, and the fact that calls were made on a unit basis rather than a bus address basis didn't matter (although, of course, parallel poll priority wasn't honored). A problem arises for CS/80, where SIMH units correspond to CS/80 units and not devices. A controller resides at a bus address and controls all SIMH units that specify that same bus address. So we need to revise DI and DI_DA to interface via bus addresses instead of unit numbers. Devices map 1:1 to bus addresses, and there is one controller per device, so controllers also map 1:1 to bus addresses. ICD and CS/80 units map 1:1 to SIMH units. While each ICD controller manages only one unit, each CS/80 controller may manage multiple units. The CS/80 Programming Manual says that a device may have up to seven units, while the Set Unit command format provides for 16 units, with unit 15 reserved for the device controller. However, we need only support one or two units, as all devices of interest contain either a disc drive or a disc-and-tape drive combination. We also need to support only a single volume on each unit, so all units are addressed as Volume 0. The only multi-volume device is the 7907 fixed/removable disc drive, which we do not need to simulate. (The 7912, et. al., may be ordered with a option specifying separate controllers for the disc and tape, each with its own bus address, but that case may be simulated by a single-unit 7912 and a single-unit 9144.) CS/80 device controllers are implemented by an array of state structures, indexed by bus address. Each controller maintains these state variables: Variable Reset Value ---------------- ----------- controller state Reporting current unit 0 current volume 0 CS/80 units are implemented by an array of state structures, indexed by bus address and unit number. Each CS/80 unit maintains these state variables: Variable Reset Value ------------------------------------------ ------------- SIMH unit number unchanged transaction state Idle last quick status code 2 or 0 last full status PF or 0 current address 0 current and default length -1 current and default burst false current and default RPS false current and default retry time 0 current and default status mask false current and default release 0 current and default options 0 current and default return addressing mode single vector The SIMH unit number connects the CS/80 unit with a SIMH unit. The bus address is kept in the "flags" field by the interface, and the unit number is kept in the "u3" field. The fields are set whenever a SET DCn BUS=n command is entered. The SIMH unit number field is used to specify the unit to activate or cancel as needed. Within the unit service routine, the bus and unit number are used to access the unit state array. Before each command phase, the default values are copied to the current values. Any complementary commands set the current values. If a complementary ends a command phase, i.e., is tagged with EOI, the current values are copied to the default values. During execution, the current values are used and updated (so, for example, the length may be decremented as each byte is transferred). Controls -------- Other than self-test and diagnostic initiation switches, the 7911, 7912, and 7914 have HP-IB address switches. The tape cartridge has UNLOAD, push-button SAVE, and puch-button RESTORE switches. The 7941 and 7945 are the same. The 7942 and 7946 have the same, including the three tape switches. There is also an ONLINE indicator to show that the drive has power and is ready for operation. Simulation device commands are: - ADDRESS= - DIAGNOSTIC - HPIB - DRIVES= (maybe, for additional drive types) or perhaps: - DRIVE=;;;; Simulation unit commands are: - ATTACH [ -N | -R ] - BUS= (n = 0-7) - UNIT= (n = 0-1) - 7nnn (perhaps enumerate all of them) - 9144 - 9145 Disc drives with removable media are the 7907 and 7935. All other disc drives use fixed media. There is no data on the CS/80 responses to commands when no media is loaded. However, the SS/80 manual suggests that Not Ready is appropriate for no media loaded. This is probably applicable to detached fixed units too. When media is loaded, SS/80 drives set the Power Fail bit and QSTAT = 2 to tell the host to reconfigure the drive. Tape drives are the 9144, 9145, and 35401. Tape cartridges come in long and short versions and must be initialized before first use. DESCRIBE reports the maximum block as either the long tape value, the short tape value, or zero if no tape is installed. A tape cartridge may be write-protected by rotating a cam on the cartridge. For simulation, protection is either explicitly specified by an ATTACH -R or implicitly by setting the file system's read-only bit. Write Protect status is set only in response to a write command -- not when the media is loaded. An Unload command unloads the cartridge but does not eject it; this must tbe done manually. In simulation, Unload corresponds to a automatic detach. We have to support two units (0 and 1) per device to handle disc + tape combinations, such as the 7946. Using a tape on unit 1 is the only way to use a single HPDrive instance to emulate a disc drive for system access and a tape drive for file save/restore. Cartridge Tape Images --------------------- CTD images are written in HPDrive's fixed SIMH-compatible format to allow both random access and file mark/character count support (FST does WFMs). An Initialize Media command writes the full image in the proper format; the FORMAT command of the FORMC program will do this. ATTACH -N will also initialize the new image. Adding the -S switch will create and initialize a short tape. ATTACHing an existing tape should check that the image is formatted. If not, Unitialized Media status should be returned on read or write access. An image is unformatted if the file does not contain an integral number of tape records, and each record does not occupy exactly 1040 bytes (1024-byte data block plus leading and trailing gap markers and leading and trailing length-word markers). In particular, a new file (created without the -N switch) and a "bare" image (.hpi) are considered unformatted. The check is made when the image is ATTACHed, and a flag is set; this is because the status can be cleared but must be reestablished when another access is attempted. The flag is cleared if an Initialize Media is done. Pre-existing "shrunken" tape images that are shorter than the 150-foot or 600-foot lengths are a problem. For disc images, random writes beyond the current file length automatically fill in the intervening bytes with zeros. This won't work with tape images, as the intervening blocks must be filled with formatted tape records. The obvious solution is to: 1. Mark a shurnken image as uninitialized. If SIMH were used by itself, this would not be a problem. Creating a new image with ATTACH -N creates a file of the correct size, as will an Initialize Media command against an uninitialized image file. Unfortunately, HPDrive generates and can use shrunken images. So we need to accommodate them, which we can do in one of several ways with increasing complexity: 2. Automatically extend a shrunken image to the proper size when the file is attached. 3. Extend a shrunken image to the proper size if ATTACH -X is done, else mark the image as uninitialized. 4. If ATTACH -X is done, extend a shrunken image to the proper size and allow reads and writes. Otherwise, automatically mark a shrunken image as read-only and return No Data Found errors for reads beyond the EOF. 5. Set a "high-water mark" at attachment and return blocks of zeros for reads beyond the mark; each time a write extends the mark, write empty records between the prior mark and the current mark. Option 2 is the simplest to implement, as initialize_media need only be extended to allow specification of the first block to initialize, rather than always starting at block 0, and all of the work is done in dc_attach. Once the file is extended, random access is guaranteed. The drawbacks are that the user gets his file extended without asking, and it fails if the host file is read-only. Option 3 is nearly as easy to implement as Option 2, and it solves the problem of extending the file without asking the user. However, it still prevents use if the file is read-only. Option 4 allows the use of shrunken files, either for reading if -X is not specified or the host file is read-only, or for reading and writing after extending to the proper length. It requires the initialize_media extension of Option 2, plus some additional, though minimal, consideration in read_block. Option 5 automatically allows full reading and writing without user intervention. But it requires the same read accommodation as Option 4, and in addition, initialize_media be extended to allow specification of both starting and ending blocks, and write_block must check for writes beyond the high-water mark and call initialize_media when one is detected. Option 4 is probably the best compromise, as it is not clear how often shrunken files will be attached. Formatting, Initializing, and Certifying Cartridge Tapes -------------------------------------------------------- The Initialize Media command must be used to initialize a cartridge tape before it is used. HPDrive and simhtool create new images containing blocks consisting only of erase gaps. Ansgar uses this to return No Data Found errors. He argues that this is the correct response for a newly initialized tape before it is written. I had originally implemented Initialize Media by writing a zero-filled data record in each block. Ansgar argues that this is wrong. This method fails because the SIMH tape library reads through erase gaps, so that initializing a tape in this way, then writing block 5, and then reading block 0 will return the data from block 5 with no error. To prevent this, a method is needed that returns a positive error to the CTD simulator and stops at the current block. The method chosen is a private tape marker, which is a feature of the extended SIMH tape format. The simulator was modified to write a block containing marker 0x77777777 followed by a gap. Detection of this marker while reading produces the No Data Found error. However, I do not think this is correct. My reasons are: - The RTE-6/VM DVM33/DVN33 Reference Manual says on page 3-4 that the No Data Found error means that "A block accessed during read has not been written. This generally occurs on [a] tape that has not been initialized." If the error occurs because initialization was not done, then reinitializing should stop the error, not guarantee that it occurs on every block access. - Using FST to back up files to a newly initialized tape produces an RTE error when the program attempts to read the tape directory: FST> GO TR 0 E 1 U 8 U SCODE 12 EQT 1 SUBCH 8 ADDRESS 1 QSTAT 1 377 0 0 2000 0 0 0 0 0 0 Unknown archive format Do you want to write over this archive (Y/N)? The DVM33 (CS/80 driver) manual says on page 7-1 that a "TR" error is an "unrecoverable error," and the driver returns this code for "severe errors." It is strange that using a new certified HP tape cartridge straight out of the box would normally produce a severe error, especially as this condition is NOT mentioned in the FST manual. - Tape drive support was added to DVM33 in 1984. In 1989, DVM33 was changed to detect No Data Found and report it as a severe error. Prior to this, the error had been ignored and not reported to the calling program. This is the last change listed in the source. The SR was 2200045856 (not 2200045956 as listed in the driver source). Again, it is strange that an error that is expected every time a new cartridge is used would not be detected in the driver until the omission was reported five years later. - The RTE-6/VM Utility Programs Reference Manual says on page 9-30 for the FOrmat command, "If the tape has not been certified (the initialized media flag on the tape has not been set), a tape certification process is performed. This includes writing a known pattern on every block of the tape and then rereading the tape to determine if there are any defective areas on it." The process continues with sparing bad blocks and finishes by setting the "initialied media" flag. There is no mention of erasing the data blocks before completing the command. The SS/80 manual says, "The data pattern left on the medium after executing [the Initialize Media] command is device dependent; the host should never rely on the device to fill each block with zeros or some other byte," so presumably the "known pattern" remains on the tape in the data blocks after the command completes. - Using the system utility FORMC to FOrmat and then VErify a tape image aborts the program: /FORMC: TASK? VE /FORMC: CS80 DISK OR TAPE LU? 24 /FORMC: VERIFY ENTIRE TAPE (Y,N)? Y /FORMC: COMMAND EXECUTING - VE,24,0,16352 /FORMC: ACCESS ERROR 2000B NO DATA FOUND IDENTIFICATION FIELD: 377B QSTAT: 1 PARAMETER FIELD P(1) THRU P(10): 0B 0B 0B 0B 0B /FORMC: ABORTED The verify command is rejected if the tape media is uninitialized. Once the tape has been initialized with the format command, the verify is accepted but then aborts immediately. So a FORMAT immediately followed by a VERIFY causes a program abort. This seems unexpected, especially as there is no mention of this in the manual. Also, if it was expected, the program would report an error and return to the TASK? prompt, not abort. Moreover, unless every block on the tape had been previously written, a full-tape verify would abort the program when it reaches a block that returns No Data Found. So virtually no tape cartridge would pass a full-tape verify. - The "LINUS External Reference Specification" says on page 7 in the Locate and Read section, "Attempting to read a block which has never been written will terminate the transfer with a No Data Found media error." But certifying a cartridge certainly does write to the data blocks, as described in the utilities manual. Perhaps "has never been written" does not mean "has not been written by the user since initialization" but instead means "has never been written since formatting" at the factory. - The "7908 Disc/Tape Drive Service Manual" says on page 2-4 that the Initialize Media command is "Used to initialize all of the data fields of the undefined media area." Presumably, those are the system-accessible blocks outside of the defined user area. There are some odd things in the documentation: - The CS/80 manual says, "If an uncertified tape is loaded, an Uninitialized Media error will result." But then why does DVM33 issue the Read Error Log utility to determine if the tape is certified, rather than simply doing a Locate and Read of zero bytes? - The CS/80 manual shows that No Data Found is NOT returned for a Locate and Verify command. This implies that such blocks are ignored, although the command description does not mention this. - The SS/80 manual says, "The data pattern left on the medium after executing [the Initialize Media] command is device dependent; the host should never rely on the device to fill each block with zeros or some other byte." Nomenclature: - Formatting: A degaussed tape is first "formatted" at the factory. This writes the tape keys across the full width of the tape to establish the block structure but does not write the data frames within each block. Nor does it set up the sparing table or other tape tables. - Initializing: A formatted tape is initialized by writing the spare table, error log, run log, etc. Initializing does not write the data frames. - Certifying writes data frames and then reads them back to determine if any bad blocks exist. There is no evidence that certification erases the data before completing. 3M sold both formatted (DC600HC) and unformatted (DC600A) tapes. HP sold both certified and uncertified tapes. All HP tapes were preformatted at the factory. The Initialize Media command can be invoked without certification (C = 1). Presumably this would write the spare blocks table, error logs, etc. but not write the data frames or populate the spare blocks table with bad blocks. So maybe: - An unformatted (erased) tape access returns Uninitialized Media because there are no keys to read. - A formatted but uninitialized tape access returns Uninitialized Media because the blocks containing tape logs or spares tables are blank (erased). - An initialized but uncertified tape returns No Data Found if a read is attempted because the data areas are blank (erased) until they are written for the first time. - A certified tape returns whatever data was written during certification without error. And for simulation: - A tape image containing only data record or tape mark blocks is certified. - A tape image containing one or more "erased mark" blocks with all other blocks, if any, containing data records or tape marks is initialized but not certified. - A newly created (zero-length) tape image or an image in which one or more blocks contains no recognizable tape format is uninitialized. - A new tape image file may be initialized or certified by including an optional command-line switch. These operations consist of writing "erased" blocks or 1024-byte data records containing 0xFF bytes, respectively, throughout the file. - The Initialize Media command with C = 0 will certify an uncertified image; if the image is already certified, then nothing is done. With C = 1, an uncertified image remains uncertified, and nothing is done; a certified image is reset to uncertified if Z = 1 or unchanged if Z = 0, and nothing else is done. - The Execute Utility C8 (Pattern Error Rate Test) command with Type = 2 will certify an image regardless of prior status. With Type = 0 or 1, the command is rejected with a Parameter Error result (these utilities are not implemented). Stuff from "7908 Disc/Tape Drives Engineering Reference Package" 07908-90907, Tape Interface Board (TIB) section: - 3.7.1 Gap Detection [PDF page 333] Gaps (blank areas of the tape) are detected by monitoring whether the RDDATB line (buffered RDDATA) is toggling in a legitimate MFM-encoded fashion. The output of the gap detector is GAPX-L and is shown in Figure 3.7.2a for a blank (though preformatted) tape and in Figure 3.7.2b for a recorded tape. [Figure 3.7.2.a on PDF page 336 shows that a preformatted tape has keys but no data frames.] - 3.B Control Lines and Modes of Operation, Task Completion Code Register [PDF page 340] Bit D6 is shown as "Blank Data Frames (BLANK)". - Address 4, Task Completion Code Register [PDF page 345] D6 = BLANK bit is set to signify that the previous block was read but contained no user data. This tells the microprocessor that there is nothing to be gained by reading the TIB buffers as the data they contain are left over from the previous transfer. - 3.8.3 Modes of Operation, 3.8.3.3 Mode 2, Read from Tape to TIB [PDF page 352] After data has been read and buffered in RAM, BLocK ENable will be checked to see if the processor has decided that this block is of value. If the enable is not set, then the data will be ignored and a search will begin for the next key mark; this constitutes provision for a fine seek under close supervision by the microprocessor. On the other hand, if the block was enabled, but contained no previously written data, it will be assumed that a mistake has been made with regard to the host's directory, a BLANK completion code will be issued and the drive brought to a stop. With a previously written tape, if the block was enabled, the TIB would signal a SUCCeSsful completion and issue on Interrupt FLaG, then await further instructions, presumably a DMA transfer. - 3.8.3.5 Mode 4, Verify N Blocks [PDF page 354] If the block was enabled, the TIB will continue to read and verify the data stored on the tape using CRCs until an error is found, whether it be in a data frame or an ECC frame or even in the key mark. Any of these errors will cause the tape to be stopped and an Interrupt FLaG to be raised along with a completion code indicating a Verify ERRor. Blank data blocks are ignored so that key marks may be verified on new tapes. (through page 357) 6D B6 DB = 01101101 10110110 11011011 (from cart, 011 repeats) B6 6D DB = 11010110 01101101 11011011 (from manual) From the "CS/80 External Exerciser Reference Manual" (5955-3462 July 1988): - Tape Error Rate Tests (page 1-3) Certification (CERT) is a special type of WTR ERT which tests every block address on the entire tape and automatically spares all those block addresses which have errors. - Tape Initialization (page 1-6) On the tape unit, initializing the media establishes the sparing technique. All sparing techniques involve altering the addressing scheme to eliminate defective blocks. The INIT MEDIA command sets aside one out of every 512 blocks on the tape as an available spare. A certification test is also permitted while initializing a tape. CERT locates all defective blocks and skip-spares them if possible. - CERT (Tape Exerciser Commands, page 3-2) This command performs a write-then-read operation on the entire tape, and skip-spares any block which has two or more frame errors or has an unreadable key. - INIT MEDIA (page 3-6) This utility performs an initialization routine on the tape cartridge currently installed in the tape drive. Two options are allowed for tape initialization. The first option defaults the sparing table so that one spare block is set aside out of every 512 blocks. The second option accesses the run log to determine whether or not the tape is certified; if the tape is not certified, then the CERT error rate test utility is performed automatically. If the tape was previously certified, then all jump spares are converted to skip spares (for optimizing throughput). INPUT FORMAT: Input the TEST name? [INIT MEDIA] This utility will destroy data, Do you wish to continue? [YES] or [NO] INIT MEDIA options are: 1 = Reset spare table and label tape uncertified. 2 = Read certification status. If tape is uncertified then certification utility is run. If tape is certified then convert jump spares to skip spares. Tape is not recertified. Input option number? The LINUS External Reference Specification (October 1984) has a table on page 11 that describes the results of the Initialize Media command for each of the eight possible CWZ option combinations and for the three initial tape conditions. They are summarized here: Formatted tape: C W Z Spare Table Data Area Result Status --- --- --- ----------- --------- ------------- 0 0 0 Populated Certified Certified 0 0 1 Populated Certified Certified 0 1 0 Cleared Unaltered Initialized 0 1 1 Cleared Unaltered Initialized 1 0 0 Preset Unaltered Initialized 1 0 1 Preset Unaltered Initialized 1 1 0 Cleared Unaltered Initialized 1 1 1 Cleared Unaltered Initialized Initialized tape: C W Z Spare Table Data Area Result Status --- --- --- ----------- --------- ------------- 0 0 0 Populated Certified Certified 0 0 1 Populated Certified Certified 0 1 0 Cleared Unaltered Initialized 0 1 1 Cleared Unaltered Initialized 1 0 0 Optimized Unaltered Initialized 1 0 1 Preset Unaltered Initialized 1 1 0 Cleared Unaltered Initialized 1 1 1 Cleared Unaltered Initialized Certified tape: C W Z Spare Table Data Area Result Status --- --- --- ----------- --------- ------------- 0 0 0 Optimized Unaltered Certified 0 0 1 Populated Certified Recertified 0 1 0 Cleared Unaltered Decertified 0 1 1 Cleared Unaltered Decertified 1 0 0 Optimized Unaltered Certified 1 0 1 Preset Unaltered Decertified 1 1 0 Cleared Unaltered Decertified 1 1 1 Cleared Unaltered Decertified Action Meaning ----------- ----------------------------------- Cleared Set up with an empty allocation Preset Set up with a specified allocation Populated Set up and then modified by testing Optimized Changed from jump to skip sparing The "HP 9145A CE Service Handbook" (09145-90039 July 1988) suggests on page 9-11 (PDF page 75) that C and Z set the options, and W is ignored. INIT MEDIA does Initialize Media, options = 101 for spare table init (inhibit cert, alloc spares, reset spares) --> no-data markers INIT MEDIA does Initialize Media, options = 000 for no spare table init (do cert, alloc spares, convert spares) --> FF data records CERT does Initiate Utility, Pattern ERT (C8 01 02 02 00) (loop, type CERT, area entire, internal pattern) --> FF data records Rik Bos tests: - new HP cert: - loc and read -> FF bytes, qstat 0 - init media 07H -> qstat 0 after 7 seconds - loc and write -> qstat 1 - req stat -> Uninitialized Media, DERRORS = D1 (Not Certified) State Machine ------------- Most commands do not have an execution state. The only exceptions are: - Locate and Read - Cold Load Read - Locate and Write - Request Status - Describe - Initiate Utility - Read Loopback - Write Loopback Receive secondary: idle -> opcode_wait Receive opcode: parameters: opcode_wait -> parameter_wait no parameters, no EOI (complementary): opcode_wait -> execution -> opcode_wait no parameters, EOI: opcode_wait -> execution -> reporting opcode_wait -> read_execution -> reporting opcode_wait -> write_execution -> reporting Receive parameter: count > 0: parameter_wait -> parameter_wait count = 0, no EOI: parameter_wait -> execution -> opcode_wait count = 0, EOI: parameter_wait -> execution -> reporting parameter_wait -> read_execution -> reporting parameter_wait -> write_execution -> reporting Error Recovery -------------- There are two basic types of CS/80 errors: "operational errors," such as an uncorrectable data error, an undefined command opcode, or requesting an address out of range, and "sequence errors," such as sending an undefined secondary or sending messages in the wrong order. Operational errors arise from the content of valid messages, whereas sequence errors arise from the framework of messages. The CS/80 documentation is somewhat vague on exactly how errors are handled, but the basic idea appears to be to terminate the current command immediately and then move to the reporting wait state. Bytes are then sourced or sunk as needed to allow the host to resynchronize at the reporting phase. Also, once an operational error has occured, a subsequent sequence error caused by the unexpected shift to the reporting phase is suppressed. It is also not clear what happens when a new command is issued after a QSTAT = 1 report. There are documentation references that a host "must" issue a Request Status command to pick up the error and clear the QSTAT value. However, the Request Status command description says that the report is "the cumulative status of all transactions which have occurred since the status report was last cleared," implying that commands may be executed while QSTAT = 1. Also, the SS/80 manual says that there is no holdoff on QSTAT = 1, implying that a new command will be accepted. However, this would be dangerous if, for example, a failed Locate and Write (which stops at the failure point) is followed by a new Locate and Write that expects to continue from the last (good) position. Testing the behavior of real hardware may be needed here. For operational errors occurring in the command phase (e.g., Address_Bounds, Illegal_Opcode, or Illegal_Parameter) the SS/80 manual says that interpretation stops at the first error and susbequent (complementary and/or real-time) commands are ignored. We call the "set_error" routine to set the specified error status bit, set QSTAT = 1, and set the controller state to Reporting_Wait. Command errors then set the controller opcode to Invalid. If the error occurred with the EOI byte, poll response is enabled to notify the host of the reporting phase. Otherwise, bytes received in the Reporting_Wait state are sunk by "process_data" until EOI, whereupon the poll response is enabled. Note that an illegal opcode in a transparent message generates a Message Sequence error, not an Illegal Opcode error (per the CS/80 manual). If an error causes a command with an execution phase (e.g., Locate and Read) to be ignored, an unexpected execution message will be received. This will be in the wrong state (Reporting_Wait instead of Execution_Wait), which would normally cause a Message Sequence error. However, the error is suppressed because of the earlier operational error; this is detected by controller opcode = Invalid. In this case, "process_secondary" changes the state either to Error_Source or Error_Sink and set up the transfer as appropriate. These states transition to Reporting_Wait and enable the poll response once EOI is sent or received to complete the execution phase. The host will then pick up the QSTAT = 1 in the reporting phase. For operational errors occurring in the execution phase (e.g., Message Length, Not Ready, or End of Volume), we call "set_error" and then optionally reset the state to Error_Source or Error_Sink as dictated by the specific error condition. If a partial write buffer is present, and the error does not preclude writing, it is written before exiting the execution phase. The only operational error in the reporting phase is not accepting the QSTAT byte, i.e., the bus source routine returns a "no listeners" indication. This causes a Message Length error. The documentation does not say if this leaves the controller in the reporting state or returns it to the command ready state. Presumably it does not matter, as untalking the controller in the middle of the reporting message must only preface a Cancel or a Clear command. During any phase, the host may asynchronously Untalk or Unlisten the device in the middle of a transaction, i.e., while the controller is busy. The documentation does not mention this case, although DVM33 does exactly this to obtain the initialization state of CTD media. After accepting the first four bytes of data in the execution phase of a 9144 Read Error Log command, the driver Untalks the device and then issues a Cancel to clean up the transaction. Whether this behavior results in a Message Length error or a Message Sequence error is not specified, and it doesn't matter as the Cancel clears the status indication. We report a Message Length error for an unexpected Untalk or Unlisten. Note that the SS/80 manual says that Cancel and the various Clears are accepted only in the command ready state, while the CS/80 manual says that, "The recommended way to terminate a transaction is to terminate the message link, then send the Cancel command." That implies that Cancel is accepted in any phase. Recovery from Read Errors ------------------------- The "read_block" routine reads from one or more blocks into the device buffer. It returns FALSE if an error prevented any data from being read or TRUE if the buffer contains data, even if an error occurred during the read. The errors that can occur are: - End_of_File - End_of_Volume - Unrecoverable_Data - Unrecoverable_Data_Overflow End_of_File occurs if a tape mark is read from a cartridge tape drive. End_of_Volume occurs if a read would extend beyond the last block of the volume. All disc I/O errors and all magnetic tape errors except MTSE_TMK are mapped to Unrecoverable_Data. Error recovery is complicated by the fact that the routine may be asked to read more than one disc block at a time. When executing a Copy Data command from disc to tape, four disc blocks will be requested at a time, and any of the errors can occur on the first block, on an intermediate block, or on the last block. Another complication is that the controller may or may not be in the middle of an execution message when the error occurs. Reads are performed wholly within the Command_Wait state for Locate_and_Verify and Copy_Data, as well as for the initial read for Locate_and_Read and Cold_Load_Read. For the latter two, additional reads occur in the Execution_Send state. Still another complication is that the error response is different depending on whether or not a Locate_and_Verify command is executing. Unrecoverable_Data errors normally are logged but do not stop the read process; these errors are fatal for Locate_and_Verify. End_of_File errors normally are fatal during a read; they are ignored for Locate_and_Verify. Recovery depends on the specific error and can take one of three forms: an abort that stops the sequence immediately, an error that is logged but allows reading to continue, and a read without errors that will fail immediately at the next entry. End_of_File causes aborts, Unrecoverable_Data logs the error and continues, and End_of_Volume logs the error but also ends the read transaction to prevent another entry. End_of_Volume is also required to wrap the address to block 0. Note that End_of_File only occurs when reading tape units, and read_block never reads more than one tape block per call. Therefore, if an End_of_File occurs, no data was read. On each entry, the file is attached and positioned correctly. A Not_Ready error cannot occur because the unit must be attached to pass the validation check made before the first read (or write) is issued, nor can it occur while a command is executing because detaching is prohibited if the unit is active. End_of_Volume can never be present on routine entry, because the address can never point beyond the end of the volume. If Set Address attempts it, the command is rejected with Address Bounds, and if a read increments it, it wraps to zero. End_of_Volume is always set after the reads complete within a call, so some data will always be read. For commands with execution phases (e.g., Locate_and_Read), aborts must terminate the phase by asserting EOI on the final byte sent. If a read sequence aborts before reading any data into the buffer, e.g., the first block addressed contains a tape mark, an error byte of value 1 must be sent with EOI when the execution phase begins. If the tape mark occurs on the first block of a subsequent read, the error byte tagged with EOI must also be sent. Within an execution phase, errors that do not abort must not change the controller state, so that the remaining data is sent. CS/80 requires that reads continue in the presence of data errors until the original request length is satisfied. End_of_Volume errors must shorten the remaining transfer length to the amount of data read into the buffer so that the last valid data byte will be tagged with EOI. When the host issues the reporting message, the error will be reported then. Here are some examples that assume that the addressed volume has 100 blocks: * A Locate and Read is issued for three blocks starting at block 97. The routine returns TRUE for all three reads, the state remains Execution_Send, and no error occurs. The address is reset to block 0. * A Locate and Read is issued for three blocks starting at block 98. The routine reads block 98 and returns TRUE. The routine reads block 99, an End_Of_Volume error is logged, the transfer length is shortened to the buffer count, the state remains Execution_Send, and the address is reset to block 0. The event service ends the transfer normally by tagging the last byte with EOI when the length is exhausted. The third block is never read. * A Locate and Read is issued for two blocks starting at block 99. The routine returns TRUE for the first read, an End_Of_Volume error is logged, the transfer length is shortened to the buffer count, the state remains Command_Wait, and the address is reset to block 0. When the execution message arrives, the first block of data is sent. The event service ends the transfer normally by tagging the last byte with EOI when the length is exhausted. The second block is never read. * A Locate and Read is issued for three blocks starting at block 0. The read of block 0 fails with a host I/O error. The routine returns TRUE, the address increments to block 1, an Uncorrectable_Data error is logged, and the state remains Command_Wait. After blocks 1 and 2 are read, the event service ends the transfer normally by tagging the last byte with EOI when the length is exhausted. * A Locate and Read is issued for three blocks starting at block 0. The read of block 0 succeeds but block 1 fails with a host I/O error. The routine returns TRUE, the address increments to block 2, an Uncorrectable_Data error is logged, and the state remains Execution_Send. After block 2 is read, the event service ends the transfer normally by tagging the last byte with EOI when the length is exhausted. * A Locate and Read is issued for three blocks starting at block 0. The read of block 0 succeeds but block 1 fails with a host I/O error. The routine returns TRUE, the address increments to block 2, an Uncorrectable_Data error is logged, and the state remains Execution_Send. The read of block 2 also fails with a host I/O error. The routine returns TRUE, the address increments to block 3, an Uncorrectable_Data_Overflow error is logged, and the state remains Execution_Send. The event service ends the transfer normally by tagging the last byte of block 2 with EOI when the length is exhausted. * A Locate and Read is issued for three blocks starting at block 0. The routine reads block 0, which contains a tape mark. The routine returns FALSE, an End_Of_File error is logged, the state changes to Error_Wait, and the address increments to 1. When the execution message arrives, the state changes to Error_Source. The event service ends the transfer by tagging an error byte with EOI. The remaining blocks are never read. * A Locate and Read is issued for three blocks starting at block 0. The routine reads block 0 and then block 1, but block 1 contains a tape mark. The routine returns FALSE, an End_Of_File error is logged, the state changes to Error_Wait, and the address increments to 2. The event service detects the FALSE return and changes to Error_Source to supply the error byte. The remaining blocks are never read. * A Locate and Verify is issued for three blocks starting at block 0. The read of block 0 succeeds but block 1 fails with a host I/O error. The routine returns FALSE despite data in the buffer, the address increments to 2, an Uncorrectable_Data error is logged, and the state remains Command_Wait. The command executor detects the FALSE return and aborts the command. The remaining blocks are never read. * A Locate and Verify is issued for three blocks starting at block 0. The routine reads block 0 and then block 1, but block 1 contains a tape mark. The tape mark is ignored, and the routine returns TRUE although the buffer is empty, the address increments to 2, no errors are logged, and the state remains Command_Wait. The remaining block is read, and the verify completes normally. * A Copy Data from disc to tape is issued for six blocks starting at block 98. The routine reads blocks 98 and 99 and returns TRUE. An End_Of_Volume error is logged, the transfer length is shortened to the buffer count, the state remains Command_Wait, and the address is reset to block 0. The command executor ends the copy normally when the length is exhausted. The remaining blocks are never read. * A Copy Data from tape to disc is issued for six blocks starting at block 0. The routine copies blocks 0 and 1 successfully and then reads block 2, but block 2 contains a tape mark. The routine returns FALSE, an End_Of_File error is logged, the state changes to Error_Wait, and the address is incremented to 3. The command executor detects the FALSE return and aborts the command. The remaining blocks are never read. * A Copy Data from tape to disc is issued for six blocks starting at block 0. The routine reads block 0, which contains a tape mark. The routine returns FALSE, an End_Of_File error is logged, the state changes to Error_Wait, and the address is incremented to 1. The command executor detects the FALSE return and aborts the command. The remaining blocks are never read. Error recovery during Copy Data depends on whether the routine returns TRUE or FALSE. For TRUE, the corresponding write routine is called, and the loop terminates because of Error_Wait. For FALSE, no data was read, so the write is skipped, and the loop terminates explicitly. The read recovery rules may be summarized as follows: - An End_of_File error is ignored and returns TRUE for Locate_and_Verify, For all other commands, it increments the address, changes to Error_Wait, and returns FALSE. - An End_of_Volume error resets the address to 0, retains the current state, shortens the transaction length to the buffer length, and returns TRUE. - An Unrecoverable_Data error sets the data_error flag, increments the address, retains the current state, and returns FALSE for Locate_and_Verify and TRUE otherwise. Current Operational Values -------------------------- Each CS/80 unit keeps two sets of operating values: a persistent set and a current set. Each command begins with identical sets, and the current set is always used during a command. A command sequence containing complementary commands change the current set for the duration of the command only; the current set values revert to the persistent set at the end of the command. However, if the sequence ends with a complementary command (i.e., contains only complementaries), then the persistent set values are changed to the current set. When the device is powered on, or after a Clear command, both sets are initialized to default values. While this is the logical picture, in practice a command transaction may end in one of several ways: with normal completion after the reporting phase, with error completion after the reporting phase, or via a channel abort (e.g., untalking while the device is sending data). Some commands complete immediately, while others require a subsequent execution phase to transfer data. The problem is that the host may not follow the transaction sequence, for example skipping a required reporting phase. This produces a Message Sequence error but is otherwise recoverable. So resetting the current set during the reporting phase is not reliable. Nor is resetting after an execution phase completes, because again the host can abort the phase with an Untalk or Unlisten. If the current set is to be reverted reliably at the end of each command, it is necessary to accommodate all of the possible ways that a command can end. A simpler and more reliable approach may be to revert the set at the start of every command, rather than at the end. Assuming that each unit starts out with its current and persistent sets equal, then every unit that had processed a command has an incorrect current set. However, that set will not be referenced until another command is issued to that unit. Resetting the current set at the start of such a command would have the same effect as resetting it at the end of the prior command. The start of a command is reliably indicated by reception of a device command secondary. On entry, the last unit number accessed for the device can be used to reset the current set. If that command includes a Set Unit complementary to change the target unit, then that unit's current set will now be changed. But the next command directed to that new unit will reset its current set. Each command, therefore, starts with a correct current set, and resets occur reliably because each command must start with a device command secondary. One exception to this is the Copy Data command. This command is directed to unit 15 but changes the current sets of two units. The next command entry will reset unit 15's current set but units 0 and 1 (e.g.) will retain the wrong current values. This can be handled in the Copy Data routine itself. A pathological command sequence containing a Set Unit after the current unit's values have been changed (e.g., Set Length, Set Unit, Set Length) could not be accommodated by resetting the last unit's current set at the next command entry. However, this cannot occur, because a Set Unit command appearing anywhere other than at the start of a sequence results in an Illegal Opcode error. So at most one unit's set must be reset per command. Message Sequence Errors ----------------------- Sequence errors occur when the required sequence of messages is not followed. CS/80 requires that the Command-Reporting or Command-Execution-Reporting messages appear in this order. Violation results in a Message Sequence error. It is not clear how the controller recovers from sequence errors. It would seem that the host that issued the "wrong" message would expect it to complete. For example, if the controller receives an unexpected execution message, it still must satisfy that message, because the host will be sending or expecting to receive some number of bytes. In particular, if a command is executing, and the "wrong" message arrives, the controller can't simply ignore the message and continue with the command. Imagine a device that has processed a Locate and Write command message and is now waiting for an execution message to receive the data. Instead, it receives a Request Status command message. This causes a Message Sequence error, but then what happens? Presumably, the host has forgotten what it was doing and is now going to send an execution message to read the status bytes, followed by a reporting message to obtain the QSTAT. Clearly, the device can't discard the Request Status command and then expect to receive bytes to continue the Locate and Write. Instead, it must absorb the full set of Command-Execution-Reporting messages before the device and host resync in the reporting phase. Note also that once a command message has been properly received, the command either executes to completion before requesting the reporting message, or it never gets started because it needs to transfer data in an execution message. Either way, the command is not in a partially completed state that somehow must be ameliorated (except...if a Set Burst has been done, then a partial read or write could occur, as bursts are in 256-byte chunks, and a partial tape block could have been transferred). Also, unless a channel abort occurs, all messages execute to completion. So, by definition, sequence errors occur between messages; aborting a message in the middle can be done only by Untalking or Unlistening the device and generally cause Message Length errors because the full complement of bytes were not transferred. This means that there are six possibilities: 1. Expecting a command message but receives an execution message. 2. Expecting a command message but receives a reporting message. 3. Expecting an execution message but receives a command message. 4. Expecting an execution message but receives a reporting message. 5. Expecting a reporting message but receives a command message. 6. Expecting a reporting message but receives an execution message. After setting a Message Sequence error (except for case 2), recoveries from these cases proceed as follows: 1. The state moves to Error_Source or Error_Sink to source one byte tagged with EOI or to sink as many bytes as the host sends. After EOI, the state moves to Reporting_Wait. 2. This case is permitted and represents a "bare" reporting message. The state moves to Execution_Send to send the QSTAT byte. 3. The state moves to Error_Wait, and all command bytes are sunk. After EOI, poll is enabled to request either the execution or reporting message, depending on the command requirement. 4. The state moves to Execution_Send to send the QSTAT byte. 5. Same as case 3. 6. Same as case 1. Cases 3 and 5 are different for transparent commands. These do not cause Message Sequence errors ("transparent" means transparent to the device command processor) and are executed normally. Cancel and the several clears are transparent commands and must be allowed to execute to terminate transactions. Extra Stuff ----------- // add a current usptr to device state? saves recalc on each service entry? // set at Set Unit command? // can't remove dsptr->unit and use dsptr->usptr->unit because Copy Data? // DO WE EVER SCHEDULE using the controller unit (unit 15, remapped to unit 0's uptr)? // --> YES, for execution phase for Request Status, Describe, etc. with unit = 15 // DO WE need to clear the unit states of unassigned units? // so that a scan of all units is valid? // memset (&devices [address].units [index], 0, sizeof (UNIT_STATE)); // implement tape character count option? How does it even work? // manual says reads partial record and skips to next record // so length = 1024 could be one block or 1024 blocks (of one byte each) // moreover, if multiple blocks are read, bytes are glued together in byte stream // with no indication where record boundaries were. // OK to execute commands with qstat = 1; each command adds to the error list until it's cleared // for Copy Data, DVM33 cache does not fault if disc is write protected!!! // but then, disc CANNOT be write protected in hardware.... // SOME QUESTIONS remain about which unit gets what errors.... // if we need to suppress sequence error, this compiles to movl, testl, je: //if (error == Message_Sequence && TO_32_BITS (usptr->status, 0) != 0) // masked = TRUE; /* 3000 does (module 43): IFC Amigo Clear Amigo Identify "bare" Reporting Locate and Read using prior Unit and Volume Locate and Write using Length 0 Set Unit 15, Release 1:0 Set Unit 0, Set Volume 0, Describe not seen ---- CDB'BLK'DISPL = %22, << SET BLOCK DISP COMMAND >> CDB'CANCEL = %11, << CANCEL COMMAND >> x CDB'CLEAR = %10, << [CI] CLEAR COMMAND >> CDB'DESCRIBE = %65, << DESCRIBE COMMAND >> CDB'INIT'DIAG = %63, << INIT DIAGNOSTIC COMMAND >> CDB'INIT'MEDIA = %67, << INITIALIZE MEDIA COMMAND >> CDB'INIT'UTIL = %60, << NO EXECUTING MSG >> CDB'NOP = %64, << NO OPERATION COMMAND >> CDB'PARITY'OP = 1, << PARITY OPCODE COMMAND >> CDB'READ = 0, << LOCATE & READ COMMAND >> CDB'READ'LOOPBK = %2, << READ LOOPBACK COMMAND >> CDB'RELEASE = %16, << RELEASE COMMAND >> CDB'RELEASE'DENY = %17, << RELEASE DENIED COMMAND >> x CDB'REQ'STATUS = %15, << REQUEST STATUS COMMAND >> CDB'SET'LENGTH = %30, << SET LENGTH COMMAND >> CDB'SET'MASK = %76, << SET STATUS MASK CMD >> CDB'SET'RELEASE = %73, << SET RELEASE COMMAND >> CDB'SET'RETADR = %110, << SET RET ADDR MODE CMD >> CDB'SET'RETRY = %72, << SET RETRY TIME COMMAND >> CDB'SET'RPS = %71, << SET RPS COMMAND >> CDB'SET'SNGL'VEC = %20, << SET SNGL VEC ADDR CMD >> CDB'SET'3'VEC = %21, << SET 3 VECTOR ADDR CMD >> CDB'SET'UNIT = %40, << SET UNIT# COMMAND >> x CDB'SET'VOL = %100, << SET VOL# COMMAND >> CDB'SPARE'BLK = %6, << SPARE BLOCK COMMAND >> CDB'VERIFY = %4, << VERIFY COMMAND >> CDB'WRITE = %2, << LOCATE & WRITE COMMAND >> CDB'WRT'LOOPBK = %3, << WRITE LOOPBACK COMMAND >> */ ---------------------------- Disc Simulation Partitioning ---------------------------- The goal is to support CS/80 drives on both the 1000 via the 12821A Disc Interface and the 3000 via the 31262A General I/O Channel with no change to the CS/80 protocol handler. An associate goal is to support MAC drives on the 1000 via the 13175D interface and the 3000 via the 30229B Cartridge Disc Interface. Both systems share the 13037 MAC Disc Controller. A further goal is to support the ICD drives on the 1000 via the 12821A Disc Interface, given that the ICD command set is a subset of the MAC command set. It may also be desirable to support the 12745A MAC-to-HP-IB interface to allow MAC drives to run via HP-IB on GIC-configured 3000 systems. Ideally, the simulations would be layered and modular: 1.1 12821 DI 1.2 31262 GIC [ connect via HP-IB ] 2.1 ICD controller 2.2 CS/80 controller 2.3 MAC controller [ connect via disc cabling ] 3.1 Disc library MAC addressing assigns each drive a logical unit number from 0-7, set by a front-panel rotary switch. ICD addressing assigns each drive an HP-IB address from 0-7, set by a front-panel rotary switch. Each MAC or ICD drive contains one addressable unit, which corresponds to one SIMH unit whose settable model number defines the drive characteristics. Attaching the unit is equivalent to installing a disc pack and making the drive ready. CS/80 addressing assigns each drive an HP-IB address from 0-7, set by DIP switches. Each drive may contain one or more addressable units -- typically, disc drives contain one unit, while disc/tape combination drives contain two units. Each addressable unit corresponds to one SIMH unit, so a given drive at a unique HP-IB address may contain two SIMH units. Each unit within the drive is separately attachable and shares the same model number but has independent characteristics. Attaching a disc is equivalent to installing a disc pack for drives with removable media (the 7907 and 7935) or applying power for drives with fixed media (all other CS/80 drives). MAC, ICD, and CS/80 controllers respond to different secondaries as well as different command opcodes. ----------------------------------- HP-IB Simulator to Drive Simulators ----------------------------------- t_bool di_bus_source (CARD_ID card, uint8 data); Source a data byte to the bus. Returns TRUE if the byte was accepted (i.e., there were one or more listeners) and FALSE if it was not. Called by the controller to send commands to devices, and called by the current talker to send data to the listener(s). ATN and EOI should be asserted as required on the bus before calling. RTE requires that all devices on a given card use the same driver, so ICD discs, CS/80 discs, and Amigo tape drives must all use separate cards. Therefore, the type of protocol may be set statically. MPE allows a mix of devices on a GIC, although some restrictions apply. So the protocol type may vary between devices on a common interface and must be determined at the device (SIMH unit) level. A CS/80 disc, Amigo tape, and PCL line printer might share a single GIC. t_bool **_bus_accept (uint32 unit, uint8 data); Accept a data byte from the bus. Returns TRUE if the byte was accepted and FALSE if it was not. Called by di_bus_source to handshake between source and acceptor. If ATN is asserted on the bus, the byte is a command; otherwise, it is data. If EOI is asserted for a data byte, it is the last byte of a transmission. An acceptor monitors the bus for primaries. As RTE requires all devices on a card to use the same protocol, the acceptor implements the secondary protocol for all devices (SIMH units). Addressing to talk or listen is also recognized by the acceptor if the address matches a device on the bus. void di_bus_control (CARD_ID card, uint32 unit, uint8 assert, uint8 deny); Set the control lines on the bus. Called by the system controller to assert or deny REN or IFC, by the current controller to assert or deny SRQ, NRFD, or ATN and EOI (to conduct or conclude a parallel poll), and by the current listener to assert or deny NRFD. All connected devices on the bus are notified of the changes. It is not necessary to call di_bus_control for changes to ATN and EOI that accompany a command or data byte, as these are picked up when the data byte is examined. void **_bus_respond (CARD_ID card, uint32 unit, uint8 new_cntl) Respond to changes in the control lines on the bus. Called by di_bus_control to inform each connected device of a change in control state. void di_poll_response (CARD_ID card, uint32 unit, FLIP_FLOP response); Set a device's poll response. Called by a device to enable or disable its response to a future parallel poll. -------------- From DI to GIC -------------- The HP2100 disc interface allows only one type of protocol (Amigo or CS/80) to reside on a given bus. So for instance the DC device processes only CS/80 commands. The HP3000 General Interface Channel allows multiple protocols on a single bus. To accommodate this, each type of bus acceptor and responder must be called for each bus broadcast. It is up to the acceptor to decide if the broadcast is meant for one of its devices. The acceptor maintains a bitmap of device addresses present on its bus and qualifies Talk and Listen addressing with the present map. The Amigo Identify command presents a bit of difficulty. A given protocol handler (e.g., CS/80) must differentiate between an Untalk and a Talk directed to another handler's device. Simply keeping track of "my talker" and "not my talker" is not sufficient, because an Other Talk Address followed by a secondary is NOT an Identify sequence. Also, the Untalk and MSA must be sequential; any intervening primary inhibits Identify recognition. One option is to set a flag when Untalk is received and clear it when any other primary or secondary is received. Then, an Identify is recognized only if the flag is set when MSA is received. NO: Therefore, the channel must record all Listen and Talk addressing but then qualify those with the presence map to decide if a secondary or data is to be recognized. The channel state, in part, is then: uint32 present; /* bus address bitmap of devices present on the bus */ uint32 listeners; /* bus address bitmap of the listeners */ uint32 talker; /* bus address bitmap of the talker */ For secondaries and data, channel.listeners or channel.talker is ANDed with channel.present to validate addressing. No special Untalk or Unlisten handling is necessary; they are just processed as Listen 31 or Talk 31, i.e., they set channel.listeners or channel.talker to just bit 31 (except that Listen would OR the bit value, while Unlisten would store the bit value). This allows Untalk to be differentiated from Other Talk Address for the Identify command. The bus responder wants to reschedule a unit waiting on a data transfer that had been held off by ATN or NRFD. The channel.talker bitmap would have to be converted to an index to determine the correct unit (via dsptr->units). Routines: (not correct...) t_bool cs80_bus_accept (uint8 data, BUS_SET signals); Each acceptor registers with the interface if any of its devices are connected to the bus. For example, if a CS/80 disc is at address 0 and an Amigo tape is at address 3, both "cs80_bus_accept" and "tape_bus_accept" would set their respective bits in the interface's "acceptors" word. A byte placed on the bus would then be sent to the two acceptors but not to "icd_bus_accept", whose bit is not set. The acceptor maintains a list of the listeners and talker on the bus, so it decodes (un)listen and (un)talk commands. t_bool cs80_bus_decode (uint8 address, uint8 data); Each bus decoder receives commands and data directed to a specific HP-IB address. The address indexes into an array of device controllers, one for each enabled device on the bus (a disabled device is disconnected). Each device controller maintains a list of SIMH units that comprise the HP-IB device, so that the controller in combination with unit addressing can determine on which SIMH unit to schedule for a given operation. When the acceptor receives a universal command, it calls the bus decoder once for each address (0-7). If the device is disabled, the decoder simply returns. Otherwise, it processes the command. When the acceptor receives a listen or talk command, it calls the bus decoder for the addressed device. For unlisten or untalk commands, it calls the bus decoder for each addressed device. If a device is addressed to talk when a talk command arrives for another device, the acceptor first generates an untalk for the prior device before calling the decoder for the talk. Secondary commands are passed from the acceptor to the decoder by calling the latter once for each talker or listener on the bus. Data received by the acceptor is passed to each listener. ----- t_bool di_bus_source (CARD_ID card, uint8 data, BUS_SET assertions); void di_bus_control (CARD_ID card, uint32 address, BUS_SET assert, BUS_SET deny); t_bool di_bus_ready (CARD_ID card); t_bool **_bus_accept (uint8 data, BUS_SET signals); void **_bus_respond (uint32 address, BUS_SET new_cntl); ----- The existing implementation requires that the device modules (DA, DC) access a few DI internals, specifically: - Testing if the card is ready for data before calling "di_bus_source": if (not (dix [dc].bus_cntl & (BUS_ATN | BUS_NRFD))) This is tested in the event service routine, so the bus state cannot be passed as a parameter. This could be abstracted to a "di_bus_ready" function: t_bool di_bus_ready (CARD_ID card); - Adding EOI to a data byte before calling "di_bus_source": di [dc].bus_cntl |= BUS_EOI; New bus signal assertions (either 0, though maybe BUS_NONE would be better, or BUS_EOI could be passed as a new parameter to "di_bus_source": t_bool di_bus_source (CARD_ID card, uint8 data, uint8 signals); - Setting or clearing the unit's bit in the card "acceptors" field as a result of an ATTACH or DETACH call: di [dc].acceptors |= (1 << unit); di [dc].acceptors &= ~(1 << unit); The "acceptors" field is maintained entirely by the device module; it is only used by the interface. "acceptors" is redefined to indicate which "**_bus_accept" routines to call, depending on the type of devices attached to the bus. For the DI, only one type is allowed, so the acceptor for that type is hard_coded. For a future GIC, multiple types are allowed, and the acceptors indicated by the corresponding bits in the "acceptors" field would determine which acceptors are called. Would want to abstract this to a "gic_set_acceptor" call, rather than manipulating a GIC state field directly. - Accepting a bus command or data: if (dix [dc].bus_cntl & BUS_ATN) Bus signal state should be passed as a parameter to "*_bus_accept": t_bool **_bus_accept (uint32 address, uint8 data, uint8 signals); - Setting or clearing the unit's bit in the card's "listeners" or "talkers" field as a result of decoding a listen command: di [dc].listeners |= (1 << unit); di [dc].talker &= ~(1 << unit); ...or an unlisten command: di [dc].listeners = 0; ...or a talk command: di [dc].talker = (1 << unit); di [dc].listeners &= ~(1 << unit); ...or an untalk or other talk command: di [dc].talker &= ~(1 << unit); The "talker" and "listeners" fields are maintained entirely by the device module; they are only cleared by the interface during a reset. "listeners" is also used by the interface to limit the data transfer bus acceptor calls to those acceptors currently listening. These should instead be part of the device acceptor's state and manipulated there. - Accepting a listen secondary or a talk secondary: if (di [dc].listeners & (1 << unit)) if (di [dc].talker & (1 << unit)) The bus decode routine should check the associated device to see if its local state indicates talker or listener addressing. - Checking for no talkers or listeners to process an Identify command: if (di [dc].talker == 0 && di [dc].listeners == 0) - Checking for the last byte of a transfer: if (di [dc].bus_cntl & BUS_EOI) The bus decode routine should be passed the ATN and EOI signal state. - Performing an Interface Clear: di [dc].listeners = 0; di [dc].talker = 0; Interface clear should be handled by the bus acceptors. -------------- Revise di.c to eliminate "di_poll_response" and incorporate it into a "di_bus_control" call. Currently, we call "di_poll_response" each time a device's PPR is enabled or disabled. When a parallel poll is not being conducted, these calls are irrelevant and represent overhead. An alternate solution would be: - The channel adds two fields to its state: "poll_response" and "poll_active". - The "xx_bus_respond" function result is redefined from void to BUS_DATA. - The "xx_bus_respond" routine sets "poll_active" true if ATN | EOI and false otherwise. The routine returns the "poll_response" value (it will be ignored if a poll is not in progress). - A device sets its address bit in "poll_response" to enable PPR and clears its bit to disable PPR. If PPR is being enabled and "poll_active" is true, the "di_bus_control" routine is called with no signals asserted or denied. This causes another parallel poll (because "poll_active" implies ATN | EOI). - For parallel polls, "di_bus_control" calls "xx_bus_respond" and passes the response to "di_bus_poll". - "di_poll_response" is removed. -------------- Amigo Identify -------------- The Identify sequence is not uniformly defined. The CS/80 manual and the September 1983 SS/80 manual say that it is: ATN UNT Untalk ATN MSA My secondary address DAB ID data byte #1 EOI DAB ID data byte #2 ATN OTA Talk 30 ...while the November 1985 SS/80 manual says it is: ATN UNL Unlisten ATN OLA Listen 30 ATN UNT Untalk ATN MSA My secondary address DAB ID data byte #1 EOI DAB ID data byte #2 ATN OTA Talk 30 ...and the 7970 HP-IB interface manual says: ATN UNL/IFC Unlisten or Interface Clear ATN UNT Untalk ATN MSA My secondary address DAB ID data byte #1 EOI DAB ID data byte #2 ...and finally the Foxtrot manual says: ATN UNL/IFC Unlisten or Interface Clear ATN OLA Listen 30 ATN UNT Untalk ATN MSA My secondary address DAB ID data byte #1 EOI DAB ID data byte #2 ATN OTA Talk 30 The Foxtrot manual says, "The ABI chip on tape drive's interface board handles the Amigo Identify function entirely transparent to the drive's controller," so presumably it requires the above sequence. On the other hand, the SS/80 manual says, "SS/80 devices have two addresses, a major address defined by the HP-IB switch and a minor address which is always 31. [...] Thus when the host sends Untalk, all the IEEE 488 devices stop talking and all of the HP discs get ready to talk. They first must receive the secondary that has their major address in it." The service manual for the 9134 disc drive, which uses the Intel 8291A GPIB chip, says, "Note the non-standard sequence using untalk followed by a secondary." This is the key. It is the IEEE-488 TE (extended talker) function, which requires that the secondary address immediately follow the primary talk address. Any other primary command (universal, listen, or talk) prevents the device from being addressed and therefore responding to the Identify. This sequential requirement prevents problems with listen secondaries that also could be interpreted as MSAs, e.g., 0 (Amigo Write Execution), 1 (Amigo tape command), 5 (CS/80 command secondary), etc. Normally, if bus protocol were followed, this would not matter, because some other device or controller would be addressed to talk. But the RTE drivers for ICD and CS/80 discs do not address the controller to talk. So a bus device will see a sequence such as: ATN UNL Unlisten ATN UNT Untalk ATN OLA Listen 0 ATN OSA Secondary 05 EOI DAB Set Unit 0 ATN UNL Unlisten ATN UNT Untalk If the Listen were absent, the Untalk + Secondary sequence would be recognized as an Identify. But the intervening Listen interrupts the TE sequence requirement, preventing the problem. It is interesting to note that the HP 3000 follows the later SS/80 description: ATN OLA Listen 30 ATN UNT Untalk ATN MSA My secondary address DAB ID data byte #1 EOI DAB ID data byte #2 ATN OTA Talk 30 ...although the Listen 30 and Talk 30 are not relevant to TE addressing. The SS/80 manual says, "The peripheral will continue to send these two bytes as long as the host asks for them," so the Talk 30 is used to allow the device controller to exit the Identify function. The ICD "XIDEN" subroutine also follows that sequence, except it omits the Talk 30 at the end. The subroutine and the ICD driver does: CLC sc,C >>DA iobus: Received data 000000 with signals CLC | CLF | SIR >>DA csrw: Master reset >>DA buf: FIFO cleared >>DA iobus: Returned data 000000 with signals (none) LDA =B4003 OTA sc >>DA iobus: Received data 004003 with signals IOO >>DA csrw: Control is LBO | Talk | CIC >>DA iobus: Returned data 000000 with signals (none) STC sc >>DA iobus: Received data 000000 with signals STC | SIR >>DA iobus: Returned data 000000 with signals SRQ LDA =B476 OTA sc,C >>DA iobus: Received data 000476 with signals IOO | CLF | SIR >>DA inco: 3EH | ATN | | | | Listen address >>DA xfer: No bus acceptors >>DA iobus: Returned data 000000 with signals SRQ LDA =B737 OTA sc,C >>DA iobus: Received data 000737 with signals IOO | CLF | SIR >>DA inco: DFH | ATN | | | | Talk address >>DA xfer: Untalk >>DA iobus: Returned data 000000 with signals SRQ LDA =B100740+address OTA sc,C >>DA iobus: Received data 100741 with signals IOO | CLF | SIR >>DA csrw: Data last byte out | ATN | 341 sent to bus >>DA inco: E1H | ATN | | | | Secondary command >>DA inco: Unit 1 Amigo identify command initiated >>DA iobus: Returned data 000000 with signals SRQ SFS sc JMP *-1 >>DA iobus: Received data 000000 with signals SFS >>DA iobus: Returned data 000000 with signals SKF CLC sc,C >>DA iobus: Received data 000000 with signals CLC | CLF | SIR >>DA csrw: Master reset >>DA buf: FIFO cleared >>DA iobus: Returned data 000000 with signals (none) LDA =B10413 OTA sc >>DA iobus: Received data 010413 with signals IOO >>DA csrw: Control is IRL | ATN | Packed | Talk | CIC >>DA iobus: Returned data 000000 with signals (none) CLC sc,C >>DA iobus: Received data 000000 with signals CLC | CLF | SIR >>DA csrw: Master reset >>DA buf: FIFO cleared >>DA iobus: Returned data 000000 with signals (none) LDA =B10015 OTA sc >>DA iobus: Received data 010015 with signals IOO >>DA csrw: Control is IRL | Packed | Listen | CIC >>DA xfer: HP-IB card 0 changed ATN bus is now (none) >>DA inco: 00H | | | | | Data >>DA iobus: Returned data 000000 with signals (none) STC sc >>DA iobus: Received data 000000 with signals STC | SIR >>DA iobus: Returned data 000000 with signals (none) >>DA inco: 03H | | EOI | | | Data >>DA buf: Data 000003 tag EOI unloaded from FIFO [0] count 0 >>DA xfer: Device 1 parallel poll response enabled >>DA inco: Unit 1 Amigo identify command completed SFS sc JMP *-1 >>DA iobus: Received data 000000 with signals SFS >>DA iobus: Returned data 000000 with signals SKF LIA sc >>DA iobus: Received data 000000 with signals IOI >>DA csrw: Data 000003 received from bus >>DA iobus: Returned data 000003 with signals (none) CLF sc >>DA iobus: Received data 000000 with signals CLF | SIR >>DA iobus: Returned data 000000 with signals (none) ----------------------------------- Dual-Channel Port Controller (DCPC) ----------------------------------- In hardware, DCPC will assert I/O signals for the following periods: ------------ Input ------------ ----------- Output ------------ Sig Normal Cycle Last Cycle Normal Cycle Last Cycle === ============== ============== ============== ============== IOI T2-T3 T2-T3 IOO T3-T4 T3-T4 STC * T3 T3 T3 CLC * T3-T4 T3-T4 CLF T3 T3 T3 EDT T4 T4 * if enabled by control word 1 This allows on-card hardware detection of coincident signals. In particular, IOO + CLF, STC + CLF, CLC + CLF, STC + CLC, and IOO + EDT. Under version 3.8-1 simulation, the "dma_cycle" routine will call the device's I/O signal handler multiple times with these signals in the indicated order: Normal input cycle: ioIOI ioCLF ( + ioSTC if enabled) Last input cycle: ioIOI (ioCLC if enabled) ioEDT Normal output cycle: ioIOO ioCLF ( + ioSTC if enabled) Last output cycle: ioIOO (ioCLF if ioSTC and ioCLC are disabled) (ioCLF + ioSTC if enabled) (ioCLF + ioCLC if enabled) ioEDT A problem arises, however, in that certain I/O cards look for coincident signals during DCPC transfers to indicate specific conditions: Card Condition Action ---------- --------- ----------------------------------------------- 12566B LPS STC + CLC Inhibit setting of device command 12821A DI CLC + CLF Master reset 12821A DI IOO + CLF Inhibit setting of end-of-data-transfer flag 12821A DI IOO + EDT Set last-byte-out flag 12875A IPL IOO + EDT Delay DMA completion interrupt for TSB For example, the 12566B Microcircuit Interface sends a device command pulse in response to asserting STC and CLC sequentially but no pulse when the signals are asserted concurrently. With the current DCPC implementation and I/O model, these actions will fail. In addition to the above, the following devices respond to EDT: Card Action ---------- ----------------------------------------------- 13175D DS Set end-of-data flag 12792C MPX (debug message only) 13183B MS Clear data channel flag buffer and flag To allow simulations of cards that act upon concurrent signals, the I/O signal simulation must be revised to support them, and the DCPC simulation must be revised to dispatch them. ---------------------------------------------- Remodeling the Remodeled HP 1000 I/O Structure ---------------------------------------------- For 3.8-1, the HP I/O structure was changed from an instruction-based model to a signal-based model. I/O backplane signals were dispatched to handlers in each peripheral that took actions appropriate for the simulated device. This allowed non-standard behavior (such as the 12936A PIF) to be accommodated cleanly. However, when implementing the 12821A Disc Interface, problems occurred with this new model. The issue was that the DI had actions that were based not only on individual signals but also on several concurrent signals. The existing model allowed only for the CLF signal to be concurrent with IOI, IOO, STC, or CLC. The DI acted upon concurrence of CLC and CLF, IOO and CLF, and IOO and EDT. During a DMA transfer, all but the first pair were dispatched to the device sequentually, resulting in failure of the simulation. Initial consideration was given to remodeling the DMA cycle to output IOO and CLF, but then the issue arose as to whether to send CLF several times during a cycle or only once. In hardware, CLF is asserted at T3, concurrently with IOO, STC, and CLC. Any of these three handlers may need to detect that concurrence, and problems may arise if CLF takes an action that results in errors if it is repeated during a single cycle. Resolving this in any way other than sending CLF three times and then depending on the individual handlers to detect this and ignore multiple assertions would make the DMA cycle logic dependent on particular peripheral simulations. This is undesirable, as this is the reason that the I/O simulation was remodeled originally. Therefore, a revised model is implemented that redefines the signal parameter from a single value to a set. Each I/O cycle will make exactly one call on the peripheral's signal handler, and several signals may be asserted concurrently. In particular, a DMA cycle may assert up to five signals (e.g., IOO, CLF, STC, CLC, and EDT), although one or two will be the usual case for programmed I/O. In this way, each signal's code will be called exactly once for each cycle, and signals are still processed sequentially within the handler in a defined order, but each signal's code will be able to determine whether other signals are asserted concurrently. This does necessitate a change to all signal handlers again, but the result should be a universal signal model that will accommodate all future peripheral simulations without further change. --> IOPRESET and IOPOWERON take handler and dibptr names. But handler is dibptr->io_handler. Just pass dibptr. --> change "data" to "stat_data" --> use IODATA macro instead of "data & DMASK" --> Add ioPON to complete set, rather than rely on SWMASK('P')? Now is the time! Would still need that test, except that it should dispatch ioPON, ioPOPIO, and ioCRS. IOPOWERON macro. CPU (1000) PON: issue POPIO and CRS > Each handler is required to use IOADDSIR at the top to ensure that ioSIR is added to the passed set of signals if a signal affecting interrupts is included. This effectively does: if (signals & IOIRQSET) signals = signals | ioSIR; ...incurring a small overhead for each call. However, the signals that require ioSIR are known at call time, so this check shouldn't be needed. Option 1 would be to define the signals so that SIR is included where needed. For instance: ioIOO = 0000020, ioSTC = 0000040 | ioSIR, ... The problem is that the signal handler "switch" statement can't use the ioNNN values because it dispatches the signals one at a time (so STC and SIR are dispatched separately). This would necessitate defining two sets of signals: swIOO = 0000020, swSTC = 0000040, ... swSIR = 0100000 and: ioIOO = swIOO, ioSTC = swSTC | swSIR, ... Now the callers would use the ioNNN values, and the "switch" statements would use the swNNN values. The downsides are that every "switch" statement would have to be changed, and it would be all too easy to pass the wrong kind of value (e.g., calling with swSTC instead of ioSTC), as there's no type checking to prevent this (all enums are ints in C). Option 2 would be to retain the current definitions and move the IOADDSIR logic to "devdisp" in the CPU simulator. The overhead would still be present, but the device signal handlers would be simplified. The downside is that direct calls to the signal handlers would not work, e.g., calling one's own handler would be an error; the call would have to be to "devdisp" instead. -------------------------------------- I/O Signal Handlers for Multiple Cards -------------------------------------- SIMH peripheral simulators are usually coded to handle a single instance of an I/O card or card set. There is no inherent provision for multiple copies of a given card, although cards are generally configurable for the maximum number of similar connected devices allowed by the hardware. For example, a disc controller card simulation may allow connection of up to eight disc drives, but a second controller card simulation with its complement of drives is not supported, except by duplicating the simulation code and assigning different device and function names. In general, then, there is a one-to-one correspondence between a card (i.e., select code) and a DEVICE structure. A good example is the common two-card disc interface. This simulation defines separate DEVICEs (e.g., DPD and DPC) for the command and data cards, as well as separate I/O signal handlers. This is proper, as the cards are different and respond differently to I/O signals. In a few cases, though, a peripheral may use two cards with identical or nearly identical behaviors. In this case, duplicating the I/O signal handler functions is unnecessary, more difficult to maintain, and may result in significant code bloat if the card operation is complex. Current examples of this are the DMA device and the IPL device. DMA consists of two channels that are identical except for priority. The IPL device consists of two identical 12566B Microcircuit Interface cards -- one used as an input device, and the other used as an output device. In each case, a common signal handler (or handlers, in the case of DMA that has primary and secondary select codes) is employed, and card state, such as control and flag flip-flop values, is kept in arrays indexed by a zero-based card number. For DMA, the channel number is derived from the last bit of the select code (6 => 0, 7 => 1). For IPL, the card number is derived from an explicit select code comparison (input select code => 0, output select code => 1). In addition, the IPL signal handler needs the device pointer associated with the card to access the debug flags and the unit pointer associated with the device. Similarly, a common unit service routine is used for both cards, which needs the card number and the debug flags (via the device pointer) for the indicated card. A simulation of the 12821A Disc Interface is planned that will support three configurations: an interface with Amigo discs, an interface with CS/80 discs, and and interface with Amigo tapes. Each configuration will consist of a device with multiple units. Because the card is complex, a single signal handler for all three interfaces is desirable. (Fortunately, the unit handlers will be specific to the devices, so there will be separate handlers for CS/80 disc, Amigo disc, and Amigo tape units.) To accommodate this, a slightly revised peripheral model is needed. The current one-to-one mapping of cards to DEVICEs to signal handlers is modified to provide a many-to-one map of cards to a common signal handler. The one-to-one map of cards to DEVICEs remains. To handle multiple cards, signal handlers must have additional information beyond the currently supplied select code, I/O signals, and output data parameters. Specifically, a handler needs: - the device select code to set the correct bit in the PRL, IRQ, and SRQ arrays - the signal set to process - the data to output for an IOO signal - the card number to access the state variables for the referenced card - the device pointer to access the device name, flags, debug flags, and units for debug printouts and sim_activate() calls for the referenced device With three or more devices, determining card number by testing select codes does not scale well. Card number is a property of the I/O card and is associated with a given select code. This suggests that the card number should be added to the DIB, which currently contains the select code and the signal handler function pointer. The DIBs for single-card handlers would contain a card number of 0, whereas the DIBs for multiple-card handlers would contain sequential card numbers starting at 0. State variables for the former would be scalars, whereas state variables for the latter would be arrays indexed by card number. Concerning implementation, option 1 would be to pass the card number and device pointer as additional parameters, but there will be a lot of overhead, as most peripheral simulations are for single cards where these extra parameters will not be used. Option 2 would be to keep the same three parameters and access the card number and device pointer via a global array that is indexed by select code. At first glance, dptr[] is a likely candidate, if the array elements are expanded to include the above information. This fails, however, when signal handlers are called from the device reset function as part of the PRESET action, as dptr[] is not set up unless the CPU is executing instructions. A workaround of initializing the target dptr[] element in the device reset routine is undesirable, as it ties each peripheral simulation to the CPU's dptr structure. Option 3 would be to pass the device pointer as an additional parameter, as the card number in the DIB may be accessed via the corresponding DEVICE's context pointer. This would allow the device reset routines to pass appropriate pointers during PRESET handling. However, some of the fixed devices, such as the front panel and the DMA secondary addresses, do not have DEVICE structures, so dummy DEVICEs would have to be added for each of these, just to provide the DIB pointers needed to access the select codes. Option 4 would be to replace the first select code parameter with an IOSTATE structure containing a DIB pointer and a DEVICE pointer. This structure would be trivial to pass if dtab[] is changed from an array of handler function pointers to an array of IOSTATE structures. However, multiple card simulations need the device pointer for debug printouts, and some of these are performed outside of the signal handler, necessitating the definition of a device pointer array indexed by card number. Given that only multiple card devices need a device pointer lookup, and that they will need to define their own lookup array anyway, including the pointer in the IOSTATE structure is redundant and introduces unnecessary overhead for the bulk of the devices. The selected option is to replace the select code parameter with the device's DIB pointer and to change dtab[] from an array of I/O signal handler pointers to an array of DIB pointers. This allows direct access to the card number, which may then be used to index into a simulator-defined array of device pointers for debug printouts, etc. This change does require that all devices have DIBs, though, so DIBs must be added for the CPU devices (CPU, DMA, null device, etc.). In addition, the standard card flip-flops for single-device simulations are changed from scalars to structure members for congruency with multiple-device simulations. For example, "sng_control" and "sng_flag" become "sng.control" and "sng.flag" for congruency with "mult [0].control" and "mult [0].flag". The "setstdPRL/IRQ/SRQ" I/O helper macros are changed to accommodate this as well. ------------------------------------------------- Problems with using "unit disable" as "power off" ------------------------------------------------- The initial thought was to use the existing SET OFFLINE mechanism, suitably renamed to SET POWEROFF, to indicate the power-off state. The intent of disabling units was to indicate that "the unit is not there," in Bob's words. That is precisely what occurs when a device cable is pulled, or the device is powered off. However, there are some operational issues that make this less desirable than upon initial inspection. The current unit-disable requirements are that files are detached and no I/O operations are in progress. When a unit is disabled, it is removed from the device display, and no configuration changes to the unit (SET ) are allowed. The file detach is important, because the device simulators do not check explicitly for disabled units. They depend, instead, on catching the missing file attachment. If a unit is allowed to be disabled without detaching, then additional I/O operations may be started (and completed) on supposedly "missing" units. The semantics of a real powered-off device are a bit different. Media remains in place (e.g., a mag tape reel remains loaded on the drive), and state modifications are allowed (e.g., a mag tape reel may have its write-enable ring removed or replaced). These imply that ATTACH and DETACH should still work, and that unit display is still required (to see the results of the modifications). A crucial function is the recovery of a powered-off device that has an I/O operation initiated. What should happen is that the operation should "hang" in the "waiting for device response" state until power is reapplied, whereupon the operation should complete. In simulation terms, the SET POWERON should reschedule the I/O event, with entry into the service routine allowing it to complete. Unfortunately, there is no existing mechanism to allow the device to trap this condition (as there is with a local modifier, e.g., ONLINE, or an ATTACH). That would imply that polling must be done while power is off. What might be better would be to separate the "is powered off" and "is not present at all" functions by leaving the unit-disable as it is and adding a new, local SET POWEROFF. By its nature, the powered-off condition only applies to devices containing at least one unit; there is no way to power off a device that consists of just the interface card, other than pulling the card from the card cage -- but this is defined as disabling the device. Existing simulations that depend on the file detach to trap the I/O operation would continue to work, and power-off handling could be added on a local, per-device basis. SET POWERON could be trapped via the modifier validation routine (as would SET ONLINE), and ATTACH would trap via the local attach routine. Also, being a local setting, we may impose any set of restrictions we want, without running afoul of those simulations dependent on a particular unit-disable behavior. NOTE: It may be possible to use SET DISABLED after all. The basic problem is that is also a synonym for , so SET PTR DISABLED could also mean SET PTR0 DISABLED. However, if we do not use unit-disable for the power-off condition, then it will only apply to multiple-unit devices. In that case, SET MSC DISABLED (e.g.) would disable the MSC/MSD device, and SET MSC0 DISABLED would disable unit 0. So maybe we want: 1. No card => SET DISABLED. 2. No peripheral => SET DISABLED. 3. No power or cable => SET POWEROFF. 4. Device offline => SET OFFLINE. 5. Device not ready => DETACH 6. Device busy => internal simulation state. 7. Device idle => internal simulation state. SET DISABLED and SET DISABLED would revert to their 3.2-3 operation. The local modifiers SET POWEROFF and SET OFFLINE would require that I/O is quiescent only. Powered-off and offline units would still display and would carry the appellations "powered off" and "offline". Conditions 2-4 would be tested and handled in the service routine, and traps for all three would cause a pending I/O operation to resume. -------------------------------- Disabled, off-line, and all that -------------------------------- A physical I/O device may be in one of several states. The state of a given device may be inferred by the CPU by the response of the device to various tests. In simulating devices, SIMH should seek to provide analogs to these states: 1. Device does not exist, i.e., no card in the I/O card cage. 2. Device exists but is powered off or interface cable is disconnected. 3. Device connected and powered on, but "online" switch is off. 4. Device is online but not ready, e.g., out of paper. 5. Device is ready but busy, e.g., print hammers are in motion. 6. Device is idle. SIMH currently simulates five of the six states above (although with some semantic restrictions). Each of the above states is characterized by certain device responses that may be tested by a program running under simulation, e.g., a device diagnostic or operating system. The physical states listed produce these testable responses: 1. With no interface card for the device in the I/O card cage, I/O instructions directed at that slot react as follows: the I/O backplane reads as -1 (all ones) on a 21MX, and 0 (all zeros) on earlier machines, and the LIA and MIA instructions behave accordingly; the OTA, CLC, CLF, STC, and STF instructions are effective NOPs; the SFC instruction always skips; and the SFS instruction never skips. 2. A disconnected or powered-off device presents an I/O card that "works," i.e., responds to the I/O instructions, but the response to any action that involves interaction with the missing device is specific to the device. For example, the particular status bits that are on or off are dependent on the electrical characteristics of the card and device. 3. An "offline" device typically sets a status bit flag. Additional offline interaction, e.g., whether the device accepts commands or ignores them, is device-specific. 4. A "not ready" device also typically sets a status bit flag. Some not-ready conditions are self-correcting, e.g., a device that is powering up. Others require operator intervention, e.g., a printer that is out of paper. 5. A "busy" device is reflected in the device status, and the device commands that are accepted in this state are device-specific. The busy state is exited automatically after the operation completes. 6. The idle state is often indicated by the absence of other-state indications (e.g., the absence of "offline," "not ready," and "busy" status flags). The set of states attributable to a given physical device may contain one or many or all of these states. For example, the time-base generator (CLK device) possesses only the first state: it's either present, and therefore powered on, or not; there's no cable, no offline switch, no media to be loaded, and no commands to execute. The paper tape punch possesses all states except the third; the punch is always online. The line printer possesses all of the states. Also, for some devices, one or more states may be indistinguishable. For instance, a magnetic tape may display no difference between having its cable disconnected and being set offline; in either case, all communication with the device (including device status) ceases. In contrast, a printer may remain able to communicate in the offline state, e.g., removing the paper while the printer is offline is noted with an "out of paper" status indication. SIMH version 3.2-3 provides three simulation environment commands to change I/O device state. These commands provide for disabling the device, disabling the unit, and detaching the associated device image file. Coupled with the "busy" and "idle" conditions, which are managed by the device simulator, rather than by the simulation environment, five distinct simulation states are possible. Devices are disabled with the SET DISABLED command. To disable a device, all units associated with the device must be detached and have no active operations. Disabling (or enabling) a device causes it to be reset. A disabled device is removed from the I/O card dispatch table, so all I/O instructions are routed to the null device ("nulio" routine). A device that may be disabled is marked with the DEV_DISABLE device flag, and a device that is currently disabled has the DEV_DIS flag set. Units are set disabled with the SET OFFLINE command (a misnomer). To be disabled, a unit must be detached and have no active operations. A disabled unit will not accept any state changes (e.g., from SET LOCKED to SET WRITEENABLED). A unit that may be disabled is marked with the UNIT_DISABLE unit flag, and a unit that is currently disabled has the UNIT_DIS flag set. Note: this command is misnamed, because it does not set a unit offline in the traditional sense, i.e., corresponding to physical state 3, but rather disables a unit, as in physical state 2. Attaching and detaching the device image file is done with ATTACH and DETACH . A disabled unit cannot be attached. Simulator action depends on checking and acting upon the "unit attached" flag. A unit that may be attached has the UNIT_ATTABLE unit flag, and a currently attached unit has the UNIT_ATT flag set. The six physical states map to the five simulation states as follows: 1. No card in card cage => SET DISABLED. 2. No power or cable => SET OFFLINE. 3. Device offline => (no mapping). 4. Device not ready => DETACH 5. Device busy => internal simulation state. 6. Device idle => internal simulation state. Disabling a device with SET DISABLED is equivalent to removing the I/O card from the card cage (state 1). The simulated device I/O handler is replaced by a "null handler" that responds as an empty slot. Disabling a unit with the SET OFFLINE command is equivalent to powering off or removing the interface cable from the unit (state 2). A disabled unit behaves identically to a detached one, unless the device simulator tests and acts upon the UNIT_DIS flag. DETACH maps to physical device not ready (state 4). Attaching a file to a simulated device is analogous to loading a physical device with media (paper, magnetic tape, disc cartridge, etc.). Each device simulation provides internal logic to handle physical states 5 and 6 (busy and idle). Transitions between these states are usually initiated by executing a device command with the STC instruction. To complete the simulation mapping, a new simulation command is needed that simulates pressing the "offline" button or equivalent on devices so equipped (state 3). To bring the command names into conformity with their actual actions, the current OFFLINE/ONLINE command (state 2) should be renamed to POWEROFF/POWERON (subject to change), and a new OFFLINE/ONLINE command (state 3) should be implemented. The POWEROFF command would still use UNIT_DISABLE and UNIT_DIS flags, and a new UNIT_OFFLINE flag would be defined for the new OFFLINE command. The new OFFLINE/ONLINE commands would be implemented as local modifiers to an affected device, so the presence or absence of the commands in the modifier structure would determine if they are valid for a given device, and no analog to UNIT_DISABLE would be needed. The revised mapping would appear as follows: 1. No card => SET DISABLED. 2. No power or cable => SET POWEROFF. 3. Device offline => SET OFFLINE. 4. Device not ready => DETACH 5. Device busy => internal simulation state. 6. Device idle => internal simulation state. Here are some examples of how this new simulator command mapping would relate to physical device states: 7900 disc: 1. SET DPC DISABLED => no 13210A interface in card cage. 2. SET DPC0 POWEROFF => unit 0 cable disconnected or POWER switch off. 3. SET DPC0 OFFLINE => unit 0 LOAD/UNLOAD switch in UNLOAD position. 4. DETACH DPC0 => unit 0 disc cartridge removed. 7970 tape: 1. SET MSC DISABLED => no 13183A interface in card cage. 2. SET MSC0 POWEROFF => unit 0 cable disconnected or POWER switch off. 3. SET MSC0 OFFLINE => unit 0 RESET button pressed, ONLINE light out. 4. DETACH MSC0 => unit 0 tape reel removed. 2607 printer: 1. SET LPT DISABLED => no 12845B interface in card cage. 2. SET LPT POWEROFF => cable disconnected or POWER switch off. 3. SET LPT OFFLINE => PRINT button up, light out. 4. DETACH LPT => printer paper removed. There is also a semantic problem with the current implementation of the renamed SET POWEROFF command. As it stands, disabling a unit is not allowed if the unit has an I/O operation in progress. This is reasonable, as disconnecting the interface cable on a device with an operation in progress surely would cause problems. However, there are two additional restrictions that do not exist with the corresponding physical implementations: an attached image file must be removed before disabling, and, once disabled, no state modifier commands are accepted. On a real device, turning the power off would allow the device media to remain loaded and positioned. For example, it is not required to remove the tape reel before powering the device down, nor does doing so change the tape position. A real printer's cable may be disconnected and then reconnected without requiring removal of its paper. Under SIMH, however, disabling and then re-enabling forces a detach and attach, so media position is reset. Additionally, a real device may still be manipulated manually when powered off. For example, media may be changed, e.g., from one paper type to another. A magnetic tape's write-enable ring may be removed or installed. SIMH doesn't allow ATTACH/DETACH or SET for disabled units. Were these restrictions accurately modelled, the restriction sets would be: * SET DISABLED -- DEV_DISABLE flag present, all units DETACHed and quiescent (i.e., no active event timers). * SET POWEROFF -- UNIT_DISABLE flag present, all units quiescent. * SET OFFLINE -- command modifier table entry present. Notably, this would allow powered-off units to remain ATTACHed and would allow in-process I/O requests to complete on a newly-OFFLINE device (device simulators would check the UNIT_OFFLINE flag before starting a new I/O operation). The latter is how a real printer behaves: setting the printer offline while a line is printing completes the print operation (hammer strikes, paper advance) before going offline. What happens when an OFFLINE, POWEROFF, or unattached unit receives a programmed STC to start an I/O operation? On a real device, CTL and CMD would set, "device command" to the device would assert, and then the interface would wait for "device flag" to assert. That wouldn't happen until the device was set online, power was restored, or media was restored. At that point, the device would notice "device command" and would respond with "device flag," setting FBF and then FLG, and generating an interrupt. Notably, the STC command would be accepted, i.e., CTL and CMD would set, regardless of the power or online status. In the simulation model, the "device command" to "device flag" wait is simulated with an I/O event timer. The simulation needs to "hang" after "device command" is asserted and resume after SET POWERON, SET ONLINE, or ATTACH command is executed. This may be accomplished either by not issuing the "sim_activate" in the "devio" routine, or by not setting the device flag in the "dev_svc" routine. When any of the foregoing commands are detected, and an operation is in progress (indicated by the CMD flip-flop being set), an immediate "sim_activate" would be scheduled to complete the I/O call. This would set the FBF and FLG, completing the pending operation. Assuming that the "sim_activate" call is made, the "dev_svc" routine should check for non-attachment, offline, or power-off conditions. If any of these exist, FBF and FLG should not be set. Instead, if STOP_IOE is set, then SCPE_UNATT, STOP_OFFLINE, or STOP_POWEROFF should be returned. If STOP_IOE is not set, then "dev_svc" should just return SCPE_OK. When the condition clears, CMD is checked, and if set, "sim_activate" should be called to reschedule the operation. This time, presuming all is OK, "dev_svc" will set FBF and FLG, clear CMD, and complete the operation. --> while ONLINE and ATTACH have user traps, POWERON doesn't! In summary, a more accurate physical device model would be produced by: 1. Renaming the existing SET OFFLINE/ONLINE command to SET POWEROFF/POWERON. 2. Altering the restrictions on the SET POWEROFF command to allow an existing image file attachment to remain. 3. Adding local SET OFFLINE/ONLINE commands and UNIT_OFFLINE flag definitions to affected devices. 4. Modifying the HP device simulators to respond appropriately to the UNIT_DIS, UNIT_OFFLINE, and UNIT_ATT flags. ------------------------------------- Loader, loader, who's got the loader? ------------------------------------- Two very different bootstrap loader mechanisms are employed by the 21xx-series (2114/15/16 and 2100) computers and the 1000-series computers. These divergent loader schemes are only partially supported by SIMH. The 21xx systems use a 64-word area of memory at the top of existing memory to hold the bootstrap loader. This area is normally inaccesable (writes are ignored, and reads return zero, just as if the memory didn't exist) but may be enabled by the LOADER ENABLE front-panel button. Bootstrapping requires manually setting the P register to the appropriate loader-dependent starting address, pressing the LOADER ENABLE button, and running the program. Loader protection is automatically restored when a HALT instruction is executed, when the user manually halts the machine, or when the user toggles the LOADER ENABLE button. Nearly a dozen 2100 loader programs are defined, although only one loader may be resident at a time. To aid in switching among loaders, a "loader loader" program (HP 24353-16001) is available to write a configured loader into the reserved memory area. Given the core-memory nature of 2100 systems, the loader would be retained indefinitely until overwritten. 1000 systems have a selection of up to four boot loader ROMs that may be copied into memory via a front-panel button. The selection of ROMs installed varies from machine to machine. Pressing the front-panel IBL button loads the selected ROM contents into the last 64 words of memory and sets the P register to the the first word of the 64-word area. SIMH provides device-specific BOOT commands that essentially provide the 1000 ROM mechanism for all CPU simulations. While convenient and practical, this presents two problems: those familiar with the 2100 boot loaders will not find the mechanism simulated, and some 2100 diagnostics try to be helpful by testing for the LOADER ENABLE button and refusing to continue until the button is turned off. Unfortunately, there is no way to do so in the current implementation. I propose adding a mechanism to specify: * for the 2100, the type of loader that is resident in core * for the 2100, a LOADER ENABLE/DISABLE setting * for the 1000, the types of loaders in each of the four ROM sockets For the 2100, loader types (using HP mnemonics) would be: * BBL ("Basic Binary Loader" -- paper tape) * BBDL ("Basic Binary Disc Loader" -- 2770 fixed disc) * BMDL7900 ("Basic Moving-head Disc Loader -- 7900 disc) * BMDL2883 ("Basic Moving-head Disc Loader -- 2883 disc) * BMDL7905 ("Basic Moving-head Disc Loader -- 7905 disc) * AMTL ("Absolute Magnetic Tape Loader" -- 7970 mag tape) * AIOPL ("Access IOP Loader" -- interprocessor link) For the 1000, loader ROMs (using HP part numbers) would be: * 12992A -- 7900/7901/2883 disc * 12992B -- 7905/7906/7920/7925 disc (RPL) * 12992C -- 2644/2645/2648 mini-cartridge tape * 12992D -- 7970 magnetic tape * 12992E -- 9885 flexible disc (RPL) * 12992F -- 7900/7901 disc (RPL) * 12992H -- 7906H/7920H/7925H/9895 disc (RPL) * 12992J -- 7908/7911/7912/7914/7933/7935 CS-80 disc (RPL) * 12992K -- paper tape * 12992L -- 7974 magnetic tape The 1000-M came with a K ROM soldered into position 0. The 1000-E/F came with a C ROM in socket 0 and a B ROM in socket 2. The existing BOOT command would not be affected by this mechanism. The mechanism could be exposed either as additional modifiers to the CPU device, or a new pseudo-device could be created. The boot loaders really are CPU functions, but implementing them would add quite a few new modifiers to the two dozen that currently exist. Here are the two possibilities (the first set would be valid for the 2100, and the second set for the 1000): As part of CPU As a pseudo-device ======================= =================== SET CPU LOADERENABLE SET LOADER ENABLE SET CPU LOADERDISABLE SET LOADER DISABLE SET CPU LOADER=BBL SET LOADER BBL SET CPU LOADER=BBDL SET LOADER BBDL SET CPU LOADER=BMDL7900 SET LOADER BMDL7900 SET CPU LOADER=BMDL2883 SET LOADER BMDL2883 SET CPU LOADER=BMDL7905 SET LOADER BMDL7905 SET CPU LOADER=AMTL SET LOADER AMTL SET CPU LOADER=AIOPL SET LOADER AIOPL As part of CPU As a pseudo-device ======================= =================== SET CPU LOADERROM0=x SET LOADER ROM0=x SET CPU LOADERROM1=x SET LOADER ROM1=x SET CPU LOADERROM2=x SET LOADER ROM2=x SET CPU LOADERROM3=x SET LOADER ROM3=x (where "x" is one of "B", "D", "F", or "K", with the potential for future expansion). For the 2100, SET CPU LOADERDISABLE would set the LWA of memory to (MEMSIZE - 1) & 077700. SET CPU LOADERENABLE would set LWA to (MEMSIZE - 1). Accesses via "ReadW" would return 0 and accesses via "WriteW" would do nothing if the address requested was greater than LWA. Doing a SET CPU LOADER=xxx would copy the specified loader program into the upper 64 locations of memory. Initially, the BBL loader would be selected and copied into memory. For the 1000, specifying the loader ROM in each socket replaces the hard-coded assignments now used by the "cpu_boot" function. LWA is always set to (MEMSIZE - 1). When accessing non-existent memory in HP 21xx/1000 systems, reads return 0, and writes are ignored. Currently, SIMH models this by zeroing all of simulated memory beyond the LWA and preventing writes beyond the LWA. Therefore, the time penalty for the check occurs on writes only. Note that attempting to add a simple "if (va > LWA) return 0" check to "ReadW" incurs an 18% increase in execution time for the 2100 core memory diagnostic, even though the check adds just a "cmpl" and a "ja" instruction to the more than 70 instructions executed for each simulated 21xx instruction. This must be some sort of cache miss phenomenon. Therefore, an implementation that does not suffer that penalty is to change the existing "MEM_ADDR_OK" macro from a comparison to "MEMSIZE" (simulated memory size) to a comparison to LWA, where the latter is the last word of addressable memory plus one. We also declare a 64-word array to serve as the shadow memory for the loader area. Now, disabling the loader copies the last 64 words of memory (i.e., MEMSIZE-64) to the shadow array and sets LWA to MEMSIZE-64. Enabling the loader copies the shadow array to the last 64 words of memory and sets LWA to MEMSIZE. Changing the memory size changes LWA accordingly (if LWA & 07777 == 0, LWA = MEMSIZE, else LWA = MEMSIZE-64). Changing the CPU to a 21xx sets LWA to MEMSIZE-64 (loader is disabled). Changing the CPU to a 1000 sets LWA to MEMSIZE (loader is enabled). ---------- CPU Idling ---------- To support idling, the following device polling service routines must be rewritten to poll synchronously: - TTY (console) - BACI - MUX (12920) - MPX (12792) Each of these devices should poll at the same rate as the RTE clock, i.e., 10 milliseconds. As the TTY device is always enabled, it should be the source of calibrated time. The TBG (CLK) should synchronize with the TTY poll when it is set for a 10 millisecond period. When set for other rates, it should calibrate independently. One slight problem is that STC TBG should give full first time period, as card counters are cleared before STC. Can't do this if we sync with TTY poll, and if we reset TTY poll to match TBG, other units synched with TTY (e.g., BACI) will become unsynched. However, first time period will be short anyway, because we don't know "real" count to use until a few calibration cycles. So, in practice, this occurs already and shouldn't be a problem (in diagnostic mode, the clock is decoupled from real time, and each period is the same length, including the first). The TTY should export its current service time. Other devices should schedule their services initially on the remaining time in the TTY poll (via sim_is_active). That initially locks each device to the TTY so that its service is called immediately after the TTY service. When each is called, it reschedules based on the exported TTY time. This keeps the service routines synchronized with the TTY poll. Idling occurs only when the unit at head of event queue has the UNIT_IDLE flag. However, UNIT_IDLE is needed only on the TTY and CLK (if not set for 10 milliseconds). Other polled devices (BACI, etc.) always appear in the event queue immediately after the TTY and so have no effect on the idle decision. ----------------------------------- Modelling the HP 1000 I/O Structure ----------------------------------- The HP 21xx/1000 computers implement a bidirectional I/O backplane with a 16-bit data path. Signals exchanged between the CPU, MP, DCPC, and I/O cards control data transfer timing and interrupt request generation. Execution of CPU I/O Group instructions connect the backplane data bus to the CPU's internal S-bus and generate the necessary control signals to operate the addressed I/O card. I/O interfaces typically -- but not necessarily -- employ control, flag buffer, flag, and interrupt request flip-flops. A device transfer is started by setting the control flip-flop. The device signals completion by setting the flag buffer flip-flop, which in turn sets the flag flip-flop. The backplane signals involved in interrupt generation are PRH (priority from higher-priority interfaces) and PRL (priority to lower-priority interfaces), IRQ (interrupt request) and FLG (flag), and IAK (interrupt acknowledge). If the control, flag buffer, and flag flip-flops are set, and no higher-priority device is currently interrupting, then the interrupt request flip-flop is set to interrupt the CPU, and priority to lower-priority cards is denied. The card responds to the CPU interrupt acknowledgement by clearing the flag buffer flip-flop, removing the interrupt request. Currently, SIMH models the interface control, flag buffer, and flag flip-flops explicitly, maintaining a global bit vector for each flip-flop type. However, the PRH/PRL, IRQ, and IAK signals are implicitly modelled -- PRL is the logical AND of the control and flag flip-flops, IRQ is the logical AND of the control, flag buffer, and flag flip-flops, and IAK clears the flag buffer. The problem is that while most cards model these signals as indicated, there are exceptions. Notably, the 12892A/B Memory Protect card clears the flag and the flag buffer on IAK, and the 12936A Privileged Interrupt Fence generates PRL as the logical OR of the control and flag. These exceptions must be special-cased to work around the implicit assumptions. In addition, SIMH bases I/O dispatch on the I/O instructions, rather than the I/O signals. This means that signals that affect interface cards but have no equivalent instructions (e.g., power on preset, control reset) must be special-cased as well in the dispatcher to provide "pseudo-instructions" for these events. There are some other suboptimal characteristics of the current design: - The STF/CLF and STC/CLC signals are dispatched together as ioFLG and ioCTL, necessitating additional decoding in each interface's I/O handler. - The IOI signal is split into ioLIX and ioMIX instructions, necessitating redundant handling in the interface. - The control, flag buffer, and flag states must be moved from the DIBs to the bit vectors when execution is started, and from the vectors to the DIBs when execution stops. - The CRS signal goes to the I/O handler, while POPIO goes to the device reset routine. As POPIO and CRS are issued together, the CRS handler must be separate and then called from the I/O handler and reset routine. However, the flip-flop states are in the bit vectors when the I/O handler is called and in the DIB when the reset routine is called. - A command flip-flop is modelled in all devices, even though it is used in very few interfaces. Several of the current simulations model some other flip-flop as "command" to reuse this definition. To allow simulation of interfaces with non-standard generation of interrupt requests without resorting to special-case code, the I/O model is restructured as follows: 1. Interfaces are sent backplane signals instead of I/O instructions. 2. Interrupt handling uses explicit PRL, IRQ, and IAK signals, rather than implicitly deriving these from control flag buffer, and flag interactions. 3. CPU bit vectors are maintained for PRL, IRQ, and SRQ. 4. Interfaces generate PRL, IRQ, and SRQ from their internal logic. 5. Control, flag buffer, and flag flip-flops become internal states in the interface simulations. Additional flip-flops (e.g., command) are just additional internal states. 6. When simulation starts, bit vectors are set by calling each enabled device with the SIR (set interrupt request) signal to regenerate the PRL, IRQ, and SRQ signals. No signal states need to be stored in the DIB, so none need to be saved when execution stops. 7. PON (power on), POPIO (power on preset to I/O), and CRS (control reset) are modelled as follows: - PON is simulated by RESET -P and calls _reset(), which does any power-on-specific handling before dispatching POPIO to the signal handler. - POPIO is simulated by RESET and calls _reset(), which dispatches POPIO to the signal handler. Within the handler, POPIO falls into CRS. - CRS is simulated by CLC 0 and dispatches CRS to the signal handler. There are four possible implementations of the interface backplane signal handler: 1. devio (select_code, signal, clear_flag, data) - case statement to handle signals (except ioCLF, ioSIR) - clear_flag TRUE handles ioCLF case - unconditionally calculate PRL, IRQ, SRQ to handle ioSIR case iodata = devdisp (dev, iosig, clf, ABREG [ab]); Advantages: - one call for STC sc,C Disadvantages: - ioCLF and ioSIR are implicit - PRL, etc. calculation done on signals that don't need it (e.g., ioSFS) 2. devio (select_code, signal, data) - case statement to handle all signals - ioSTC, etc. do recursive call for ioSIR iodata = devdisp (dev, iosig, ABREG [ab]); if (clf) devdisp (dev, ioCLF, 0); Advantages: - uniform processing within case statement Disadvantages: - two calls for STC sc,C case - ioSIR calculations done twice (once on STC, again on CLF) ------------------ PON notes (review) ------------------ The remaining I/O case, power-on, must be considered in light of the existing simulator commands and the "dev_reset" function. From examining "scp.c", it appears that the "dev_reset" function is called: * at simulator startup before the first command prompt * while processing a SET DISABLED or SET ENABLED command * while processing a RESET ALL or RESET command * while processing a RUN or BOOT command From examining the various "dev_reset" routines in the existing HP device simulators, it appears that this routine: * performs simulation-specific initialization (allocating heap space to simulate main memory, making two-card setups consistent) * performs device-specific power-on initialization (setting machine registers to zero, clearing buffer register values) * stops I/O operations in progress (clear CTL, set FLG, cancel pending events, etc.) The last function (clearing I/O) is precisely what the PRESET action does. For the other two, they could either be combined into a unified "dev_reset" execution, or differentiated by a command switch, e.g., "-P" for power-on reset. Referring to the four instances of "dev_reset" calls above, it would appear that simulator startup would want to invoke both simulation-specific and power-on reset actions. SET ENABLED would want to perform both as well, given that the "dev_reset" routine isn't called at startup for a device that is disabled. SET DISABLED only needs the simulation-specific "hp_enbdis_pair" call to ensure that disabling one card of a two-card setup disables the other as well. The RESET command is intended to bring the simulation to a known state, so both simulation-specific and power-on actions should be invoked. And given that the RUN and BOOT commands are intended to proceed from a known state, they too should invoke both. So it would seem that, with the possible exception of SET DISABLED, it is not necessary to separate out the simulator initialization function from the device power-on function within the "dev_reset" routines. Executing a single "dev_reset" code path in response to any of these actions is acceptable. Implementation of the four I/O events is not straightforward. The problem is that the simulator maintains two sets of device state bits (control, flag, etc.). One set is stored in the device's register set and points to variables contained in the "device information block" (DIB). These are referenced during simulator stop and are accessed by referring to the DIB structure fields directly. The other set is stored in the CPU's flag arrays. These are referenced during simulator run and are accessed via bitfield macros. Because power-on reset and PRESET are simulator-stopped commands, the device flags will be in the DIBs, whereas the CLC 0 and CLC sc instruction executions occur during simulator run, so the flags will be in the flag arrays. Because PRESET asserts CRS, device responses to PRESET and CLC 0 will likely be the same. However, because these events occur in contrasting simulator states (stop and run, respectively), they cannot use the same code to access the flags. There are basically two ways to implement the four events: * as four I/O dispatch states ("ioPON", "ioPOPIO", "ioCRS", and "ioCTL"), or * as I/O dispatch states for CRS and CLC and as procedures for power-on and PRESET. Because power-on and PRESET both assert CRS, it is desirable to reuse the CRS code for these events, but the differing flag locations hinders this. There are several approaches: 1. Use four dispatch states and test each access in the dispatch routine to determine whether to access via DIBs or flag arrays. 2. Use four dispatch states and save/restore the flags from the DIBs to the flag arrays before/after the dispatch call for power-on and PRESET. 3. Use four dispatch states and do away with DIB storage by dynamically setting the flag array access pointers when the device select code is changed. 4. Use two dispatch states and two procedures and duplicate the CRS dispatch code in the PRESET procedure. 5. Use two dispatch states and two procedures and abstract the CRS dispatch code that does not involve flag access to subordinate procedures, called from both the dispatch and PRESET routines. There are drawbacks to all of these: 1. The multiple flag access tests would be executed each time a CLC instruction was simulated (presuming CLC 0 or CRS that falls into CLC sc). That would slow down the simulator run performance. 2. While PRESET affects all devices (or two large subsets thereof), power-on RESET may be directed to a single device, so DIB/array save/restores would have to be on a device-by-device basis. 3. In the stopped state, the flags essentially become a property of the select code, rather than the device. A change of select code could copy the flags to the new select code's flag array locations, thereby retaining the apparent device state. However, with two devices temporarily assigned to the same select code, changing one would surreptitiously change the other. 4. Duplicating code that may be quite involved (e.g., the 7970 interface issues a tape CLEAR command in response to CRS) will lead to maintenance problems, and clarity will be reduced by having I/O event code physically separated into multiple procedures and utilizing two unrelated flag-access mechanisms. 5. Abstracting the non-flag code bits may lead to fracturing the clarity of the original I/O handling code if the logical flow has several code segments surrounding flag-access points (results in multiple "procedure slices" that must be executed in sequence to perform a single operation). The general benefits of the all-dispatch approach is that all of the code responding to I/O backplane signals is centralized and uses the same access mechanisms. Also, the implication of PRESET by power-on and of CRS by PRESET may be expressed simply by having the I/O event cases lead into one another. The benefit of the split dispatch/procedure approach is that the separate parts may work directly on the relevant flag locations without the overhead of saving and restoring. Also, support for resetting a single device is inherent. Code for the all-dispatch approach would look like this (assume that PON clears the device buffer register, PRESET sets the device flag, CRS clears the device command, and CLC clears the device control): switch (inst) { ... case ioPON: // do other power-on stuff here dev_buf = 0; // PON implies POPIO, so fall into next case case ioPOPIO: // do other PRESET stuff here setFLG (dev); // POPIO implies CRS, so fall into next case case ioCRS: // do other CLC 0 stuff here clrCMD (dev); // use CLC sc routine to complete device I/O stop case ioCTL: // do other CLC sc stuff here clrCTL (dev); ... } Again, clarity of expression and simple reuse of states is the primary advantage. Code for the split dispatch/procedure approach would look like this: switch (inst) { ... case ioCRS: do_crs_stuff (); // do other CRS stuff clrCMD (dev); // use CLC sc routine to complete device I/O stop case ioCTL: do_clc_stuff (); // do other CLC stuff clrCTL (dev); ... } t_stat dev_preset (...); { // do other PRESET stuff here dev_dib.flg = 1; // POPIO implies CRS, so do CRS stuff do_crs_stuff (); dev_dib.cmd = 0; do_clc_stuff (); dev_dib.ctl = 0; return ...; } t_stat dev_reset (...); { // do other power-on stuff here dev_buf = 0; return dev_preset (...); // PON implies POPIO } The primary disadvantage is the potential for fracturing the CRS and CLC dispatch flow if multiple flag-access points exist. For instance, given: case ioCTL: ... switch (buf) { case FN_A: ... break; case FN_B: ... setCMD (dev); ... break; } ... setCTL (dev); ... ...and that CRS causes FN_B to occur, we would have: case ioCTL: sub_1 (); // was ... switch (buf) { case FN_A: ... break; case FN_B: sub_2 (); // was ... setCMD (dev); sub_3 (); // was ... break; } sub_4 (); // was ... setCTL (dev); sub_5 (); // was ... ...in order to have: t_stat dev_preset (...); { sub_1 (); sub_2 (); dev_dib.cmd = 1; sub_3 (); sub_4 (); dev_dib.ctl = 1; sub_5 (); return ...; } Approach #2 (save/restore DIBs) would seem to be the best. It has these advantages over the other methods: * The I/O dispatch code incurs no run-time penalty (the time penalty is in the save/restore operation, which occurs during simulator stop). * The DIBs are retained, so flags stay with the device during select code changes. * The code is clear. There are no duplicated or extracted sections, the flag access method is uniform, I/O signal handling is localized, and the signal implications are expressed by the code flow. Implementing approach #2 requires that the DIB flags be restored to the flag arrays before dispatching PON or PRESET and then copied back to the DIBs upon return. This is simple for the PRESET command: the code to copy to and from the DIBs already exists at the start and end of the "sim_instr" routine (hp2100_cpu.c). These two code sections may be extracted to procedures to do this and called from the PRESET command routine. Power-on is a bit more problematic. The RESET command may be invoked to reset all devices or just a single device. As mentioned previously with PRESET, power on has no analog to RESET , i.e., the 21xx system cannot be made to assert PON to a single device. So dispatching "ioPON" should be an all-or-nothing affair. One solution would be to have the "dev_reset" routine perform only the simulator reset function ("Typically, RESET stops any in-progress I/O operation, clears any interrupt request, and returns the device to a quiescent state"). Power-on would be dispatched only when the CPU device was reset, and it would be dispatched to all devices. In fact, "cpu_reset" could call the "preset_cmd" function with a flag that says to dispatch "ioPON" instead of "ioPOPIO" (as the PRESET command would do). This would allow reuse of the DIB save/restore and dispatching loop code. QUESTION: would this allow "stopping any in-progress I/O operation?" For that matter, is CLC sc guaranteed to do that (if we wanted to reuse the CLC code)? In summary, a more accurate physical device model would be produced by: 1. Adding new I/O dispatch states "ioPON", "ioPOPIO", and "ioCRS". 2. Altering the existing "CLC 0" routine to dispatch "ioCRS" to each device. 3. Adding a new, VM-specific "PRESET [-I | -E]" command that will dispatch "ioPOPIO" to the appropriate devices with the necessary save/restore of DIB flags. 4. Modifying "cpu_reset" to dispatch "ioPON" to each device via the preset command processor. 5. Modifying each device's "dev_io" routine to implement the appropriate actions for each of the new I/O dispatch states. --------------------------- MP and MEM Violation Checks --------------------------- The Memory Protect (MP) and Memory Expansion Module (MEM) cards work closely together to implement a protected mode. Yet, both cards are optional, and each can be used without the other. Protection, however, requires MP; without it, the MEM will do memory mapping, but no protection is offered. When enabled, MP prohibits HLTs and any I/O instructions not directed at SC 01 (the overflow register), as well as writes to "protected memory." Protected memory is delineated by a lower bound of either location 0 or 2 and an upper bound set by the value in the memory protect fence register (MPFR). Any attempted write access to the area LB <= M < UB is aborted, and an interrupt to location 5 is generated. I/O violations are checked automatically. MP monitors the instruction register (IR), and a violation is indicated if a HLT is present, or if the IOG signal is asserted and the select code indicated by the IR is not 01. Memory violation checks must be requested explicitly by the instruction set firmware. The presence of an MPCK micro-order causes a check of the memory address currently on the M Bus with the lower and upper protection bounds. The lower bound is established automatically by examining the IR. If a JMP instruction is present, or a JSB instruction is present and jumper W5 is out, then the lower bound is location 0. Otherwise, the lower bound is location 2. The upper bound is established by the value in the MPFR. MPCK provides one of two qualifiers for the MPCARRY (memory protect carry) and the MPCND (memory protect conditional) signals. MPCARRY is also qualified by a comparison of the M Bus with the MPFR. If the address is lower than the MPFR value, MPCARRY is generated. MPCND is also qualified by a comparison of the M Bus with the lower bound as established above. If the address is above the lower bound, MPCND is generated. A violation is indicated if MPCARRY and MPCND are both asserted. Violations set the MPVFF (memory protect violation). This inhibits the CPU and memory from altering the addressed contents. It also sets the flag buffer and flag to request an interrupt. Violations are conditional on the MP control flip-flop. If control is not set, no violations are signalled. Note that memory protection isn't strictly tied to write attempts. Any use of the MPCK micro-order will perform an address check, regardless of the state of the memory controller. All that's required for a check is a value on the M Bus, a value in the IR, and an MPCK micro-order. The instruction set firmware provided by HP issues MPCKs on these instructions: all IOG instructions STA, STB JSB ISZ JMP DST (both M and M+1) SAX, SBX, SAY, SBY STX, STY SBS, CBS MVW SBT (as a word address) MBT (as a word address) JLY, JPY, JRS XMM, XSA, XSB DJP, SJP, DJS, SJS UJS, UJP MBF, MBI, MBW, MWF, MWI, MWW USA, USB, SSM, SYA, SYB, PAA, PAB, PBA, PBB All of the above use a lower bound of 2, except for JMP, JLY, and JPY, which use a lower bound of 0 (the JLY and JPY firmware routines store a JMP opcode in the IR to cause the MP card to use 0 instead of 2). MEM checks are made by the Memory Expansion Module concurrently with MP checks. The MEM signals a mapping violation by asserting the MEV signal to the MP card. MEV sets the MEVFF, the MPVFF, and the flag buffer and flag on the MP card. MEV is asserted for a read, write, base-page, or privilege violation. MEV is qualified by DMAENB and CTL5, so MEV is inhibited for DMA cycles and if MP control is not set. In other words, an MEV cannot occur if MP is off or not present. A read violation occurs if the read-protect bit is set in the map register corresponding to the currently accessed page. Every read request is checked unconditionally. A write violation occurs if the write-protect bit is set and MPCND is asserted. As with MP, writes are checked for violations only when the MPCK micro-order is employed. That is, a write without MPCK will be allowed to a write-protected page. A base-page violation occurs if a write (MPCND) is attempted to the unmapped portion of the base page. The mapped portion is indicated by the settings in the base-page fence register. A privilege violation occurs if MEM control signals CL0 or CL1 are asserted, or if CL5 is asserted with S[15:13] = 001. These occur for writes to the map registers (MEU micro-order appears in the STORE field of a microinstruction), and for writes to the MEM state or fence registers. Read, write, and base-page violations are qualified by MAPON, so they cannot occur if mapping is disabled. Privilege violations are not qualified, so they are inhibited only if MP is off (CTL5 is low). Even though MEV may be inhibited in the presence of a violation, the violation is still recorded in the MEM violation register. The VR is clocked on every memory read, on every MPCK use, and on every privilege violation. The VR is updated continuously until MP is turned off (CTL5 is lowered) or MEV is asserted. That is, a MP or MEM violation will "freeze" the VR, so that it can be examined in an interrupt routine. The VR also stores the state of the MEBEN (ME bus enabled) signal. MEBEN is asserted when mapping is on and the current access is not to the unmapped portion of the base page. MEBEN is sent to the memory controller to select whether the memory address comes from the ME bus or from the M register contents, i.e., whether the access is mapped or unmapped. Note that even on unmapped accesses, the protection bits for the base page are checked, so a read from the unmapped portion can still generate a read violation if page 0 is read-protected.