From bbbe526f8cbee14be6507143a4bcbc80479b8a72 Mon Sep 17 00:00:00 2001 From: Mark Pizzolato Date: Wed, 24 May 2017 09:15:16 -0700 Subject: [PATCH] HP2100, HP3000: HP2100 release 26 and HP3000 release 5 (Dave Bryan) master branch code is identical --- HP2100/hp2100_baci.c | 34 +- HP2100/hp2100_bugfixes.txt | 340 +- HP2100/hp2100_cpu.c | 8060 ++++++++++++++++++------------------ HP2100/hp2100_cpu.h | 671 +-- HP2100/hp2100_cpu0.c | 542 +-- HP2100/hp2100_cpu1.c | 1936 ++++----- HP2100/hp2100_cpu1.h | 646 +-- HP2100/hp2100_cpu2.c | 2261 +++++----- HP2100/hp2100_cpu3.c | 1655 ++++---- HP2100/hp2100_cpu4.c | 2257 +++++----- HP2100/hp2100_cpu5.c | 2885 ++++++------- HP2100/hp2100_cpu6.c | 1658 ++++---- HP2100/hp2100_cpu7.c | 1885 ++++----- HP2100/hp2100_defs.h | 1251 +++--- HP2100/hp2100_di.c | 3828 ++++++++--------- HP2100/hp2100_di.h | 584 ++- HP2100/hp2100_di_da.c | 4273 +++++++++---------- HP2100/hp2100_diag.txt | 18 +- HP2100/hp2100_disclib.c | 2408 +++++++++++ HP2100/hp2100_disclib.h | 387 ++ HP2100/hp2100_dp.c | 2402 +++++------ HP2100/hp2100_dq.c | 1955 ++++----- HP2100/hp2100_dr.c | 1582 +++---- HP2100/hp2100_ds.c | 2962 ++++++------- HP2100/hp2100_fp.c | 820 ++-- HP2100/hp2100_fp.h | 96 +- HP2100/hp2100_fp1.c | 2923 ++++++------- HP2100/hp2100_fp1.h | 110 +- HP2100/hp2100_ipl.c | 1671 ++++---- HP2100/hp2100_lps.c | 27 +- HP2100/hp2100_lpt.c | 11 +- HP2100/hp2100_mpx.c | 5460 ++++++++++++------------ HP2100/hp2100_ms.c | 2745 ++++++------ HP2100/hp2100_mt.c | 1395 +++---- HP2100/hp2100_mux.c | 2814 ++++++------- HP2100/hp2100_pif.c | 11 +- HP2100/hp2100_release.txt | 496 +++ HP2100/hp2100_stddev.c | 2517 +++++------ HP2100/hp2100_sys.c | 1814 ++++---- HP3000/hp3000_atc.c | 2865 +++++++++++++ HP3000/hp3000_clk.c | 942 +++++ HP3000/hp3000_cpu.c | 4606 +++++++++++++++++++++ HP3000/hp3000_cpu.h | 1116 +++++ HP3000/hp3000_cpu_base.c | 4023 ++++++++++++++++++ HP3000/hp3000_cpu_cis.c | 1977 +++++++++ HP3000/hp3000_cpu_fp.c | 892 ++++ HP3000/hp3000_cpu_fp.h | 112 + HP3000/hp3000_cpu_ims.h | 207 + HP3000/hp3000_defs.h | 717 ++++ HP3000/hp3000_diag.txt | 2142 ++++++++++ HP3000/hp3000_ds.c | 1354 ++++++ HP3000/hp3000_io.h | 385 ++ HP3000/hp3000_iop.c | 920 ++++ HP3000/hp3000_lp.c | 3715 +++++++++++++++++ HP3000/hp3000_mem.c | 1051 +++++ HP3000/hp3000_mem.h | 162 + HP3000/hp3000_mpx.c | 1974 +++++++++ HP3000/hp3000_ms.c | 1344 ++++++ HP3000/hp3000_release.txt | 836 ++++ HP3000/hp3000_scmb.c | 1227 ++++++ HP3000/hp3000_sel.c | 1337 ++++++ HP3000/hp3000_sys.c | 3645 ++++++++++++++++ HP3000/hp_disclib.c | 4344 +++++++++++++++++++ HP3000/hp_disclib.h | 562 +++ HP3000/hp_tapelib.c | 3205 ++++++++++++++ HP3000/hp_tapelib.h | 548 +++ descrip.mms | 27 +- makefile | 11 +- 68 files changed, 83098 insertions(+), 32538 deletions(-) create mode 100644 HP2100/hp2100_disclib.c create mode 100644 HP2100/hp2100_disclib.h create mode 100644 HP2100/hp2100_release.txt create mode 100644 HP3000/hp3000_atc.c create mode 100644 HP3000/hp3000_clk.c create mode 100644 HP3000/hp3000_cpu.c create mode 100644 HP3000/hp3000_cpu.h create mode 100644 HP3000/hp3000_cpu_base.c create mode 100644 HP3000/hp3000_cpu_cis.c create mode 100644 HP3000/hp3000_cpu_fp.c create mode 100644 HP3000/hp3000_cpu_fp.h create mode 100644 HP3000/hp3000_cpu_ims.h create mode 100644 HP3000/hp3000_defs.h create mode 100644 HP3000/hp3000_diag.txt create mode 100644 HP3000/hp3000_ds.c create mode 100644 HP3000/hp3000_io.h create mode 100644 HP3000/hp3000_iop.c create mode 100644 HP3000/hp3000_lp.c create mode 100644 HP3000/hp3000_mem.c create mode 100644 HP3000/hp3000_mem.h create mode 100644 HP3000/hp3000_mpx.c create mode 100644 HP3000/hp3000_ms.c create mode 100644 HP3000/hp3000_release.txt create mode 100644 HP3000/hp3000_scmb.c create mode 100644 HP3000/hp3000_sel.c create mode 100644 HP3000/hp3000_sys.c create mode 100644 HP3000/hp_disclib.c create mode 100644 HP3000/hp_disclib.h create mode 100644 HP3000/hp_tapelib.c create mode 100644 HP3000/hp_tapelib.h diff --git a/HP2100/hp2100_baci.c b/HP2100/hp2100_baci.c index 5018c8f9..98079294 100644 --- a/HP2100/hp2100_baci.c +++ b/HP2100/hp2100_baci.c @@ -1,6 +1,6 @@ /* hp2100_baci.c: HP 12966A buffered asynchronous communications interface simulator - Copyright (c) 2007-2014, J. David Bryan + Copyright (c) 2007-2016, J. David Bryan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -25,6 +25,8 @@ BACI 12966A BACI card + 02-Aug-16 JDB "baci_poll_svc" now calls "tmxr_poll_conn" unilaterally + 13-May-16 JDB Modified for revised SCP API function parameter types 24-Dec-14 JDB Added casts for explicit downward conversions 10-Jan-13 MP Added DEV_MUX and additional DEVICE field values 10-Feb-12 JDB Deprecated DEVNO in favor of SC @@ -378,7 +380,7 @@ IOHANDLER baci_io; t_stat baci_term_svc (UNIT *uptr); t_stat baci_poll_svc (UNIT *uptr); t_stat baci_reset (DEVICE *dptr); -t_stat baci_attach (UNIT *uptr, char *cptr); +t_stat baci_attach (UNIT *uptr, CONST char *cptr); t_stat baci_detach (UNIT *uptr); @@ -993,22 +995,24 @@ return status; scheduled. It starts when the socket is attached and stops when the socket is detached. - As there is only one line, we only poll for a new connection when the line is - disconnected. + + Implementation notes: + + 1. Even though there is only one line, we poll for new connections + unconditionally. This is so that "tmxr_poll_conn" will report "All + connections busy" to a second Telnet connection. Otherwise, the user's + client would connect but then would be silently unresponsive. */ t_stat baci_poll_svc (UNIT *uptr) { -if (baci_term.flags & UNIT_ATT) { /* attached to line? */ - if ((baci_ldsc.conn == 0) && /* line not connected? */ - (tmxr_poll_conn (&baci_desc) >= 0)) /* and new connection established? */ - baci_ldsc.rcve = 1; /* enable line to receive */ +if (tmxr_poll_conn (&baci_desc) >= 0) /* if new connection is established */ + baci_ldsc.rcve = 1; /* then enable line to receive */ - tmxr_poll_rx (&baci_desc); /* poll for input */ +tmxr_poll_rx (&baci_desc); /* poll for input */ - if (tmxr_rqln (&baci_ldsc)) /* chars available? */ - sim_activate (&baci_term, baci_term.wait); /* activate I/O service */ - } +if (tmxr_rqln (&baci_ldsc)) /* chars available? */ + sim_activate (&baci_term, baci_term.wait); /* activate I/O service */ if (uptr->wait == POLL_FIRST) /* first poll? */ uptr->wait = sync_poll (INITIAL); /* initial synchronization */ @@ -1049,7 +1053,7 @@ return SCPE_OK; /* Attach controller */ -t_stat baci_attach (UNIT *uptr, char *cptr) +t_stat baci_attach (UNIT *uptr, CONST char *cptr) { t_stat status = SCPE_OK; @@ -1062,18 +1066,20 @@ if (status == SCPE_OK) { return status; } + /* Detach controller */ t_stat baci_detach (UNIT *uptr) { t_stat status; -status = tmxr_detach (&baci_desc, uptr); /* detach socket */ baci_ldsc.rcve = 0; /* disable line reception */ sim_cancel (&baci_poll); /* stop Telnet poll */ +status = tmxr_detach (&baci_desc, uptr); /* detach socket */ return status; } + /* Local routines */ diff --git a/HP2100/hp2100_bugfixes.txt b/HP2100/hp2100_bugfixes.txt index 4dd55c48..e99b1131 100644 --- a/HP2100/hp2100_bugfixes.txt +++ b/HP2100/hp2100_bugfixes.txt @@ -1,6 +1,9 @@ - HP 2100 SIMULATOR BUG FIX WRITEUPS - ================================== - Last update: 2015-01-03 + HP 2100 SIMULATOR FIX ARCHIVE + ============================= + Final update: 2016-08-05 + +See the "SIMH/HP 2100 Release Notes" file for bug fixes implemented after the +date above. 1. PROBLEM: Booting from magnetic tape reports "HALT instruction, P: 77756 @@ -6746,8 +6749,8 @@ -265. PROBLEM: Compiling the HP simulator for 64-bit addressing generates - warnings. +265. PROBLEM: Compiling the HP simulator for 64-bit addressing produces many + conversion warnings. VERSION: 3.9-0 @@ -6789,3 +6792,330 @@ modifying the S register directly. STATUS: Fixed in version 4.0-0. + + + +267. ENHANCEMENT: Add a register to the IPLI device to configure the DMA delay. + + VERSION: 3.9-0 + + OBSERVATION: Occasionally, the 2000 Access terminal multiplexer does not + initialize properly; the problem is more prevalent on multiprocessor + systems. + + There is a race condition between the system processor (SP) and the I/O + processor (IOP) during initialization. The problem occurs if the IOP DMA + completion interrupt routine finishes before the SP DMA completion + interrupt routine. As a workaround, the simulator suspends the IOP process + for one millisecond before signaling a DMA completion (EDT) interrupt. + Depending on system loading, this time may be insufficient. + + RESOLUTION: Modify "ipli_reg" (hp2100_ipl.c) to add a hidden register, + EDTDELAY, that allows the user to configure the completion delay. The + register value is expressed in milliseconds and defaults to one + millisecond. + + STATUS: Fixed in version 4.0-0. + + + +268. ENHANCEMENT: Provide "fprint_sym" and "parse_sym" with register-specific + information when a register is being examined or deposited. + + VERSION: 3.9-0 + + OBSERVATION: When EXAMINE or DEPOSIT specifies a register that has the + REG_VMIO flag, "fprint_sym" or "parse_sym", respectively, is called in lieu + of "fprint_val" or "get_uint". This allows VM-specific interpretations, + such as symbolic display of a status or current instruction register value, + by specifying a switch with the command: + + sim> examine STAT-CIR + STAT: 060001 + CIR: 030020 + + sim> examine -s STAT + STAT: mITroc CCG 001 + + sim> examine -m CIR + CIR: PAUS 0 + + The REG definition contains a radix field that allows a register to have a + default radix that is different than that of the device. This is + particularly helpful in connection with the EXAMINE STATE command, as + different registers may have different preferred radices: + + sim> examine state + P: 012077 (defaults to octal) + CNTR: 9 (defaults to decimal) + MASK: FFF0 (defaults to hex) + + However, there is no way to specify that the preferred display or input is + symbolic. So a CPU state that contains such registers would have to be + displayed multiple times -- once to get most of the values in their default + forms, and again to display those registers whose preferred form is + symbolic: + + sim> examine state + PB: 000100 + P: 012077 + PL: 023000 + STAT: 060001 + CIR: 030020 + + sim> examine -s STAT + STAT: mITroc CCG 001 + + sim> examine -m CIR + CIR: PAUS 0 + + It would be useful if a default symbolic interpretation could be specified + in the REG structure and passed to "fprint_sym" and "parse_sym" to be used + in the absence of overriding command-line switches. Currently, no + information is provided to these routines to indicate which register is + being manipulated, so there is no way to provide register-specific default + interpretations within the routines. + + RESOLUTION: Reserve space for register-specific flags in the "flags" field + of the REG structure (sim_defs.h) starting at REG_V_UF. Modify "ex_reg" + and "dep_reg" (scp.c) to merge the register-specific flags into the radix + value passed as the "addr" parameter to "fprint_sym" and "parse_sym". + + A VM that defines register-specific flags will know to separate them from + the passed radix value by the presence of the SIM_SW_REG value in the + "switch" parameter. VMs that do not define register-specific flags are not + affected by this change. + + STATUS: Fixed in version 4.0-0. + + + +269. PROBLEM: Tape read reports "end of medium" even if a gap precedes it. + + VERSION: 4.0-0 + + OBSERVATION: Calling "sim_tape_rdrecf" to read a tape record sometimes + returns MTSE_EOM and sets the "position not updated" (PNU) flag, even when + an erase gap precedes the EOM. The correct response should be to return + MTSE_RUNAWAY to indicate that spacing over a gap did not end with a data + record or tape mark. Moreover, PNU should not be set, as the position has + been updated. + + CAUSE: The routine attempts to handle this case by returning MTSE_RUNAWAY + if the EOF was detected while reading a buffer of gap markers. However, if + a buffer read ends immediately before an EOM marker or the physical EOF, + the next read attempt will return a zero buffer length. The routine + misinterprets this to mean that no gap was present and returns MTSE_EOM and + sets the PNU flag. + + RESOLUTION: Modify "sim_tape_rdlntf" (sim_tape.c) to determine whether the + EOM marker or physical EOF was seen on the first or a subsequent buffer + read, and to return MTSE_EOM with PNU or MTSE_RUNAWAY without PNU, + respectively. + + STATUS: Fixed in version 4.0-0. + + + +270. PROBLEM: The disc libraries in the HP2100 and HP3000 directories differ. + + VERSION: 4.0-0 + + OBSERVATION: The "hp_disclib.c" and "hp_disclib.h" files appear in both + the HP2100 and HP3000 subdirectories. However, the contents of the files + are different. If both source sets are compiled to a common object + subdirectory, one of the two executables will not link, due to unresolved + externals. + + CAUSE: The disc library in the HP3000 subdirectory is an extension of the + one in the HP2100 subdirectory. It is intended as an eventual replacement + for the HP2100 version, so that both simulators can share the library. + Until then, however, they are not interchangeable, as they export different + routines, leading to link errors if one is accidentally substituted for the + other. + + RESOLUTION: Rename "hp_disclib.c/h" in the HP2100 subdirectory to + "hp2100_disclib.c/h" to indicate that it is HP2100-specific. Alter + "hp2100_ds.c" and "hp2100_di_da.c" to use the new include file name. + + STATUS: Fixed in version 4.0-0. + + + +271. PROBLEM: The MPX device incorrectly reports a currently filling read + buffer as complete. + + VERSION: 4.0-0 + + OBSERVATION: Using Kermit to upload a file to RTE and specifying a packet + size larger than 254 bytes fails with transmission errors. Uploads with + packet sizes under 254 bytes work properly. + + CAUSE: The 12792 multiplexer provides two 254-byte receive buffers per + line. A single reception larger than 254 bytes returns "partial buffer" + status when the initial read terminates because of the buffer full + condition. This informs RTE that the reception is incomplete and that it + should issue additional reads to obtain the remainder of the data. + Complete reception is indicated by the final read not returning "partial + buffer" status. + + When Kermit sends a large packet, the multiplexer fills the first 254-byte + buffer, terminating on the buffer-full condition, and sends an unsolicited + interrupt to the CPU with "read buffer available" status. In response, RTE + sends an "acknowledge" command to the multiplexer, which returns the + "partial buffer" status and the buffer length. RTE then follows with a + "read buffer" command and prepares to receive the first buffer data. + Concurrently, the multiplexer begins filling the second 254-byte buffer. + + RTE uses DCPC to transfer the data from the multiplexer, which is faster + than the incoming data rate. Therefore, the transfer completes while the + second buffer is still filling. On DCPC completion, the multiplexer checks + to see if the other receive buffer is complete, and if it is, issues + another unsolicited interrupt with "read buffer available" status. + + The buffer completion check incorrectly returns TRUE if the buffer contains + received data. It should return TRUE only if the buffer contains data and + it is not currently being filled. + + For short reads, such as EDIT screen reads, the second buffer fill + completes before the first buffer DCPC transfer, and the returned "read + buffer available" status is appropriate. For long reads, such as Kermit + transfers, the incorrect buffer check causes RTE to transfer data from the + second buffer while it is incomplete. This leads to corrupted data and + Kermit transmission errors. + + RESOLUTION: Modify "mpx_cntl_svc" (hp2100_mpx.c) to indicate read buffer + availability only when filling is complete. + + STATUS: Fixed in version 4.0-0. + + + +272. PROBLEM: An MPX binary read larger than 254 bytes never completes. + + VERSION: 4.0-0. + + OBSERVATION: The MPX device may be configured to terminate a read on + reception of either a special character (e.g., CR) or a certain character + count. Counts up to 64K bytes are permitted, and such reads will succeed + if the alternating buffers are concurrently transferred to the CPU before + filling completely. However, while reads that specify character counts of + 254 or less complete as expected, reads of more than 254 bytes never + complete. + + CAUSE: As each character is received, the current character count is + compared to the termination count, and the read completes if the counts are + equal. However, the current character count is determined by the number of + characters in the current buffer and not the accumulated count of all + characters received. As the read buffers are 254 bytes long, the current + count will never exceed 254. Therefore, any read terminating on a count + greater than that will never complete. + + RESOLUTION: Define a new "mpx_termcnt" array to hold the termination + counts for the eight lines, and reassign the "mpx_charcnt" array to hold + the current character counts. Modify "exec_command" and "mpx_line_svc" + (hp2100_mpx.c) to set the termination count, compare it against the current + character count as characters are received, and terminate reception if + enabled and the values are equal. + + STATUS: Fixed in version 4.0-0. + + + +273. ENHANCEMENT: Burst-fill only the first of two MPX receive buffers in + FASTTIME mode. + + VERSION: 4.0-0. + + OBSERVATION: When the 8-channel multiplexer is set for "optimized timing" + mode, buffered characters are transferred in blocks to and from the Telnet + connection. That is, the line service routine will send or receive + characters as long as they are available. This is more efficient than the + "realistic timing" mode, which sends or receives one character per service + invocation. Effectively, this means that up to 508 characters (two buffers + of 254 bytes each) may be sent or received between one CPU instruction or + DCPC cycle and the next. This works well for sending, but it can cause + buffer overflows when receiving. + + Consider an application (such as Kermit) that receives large blocks of data + at high speed from a client. The multiplexer is designed to handle this + condition by interrupting the CPU when the first buffer is filled and + filling the second buffer while the CPU is unloading data from the first. + In realistic mode at 19,200 baud, the CPU has approximately 800 + instructions or DCPC cycles available per character received. With a + second buffer of 254 bytes, the CPU has approximately 203,000 instructions + available to unload the first buffer after receiving the interrupt + notification. Once started, the DCPC transfer takes no more than 508 + instruction times, so the CPU can easily keep up with data arriving at the + maximum baud rate. + + In fast timing mode, however, the first buffer burst-fills in a single CPU + instruction time, and, if available from the Telnet connection, the second + buffer fills in the next instruction time. At that point, any additional + characters received will result in a buffer overflow condition. The + problem is that the CPU has no time between the first burst and the second + to empty the first buffer. + + RESOLUTION: Modify "mpx_line_svc" (hp2100_mpx.c) to shift from burst + transfers to character-at-a-time transfers when a receive buffer is full + and awaiting unloading by the CPU. This allows the CPU and DCPC time to + read the buffer contents into memory before the second multiplexer buffer + is full. Once the completed buffer is freed, the service routine returns + to burst mode to fill the remainder of the other buffer, permitting the + efficiency of block transfers while avoiding buffer overruns with large + data transfers. + + STATUS: Fixed in version 4.0-0. + + + +274. PROBLEM: A second connection to the BACI device leaves the client + unresponsive. + + VERSION: 4.0-0. + + OBSERVATION: The BACI device supports a single terminal client connection. + If a second connection is attempted, the client connects but is otherwise + unresponsive. It would be better if the client received the "All + connections busy" message that is reported by the terminal multiplexers + (MPX and MUX devices) when the number of connections is exceeded. + + CAUSE: The "baci_poll_svc" is calling the "tmxr_poll_conn" routine only if + the port is not connected. The routine should be called unilaterally, so + that it will report an error and disconnect the client when all lines are + in use and another connection is attempted. + + RESOLUTION: Modify "baci_poll_svc" (hp2100_baci.c) to call + "tmxr_poll_conn" unilaterally, so that a second concurrent connection + attempt will be rejected with "All connections busy". + + STATUS: Fixed in version 4.0-0. + + + +275. PROBLEM: The exported program counter name (PC) clashes with other libraries. + + VERSION: 4.0-0. + + OBSERVATION: In HP 21xx/1000 systems, the P register is the program + counter. In keeping with the naming of the other register variables (e.g., + for A register, B register, etc.) in the simulator, the variable used + should be named "PR". However, for traditional reasons, the program + counter in SIMH is named "PC". + + The main CPU module declares its hardware register variables as global, so + that they may be accessed by other CPU helper modules. Unfortunately, the + "curses" library also declares the symbol "PC" as global, leading to + conflicts when it is loaded by SCP. A workaround had been implemented that + renamed "PC" to "PC_Global" in the HP2100 simulator, but that meant that + the new name had to be used when debugging, which was awkward. + + CAUSE: A poor choice of global symbol names from the "termcap" library, + which was inherited by the "curses" library. + + RESOLUTION: Change the program counter variable name from "PC" to "PR" + (hp2100_cpu.c, hp2100_cpu1.c, hp2100_cpu2.c, hp2100_cpu3.c, hp2100_cpu4.c, + hp2100_cpu5.c, hp2100_cpu6.c, hp2100_cpu7.c, hp2100_dr.c, and hp2100_ipl.c) + to avoid a name clash and for register naming consistency. + + STATUS: Fixed in version 4.0-0. diff --git a/HP2100/hp2100_cpu.c b/HP2100/hp2100_cpu.c index b09d27a3..862a29be 100644 --- a/HP2100/hp2100_cpu.c +++ b/HP2100/hp2100_cpu.c @@ -1,4029 +1,4031 @@ -/* hp2100_cpu.c: HP 21xx/1000 CPU simulator - - Copyright (c) 1993-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - CPU 2114C/2115A/2116C/2100A/1000-M/E/F central processing unit - 12731A memory expansion module - MP 12581A/12892B memory protect - DMA1,DMA2 12607B/12578A/12895A direct memory access controller - DCPC1,DCPC2 12897B dual channel port controller - - 31-Dec-14 JDB Corrected devdisp data parameters - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 24-Dec-14 JDB Added casts for explicit downward conversions - 18-Mar-13 JDB Removed redundant extern declarations - 05-Feb-13 JDB HLT instruction handler now relies on sim_vm_fprint_stopped - 09-May-12 JDB Separated assignments from conditional expressions - 13-Jan-12 JDB Minor speedup in "is_mapped" - Added casts to cpu_mod, dmasio, dmapio, cpu_reset, dma_reset - 07-Apr-11 JDB Fixed I/O return status bug for DMA cycles - Failed I/O cycles now stop on failing instruction - 28-Mar-11 JDB Tidied up signal handling - 29-Oct-10 JDB Revised DMA for new multi-card paradigm - Consolidated DMA reset routines - DMA channels renamed from 0,1 to 1,2 to match documentation - 27-Oct-10 JDB Changed I/O instructions, handlers, and DMA for revised signal model - Changed I/O dispatch table to use DIB pointers - 19-Oct-10 JDB Removed DMA latency counter - 13-Oct-10 JDB Fixed DMA requests to enable stealing every cycle - Fixed DMA priority for channel 1 over channel 2 - Corrected comments for "cpu_set_idle" - 30-Sep-08 JDB Breakpoints on interrupt trap cells now work - 05-Sep-08 JDB VIS and IOP are now mutually exclusive on 1000-F - 11-Aug-08 JDB Removed A/B shadow register variables - 07-Aug-08 JDB Moved hp_setdev, hp_showdev to hp2100_sys.c - Moved non-existent memory checks to WritePW - 05-Aug-08 JDB Fixed mp_dms_jmp to accept lower bound, check write protection - 30-Jul-08 JDB Corrected DMS violation register set conditions - Refefined ABORT to pass address, moved def to hp2100_cpu.h - Combined dms and dms_io routines - 29-Jul-08 JDB JSB to 0/1 with W5 out and fence = 0 erroneously causes MP abort - 11-Jul-08 JDB Unified I/O slot dispatch by adding DIBs for CPU, MP, and DMA - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - EDT no longer passes DMA channel - 30-Apr-08 JDB Enabled SIGNAL instructions, SIG debug flag - 28-Apr-08 JDB Added SET CPU IDLE/NOIDLE, idle detection for DOS/RTE - 24-Apr-08 JDB Fixed single stepping through interrupts - 20-Apr-08 JDB Enabled EMA and VIS, added EMA, VIS, and SIGNAL debug flags - 03-Dec-07 JDB Memory ex/dep and bkpt type default to current map mode - 26-Nov-07 JDB Added SET CPU DEBUG and OS/VMA flags, enabled OS/VMA - 15-Nov-07 JDB Corrected MP W5 (JSB) jumper action, SET/SHOW reversal, - mp_mevff clear on interrupt with I/O instruction in trap cell - 04-Nov-07 JDB Removed DBI support from 1000-M (was temporary for RTE-6/VM) - 28-Apr-07 RMS Removed clock initialization - 02-Mar-07 JDB EDT passes input flag and DMA channel in dat parameter - 11-Jan-07 JDB Added 12578A DMA byte packing - 28-Dec-06 JDB CLC 0 now sends CRS instead of CLC to devices - 26-Dec-06 JDB Fixed improper IRQ deferral for 21xx CPUs - Fixed improper interrupt servicing in resolve - 21-Dec-06 JDB Added 21xx loader enable/disable support - 16-Dec-06 JDB Added 2114 and 2115 CPU options. - Added support for 12607B (2114) and 12578A (2115/6) DMA - 01-Dec-06 JDB Added 1000-F CPU option (requires HAVE_INT64) - SHOW CPU displays 1000-M/E instead of 21MX-M/E - 16-Oct-06 JDB Moved ReadF to hp2100_cpu1.c - 12-Oct-06 JDB Fixed INDMAX off-by-one error in resolve - 26-Sep-06 JDB Added iotrap parameter to UIG dispatchers for RTE microcode - 12-Sep-06 JDB iogrp returns NOTE_IOG to recalc interrupts - resolve returns NOTE_INDINT to service held-off interrupt - 16-Aug-06 JDB Added support for future microcode options, future F-Series - 09-Aug-06 JDB Added double integer microcode, 1000-M/E synonyms - Enhanced CPU option validity checking - Added DCPC as a synonym for DMA for 21MX simulations - 26-Dec-05 JDB Improved reporting in dev_conflict - 22-Sep-05 RMS Fixed declarations (from Sterling Garwood) - 21-Jan-05 JDB Reorganized CPU option flags - 15-Jan-05 RMS Split out EAU and MAC instructions - 26-Dec-04 RMS DMA reset doesn't clear alternate CTL flop (from Dave Bryan) - DMA reset shouldn't clear control words (from Dave Bryan) - Alternate CTL flop not visible as register (from Dave Bryan) - Fixed CBS, SBS, TBS to perform virtual reads - Separated A/B from M[0/1] for DMA IO (from Dave Bryan) - Fixed bug in JPY (from Dave Bryan) - 25-Dec-04 JDB Added SET CPU 21MX-M, 21MX-E (21MX defaults to MX-E) - TIMER/EXECUTE/DIAG instructions disabled for 21MX-M - T-register reflects changes in M-register when halted - 25-Sep-04 JDB Moved MP into its own device; added MP option jumpers - Modified DMA to allow disabling - Modified SET CPU 2100/2116 to truncate memory > 32K - Added -F switch to SET CPU to force memory truncation - Fixed S-register behavior on 2116 - Fixed LIx/MIx behavior for DMA on 2116 and 2100 - Fixed LIx/MIx behavior for empty I/O card slots - Modified WRU to be REG_HRO - Added BRK and DEL to save console settings - Fixed use of "unsigned int16" in cpu_reset - Modified memory size routine to return SCPE_INCOMP if - memory size truncation declined - 20-Jul-04 RMS Fixed bug in breakpoint test (reported by Dave Bryan) - Back up PC on instruction errors (from Dave Bryan) - 14-May-04 RMS Fixed bugs and added features from Dave Bryan - - SBT increments B after store - - DMS console map must check dms_enb - - SFS x,C and SFC x,C work - - MP violation clears automatically on interrupt - - SFS/SFC 5 is not gated by protection enabled - - DMS enable does not disable mem prot checks - - DMS status inconsistent at simulator halt - - Examine/deposit are checking wrong addresses - - Physical addresses are 20b not 15b - - Revised DMS to use memory rather than internal format - - Added instruction printout to HALT message - - Added M and T internal registers - - Added N, S, and U breakpoints - Revised IBL facility to conform to microcode - Added DMA EDT I/O pseudo-opcode - Separated DMA SRQ (service request) from FLG - 12-Mar-03 RMS Added logical name support - 02-Feb-03 RMS Fixed last cycle bug in DMA output (found by Mike Gemeny) - 22-Nov-02 RMS Added 21MX IOP support - 24-Oct-02 RMS Fixed bugs in IOP and extended instructions - Fixed bugs in memory protection and DMS - Added clock calibration - 25-Sep-02 RMS Fixed bug in DMS decode (found by Robert Alan Byer) - 26-Jul-02 RMS Restructured extended instructions, added IOP support - 22-Mar-02 RMS Changed to allocate memory array dynamically - 11-Mar-02 RMS Cleaned up setjmp/auto variable interaction - 17-Feb-02 RMS Added DMS support - Fixed bugs in extended instructions - 03-Feb-02 RMS Added terminal multiplexor support - Changed PCQ macro to use unmodified PC - Fixed flop restore logic (found by Bill McDermith) - Fixed SZx,SLx,RSS bug (found by Bill McDermith) - Added floating point support - 16-Jan-02 RMS Added additional device support - 07-Jan-02 RMS Fixed DMA register tables (found by Bill McDermith) - 07-Dec-01 RMS Revised to use breakpoint package - 03-Dec-01 RMS Added extended SET/SHOW support - 10-Aug-01 RMS Removed register in declarations - 26-Nov-00 RMS Fixed bug in dual device number routine - 21-Nov-00 RMS Fixed bug in reset routine - 15-Oct-00 RMS Added dynamic device number support - - References: - - 2100A Computer Reference Manual (02100-90001, Dec-1971) - - Model 2100A Computer Installation and Maintenance Manual - (02100-90002, Aug-1972) - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - HP 1000 M/E/F-Series Computers I/O Interfacing Guide - (02109-90006, Sep-1980) - - 12607A Direct Memory Access Operating and Service Manual - (12607-90002, Jan-1970) - - 12578A/12578A-01 Direct Memory Access Operating and Service Manual - (12578-9001, Mar-1972) - - 12892B Memory Protect Installation Manual (12892-90007, Jun-1978) - - - The register state for the HP 2116 CPU is: - - AR<15:0> A register - addressable as location 0 - BR<15:0> B register - addressable as location 1 - PC<14:0> P register - program counter - SR<15:0> S register - switch register - MR<14:0> M register - memory address - TR<15:0> T register - memory data - E extend flag (carry out) - O overflow flag - - The 2100 adds memory protection logic: - - mp_fence<14:0> memory fence register - mp_viol<15:0> memory protection violation register (F register) - - The 21MX adds a pair of index registers and memory expansion logic: - - XR<15:0> X register - YR<15:0> Y register - dms_sr<15:0> dynamic memory system status register - dms_vr<15:0> dynamic memory system violation register - - The original HP 2116 has four instruction formats: memory reference, - shift, alter/skip, and I/O. The HP 2100 added extended memory reference - and extended arithmetic. The HP21MX added extended byte, bit, and word - instructions as well as extended memory. - - The memory reference format is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |in| op |cp| offset | memory reference - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - - <14:11> mnemonic action - - 0010 AND A = A & M[MA] - 0011 JSB M[MA] = P, P = MA + 1 - 0100 XOR A = A ^ M[MA] - 0101 JMP P = MA - 0110 IOR A = A | M[MA] - 0111 ISZ M[MA] = M[MA] + 1, skip if M[MA] == 0 - 1000 ADA A = A + M[MA] - 1001 ADB B = B + M[MA] - 1010 CPA skip if A != M[MA] - 1011 CPB skip if B != M[MA] - 1100 LDA A = M[MA] - 1101 LDB B = M[MA] - 1110 STA M[MA] = A - 1111 STB M[MA] = B - - <15,10> mode action - - 0,0 page zero direct MA = IR<9:0> - 0,1 current page direct MA = PC<14:0>'IR,9:0> - 1,0 page zero indirect MA = M[IR<9:0>] - 1,1 current page indirect MA = M[PC<14:10>'IR<9:0>] - - Memory reference instructions can access an address space of 32K words. - An instruction can directly reference the first 1024 words of memory - (called page zero), as well as 1024 words of the current page; it can - indirectly access all 32K. - - The shift format is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 0 0 0 0|ab| 0|s1| op1 |ce|s2|sl| op2 | shift - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | | \---+---/ | | | \---+---/ - | | | | | | | - | | | | | | +---- shift 2 opcode - | | | | | +---------- skip if low bit == 0 - | | | | +------------- shift 2 enable - | | | +---------------- clear Extend - | | +---------------------- shift 1 opcode - | +---------------------------- shift 1 enable - +---------------------------------- A/B select - - The alter/skip format is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 0 0 0 0|ab| 1|regop| e op|se|ss|sl|in|sz|rs| alter/skip - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | \-+-/ \-+-/ | | | | | | - | | | | | | | | +- reverse skip sense - | | | | | | | +---- skip if register == 0 - | | | | | | +------- increment register - | | | | | +---------- skip if low bit == 0 - | | | | +------------- skip if sign bit == 0 - | | | +---------------- skip if Extend == 0 - | | +--------------------- clr/com/set Extend - | +--------------------------- clr/com/set register - +---------------------------------- A/B select - - The I/O transfer format is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 1 0 0 0|ab| 1|hc| opcode | device | I/O transfer - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | | \---+---/\-------+-------/ - | | | | - | | | +--------- device select - | | +---------------------- opcode - | +---------------------------- hold/clear flag - +---------------------------------- A/B select - - The IO transfer instruction controls the specified device. - Depending on the opcode, the instruction may set or clear - the device flag, start or stop I/O, or read or write data. - - The 2100 added an extended memory reference instruction; - the 21MX added extended arithmetic, operate, byte, word, - and bit instructions. Note that the HP 21xx is, despite - the right-to-left bit numbering, a big endian system. - Bits <15:8> are byte 0, and bits <7:0> are byte 1. - - - The extended memory reference format (HP 2100) is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 1| 0 0 0|op| 0| opcode | extended mem ref - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |in| operand address | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - - The extended arithmetic format (HP 2100) is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 1| 0 0 0 0 0|dr| 0 0| opcode |shift count| extended arithmetic - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - - The extended operate format (HP 21MX) is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 1| 0 0 0|op| 0| 1 1 1 1 1| opcode | extended operate - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - - The extended byte and word format (HP 21MX) is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 1| 0 0 0 1 0 1 1 1 1 1 1| opcode | extended byte/word - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |in| operand address | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0| - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - - The extended bit operate format (HP 21MX) is: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | 1| 0 0 0 1 0 1 1 1 1 1 1 1| opcode | extended bit operate - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |in| operand address | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |in| operand address | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - - General notes: - - 1. Reasons to stop. The simulator can be stopped by: - - HALT instruction - breakpoint encountered - infinite indirection loop - unimplemented instruction and stop_inst flag set - unknown I/O device and stop_dev flag set - I/O error in I/O simulator - - 2. Interrupts. I/O devices are modelled by substituting software states for - I/O backplane signals. Signals generated by I/O instructions and DMA - cycles are dispatched to the target device for action. Backplane signals - are processed sequentially, except for the "clear flag" signal, which may - be generated in parallel with another signal. For example, the "STC sc,C" - instruction generates the "set control" and the "clear flag" signals - concurrently. - - CPU interrupt signals are modelled as three parallel arrays: - - - device request priority as bit vector dev_prl [2] [31..0] - - device interrupt requests as bit vector dev_irq [2] [31..0] - - device service requests as bit vector dev_srq [2] [31..0] - - Each array forms a 64-bit vector, with bits 0-31 of the first element - corresponding to select codes 00-37 octal, and bits 0-31 of the second - element corresponding to select codes 40-77 octal. - - The HP 2100 interrupt structure is based on the PRH, PRL, IRQ, and IAK - signals. PRH indicates that no higher-priority device is interrupting. - PRL indicates to lower-priority devices that a given device is not - interrupting. IRQ indicates that a given device is requesting an - interrupt. IAK indicates that the given device's interrupt request is - being acknowledged. - - PRH and PRL form a hardware priority chain that extends from interface to - interface on the backplane. We model just PRL, as PRH is calculated from - the PRLs of higher-priority devices. - - Typical I/O devices have a flag, flag buffer, and control flip-flop. If a - device's flag, flag buffer, and control bits are set, and the device is - the highest priority on the interrupt chain, it requests an interrupt by - asserting IRQ. When the interrupt is acknowledged with IAK, the flag - buffer is cleared, preventing further interrupt requests from that device. - The combination of flag and control set blocks interrupts from lower - priority devices. - - Service requests are used to trigger the DMA service logic. Setting the - device flag typically also sets SRQ, although SRQ may be calculated - independently. - - 3. Non-existent memory. On the HP 2100, reads to non-existent memory - return zero, and writes are ignored. In the simulator, the - largest possible memory is instantiated and initialized to zero. - Thus, only writes need be checked against memory size. - - On the 21xx machines, doing SET CPU LOADERDISABLE decreases available - memory size by 64 words. - - 4. Adding I/O devices. These modules must be modified: - - hp2100_defs.h add interrupt request definition - hp2100_sys.c add sim_devices table entry - - 5. Instruction interruptibility. The simulator is fast enough, compared - to the run-time of the longest instructions, for interruptibility not - to matter. But the HP diagnostics explicitly test interruptibility in - EIS and DMS instructions, and long indirect address chains. Accordingly, - the simulator does "just enough" to pass these tests. In particular, if - an interrupt is pending but deferred at the beginning of an interruptible - instruction, the interrupt is taken at the appropriate point; but there - is no testing for new interrupts during execution (that is, the event - timer is not called). - - 6. Interrupt deferral. At instruction fetch time, a pending interrupt - request will be deferred if the previous instruction was a JMP indirect, - JSB indirect, STC, CLC, STF, CLF, or was executing from an interrupt trap - cell. In addition, the following instructions will cause deferral on the - 1000 series: SFS, SFC, JRS, DJP, DJS, SJP, SJS, UJP, and UJS. - - On the HP 1000, the request is always deferred until after the current - instruction completes. On the 21xx, the request is deferred unless the - current instruction is an MRG instruction other than JMP or JMP,I or - JSB,I. Note that for the 21xx, SFS and SFC are not included in the - deferral criteria. - - 7. Terminology. The 1000 series of computers was originally called the 21MX - at introduction. The 21MX (occasionally, 21MXM) corresponds to the 1000 - M-Series, and the 21MXE (occasionally, 21XE) corresponds to the 1000 - E-Series. The model numbers were changed before the introduction of the - 1000 F-Series, although some internal HP documentation refers to a 21MXF. - - The terms MEM (Memory Expansion Module), MEU (Memory Expansion Unit), DMI - (Dynamic Mapping Instructions), and DMS (Dynamic Mapping System) are used - somewhat interchangeably to refer to the logical-to-physical memory - address translation option provided on the 1000-Series. DMS consists of - the MEM card (12731A) and the DMI firmware (13307A). However, MEM and MEU - have been used interchangeably to refer to the mapping card, as have DMI - and DMS to refer to the firmware instructions. -*/ - - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - - -/* Memory protect constants */ - -#define UNIT_V_MP_JSB (UNIT_V_UF + 0) /* MP jumper W5 */ -#define UNIT_V_MP_INT (UNIT_V_UF + 1) /* MP jumper W6 */ -#define UNIT_V_MP_SEL1 (UNIT_V_UF + 2) /* MP jumper W7 */ -#define UNIT_MP_JSB (1 << UNIT_V_MP_JSB) /* 1 = W5 is out */ -#define UNIT_MP_INT (1 << UNIT_V_MP_INT) /* 1 = W6 is out */ -#define UNIT_MP_SEL1 (1 << UNIT_V_MP_SEL1) /* 1 = W7 is out */ - -/* DMA channels */ - -typedef enum { ch1, ch2 } CHANNEL; /* channel number */ - -#define DMA_CHAN_COUNT 2 /* number of DMA channels */ - -#define DMA_OE 020000000000 /* byte packing odd/even flag */ -#define DMA1_STC 0100000 /* DMA - issue STC */ -#define DMA1_PB 0040000 /* DMA - pack bytes */ -#define DMA1_CLC 0020000 /* DMA - issue CLC */ -#define DMA2_OI 0100000 /* DMA - output/input */ - -typedef struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - FLIP_FLOP xferen; /* transfer enable flip-flop */ - FLIP_FLOP select; /* register select flip-flop */ - - uint32 cw1; /* device select */ - uint32 cw2; /* direction, address */ - uint32 cw3; /* word count */ - uint32 packer; /* byte-packer holding reg */ - } DMA_STATE; - -#define DMA_1_REQ (1 << ch1) /* channel 1 request */ -#define DMA_2_REQ (1 << ch2) /* channel 2 request */ - - -/* Command line switches */ - -#define ALL_BKPTS (SWMASK('E')|SWMASK('N')|SWMASK('S')|SWMASK('U')) -#define ALL_MAPMODES (SWMASK('S')|SWMASK('U')|SWMASK('P')|SWMASK('Q')) - - -/* RTE base-page addresses. */ - -static const uint32 xeqt = 0001717; /* XEQT address */ -static const uint32 tbg = 0001674; /* TBG address */ - -/* DOS base-page addresses. */ - -static const uint32 m64 = 0000040; /* constant -64 address */ -static const uint32 p64 = 0000067; /* constant +64 address */ - -/* CPU local data */ - -static uint32 jsb_plb = 2; /* protected lower bound for JSB */ -static uint32 saved_MR = 0; /* between executions */ -static uint32 fwanxm = 0; /* first word addr of nx mem */ - -/* CPU global data */ - -uint16 *M = NULL; /* memory */ -uint16 ABREG[2]; /* A/B registers */ -uint32 PC = 0; /* P register */ -uint32 SR = 0; /* S register */ -uint32 MR = 0; /* M register */ -uint32 TR = 0; /* T register */ -uint32 XR = 0; /* X register */ -uint32 YR = 0; /* Y register */ -uint32 E = 0; /* E register */ -uint32 O = 0; /* O register */ -FLIP_FLOP ion = CLEAR; /* interrupt enable */ -t_bool ion_defer = FALSE; /* interrupt defer */ -uint32 intaddr = 0; /* interrupt addr */ -uint32 stop_inst = 1; /* stop on ill inst */ -uint32 stop_dev = 0; /* stop on ill dev */ -uint16 pcq[PCQ_SIZE] = { 0 }; /* PC queue */ -uint32 pcq_p = 0; /* PC queue ptr */ -REG *pcq_r = NULL; /* PC queue reg ptr */ - -uint32 dev_prl [2] = { ~(uint32) 0, ~(uint32) 0 }; /* device priority low bit vector */ -uint32 dev_irq [2] = { 0, 0 }; /* device interrupt request bit vector */ -uint32 dev_srq [2] = { 0, 0 }; /* device service request bit vector */ - -/* Memory protect global data */ - -FLIP_FLOP mp_control = CLEAR; /* MP control flip-flop */ -FLIP_FLOP mp_flag = CLEAR; /* MP flag flip-flop */ -FLIP_FLOP mp_flagbuf = CLEAR; /* MP flag buffer flip-flop */ -FLIP_FLOP mp_mevff = CLEAR; /* memory expansion violation flip-flop */ -FLIP_FLOP mp_evrff = SET; /* enable violation register flip-flop */ - -uint32 mp_fence = 0; /* MP fence register */ -uint32 mp_viol = 0; /* MP violation register */ - -uint32 iop_sp = 0; /* iop stack reg */ -uint32 ind_max = 16; /* iadr nest limit */ -uint32 err_PC = 0; /* error PC */ -jmp_buf save_env; /* MP abort handler */ - -/* DMA global data */ - -DMA_STATE dma [DMA_CHAN_COUNT]; /* per-channel state */ - -/* Dynamic mapping system global data */ - -uint32 dms_enb = 0; /* dms enable */ -uint32 dms_ump = 0; /* dms user map */ -uint32 dms_sr = 0; /* dms status reg */ -uint32 dms_vr = 0; /* dms violation reg */ -uint16 dms_map[MAP_NUM * MAP_LNT] = { 0 }; /* dms maps */ - -/* External data */ - -extern DIB clk_dib; /* CLK DIB for idle check */ -extern const BOOT_ROM ptr_rom, dq_rom, ms_rom, ds_rom; /* boot ROMs for cpu_boot routine */ - -/* CPU local routines */ - -static t_stat Ea (uint32 IR, uint32 *addr, uint32 irq); -static uint16 ReadTAB (uint32 va); -static uint32 dms (uint32 va, uint32 map, uint32 prot); -static uint32 shift (uint32 inval, uint32 flag, uint32 oper); -static t_stat dma_cycle (CHANNEL chan, uint32 map); -static uint32 calc_dma (void); -static t_bool dev_conflict (void); -static uint32 devdisp (uint32 select_code, IOCYCLE signal_set, uint16 data); - -/* CPU global routines */ - -t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); -t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); -t_stat cpu_reset (DEVICE *dptr); -t_stat cpu_boot (int32 unitno, DEVICE *dptr); -t_stat mp_reset (DEVICE *dptr); -t_stat dma_reset (DEVICE *dptr); -t_stat cpu_set_size (UNIT *uptr, int32 new_size, char *cptr, void *desc); -t_stat cpu_set_model (UNIT *uptr, int32 new_model, char *cptr, void *desc); -t_stat cpu_show_model (FILE *st, UNIT *uptr, int32 val, void *desc); -t_stat cpu_set_opt (UNIT *uptr, int32 option, char *cptr, void *desc); -t_stat cpu_clr_opt (UNIT *uptr, int32 option, char *cptr, void *desc); -t_stat cpu_set_ldr (UNIT *uptr, int32 enable, char *cptr, void *desc); -t_stat cpu_set_idle (UNIT *uptr, int32 option, char *cptr, void *desc); -t_stat cpu_show_idle (FILE *st, UNIT *uptr, int32 val, void *desc); -void hp_post_cmd (t_bool from_scp); - -IOHANDLER cpuio; -IOHANDLER ovflio; -IOHANDLER pwrfio; -IOHANDLER protio; -IOHANDLER dmapio; -IOHANDLER dmasio; -IOHANDLER nullio; - - -/* Table of CPU features by model. - - Fields: - - typ: standard features plus typically configured options. - - opt: complete list of optional features. - - maxmem: maximum configurable memory in 16-bit words. - - Features in the "typical" list are enabled when the CPU model is selected. - If a feature appears in the "typical" list but NOT in the "optional" list, - then it is standard equipment and cannot be disabled. If a feature appears - in the "optional" list, then it may be enabled or disabled as desired by the - user. -*/ - -struct FEATURE_TABLE { /* CPU model feature table: */ - uint32 typ; /* - typical features */ - uint32 opt; /* - optional features */ - uint32 maxmem; /* - maximum memory */ - }; - -static struct FEATURE_TABLE cpu_features[] = { /* features in UNIT_xxxx order*/ - { UNIT_DMA | UNIT_MP, /* UNIT_2116 */ - UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_EAU, - 32768 }, - { UNIT_DMA, /* UNIT_2115 */ - UNIT_PFAIL | UNIT_DMA | UNIT_EAU, - 8192 }, - { UNIT_DMA, /* UNIT_2114 */ - UNIT_PFAIL | UNIT_DMA, - 16384 }, - { 0, 0, 0 }, - { UNIT_PFAIL | UNIT_MP | UNIT_DMA | UNIT_EAU, /* UNIT_2100 */ - UNIT_DMA | UNIT_FP | UNIT_IOP | UNIT_FFP, - 32768 }, - { 0, 0, 0 }, - { 0, 0, 0 }, - { 0, 0, 0 }, - { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | UNIT_DMS, /* UNIT_1000_M */ - UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_DMS | - UNIT_IOP | UNIT_FFP | UNIT_DS, - 1048576 }, - { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | UNIT_DMS, /* UNIT_1000_E */ - UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_DMS | - UNIT_IOP | UNIT_FFP | UNIT_DBI | UNIT_DS | UNIT_EMA_VMA, - 1048576 }, - { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | /* UNIT_1000_F */ - UNIT_FFP | UNIT_DBI | UNIT_DMS, - UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_VIS | - UNIT_IOP | UNIT_DS | UNIT_SIGNAL | UNIT_EMA_VMA, - 1048576 } - }; - - -/* Null device information block */ - -DIB null_dib = { &nullio, 0 }; - -/* CPU data structures - - cpu_dib CPU device information block - cpu_dev CPU device descriptor - cpu_unit CPU unit descriptor - cpu_reg CPU register list - cpu_mod CPU modifiers list - cpu_deb CPU debug flags -*/ - -DIB cpu_dib = { &cpuio, CPU }; - -UNIT cpu_unit = { UDATA (NULL, UNIT_FIX + UNIT_BINK, 0) }; - -REG cpu_reg[] = { - { ORDATA (P, PC, 15) }, - { ORDATA (A, AR, 16), REG_FIT }, - { ORDATA (B, BR, 16), REG_FIT }, - { ORDATA (M, MR, 15) }, - { ORDATA (T, TR, 16), REG_RO }, - { ORDATA (X, XR, 16) }, - { ORDATA (Y, YR, 16) }, - { ORDATA (S, SR, 16) }, - { FLDATA (E, E, 0) }, - { FLDATA (O, O, 0) }, - { FLDATA (ION, ion, 0) }, - { FLDATA (ION_DEFER, ion_defer, 0) }, - { ORDATA (CIR, intaddr, 6) }, - { FLDATA (DMSENB, dms_enb, 0) }, - { FLDATA (DMSCUR, dms_ump, VA_N_PAG) }, - { ORDATA (DMSSR, dms_sr, 16) }, - { ORDATA (DMSVR, dms_vr, 16) }, - { BRDATA (DMSMAP, dms_map, 8, 16, MAP_NUM * MAP_LNT) }, - { ORDATA (IOPSP, iop_sp, 16) }, - { FLDATA (STOP_INST, stop_inst, 0) }, - { FLDATA (STOP_DEV, stop_dev, 1) }, - { DRDATA (INDMAX, ind_max, 16), REG_NZ + PV_LEFT }, - { BRDATA (PCQ, pcq, 8, 15, PCQ_SIZE), REG_RO+REG_CIRC }, - { ORDATA (PCQP, pcq_p, 6), REG_HRO }, - { ORDATA (JSBPLB, jsb_plb, 32), REG_HRO }, - { ORDATA (SAVEDMR, saved_MR, 32), REG_HRO }, - { ORDATA (FWANXM, fwanxm, 32), REG_HRO }, - { ORDATA (WRU, sim_int_char, 8), REG_HRO }, - { ORDATA (BRK, sim_brk_char, 8), REG_HRO }, - { ORDATA (DEL, sim_del_char, 8), REG_HRO }, - { BRDATA (PRL, dev_prl, 8, 32, 2), REG_HRO }, - { BRDATA (IRQ, dev_irq, 8, 32, 2), REG_HRO }, - { BRDATA (SRQ, dev_srq, 8, 32, 2), REG_HRO }, - { NULL } - }; - -/* CPU modifier table. - - The 21MX monikers are deprecated in favor of the 1000 designations. See the - "HP 1000 Series Naming History" on the back inside cover of the Technical - Reference Handbook. */ - -MTAB cpu_mod[] = { - { UNIT_MODEL_MASK, UNIT_2116, "", "2116", &cpu_set_model, &cpu_show_model, (void *) "2116" }, - { UNIT_MODEL_MASK, UNIT_2115, "", "2115", &cpu_set_model, &cpu_show_model, (void *) "2115" }, - { UNIT_MODEL_MASK, UNIT_2114, "", "2114", &cpu_set_model, &cpu_show_model, (void *) "2114" }, - { UNIT_MODEL_MASK, UNIT_2100, "", "2100", &cpu_set_model, &cpu_show_model, (void *) "2100" }, - { UNIT_MODEL_MASK, UNIT_1000_E, "", "1000-E", &cpu_set_model, &cpu_show_model, (void *) "1000-E" }, - { UNIT_MODEL_MASK, UNIT_1000_E, NULL, "21MX-E", &cpu_set_model, &cpu_show_model, (void *) "1000-E" }, - { UNIT_MODEL_MASK, UNIT_1000_M, "", "1000-M", &cpu_set_model, &cpu_show_model, (void *) "1000-M" }, - { UNIT_MODEL_MASK, UNIT_1000_M, NULL, "21MX-M", &cpu_set_model, &cpu_show_model, (void *) "1000-M" }, - -#if defined (HAVE_INT64) - { UNIT_MODEL_MASK, UNIT_1000_F, "", "1000-F", &cpu_set_model, &cpu_show_model, (void *) "1000-F" }, -#endif - - { MTAB_XTD | MTAB_VDV, 1, "IDLE", "IDLE", &cpu_set_idle, &cpu_show_idle, NULL }, - { MTAB_XTD | MTAB_VDV, 0, NULL, "NOIDLE", &cpu_set_idle, NULL, NULL }, - - { MTAB_XTD | MTAB_VDV, 1, NULL, "LOADERENABLE", &cpu_set_ldr, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 0, NULL, "LOADERDISABLE", &cpu_set_ldr, NULL, NULL }, - - { UNIT_EAU, UNIT_EAU, "EAU", "EAU", &cpu_set_opt, NULL, NULL }, - { UNIT_EAU, 0, "no EAU", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_EAU, NULL, "NOEAU", &cpu_clr_opt, NULL, NULL }, - - { UNIT_FP, UNIT_FP, "FP", "FP", &cpu_set_opt, NULL, NULL }, - { UNIT_FP, 0, "no FP", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_FP, NULL, "NOFP", &cpu_clr_opt, NULL, NULL }, - - { UNIT_IOP, UNIT_IOP, "IOP", "IOP", &cpu_set_opt, NULL, NULL }, - { UNIT_IOP, 0, "no IOP", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_IOP, NULL, "NOIOP", &cpu_clr_opt, NULL, NULL }, - - { UNIT_DMS, UNIT_DMS, "DMS", "DMS", &cpu_set_opt, NULL, NULL }, - { UNIT_DMS, 0, "no DMS", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_DMS, NULL, "NODMS", &cpu_clr_opt, NULL, NULL }, - - { UNIT_FFP, UNIT_FFP, "FFP", "FFP", &cpu_set_opt, NULL, NULL }, - { UNIT_FFP, 0, "no FFP", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_FFP, NULL, "NOFFP", &cpu_clr_opt, NULL, NULL }, - - { UNIT_DBI, UNIT_DBI, "DBI", "DBI", &cpu_set_opt, NULL, NULL }, - { UNIT_DBI, 0, "no DBI", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_DBI, NULL, "NODBI", &cpu_clr_opt, NULL, NULL }, - - { UNIT_EMA_VMA, UNIT_EMA, "EMA", "EMA", &cpu_set_opt, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_EMA, NULL, "NOEMA", &cpu_clr_opt, NULL, NULL }, - - { UNIT_EMA_VMA, UNIT_VMAOS, "VMA", "VMA", &cpu_set_opt, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_VMAOS, NULL, "NOVMA", &cpu_clr_opt, NULL, NULL }, - - { UNIT_EMA_VMA, 0, "no EMA/VMA", NULL, &cpu_set_opt, NULL, NULL }, - -#if defined (HAVE_INT64) - { UNIT_VIS, UNIT_VIS, "VIS", "VIS", &cpu_set_opt, NULL, NULL }, - { UNIT_VIS, 0, "no VIS", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_VIS, NULL, "NOVIS", &cpu_clr_opt, NULL, NULL }, - - { UNIT_SIGNAL, UNIT_SIGNAL,"SIGNAL", "SIGNAL", &cpu_set_opt, NULL, NULL }, - { UNIT_SIGNAL, 0, "no SIGNAL", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_SIGNAL, NULL, "NOSIGNAL", &cpu_clr_opt, NULL, NULL }, -#endif - -/* Future microcode support. - { UNIT_DS, UNIT_DS, "DS", "DS", &cpu_set_opt, NULL, NULL }, - { UNIT_DS, 0, "no DS", NULL, NULL, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, UNIT_DS, NULL, "NODS", &cpu_clr_opt, NULL, NULL }, -*/ - - { MTAB_XTD | MTAB_VDV, 4096, NULL, "4K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 8192, NULL, "8K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 12288, NULL, "12K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 16384, NULL, "16K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 24576, NULL, "24K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 32768, NULL, "32K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 65536, NULL, "64K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 131072, NULL, "128K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 262144, NULL, "256K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 524288, NULL, "512K", &cpu_set_size, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 1048576, NULL, "1024K", &cpu_set_size, NULL, NULL }, - { 0 } - }; - -DEBTAB cpu_deb[] = { - { "OS", DEB_OS }, - { "OSTBG", DEB_OSTBG }, - { "VMA", DEB_VMA }, - { "EMA", DEB_EMA }, - { "VIS", DEB_VIS }, - { "SIG", DEB_SIG }, - { NULL, 0 } - }; - -DEVICE cpu_dev = { - "CPU", /* device name */ - &cpu_unit, /* unit array */ - cpu_reg, /* register array */ - cpu_mod, /* modifier array */ - 1, /* number of units */ - 8, /* address radix */ - PA_N_SIZE, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 16, /* data width */ - &cpu_ex, /* examine routine */ - &cpu_dep, /* deposit routine */ - &cpu_reset, /* reset routine */ - &cpu_boot, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - &cpu_dib, /* device information block */ - DEV_DEBUG, /* device flags */ - 0, /* debug control flags */ - cpu_deb, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL }; /* logical device name */ - -/* Overflow device information block */ - -DIB ovfl_dib = { &ovflio, OVF }; - -/* Powerfail device information block */ - -DIB pwrf_dib = { &pwrfio, PWR }; - -/* Memory protect data structures - - mp_dib MP device information block - mp_dev MP device descriptor - mp_unit MP unit descriptor - mp_reg MP register list - mp_mod MP modifiers list -*/ - -DIB mp_dib = { &protio, PRO }; - -UNIT mp_unit = { UDATA (NULL, UNIT_MP_SEL1, 0) }; /* default is JSB in, INT in, SEL1 out */ - -REG mp_reg[] = { - { FLDATA (CTL, mp_control, 0) }, - { FLDATA (FLG, mp_flag, 0) }, - { FLDATA (FBF, mp_flagbuf, 0) }, - { ORDATA (FR, mp_fence, 15) }, - { ORDATA (VR, mp_viol, 16) }, - { FLDATA (EVR, mp_evrff, 0) }, - { FLDATA (MEV, mp_mevff, 0) }, - { NULL } - }; - -MTAB mp_mod[] = { - { UNIT_MP_JSB, UNIT_MP_JSB, "JSB (W5) out", "JSBOUT", NULL }, - { UNIT_MP_JSB, 0, "JSB (W5) in", "JSBIN", NULL }, - { UNIT_MP_INT, UNIT_MP_INT, "INT (W6) out", "INTOUT", NULL }, - { UNIT_MP_INT, 0, "INT (W6) in", "INTIN", NULL }, - { UNIT_MP_SEL1, UNIT_MP_SEL1, "SEL1 (W7) out", "SEL1OUT", NULL }, - { UNIT_MP_SEL1, 0, "SEL1 (W7) in", "SEL1IN", NULL }, - { 0 } - }; - -DEVICE mp_dev = { - "MP", /* device name */ - &mp_unit, /* unit array */ - mp_reg, /* register array */ - mp_mod, /* modifier array */ - 1, /* number of units */ - 8, /* address radix */ - 1, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 16, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &mp_reset, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - &mp_dib, /* device information block */ - DEV_DISABLE | DEV_DIS, /* device flags */ - 0, /* debug control flags */ - NULL, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL }; /* logical device name */ - -/* DMA controller data structures - - dmax_dib DMAx device information block - dmax_dev DMAx device descriptor - dmax_reg DMAx register list -*/ - -DIB dmap1_dib = { &dmapio, DMA1, ch1 }; -DIB dmas1_dib = { &dmasio, DMALT1, ch1 }; - -UNIT dma1_unit = { UDATA (NULL, 0, 0) }; - -REG dma1_reg[] = { - { FLDATA (XFR, dma [ch1].xferen, 0) }, - { FLDATA (CTL, dma [ch1].control, 0) }, - { FLDATA (FLG, dma [ch1].flag, 0) }, - { FLDATA (FBF, dma [ch1].flagbuf, 0) }, - { FLDATA (CTL2, dma [ch1].select, 0) }, - { ORDATA (CW1, dma [ch1].cw1, 16) }, - { ORDATA (CW2, dma [ch1].cw2, 16) }, - { ORDATA (CW3, dma [ch1].cw3, 16) }, - { FLDATA (BYTE, dma [ch1].packer, 31) }, - { ORDATA (PACKER, dma [ch1].packer, 8) }, - { NULL } - }; - -DEVICE dma1_dev = { - "DMA1", /* device name */ - &dma1_unit, /* unit array */ - dma1_reg, /* register array */ - NULL, /* modifier array */ - 1, /* number of units */ - 8, /* address radix */ - 1, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 16, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &dma_reset, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - &dmap1_dib, /* device information block */ - DEV_DISABLE, /* device flags */ - 0, /* debug control flags */ - NULL, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL }; /* logical device name */ - -DIB dmap2_dib = { &dmapio, DMA2, ch2 }; -DIB dmas2_dib = { &dmasio, DMALT2, ch2 }; - -UNIT dma2_unit = { UDATA (NULL, 0, 0) }; - -REG dma2_reg[] = { - { FLDATA (XFR, dma [ch2].xferen, 0) }, - { FLDATA (CTL, dma [ch2].control, 0) }, - { FLDATA (FLG, dma [ch2].flag, 0) }, - { FLDATA (FBF, dma [ch2].flagbuf, 0) }, - { FLDATA (CTL2, dma [ch2].select, 0) }, - { ORDATA (CW1, dma [ch2].cw1, 16) }, - { ORDATA (CW2, dma [ch2].cw2, 16) }, - { ORDATA (CW3, dma [ch2].cw3, 16) }, - { FLDATA (BYTE, dma [ch2].packer, 31) }, - { ORDATA (PACKER, dma [ch2].packer, 8) }, - { NULL } - }; - -DEVICE dma2_dev = { - "DMA2", /* device name */ - &dma2_unit, /* unit array */ - dma2_reg, /* register array */ - NULL, /* modifier array */ - 1, /* number of units */ - 8, /* address radix */ - 1, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 16, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &dma_reset, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - &dmap2_dib, /* device information block */ - DEV_DISABLE, /* device flags */ - 0, /* debug control flags */ - NULL, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL }; /* logical device name */ - -static DEVICE *dma_dptrs [] = { &dma1_dev, &dma2_dev }; - - -/* Interrupt deferral table (1000 version) */ -/* Deferral for I/O subops: soHLT, soFLG, soSFC, soSFS, soMIX, soLIX, soOTX, soCTL */ -static t_bool defer_tab [] = { FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE }; - -/* Device I/O dispatch table */ - -DIB *dtab [64] = { &cpu_dib, &ovfl_dib }; /* init with immutable devices */ - - - -/* Execute CPU instructions. - - This routine is the instruction decode routine for the HP 2100. It is called - from the simulator control program to execute instructions in simulated - memory, starting at the simulated PC. It runs until 'reason' is set to a - status other than SCPE_OK. -*/ - -t_stat sim_instr (void) -{ -uint32 intrq, dmarq; /* set after setjmp */ -uint32 iotrap = 0; /* set after setjmp */ -t_stat reason = SCPE_OK; /* set after setjmp */ -int32 i; /* temp */ -DEVICE *dptr; /* temp */ -DIB *dibptr; /* temp */ -int abortval; - -/* Restore register state */ - -if (dev_conflict ()) /* check device assignment consistency */ - return SCPE_STOP; /* conflict; stop execution */ - -err_PC = PC = PC & VAMASK; /* load local PC */ - -/* Restore I/O state */ - -dev_prl [0] = dev_prl [1] = ~(uint32) 0; /* set all priority lows */ -dev_irq [0] = dev_irq [1] = 0; /* clear all interrupt requests */ -dev_srq [0] = dev_srq [1] = 0; /* clear all service requests */ - -for (i = OPTDEV; i <= MAXDEV; i++) /* default optional devices */ - dtab [i] = &null_dib; - -dtab [PWR] = &pwrf_dib; /* for now, powerfail is always present */ - -for (i = 0; sim_devices [i] != NULL; i++) { /* loop thru dev */ - dptr = sim_devices [i]; - dibptr = (DIB *) dptr->ctxt; /* get DIB */ - - if (dibptr && !(dptr->flags & DEV_DIS)) { /* handler exists and device is enabled? */ - dtab [dibptr->select_code] = dibptr; /* set DIB pointer into dispatch table */ - dibptr->io_handler (dibptr, ioSIR, 0); /* set interrupt request state */ - } - } - -if (dtab [DMA1] != &null_dib) /* first DMA channel enabled? */ - dtab [DMALT1] = &dmas1_dib; /* set up secondary device handler */ - -if (dtab [DMA2] != &null_dib) /* second DMA channel enabled? */ - dtab [DMALT2] = &dmas2_dib; /* set up secondary device handler */ - -/* Configure interrupt deferral table */ - -if (UNIT_CPU_FAMILY == UNIT_FAMILY_21XX) /* 21xx series? */ - defer_tab [soSFC] = defer_tab [soSFS] = FALSE; /* SFC/S doesn't defer */ -else /* 1000 series */ - defer_tab [soSFC] = defer_tab [soSFS] = TRUE; /* SFC/S does defer */ - - -/* Set MP abort handling. - - If an abort occurs in memory protection, the relocation routine executes a - longjmp to this area OUTSIDE the main simulation loop. Memory protection - errors are the only sources of aborts in the HP 2100. All referenced - variables must be globals, and all sim_instr scoped automatics must be set - after the setjmp. - - To initiate an MP abort, use the MP_ABORT macro and pass the violation - address. MP_ABORT should only be called if "mp_control" is SET, as aborts do - not occur if MP is turned off. - - An MP interrupt (SC 05) is qualified by "ion" but not by "ion_defer". If the - interrupt system is off when an MP violation is detected, the violating - instruction will be aborted, even though no interrupt occurs. In this case, - neither the flag nor flag buffer are set, and EVR is not cleared. - - Implementation notes: - - 1. The protected lower bound address for the JSB instruction depends on the - W5 jumper setting. If W5 is in, then the lower bound is 2, allowing JSBs - to the A and B registers. If W5 is out, then the lower bound is 0, just - as with JMP. - - 2. The violation address is passed to enable the MEM violation register to - be updated. The "longjmp" routine will not pass a value of 0; it is - converted internally to 1. This is OK, because only the page number - of the address value is used, and locations 0 and 1 are both on page 0. - - 3. This routine is used both for MP and MEM violations. The MEV flip-flop - will be clear for the former and set for the latter. The MEV violation - register will be updated by "dms_upd_vr" only if the call is NOT for an - MEM violation; if it is, then the VR has already been set and should not - be disturbed. -*/ - -jsb_plb = (mp_unit.flags & UNIT_MP_JSB) ? 0 : 2; /* set protected lower bound for JSB */ - -abortval = setjmp (save_env); /* set abort hdlr */ - -if (abortval) { /* memory protect abort? */ - dms_upd_vr (abortval); /* update violation register (if not MEV) */ - - if (ion) /* interrupt system on? */ - protio (dtab [PRO], ioENF, 0); /* set flag */ - } - -dmarq = calc_dma (); /* initial recalc of DMA masks */ -intrq = calc_int (); /* initial recalc of interrupts */ - - -/* Main instruction fetch/decode loop */ - -while (reason == SCPE_OK) { /* loop until halted */ - uint32 IR, MA, absel, v1, t, skip; - - err_PC = PC; /* save PC for error recovery */ - - if (sim_interval <= 0) { /* event timeout? */ - reason = sim_process_event (); /* process event service */ - - if (reason != SCPE_OK) /* service failed? */ - break; /* stop execution */ - - dmarq = calc_dma (); /* recalc DMA reqs */ - intrq = calc_int (); /* recalc interrupts */ - } - -/* DMA cycles are requested by an I/O card asserting its SRQ signal. If a DMA - channel is programmed to respond to that card's select code, a DMA cycle will - be initiated. A DMA cycle consists of a memory cycle and an I/O cycle. - These cycles are synchronized with the control processor on the 21xx CPUs. - On the 1000s, memory cycles are asynchronous, while I/O cycles are - synchronous. Memory cycle time is about 40% of the I/O cycle time. - - With properly designed interface cards, DMA is capable of taking consecutive - I/O cycles. On all machines except the 1000 M-Series, a DMA cycle freezes - the CPU for the duration of the cycle. On the M-Series, a DMA cycle freezes - the CPU if it attempts an I/O cycle (including IAK) or a directly-interfering - memory cycle. An interleaved memory cycle is allowed. Otherwise, the - control processor is allowed to run. Therefore, during consecutive DMA - cycles, the M-Series CPU will run until an IOG instruction is attempted, - whereas the other CPUs will freeze completely. - - All DMA cards except the 12607B provide two independent channels. If both - channels are active simultaneously, channel 1 has priority for I/O cycles - over channel 2. - - Most I/O cards assert SRQ no more than 50% of the time. A few buffered - cards, such as the 12821A and 13175A Disc Interfaces, are capable of - asserting SRQ continuously while filling or emptying the buffer. If SRQ for - channel 1 is asserted continuously when both channels are active, then no - channel 2 cycles will occur until channel 1 completes. - - Implementation notes: - - 1. CPU freeze is simulated by skipping instruction execution during the - current loop cycle. - - 2. If both channels have SRQ asserted, DMA priority is simulated by skipping - the channel 2 cycle if channel 1's SRQ is still asserted at the end of - its cycle. If it is not, then channel 2 steals the next cycle from the - CPU. - - 3. The 1000 M-Series allows some CPU processing concurrently with - continuous DMA cycles, whereas all other CPUs freeze. The processor - freezes if an I/O cycle is attempted, including an interrupt - acknowledgement. Because some microcode extensions (e.g., Access IOP, - RTE-6/VM OS) perform I/O cycles, advance detection of I/O cycles is - difficult. Therefore, we freeze all processing for the M-Series as well. -*/ - - if (dmarq) { - if (dmarq & DMA_1_REQ) { /* DMA channel 1 request? */ - reason = dma_cycle (ch1, PAMAP); /* do one DMA cycle using port A map */ - - if (reason == SCPE_OK) /* cycle OK? */ - dmarq = calc_dma (); /* recalc DMA requests */ - else - break; /* cycle failed, so stop */ - } - - if ((dmarq & (DMA_1_REQ | DMA_2_REQ)) == DMA_2_REQ) { /* DMA channel 1 idle and channel 2 request? */ - reason = dma_cycle (ch2, PBMAP); /* do one DMA cycle using port B map */ - - if (reason == SCPE_OK) /* cycle OK? */ - dmarq = calc_dma (); /* recalc DMA requests */ - else - break; /* cycle failed, so stop */ - } - - if (dmarq) /* DMA request still pending? */ - continue; /* service it before instruction execution */ - - intrq = calc_int (); /* recalc interrupts */ - } - - if (intrq && ion_defer) /* interrupt pending but deferred? */ - ion_defer = calc_defer (); /* confirm deferral */ - -/* Check for pending interrupt request. - - Interrupt recognition is controlled by three state variables: "ion", - "ion_defer", and "intrq". "ion" corresponds to the INTSYS flip-flop in the - 1000 CPU, "ion_defer" corresponds to the INTEN flip-flop, and "intrq" - corresponds to the NRMINT flip-flop. STF 00 and CLF 00 set and clear INTSYS, - turning the interrupt system on and off. Micro-orders ION and IOFF set and - clear INTEN, deferring or allowing certain interrupts. An IRQ signal from a - device, qualified by the corresponding PRL signal, will set NRMINT to request - a normal interrupt; an IOFF or IAK will clear it. - - Under simulation, "ion" is controlled by STF/CLF 00. "ion_defer" is set or - cleared as appropriate by the individual instruction simulators. "intrq" is - set to the successfully interrupting device's select code, or to zero if - there is no qualifying interrupt request. - - Presuming PRL is set to allow priority to an interrupting device: - - 1. Power fail (SC 04) may interrupt if "ion_defer" is clear; this is not - conditional on "ion" being set. - - 2. Memory protect (SC 05) may interrupt if "ion" is set; this is not - conditional on "ion_defer" being clear. - - 3. Parity error (SC 05) may interrupt always; this is not conditional on - "ion" being set or "ion_defer" being clear. - - 4. All other devices (SC 06 and up) may interrupt if "ion" is set and - "ion_defer" is clear. - - Qualification with "ion" is performed by "calc_int", except for case 2, which - is qualified by the MP abort handler above (because qualification occurs on - the MP card, rather than in the CPU). Therefore, we need only qualify by - "ion_defer" here. -*/ - - if (intrq && ((intrq == PRO) || !ion_defer)) { /* interrupt request? */ - if (sim_brk_summ && /* any breakpoints? */ - sim_brk_test (intrq, SWMASK ('E') | /* unconditional or right type for DMS? */ - (dms_enb ? SWMASK ('S') : SWMASK ('N')))) { - reason = STOP_IBKPT; /* stop simulation */ - break; - } - - intaddr = intrq; /* save int addr in CIR */ - intrq = 0; /* clear request */ - ion_defer = TRUE; /* defer interrupts */ - iotrap = 1; /* mark as I/O trap cell instr */ - - if (dms_enb) /* dms enabled? */ - dms_sr = dms_sr | MST_ENBI; /* set in status */ - else /* not enabled */ - dms_sr = dms_sr & ~MST_ENBI; /* clear in status */ - - if (dms_ump) { /* user map enabled at interrupt? */ - dms_sr = dms_sr | MST_UMPI; /* set in status */ - dms_ump = SMAP; /* switch to system map */ - } - else /* system map enabled at interrupt */ - dms_sr = dms_sr & ~MST_UMPI; /* clear in status */ - - IR = ReadW (intaddr); /* get trap cell instruction */ - - devdisp (intaddr, ioIAK, (uint16) IR); /* acknowledge interrupt */ - - if (intaddr != PRO) /* not MP interrupt? */ - protio (dtab [intaddr], ioIAK, IR); /* send IAK for device to MP too */ - } - - else { /* normal instruction */ - iotrap = 0; /* not a trap cell instruction */ - - if (sim_brk_summ && /* any breakpoints? */ - sim_brk_test (PC, SWMASK ('E') | /* unconditional or */ - (dms_enb ? /* correct type for DMS state? */ - (dms_ump ? - SWMASK ('U') : SWMASK ('S')) : - SWMASK ('N')))) { - reason = STOP_IBKPT; /* stop simulation */ - break; - } - - if (mp_evrff) /* violation register enabled */ - mp_viol = PC; /* update with current PC */ - - IR = ReadW (PC); /* fetch instr */ - PC = (PC + 1) & VAMASK; - ion_defer = FALSE; - } - - sim_interval = sim_interval - 1; /* count instruction */ - -/* Instruction decode. The 21MX does a 256-way decode on IR<15:8> - - 15 14 13 12 11 10 09 08 instruction - - x <-!= 0-> x x x x memory reference - 0 0 0 0 x 0 x x shift - 0 0 0 0 x 0 x x alter-skip - 1 0 0 0 x 1 x x IO - 1 0 0 0 0 0 x 0 extended arithmetic - 1 0 0 0 0 0 0 1 divide (decoded as 100400) - 1 0 0 0 1 0 0 0 double load (decoded as 104000) - 1 0 0 0 1 0 0 1 double store (decoded as 104400) - 1 0 0 0 1 0 1 0 extended instr group 0 (A/B must be set) - 1 0 0 0 x 0 1 1 extended instr group 1 (A/B ignored) */ - - absel = (IR & I_AB) ? 1 : 0; /* get A/B select */ - - switch ((IR >> 8) & 0377) { /* decode IR<15:8> */ - -/* Memory reference instructions */ - - case 0020:case 0021:case 0022:case 0023: - case 0024:case 0025:case 0026:case 0027: - case 0220:case 0221:case 0222:case 0223: - case 0224:case 0225:case 0226:case 0227: - reason = Ea (IR, &MA, intrq); /* AND */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - AR = AR & ReadW (MA); - break; - -/* JSB is a little tricky. It is possible to generate both an MP and a DM - violation simultaneously, as the MP and MEM cards validate in parallel. - Consider a JSB to a location under the MP fence and on a write-protected - page. This situation must be reported as a DM violation, because it has - priority (SFS 5 and SFC 5 check only the MEVFF, which sets independently of - the MP fence violation). Under simulation, this means that DM violations - must be checked, and the MEVFF must be set, before an MP abort is taken. - This is done by the "mp_dms_jmp" routine. -*/ - - case 0230:case 0231:case 0232:case 0233: - case 0234:case 0235:case 0236:case 0237: - ion_defer = TRUE; /* defer if JSB,I */ - - case 0030:case 0031:case 0032:case 0033: - case 0034:case 0035:case 0036:case 0037: - reason = Ea (IR, &MA, intrq); /* JSB */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - mp_dms_jmp (MA, jsb_plb); /* validate jump address */ - - WriteW (MA, PC); /* store PC */ - PCQ_ENTRY; - PC = (MA + 1) & VAMASK; /* jump */ - break; - - case 0040:case 0041:case 0042:case 0043: - case 0044:case 0045:case 0046:case 0047: - case 0240:case 0241:case 0242:case 0243: - case 0244:case 0245:case 0246:case 0247: - reason = Ea (IR, &MA, intrq); /* XOR */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - AR = AR ^ ReadW (MA); - break; - -/* CPU idle processing. - - The 21xx/1000 CPUs have no "wait for interrupt" instruction. Idling in HP - operating systems consists of sitting in "idle loops" that end with JMP - instructions. We test for certain known patterns when a JMP instruction is - executed to decide if the simulator should idle. - - Idling must not occur if an interrupt is pending. As mentioned in the - "General Notes" above, HP CPUs will defer interrupts if certain instructions - are executed. OS interrupt handlers exit via such deferring instructions. - If there is a pending interrupt when the OS is otherwise idle, the idle loop - will execute one instruction before reentering the interrupt handler. If we - call sim_idle() in this case, we will lose interrupts. - - Consider the situation in RTE. Under simulation, the TTY and CLK events are - co-scheduled, with the CLK expiring one instruction after the TTY. When the - TTY interrupts, $CIC in RTE is entered. One instruction later, the CLK - expires and posts its interrupt, but it is not immediately handled, because - the JSB $CIC,I / JMP $CIC0,I / SFS 0,C instruction entry sequence continually - defers interrupts until the interrupt system is turned off. When $CIC - returns via $IRT, one instruction of the idle loop is executed, even though - the CLK interrupt is still pending, because the UJP instruction used to - return also defers interrupts. - - If sim_idle() is called at this point, the simulator will sleep when it - should be handling the pending CLK interrupt. When it awakes, TTY expiration - will be moved forward to the next instruction. The still-pending CLK - interrupt will then be recognized, and $CIC will be entered. But the TTY and - then the CLK will then expire and attempt to interrupt again, although they - are deferred by the $CIC entry sequence. This causes the second CLK - interrupt to be missed, as processing of the first one is just now being - started. - - Similarly, at the end of the CLK handling, the TTY interrupt is still - pending. When $IRT returns to the idle loop, sim_idle() would be called - again, so the TTY and then CLK interrupt a third time. Because the second - TTY interrupt is still pending, $CIC is entered, but the third TTY interrupt - is lost. - - We solve this problem by testing for a pending interrupt before calling - sim_idle(). The system isn't really quiescent if it is just about to handle - an interrupt. -*/ - - case 0250:case 0251:case 0252:case 0253: - case 0254:case 0255:case 0256:case 0257: - ion_defer = TRUE; /* defer if JMP,I */ - - case 0050:case 0051:case 0052:case 0053: - case 0054:case 0055:case 0056:case 0057: - reason = Ea (IR, &MA, intrq); /* JMP */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - mp_dms_jmp (MA, 0); /* validate jump addr */ - PCQ_ENTRY; - PC = MA; /* jump */ - -/* Idle conditions by operating system: - - RTE-6/VM: - - ISZ / JMP *-1 - - mp_fence = 0 - - XEQT (address 1717B) = 0 - - DMS on with system map enabled - - RTE verification: TBG (address 1674B) = CLK select code - - RTE though RTE-IVB: - - JMP * - - mp_fence = 0 - - XEQT (address 1717B) = 0 - - DMS on with user map enabled (RTE-III through RTE-IVB only) - - RTE verification: TBG (address 1674B) = CLK select code - - DOS through DOS-III: - - STF 0 / CCA / CCB / JMP *-3 - - DOS verification: A = B = -1, address 40B = -64, address 67B = +64 - - Note that in DOS, the TBG is set to 100 milliseconds -*/ - - if ((sim_idle_enab) && (intrq == 0)) /* idle enabled w/o pending irq? */ - if (((PC == err_PC) || /* RTE through RTE-IVB */ - ((PC == (err_PC - 1)) && /* RTE-6/VM */ - ((ReadW (PC) & I_MRG) == I_ISZ))) && /* RTE jump target */ - (mp_fence == CLEAR) && (M [xeqt] == 0) && /* RTE idle indications */ - (M [tbg] == clk_dib.select_code) || /* RTE verification */ - - (PC == (err_PC - 3)) && /* DOS through DOS-III */ - (ReadW (PC) == I_STF) && /* DOS jump target */ - (AR == 0177777) && (BR == 0177777) && /* DOS idle indication */ - (M [m64] == 0177700) && /* DOS verification */ - (M [p64] == 0000100)) /* DOS verification */ - - sim_idle (TMR_POLL, FALSE); /* idle the simulator */ - break; - - case 0060:case 0061:case 0062:case 0063: - case 0064:case 0065:case 0066:case 0067: - case 0260:case 0261:case 0262:case 0263: - case 0264:case 0265:case 0266:case 0267: - reason = Ea (IR, &MA, intrq); /* IOR */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - AR = AR | ReadW (MA); - break; - - case 0070:case 0071:case 0072:case 0073: - case 0074:case 0075:case 0076:case 0077: - case 0270:case 0271:case 0272:case 0273: - case 0274:case 0275:case 0276:case 0277: - reason = Ea (IR, &MA, intrq); /* ISZ */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - t = (ReadW (MA) + 1) & DMASK; - WriteW (MA, t); - - if (t == 0) - PC = (PC + 1) & VAMASK; - break; - - case 0100:case 0101:case 0102:case 0103: - case 0104:case 0105:case 0106:case 0107: - case 0300:case 0301:case 0302:case 0303: - case 0304:case 0305:case 0306:case 0307: - reason = Ea (IR, &MA, intrq); /* ADA */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - v1 = ReadW (MA); - t = AR + v1; - - if (t > DMASK) - E = 1; - - if (((~AR ^ v1) & (AR ^ t)) & SIGN) - O = 1; - - AR = t & DMASK; - break; - - case 0110:case 0111:case 0112:case 0113: - case 0114:case 0115:case 0116:case 0117: - case 0310:case 0311:case 0312:case 0313: - case 0314:case 0315:case 0316:case 0317: - reason = Ea (IR, &MA, intrq); /* ADB */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - v1 = ReadW (MA); - t = BR + v1; - - if (t > DMASK) - E = 1; - - if (((~BR ^ v1) & (BR ^ t)) & SIGN) - O = 1; - - BR = t & DMASK; - break; - - case 0120:case 0121:case 0122:case 0123: - case 0124:case 0125:case 0126:case 0127: - case 0320:case 0321:case 0322:case 0323: - case 0324:case 0325:case 0326:case 0327: - reason = Ea (IR, &MA, intrq); /* CPA */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - if (AR != ReadW (MA)) - PC = (PC + 1) & VAMASK; - break; - - case 0130:case 0131:case 0132:case 0133: - case 0134:case 0135:case 0136:case 0137: - case 0330:case 0331:case 0332:case 0333: - case 0334:case 0335:case 0336:case 0337: - reason = Ea (IR, &MA, intrq); /* CPB */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - if (BR != ReadW (MA)) - PC = (PC + 1) & VAMASK; - break; - - case 0140:case 0141:case 0142:case 0143: - case 0144:case 0145:case 0146:case 0147: - case 0340:case 0341:case 0342:case 0343: - case 0344:case 0345:case 0346:case 0347: - reason = Ea (IR, &MA, intrq); /* LDA */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - AR = ReadW (MA); - break; - - case 0150:case 0151:case 0152:case 0153: - case 0154:case 0155:case 0156:case 0157: - case 0350:case 0351:case 0352:case 0353: - case 0354:case 0355:case 0356:case 0357: - reason = Ea (IR, &MA, intrq); /* LDB */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - BR = ReadW (MA); - break; - - case 0160:case 0161:case 0162:case 0163: - case 0164:case 0165:case 0166:case 0167: - case 0360:case 0361:case 0362:case 0363: - case 0364:case 0365:case 0366:case 0367: - reason = Ea (IR, &MA, intrq); /* STA */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - WriteW (MA, AR); - break; - - case 0170:case 0171:case 0172:case 0173: - case 0174:case 0175:case 0176:case 0177: - case 0370:case 0371:case 0372:case 0373: - case 0374:case 0375:case 0376:case 0377: - reason = Ea (IR, &MA, intrq); /* STB */ - - if (reason != SCPE_OK) /* address failed to resolve? */ - break; /* stop execution */ - - WriteW (MA, BR); - break; - -/* Alter/skip instructions */ - - case 0004:case 0005:case 0006:case 0007: - case 0014:case 0015:case 0016:case 0017: - skip = 0; /* no skip */ - - if (IR & 000400) /* CLx */ - t = 0; - else - t = ABREG[absel]; - - if (IR & 001000) /* CMx */ - t = t ^ DMASK; - - if (IR & 000001) { /* RSS? */ - if ((IR & 000040) && (E != 0)) /* SEZ,RSS */ - skip = 1; - - if (IR & 000100) /* CLE */ - E = 0; - - if (IR & 000200) /* CME */ - E = E ^ 1; - - if (((IR & 000030) == 000030) && /* SSx,SLx,RSS */ - ((t & 0100001) == 0100001)) - skip = 1; - - if (((IR & 000030) == 000020) && /* SSx,RSS */ - ((t & SIGN) != 0)) - skip = 1; - - if (((IR & 000030) == 000010) && /* SLx,RSS */ - ((t & 1) != 0)) - skip = 1; - - if (IR & 000004) { /* INx */ - t = (t + 1) & DMASK; - - if (t == 0) - E = 1; - - if (t == SIGN) - O = 1; - } - - if ((IR & 000002) && (t != 0)) /* SZx,RSS */ - skip = 1; - - if ((IR & 000072) == 0) /* RSS */ - skip = 1; - } /* end if RSS */ - - else { - if ((IR & 000040) && (E == 0)) /* SEZ */ - skip = 1; - - if (IR & 000100) /* CLE */ - E = 0; - - if (IR & 000200) /* CME */ - E = E ^ 1; - - if ((IR & 000020) && /* SSx */ - ((t & SIGN) == 0)) - skip = 1; - - if ((IR & 000010) && /* SLx */ - ((t & 1) == 0)) - skip = 1; - - if (IR & 000004) { /* INx */ - t = (t + 1) & DMASK; - - if (t == 0) - E = 1; - - if (t == SIGN) - O = 1; - } - if ((IR & 000002) && (t == 0)) /* SZx */ - skip = 1; - } /* end if ~RSS */ - - ABREG[absel] = (uint16) t; /* store result */ - PC = (PC + skip) & VAMASK; /* add in skip */ - break; /* end if alter/skip */ - -/* Shift instructions */ - - case 0000:case 0001:case 0002:case 0003: - case 0010:case 0011:case 0012:case 0013: - t = shift (ABREG[absel], IR & 01000, IR >> 6); /* do first shift */ - - if (IR & 000040) /* CLE */ - E = 0; - - if ((IR & 000010) && ((t & 1) == 0)) /* SLx */ - PC = (PC + 1) & VAMASK; - - ABREG[absel] = (uint16) shift (t, IR & 00020, IR); /* do second shift */ - break; /* end if shift */ - -/* I/O instructions */ - - case 0204:case 0205:case 0206:case 0207: - case 0214:case 0215:case 0216:case 0217: - reason = iogrp (IR, iotrap); /* execute instr */ - break; /* end if I/O */ - -/* Extended arithmetic */ - - case 0200: /* EAU group 0 */ - case 0201: /* divide */ - case 0202: /* EAU group 2 */ - case 0210: /* DLD */ - case 0211: /* DST */ - reason = cpu_eau (IR, intrq); /* extended arith */ - break; - -/* Extended instructions */ - - case 0212: /* UIG 0 extension */ - reason = cpu_uig_0 (IR, intrq, iotrap); /* extended opcode */ - break; - - case 0203: /* UIG 1 extension */ - case 0213: - reason = cpu_uig_1 (IR, intrq, iotrap); /* extended opcode */ - break; - } /* end case IR */ - - if (reason == NOTE_IOG) { /* I/O instr exec? */ - dmarq = calc_dma (); /* recalc DMA masks */ - intrq = calc_int (); /* recalc interrupts */ - reason = SCPE_OK; /* continue */ - } - - else if (reason == NOTE_INDINT) { /* intr pend during indir? */ - PC = err_PC; /* back out of inst */ - reason = SCPE_OK; /* continue */ - } - } /* end while */ - -/* Simulation halted */ - -if (iotrap && (reason == STOP_HALT)) /* HLT in trap cell? */ - MR = intaddr; /* M = interrupt address */ -else /* normal HLT */ - MR = (PC - 1) & VAMASK; /* M = P - 1 */ - -TR = ReadTAB (MR); /* T = last word fetched */ -saved_MR = MR; /* save for T cmd update */ - -if (reason == STOP_HALT) /* programmed halt? */ - cpu_set_ldr (NULL, FALSE, NULL, NULL); /* disable loader (after T is read) */ -else /* simulation stop */ - PC = err_PC; /* back out instruction */ - -dms_upd_sr (); /* update dms_sr */ -dms_upd_vr (MR); /* update dms_vr */ -pcq_r->qptr = pcq_p; /* update pc q ptr */ - -if (dms_enb) /* DMS enabled? */ - if (dms_ump) /* set default */ - sim_brk_dflt = SWMASK ('U'); /* breakpoint type */ - else /* to current */ - sim_brk_dflt = SWMASK ('S'); /* map mode */ - -else /* DMS disabled */ - sim_brk_dflt = SWMASK ('N'); /* set breakpoint type to non-DMS */ - -return reason; /* return status code */ -} - - -/* Resolve indirect addresses. - - An indirect chain is followed until a direct address is obtained. Under - simulation, a maximum number of indirect levels are allowed (typically 16), - after which the instruction will be aborted. - - If the memory protect feature is present, an indirect counter is used that - allows a pending interrupt to be serviced if more than three levels of - indirection are encountered. If MP jumper W6 ("INT") is out and MP is - enabled, then pending interrupts are serviced immediately. When employing - the indirect counter, the hardware clears a pending interrupt deferral after - the third indirection and aborts the instruction after the fourth. -*/ - -t_stat resolve (uint32 MA, uint32 *addr, uint32 irq) -{ -uint32 i; -t_bool pending = (irq && !(mp_unit.flags & DEV_DIS)); -t_bool int_enable = ((mp_unit.flags & UNIT_MP_INT) && mp_control); - -for (i = 0; (i < ind_max) && (MA & I_IA); i++) { /* resolve multilevel */ - if (pending) { /* interrupt pending and MP enabled? */ - if ((i == 2) || int_enable) /* 3rd level indirect or INT out? */ - ion_defer = FALSE; /* reenable interrrupts */ - if ((i > 2) || int_enable) /* 4th or higher or INT out? */ - return NOTE_INDINT; /* break out now */ - } - - MA = ReadW (MA & VAMASK); /* follow address chain */ - } - -if (MA & I_IA) /* indirect loop? */ - return STOP_IND; /* stop simulation */ - -*addr = MA; -return SCPE_OK; -} - - -/* Get effective address from IR */ - -static t_stat Ea (uint32 IR, uint32 *addr, uint32 irq) -{ -uint32 MA; - -MA = IR & (I_IA | I_DISP); /* ind + disp */ - -if (IR & I_CP) /* current page? */ - MA = ((PC - 1) & I_PAGENO) | MA; /* merge in page from PC */ - -return resolve (MA, addr, irq); /* resolve indirects */ -} - - -/* Shift micro operation */ - -static uint32 shift (uint32 t, uint32 flag, uint32 op) -{ -uint32 oldE; - -op = op & 07; /* get shift op */ -if (flag) { /* enabled? */ - switch (op) { /* case on operation */ - - case 00: /* signed left shift */ - return ((t & SIGN) | ((t << 1) & 077777)); - - case 01: /* signed right shift */ - return ((t & SIGN) | (t >> 1)); - - case 02: /* rotate left */ - return (((t << 1) | (t >> 15)) & DMASK); - - case 03: /* rotate right */ - return (((t >> 1) | (t << 15)) & DMASK); - - case 04: /* left shift, 0 sign */ - return ((t << 1) & 077777); - - case 05: /* ext right rotate */ - oldE = E; - E = t & 1; - return ((t >> 1) | (oldE << 15)); - - case 06: /* ext left rotate */ - oldE = E; - E = (t >> 15) & 1; - return (((t << 1) | oldE) & DMASK); - - case 07: /* rotate left four */ - return (((t << 4) | (t >> 12)) & DMASK); - } /* end case */ - } /* end if */ - -if (op == 05) /* disabled ext rgt rot */ - E = t & 1; - -if (op == 06) /* disabled ext lft rot */ - E = (t >> 15) & 1; - -return t; /* input unchanged */ -} - - -/* I/O instruction decode. - - If memory protect is enabled, and the instruction is not in a trap cell, then - HLT instructions are illegal and will cause a memory protect violation. If - jumper W7 (SEL1) is in, then all other I/O instructions are legal; if W7 is - out, then only I/O instructions to select code 1 are legal, and I/O to other - select codes will cause a violation. - - If the instruction is allowed, then the I/O signal corresponding to the - instruction is determined, the state of the interrupt deferral flag is set. - Then the signal is dispatched to the device simulator indicated by the target - select code. The return value is split into status and data values, with the - latter containing the SKF signal state or data to be returned in the A or B - registers. - - Implementation notes: - - 1. If the H/C (hold/clear flag) bit is set, then the ioCLF signal is added - (not ORed) to the base signal derived from the I/O instruction. - - 2. ioNONE is dispatched for HLT instructions because although HLT does not - assert any backplane signals, the H/C bit may be set. If it is, then the - result will be to dispatch ioCLF. - - 3. Device simulators return either ioSKF or ioNONE in response to an SFC or - SFS signal. ioSKF means that the instruction should skip. Because - device simulators return the "data" parameter value by default, we - initialize that parameter to ioNONE to ensure that a simulator that does - not implement SFC or SFS does not skip, which is the correct action for - an interface that does not drive the SKF signal. - - 4. STF/CLF and STC/CLC share sub-opcode values and must be further decoded - by the state of instruction register bits 9 and 11, respectively. - - 5. We return NOTE_IOG for normal status instead of SCPE_OK to request that - interrupts be recalculated at the end of the instruction (execution of - the I/O group instructions can change the interrupt priority chain). -*/ - -t_stat iogrp (uint32 ir, uint32 iotrap) -{ - -/* Translation for I/O subopcodes: soHLT, soFLG, soSFC, soSFS, soMIX, soLIX, soOTX, soCTL */ -static const IOSIGNAL generate_signal [] = { ioNONE, ioSTF, ioSFC, ioSFS, ioIOI, ioIOI, ioIOO, ioSTC }; - -const uint32 dev = ir & I_DEVMASK; /* device select code */ -const uint32 sop = I_GETIOOP (ir); /* I/O subopcode */ -const uint32 ab = (ir & I_AB) != 0; /* A/B register select */ -const t_bool clf = (ir & I_HC) != 0; /* H/C flag select */ -uint16 iodata = (uint16) ioNONE; /* initialize for SKF test */ -uint32 ioreturn; -t_stat iostat; -IOCYCLE signal_set; - -if (!iotrap && mp_control && /* instr not in trap cell and MP on? */ - ((sop == soHLT) || /* and is HLT? */ - ((dev != OVF) && (mp_unit.flags & UNIT_MP_SEL1)))) { /* or is not SC 01 and SEL1 out? */ - if (sop == soLIX) /* MP violation; is LIA/B instruction? */ - ABREG [ab] = 0; /* A/B writes anyway */ - - MP_ABORT (err_PC); /* MP abort */ - } - -signal_set = generate_signal [sop]; /* generate I/O signal from instruction */ -ion_defer = defer_tab [sop]; /* defer depending on instruction */ - -if (sop == soOTX) /* OTA/B instruction? */ - iodata = ABREG [ab]; /* pass A/B register value */ - -else if ((sop == soCTL) && (ir & I_CTL)) /* CLC instruction? */ - signal_set = ioCLC; /* change STC to CLC signal */ - -if ((sop == soFLG) && clf) /* CLF instruction? */ - signal_set = ioCLF; /* change STF to CLF signal */ - -else if (clf) /* CLF with another instruction? */ - signal_set = signal_set | ioCLF; /* add CLF signal */ - -ioreturn = devdisp (dev, signal_set, iodata); /* dispatch I/O signal */ - -iostat = IOSTATUS (ioreturn); /* extract status */ -iodata = IODATA (ioreturn); /* extract return data value */ - -if (((sop == soSFC) || (sop == soSFS)) && /* testing flag state? */ - ((IOSIGNAL) iodata == ioSKF)) /* and SKF asserted? */ - PC = (PC + 1) & VAMASK; /* bump P to skip next instruction */ - -else if (sop == soLIX) /* LIA/B instruction? */ - ABREG [ab] = iodata; /* load returned data */ - -else if (sop == soMIX) /* MIA/B instruction? */ - ABREG [ab] = ABREG [ab] | iodata; /* merge returned data */ - -else if (sop == soHLT) { /* HLT instruction? */ - return STOP_HALT; /* return halt status */ - } - -if (iostat == SCPE_OK) /* normal status? */ - return NOTE_IOG; /* request interrupt recalc */ -else /* abnormal status */ - return iostat; /* return it */ -} - - -/* Device I/O signal dispatcher */ - -static uint32 devdisp (uint32 select_code, IOCYCLE signal_set, uint16 data) -{ -return dtab [select_code]->io_handler (dtab [select_code], - signal_set, - IORETURN (SCPE_OK, data)); -} - - -/* Calculate DMA requests */ - -static uint32 calc_dma (void) -{ -uint32 r = 0; - -if (dma [ch1].xferen && SRQ (dma [ch1].cw1 & I_DEVMASK)) /* check DMA1 cycle */ - r = r | DMA_1_REQ; -if (dma [ch2].xferen && SRQ (dma [ch2].cw1 & I_DEVMASK)) /* check DMA2 cycle */ - r = r | DMA_2_REQ; -return r; -} - - -/* Determine whether a pending interrupt deferral should be inhibited. - - Execution of certain instructions generally cause a pending interrupt to be - deferred until the succeeding instruction completes. However, the interrupt - deferral rules differ on the 21xx vs. the 1000. - - The 1000 always defers until the completion of the instruction following a - deferring instruction. The 21xx defers unless the following instruction is - an MRG instruction other than JMP or JMP,I or JSB,I. If it is, then the - deferral is inhibited, i.e., the pending interrupt will be serviced. - - See the "Set Phase Logic Flowchart," transition from phase 1A to phase 1B, - and the "Theory of Operation," "Control Section Detailed Theory," "Phase - Control Logic," "Phase 1B" paragraph in the Model 2100A Computer Installation - and Maintenance Manual for details. -*/ - -t_bool calc_defer (void) -{ -uint16 IR; - -if (UNIT_CPU_FAMILY == UNIT_FAMILY_21XX) { /* 21xx series? */ - IR = ReadW (PC); /* prefetch next instr */ - - if (((IR & I_MRG & ~I_AB) != 0000000) && /* is MRG instruction? */ - ((IR & I_MRG_I) != I_JSB_I) && /* but not JSB,I? */ - ((IR & I_MRG) != I_JMP)) /* and not JMP or JMP,I? */ - return FALSE; /* yes, so inhibit deferral */ - else - return TRUE; /* no, so allow deferral */ - } -else - return TRUE; /* 1000 always allows deferral */ -} - - -/* Calculate interrupt requests. - - The interrupt request (IRQ) of the highest-priority device for which all - higher-priority PRL bits are set is granted. That is, there must be an - unbroken chain of priority to a device requesting an interrupt for that - request to be granted. - - A device sets its IRQ bit to request an interrupt, and it clears its PRL bit - to prevent lower-priority devices from interrupting. IRQ is cleared by an - interrupt acknowledge (IAK) signal. PRL generally remains low while a - device's interrupt service routine is executing to prevent preemption. - - IRQ and PRL indicate one of four possible states for a device: - - IRQ PRL Device state - --- --- ---------------------- - 0 1 Not interrupting - 1 0 Interrupt requested - 0 0 Interrupt acknowledged - 1 1 (not allowed) - - Note that PRL must be dropped when requesting an interrupt (IRQ set). This - is a hardware requirement of the 1000 series. The IRQ lines from the - backplane are not priority encoded. Instead, the PRL chain expresses the - priority by allowing only one IRQ line to be active at a time. This allows a - simple pull-down encoding of the CIR inputs. - - The end of priority chain is marked by the highest-priority (lowest-order) - bit that is clear. The device corresponding to that bit is the only device - that may interrupt (a higher priority device that had IRQ set would also have - had PRL set, which is a state violation). We calculate a priority mask by - ANDing the complement of the PRL bits with an increment of the PRL bits. - Only the lowest-order bit will differ. For example: - - dev_prl : ...1 1 0 1 1 0 1 1 1 1 1 1 (PRL denied for SC 06 and 11) - - dev_prl + 1 : ...1 1 0 1 1 1 0 0 0 0 0 0 - ~dev_prl : ...0 0 1 0 0 1 0 0 0 0 0 0 - ANDed value : ...0 0 0 0 0 1 0 0 0 0 0 0 (break is at SC 06) - - The interrupt requests are then ANDed with the priority mask to determine if - a request is pending: - - pri mask : ...0 0 0 0 0 1 0 0 0 0 0 0 (allowed interrupt source) - dev_irq : ...0 0 1 0 0 1 0 0 0 0 0 0 (devices requesting interrupts) - ANDed value : ...0 0 0 0 0 1 0 0 0 0 0 0 (request to grant) - - The select code corresponding to the granted request is then returned to the - caller. - - If ION is clear, only power fail (SC 04) and parity error (SC 05) are - eligible to interrupt (memory protect shares SC 05, but qualification occurs - in the MP abort handler, so if SC 05 is interrupting when ION is clear, it - must be a parity error interrupt). -*/ - -uint32 calc_int (void) -{ -uint32 sc, pri_mask [2], req_grant [2]; - -pri_mask [0] = ~dev_prl [0] & (dev_prl [0] + 1); /* calculate lower priority mask */ -req_grant [0] = pri_mask [0] & dev_irq [0]; /* calculate lower request to grant */ - -if (ion) /* interrupt system on? */ - if ((req_grant [0] == 0) && (pri_mask [0] == 0)) { /* no requests in lower set and PRL unbroken? */ - pri_mask [1] = ~dev_prl [1] & (dev_prl [1] + 1); /* calculate upper priority mask */ - req_grant [1] = pri_mask [1] & dev_irq [1]; /* calculate upper request to grant */ - } - else /* lower set has request */ - req_grant [1] = 0; /* no grants to upper set */ - -else { /* interrupt system off */ - req_grant [0] = req_grant [0] & /* only PF and PE can interrupt */ - (BIT_M (PWR) | BIT_M (PRO)); - req_grant [1] = 0; - } - -if (req_grant [0]) /* device in lower half? */ - for (sc = 0; sc <= 31; sc++) /* determine interrupting select code */ - if (req_grant [0] & 1) /* grant this request? */ - return sc; /* return this select code */ - else /* not this one */ - req_grant [0] = req_grant [0] >> 1; /* position next request */ - -else if (req_grant [1]) /* device in upper half */ - for (sc = 32; sc <= 63; sc++) /* determine interrupting select code */ - if (req_grant [1] & 1) /* grant this request? */ - return sc; /* return this select code */ - else /* not this one */ - req_grant [1] = req_grant [1] >> 1; /* position next request */ - -return 0; /* no interrupt granted */ -} - - -/* Memory access routines. - - These routines access memory for reads and writes. They validate the - accesses for MP and MEM violations, if enabled. The following routines are - provided: - - - 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 - - - WritePW : Write a word using a physical address - - WriteB : Write a byte using the current map - - WriteBA : Write a byte using the alternate map - - WriteW : Write a word using the current map - - WriteWA : Write a word using the alternate map - - WriteIO : Write a word using the specified map without protection - - The memory protect (MP) and memory expansion module (MEM) accessories provide - a protected mode that guards against improper accesses by user programs. - They may be enabled or disabled independently, although protection requires - that both be enabled. MP checks that memory writes do not fall below the - Memory Protect Fence Register (MPFR) value, and MEM checks that read/write - protection rules on the target page are compatible with the access desired. - If either check fails, and MP is enabled, then the request is aborted. - - Each mapped routine calls "dms" if DMS is enabled to translate the logical - address supplied to a physical address. "dms" performs a protection check - and aborts without returning if the check fails. The write routines perform - an additional memory-protect check and abort if a violation occurs (so, to - pass, a page must be writable AND the target must be above the MP fence). - - Note that MP uses a lower bound of 2 for memory writes, allowing unrestricted - access to the A and B registers (addressed as locations 0 and 1). -*/ - -#define MP_TEST(va) (mp_control && ((va) >= 2) && ((va) < mp_fence)) - - -/* Read a word using a physical address */ - -uint16 ReadPW (uint32 pa) -{ -if (pa <= 1) /* read locations 0 or 1? */ - return ABREG[pa]; /* return A/B register */ -else /* location >= 2 */ - return M[pa]; /* return physical memory value */ -} - - -/* Read a byte using the current map */ - -uint8 ReadB (uint32 va) -{ -int32 pa; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va >> 1, dms_ump, RDPROT); /* translate address */ -else /* MEM disabled */ - pa = va >> 1; /* use logical as physical address */ - -if (va & 1) /* low byte addressed? */ - return (ReadPW (pa) & 0377); /* mask to lower byte */ -else /* high byte addressed */ - return ((ReadPW (pa) >> 8) & 0377); /* position higher byte and mask */ -} - - -/* Read a byte using the alternate map */ - -uint8 ReadBA (uint32 va) -{ -uint32 pa; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va >> 1, dms_ump ^ MAP_LNT, RDPROT); /* translate address using alternate map */ -else /* MEM disabled */ - pa = va >> 1; /* use logical as physical address */ - -if (va & 1) /* low byte addressed? */ - return (ReadPW (pa) & 0377); /* mask to lower byte */ -else /* high byte addressed */ - return ((ReadPW (pa) >> 8) & 0377); /* position higher byte and mask */ -} - - -/* Read a word using the current map */ - -uint16 ReadW (uint32 va) -{ -uint32 pa; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va, dms_ump, RDPROT); /* translate address */ -else /* MEM disabled */ - pa = va; /* use logical as physical address */ - -return ReadPW (pa); /* return word */ -} - - -/* Read a word using the alternate map */ - -uint16 ReadWA (uint32 va) -{ -uint32 pa; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va, dms_ump ^ MAP_LNT, RDPROT); /* translate address using alternate map */ -else /* MEM disabled */ - pa = va; /* use logical as physical address */ - -return ReadPW (pa); /* return word */ -} - - -/* Read a word using the specified map without protection */ - -uint16 ReadIO (uint32 va, uint32 map) -{ -uint32 pa; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va, map, NOPROT); /* translate address with no protection */ -else /* MEM disabled */ - pa = va; /* use logical as physical address */ - -return M[pa]; /* return word without A/B interception */ -} - - -/* Read a word using the current map without protection */ - -static uint16 ReadTAB (uint32 va) -{ -uint32 pa; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va, dms_ump, NOPROT); /* translate address with no protection */ -else /* MEM disabled */ - pa = va; /* use logical as physical address */ - -return ReadPW (pa); /* return word */ -} - - -/* Write a word using a physical address */ - -void WritePW (uint32 pa, uint32 dat) -{ -if (pa <= 1) /* write locations 0 or 1? */ - ABREG[pa] = dat & DMASK; /* store A/B register */ -else if (pa < fwanxm) /* 2 <= location <= LWA memory? */ - M[pa] = dat & DMASK; /* store physical memory value */ - -return; -} - - -/* Write a byte using the current map */ - -void WriteB (uint32 va, uint32 dat) -{ -uint32 pa, t; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va >> 1, dms_ump, WRPROT); /* translate address */ -else /* MEM disabled */ - pa = va >> 1; /* use logical as physical address */ - -if (MP_TEST (va >> 1)) /* MPCK? */ - MP_ABORT (va >> 1); /* MP violation */ - -t = ReadPW (pa); /* get word */ - -if (va & 1) /* low byte addressed? */ - t = (t & 0177400) | (dat & 0377); /* merge in lower byte */ -else /* high byte addressed */ - t = (t & 0377) | ((dat & 0377) << 8); /* position higher byte and merge */ - -WritePW (pa, t); /* store word */ -return; -} - - -/* Write a byte using the alternate map */ - -void WriteBA (uint32 va, uint32 dat) -{ -uint32 pa, t; - -if (dms_enb) { /* MEM enabled? */ - dms_viol (va >> 1, MVI_WPR); /* always a violation if protected */ - pa = dms (va >> 1, dms_ump ^ MAP_LNT, WRPROT); /* translate address using alternate map */ - } -else /* MEM disabled */ - pa = va >> 1; /* use logical as physical address */ - -if (MP_TEST (va >> 1)) /* MPCK? */ - MP_ABORT (va >> 1); /* MP violation */ - -t = ReadPW (pa); /* get word */ - -if (va & 1) /* low byte addressed? */ - t = (t & 0177400) | (dat & 0377); /* merge in lower byte */ -else /* high byte addressed */ - t = (t & 0377) | ((dat & 0377) << 8); /* position higher byte and merge */ - -WritePW (pa, t); /* store word */ -return; -} - - -/* Write a word using the current map */ - -void WriteW (uint32 va, uint32 dat) -{ -uint32 pa; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va, dms_ump, WRPROT); /* translate address */ -else /* MEM disabled */ - pa = va; /* use logical as physical address */ - -if (MP_TEST (va)) /* MPCK? */ - MP_ABORT (va); /* MP violation */ - -WritePW (pa, dat); /* store word */ -return; -} - - -/* Write a word using the alternate map */ - -void WriteWA (uint32 va, uint32 dat) -{ -int32 pa; - -if (dms_enb) { /* MEM enabled? */ - dms_viol (va, MVI_WPR); /* always a violation if protected */ - pa = dms (va, dms_ump ^ MAP_LNT, WRPROT); /* translate address using alternate map */ - } -else /* MEM disabled */ - pa = va; /* use logical as physical address */ - -if (MP_TEST (va)) /* MPCK? */ - MP_ABORT (va); /* MP violation */ - -WritePW (pa, dat); /* store word */ -return; -} - - -/* Write a word using the specified map without protection */ - -void WriteIO (uint32 va, uint32 dat, uint32 map) -{ -uint32 pa; - -if (dms_enb) /* MEM enabled? */ - pa = dms (va, map, NOPROT); /* translate address with no protection */ -else /* MEM disabled */ - pa = va; /* use logical as physical address */ - -if (pa < fwanxm) - M[pa] = dat & DMASK; /* store word without A/B interception */ -return; -} - - -/* Mapped access check. - - Returns TRUE if the address will be mapped (presuming MEM is enabled). -*/ - -static t_bool is_mapped (uint32 va) -{ -uint32 dms_fence; - -if (va >= 02000) /* above the base bage? */ - return TRUE; /* always mapped */ -else { - dms_fence = dms_sr & MST_FENCE; /* get BP fence value */ - return (dms_sr & MST_FLT) ? (va < dms_fence) : /* below BP fence and lower portion mapped? */ - (va >= dms_fence); /* or above BP fence and upper portion mapped? */ - } -} - - -/* DMS relocation. - - This routine translates logical into physical addresses. It must be called - only when DMS is enabled, as that condition is not checked. The logical - address, desired map, and desired access type are supplied. If the access is - legal, the mapped physical address is returned; if it is not, then a MEM - violation is indicated. - - The current map may be specified by passing "dms_ump" as the "map" parameter, - or a specific map may be used. Normally, read and write accesses pass RDPROT - or WRPROT as the "prot" parameter to request access checking. For DMA - accesses, NOPROT must be passed to inhibit access checks. - - This routine checks for read, write, and base-page violations and will call - "dms_viol" as appropriate. The latter routine will abort if MP is enabled, - or will return if protection is off. -*/ - -static uint32 dms (uint32 va, uint32 map, uint32 prot) -{ -uint32 pgn, mpr; - -if (va <= 1) /* reference to A/B register? */ - return va; /* use address */ - -if (!is_mapped (va)) { /* unmapped? */ - if ((va >= 2) && (prot == WRPROT)) /* base page write access? */ - dms_viol (va, MVI_BPG); /* signal a base page violation */ - return va; /* use unmapped address */ - } - -pgn = VA_GETPAG (va); /* get page num */ -mpr = dms_map[map + pgn]; /* get map reg */ - -if (mpr & prot) /* desired access disallowed? */ - dms_viol (va, prot); /* signal protection violation */ - -return (MAP_GETPAG (mpr) | VA_GETOFF (va)); /* return mapped address */ -} - - -/* DMS relocation for console access. - - Console access allows the desired map to be specified by switches on the - command line. All protection checks are off for console access. - - This routine is called to restore a saved configuration, and mapping is not - used for restoration. -*/ - -static uint32 dms_cons (t_addr va, int32 sw) -{ -uint32 map_sel; - -if ((dms_enb == 0) || /* DMS off? */ - (sw & (SWMASK ('N') | SIM_SW_REST))) /* no mapping rqst or save/rest? */ - return (uint32) va; /* use physical address */ - -else if (sw & SWMASK ('S')) - map_sel = SMAP; - -else if (sw & SWMASK ('U')) - map_sel = UMAP; - -else if (sw & SWMASK ('P')) - map_sel = PAMAP; - -else if (sw & SWMASK ('Q')) - map_sel = PBMAP; - -else /* dflt to log addr, cur map */ - map_sel = dms_ump; - -if (va >= VASIZE) /* virtual, must be 15b */ - return (uint32) MEMSIZE; - -else if (dms_enb) /* DMS on? go thru map */ - return dms ((uint32) va, map_sel, NOPROT); - -else /* else return virtual */ - return (uint32) va; -} - - -/* Memory protect and DMS validation for jumps. - - Jumps are a special case of write validation. The target address is treated - as a write, even when no physical write takes place, so jumping to a - write-protected page causes a MEM violation. In addition, a MEM violation is - indicated if the jump is to the unmapped portion of the base page. Finally, - jumping to a location under the memory-protect fence causes an MP violation. - - Because the MP and MEM hardware works in parallel, all three violations may - exist concurrently. For example, a JMP to the unmapped portion of the base - page that is write protected and under the MP fence will indicate a - base-page, write, and MP violation, whereas a JMP to the mapped portion will - indicate a write and MP violation (BPV is inhibited by the MEBEN signal). If - MEM and MP violations occur concurrently, the MEM violation takes precedence, - as the SFS and SFC instructions test the MEV flip-flop. - - The lower bound of protected memory is passed in the "plb" argument. This - must be either 0 or 2. All violations are qualified by the MPCND signal, - which responds to the lower bound. Therefore, if the lower bound is 2, and - if the part below the base-page fence is unmapped, or if the base page is - write-protected, then a MEM violation will occur only if the access is not to - locations 0 or 1. The instruction set firmware uses a lower bound of 0 for - JMP, JLY, and JPY (and for JSB with W5 out), and of 2 for DJP, SJP, UJP, JRS, - and .GOTO (and JSB with W5 in). - - Finally, all violations are inhibited if MP is off (mp_control is CLEAR), and - MEM violations are inhibited if the MEM is disabled. -*/ - -void mp_dms_jmp (uint32 va, uint32 plb) -{ -uint32 violation = 0; -uint32 pgn = VA_GETPAG (va); /* get page number */ - -if (mp_control) { /* MP on? */ - if (dms_enb) { /* MEM on? */ - if (dms_map [dms_ump + pgn] & WRPROT) /* page write protected? */ - violation = MVI_WPR; /* write violation occured */ - - if (!is_mapped (va) && (va >= plb)) /* base page target? */ - violation = violation | MVI_BPG; /* base page violation occured */ - - if (violation) /* any violation? */ - dms_viol (va, violation); /* signal MEM violation */ - } - - if ((va >= plb) && (va < mp_fence)) /* jump under fence? */ - MP_ABORT (va); /* signal MP violation */ - } - -return; -} - - -/* DMS read and write map registers */ - -uint16 dms_rmap (uint32 mapi) -{ -mapi = mapi & MAP_MASK; -return (dms_map[mapi] & ~MAP_RSVD); -} - -void dms_wmap (uint32 mapi, uint32 dat) -{ -mapi = mapi & MAP_MASK; -dms_map[mapi] = (uint16) (dat & ~MAP_RSVD); -return; -} - - -/* Process a MEM violation. - - A MEM violation will report the cause in the violation register. This occurs - even if the MEM is not in the protected mode (i.e., MP is not enabled). If - MP is enabled, an MP abort is taken with the MEV flip-flop set. Otherwise, - we return to the caller. -*/ - -void dms_viol (uint32 va, uint32 st) -{ -dms_vr = st | dms_upd_vr (va); /* set violation cause in register */ - -if (mp_control) { /* memory protect on? */ - mp_mevff = SET; /* record memory expansion violation */ - MP_ABORT (va); /* abort */ - } -return; -} - - -/* Update the MEM violation register. - - In hardware, the MEM violation register (VR) is clocked on every memory read, - every memory write above the lower bound of protected memory, and every - execution of a privileged DMS instruction. The register is not clocked when - MP is disabled by an MP or MEM error (i.e., when MEVFF sets or CTL5FF - clears), in order to capture the state of the MEM. In other words, the VR - continually tracks the memory map register accessed plus the MEM state - (MEBEN, MAPON, and USR) until a violation occurs, and then it's "frozen." - - Under simulation, we do not have to update the VR on every memory access, - because the visible state is only available via a programmed RVA/B - instruction or via the SCP interface. Therefore, it is sufficient if the - register is updated: - - - at a MEM violation (when freezing) - - at an MP violation (when freezing) - - during RVA/B execution (if not frozen) - - before returning to SCP after a simulator stop (if not frozen) -*/ - -uint32 dms_upd_vr (uint32 va) -{ -if (mp_control && (mp_mevff == CLEAR)) { /* violation register unfrozen? */ - dms_vr = VA_GETPAG (va) | /* set map address */ - (dms_enb ? MVI_MEM : 0) | /* and MEM enabled */ - (dms_ump ? MVI_UMP : 0); /* and user map enabled */ - - if (is_mapped (va)) /* is addressed mapped? */ - dms_vr = dms_vr | MVI_MEB; /* ME bus is enabled */ - } - -return dms_vr; -} - - -/* Update the MEM status register */ - -uint32 dms_upd_sr (void) -{ -dms_sr = dms_sr & ~(MST_ENB | MST_UMP | MST_PRO); - -if (dms_enb) - dms_sr = dms_sr | MST_ENB; - -if (dms_ump) - dms_sr = dms_sr | MST_UMP; - -if (mp_control) - dms_sr = dms_sr | MST_PRO; - -return dms_sr; -} - - -/* CPU (SC 0) I/O signal handler. - - I/O instructions for select code 0 manipulate the interrupt system. STF and - CLF turn the interrupt system on and off, and SFS and SFC test the state of - the interrupt system. When the interrupt system is off, only power fail and - parity error interrupts are allowed. - - A PON reset initializes certain CPU registers. The 1000 series does a - microcoded memory clear and leaves the T and P registers set as a result. - - Front-panel PRESET performs additional initialization. We also handle MEM - preset here. - - Implementation notes: - - 1. An IOI signal reads the floating I/O bus (0 on all machines). - - 2. A CLC 0 issues CRS to all devices, not CLC. While most cards react - identically to CRS and CLC, some do not, e.g., the 12566B when used as an - I/O diagnostic target. - - 3. RTE uses the undocumented SFS 0,C instruction to both test and turn off - the interrupt system. This is confirmed in the "RTE-6/VM Technical - Specifications" manual (HP 92084-90015), section 2.3.1 "Process the - Interrupt", subsection "A.1 $CIC": - - "Test to see if the interrupt system is on or off. This is done with - the SFS 0,C instruction. In either case, turn it off (the ,C does - it)." - - ...and in section 5.8, "Parity Error Detection": - - "Because parity error interrupts can occur even when the interrupt - system is off, the code at $CIC must be able to save the complete - system status. The major hole in being able to save the complete state - is in saving the interrupt system state. In order to do this in both - the 21MX and the 21XE the instruction 103300 was used to both test the - interrupt system and turn it off." - - 4. Select code 0 cannot interrupt, so there is no SIR handler. -*/ - -uint32 cpuio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -uint32 sc; -IOSIGNAL signal; -IOCYCLE working_set = signal_set; /* no SIR handler needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - ion = CLEAR; /* turn interrupt system off */ - break; - - case ioSTF: /* set flag flip-flop */ - ion = SET; /* turn interrupt system on */ - break; - - case ioSFC: /* skip if flag is clear */ - setSKF (!ion); /* skip if interrupt system is off */ - break; - - case ioSFS: /* skip if flag is set */ - setSKF (ion); /* skip if interupt system is on */ - break; - - case ioIOI: /* I/O input */ - stat_data = IORETURN (SCPE_OK, 0); /* returns 0 */ - break; - - case ioPON: /* power on normal */ - AR = 0; /* clear A register */ - BR = 0; /* clear B register */ - SR = 0; /* clear S register */ - TR = 0; /* clear T register */ - E = 1; /* set E register */ - - if (UNIT_CPU_FAMILY == UNIT_FAMILY_1000) { /* 1000 series? */ - memset (M, 0, (uint32) MEMSIZE * 2); /* zero allocated memory */ - MR = 0077777; /* set M register */ - PC = 0100000; /* set P register */ - } - - else { /* 21xx series */ - MR = 0; /* clear M register */ - PC = 0; /* clear P register */ - } - break; - - case ioPOPIO: /* power-on preset to I/O */ - O = 0; /* clear O register */ - ion = CLEAR; /* turn off interrupt system */ - ion_defer = FALSE; /* clear interrupt deferral */ - - dms_enb = 0; /* turn DMS off */ - dms_ump = 0; /* init to system map */ - dms_sr = 0; /* clear status register and BP fence */ - dms_vr = 0; /* clear violation register */ - break; - - case ioCLC: /* clear control flip-flop */ - for (sc = CRSDEV; sc <= MAXDEV; sc++) /* send CRS to devices */ - devdisp (sc, ioCRS, 0); /* from select code 6 and up */ - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Overflow/S-register (SC 1) I/O signal handler. - - Flag instructions directed to select code 1 manipulate the overflow (O) - register. Input and output instructions access the switch (S) register. On - the 2115 and 2116, there is no S-register indicator, so it is effectively - read-only. On the other machines, a front-panel display of the S-register is - provided. On all machines, front-panel switches are provided to set the - contents of the S register. - - Implementation notes: - - 1. Select code 1 cannot interrupt, so there is no SIR handler. -*/ - -uint32 ovflio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = signal_set; /* no SIR handler needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - O = 0; /* clear overflow */ - break; - - case ioSTF: /* set flag flip-flop */ - O = 1; /* set overflow */ - break; - - case ioSFC: /* skip if flag is clear */ - setSKF (!O); /* skip if overflow is clear */ - break; - - case ioSFS: /* skip if flag is set */ - setSKF (O); /* skip if overflow is set */ - break; - - case ioIOI: /* I/O input */ - stat_data = IORETURN (SCPE_OK, SR); /* read switch register value */ - break; - - case ioIOO: /* I/O output */ - if ((UNIT_CPU_MODEL != UNIT_2116) && /* no S register display on */ - (UNIT_CPU_MODEL != UNIT_2115)) /* 2116 and 2115 machines */ - SR = IODATA (stat_data); /* write S register value */ - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Power fail (SC 4) I/O signal handler. - - Power fail detection is standard on 2100 and 1000 systems and is optional on - 21xx systems. Power fail recovery is standard on the 2100 and optional on - the others. Power failure or restoration will cause an interrupt on select - code 4. The direction of power change (down or up) can be tested by SFC. - - We do not implement power fail under simulation. However, the central - interrupt register (CIR) is always read by an IOI directed to select code 4. -*/ - -uint32 pwrfio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioSTC: /* set control flip-flop */ - break; /* reinitializes power fail */ - - case ioCLC: /* clear control flip-flop */ - break; /* reinitializes power fail */ - - case ioSFC: /* skip if flag is clear */ - break; /* skips if power fail occurred */ - - case ioIOI: /* I/O input */ - stat_data = IORETURN (SCPE_OK, intaddr); /* input CIR value */ - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Memory protect/parity error (SC 5) I/O signal handler. - - The memory protect card has a number of non-standard features: - - - CLF and STF affect the parity error enable flip-flop, not the flag - - SFC and SFS test the memory expansion violation flip-flop, not the flag - - POPIO clears control, flag, and flag buffer instead of setting the flags - - CLC does not clear control (the only way to turn off MP is to cause a - violation) - - PRL and IRQ are a function of the flag only, not flag and control - - IAK is used unqualified by IRQ - - The IAK backplane signal is asserted when any interrupt is acknowledged by - the CPU. Normally, an interface qualifies IAK with its own IRQ to ensure - that it responds only to an acknowledgement of its own request. The MP card - does this to reset its flag buffer and flag flip-flops, and to reset the - parity error indication. However, it also responds to an unqualified IAK - (i.e., for any interface) as follows: - - - clears the MPV flip-flop - - clears the indirect counter - - clears the control flip-flop - - sets the INTPT flip-flop - - The INTPT flip-flop indicates an occurrence of an interrupt. If the trap - cell of the interrupting device contains an I/O instruction that is not a - HLT, action equivalent to STC 05 is taken, i.e.: - - - sets the control flip-flop - - set the EVR flip-flop - - clears the MEV flip-flop - - clears the PARERR flip-flop - - In other words, an interrupt for any device will disable MP unless the trap - cell contains an I/O instruction other than a HLT. - - Implementation notes: - - 1. Because the card uses IAK unqualified, this routine is called whenever - any interrupt occurs. If the MP card itself is not interrupting, the - select code passed will not be SC 05. In either case, the trap cell - instruction is passed in the data portion of the "stat_data" parameter. - - 2. The MEV flip-flop records memory expansion (a.k.a. dynamic mapping) - violations. It is set when an DM violation is encountered and can be - tested via SFC/SFS. - - 3. MP cannot be turned off in hardware, except by causing a violation. - Microcode typically does this by executing an IOG micro-order with select - code /= 1, followed by an IAK to clear the interrupt and a FTCH to clear - the INTPT flip-flop. Under simulation, mp_control may be set to CLEAR to - produce the same effect. - - 4. Parity error logic is not implemented. -*/ - -uint32 protio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -uint16 data; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - break; /* turns off PE interrupt */ - - case ioSTF: /* set flag flip-flop */ - break; /* turns on PE interrupt */ - - case ioENF: /* enable flag */ - mp_flag = mp_flagbuf = SET; /* set flag buffer and flag flip-flops */ - mp_evrff = CLEAR; /* inhibit violation register updates */ - break; - - case ioSFC: /* skip if flag is clear */ - setSKF (!mp_mevff); /* skip if MP interrupt */ - break; - - case ioSFS: /* skip if flag is set */ - setSKF (mp_mevff); /* skip if DMS interrupt */ - break; - - case ioIOI: /* I/O input */ - stat_data = IORETURN (SCPE_OK, mp_viol); /* read MP violation register */ - break; - - case ioIOO: /* I/O output */ - mp_fence = IODATA (stat_data) & VAMASK; /* write to MP fence register */ - - if (cpu_unit.flags & UNIT_2100) /* 2100 IOP uses MP fence */ - iop_sp = mp_fence; /* as a stack pointer */ - break; - - case ioPOPIO: /* power-on preset to I/O */ - mp_control = CLEAR; /* clear control flip-flop */ - mp_flag = mp_flagbuf = CLEAR; /* clear flag and flag buffer flip-flops */ - mp_mevff = CLEAR; /* clear memory expansion violation flip-flop */ - mp_evrff = SET; /* set enable violation register flip-flop */ - break; - - case ioSTC: /* set control flip-flop */ - mp_control = SET; /* turn on MP */ - mp_mevff = CLEAR; /* clear memory expansion violation flip-flop */ - mp_evrff = SET; /* set enable violation register flip-flop */ - break; - - case ioSIR: /* set interrupt request */ - setPRL (PRO, !mp_flag); /* set PRL signal */ - setIRQ (PRO, mp_flag); /* set IRQ signal */ - break; - - case ioIAK: /* interrupt acknowledge */ - if (dibptr->select_code == PRO) /* MP interrupt acknowledgement? */ - mp_flag = mp_flagbuf = CLEAR; /* clear flag and flag buffer */ - - data = IODATA (stat_data); /* get trap cell instruction */ - - if (((data & I_NMRMASK) != I_IO) || /* trap cell instruction not I/O */ - (I_GETIOOP (data) == soHLT)) /* or is halt? */ - mp_control = CLEAR; /* turn protection off */ - else { /* non-HLT I/O instruction leaves MP on */ - mp_mevff = CLEAR; /* but clears MEV flip-flop */ - mp_evrff = SET; /* and reenables violation register flip-flop */ - } - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* DMA/DCPC secondary (SC 2/3) I/O signal handler. - - DMA consists of one (12607B) or two (12578A/12895A/12897B) channels. Each - channel uses two select codes: 2 and 6 for channel 1, and 3 and 7 for channel - 2. The lower select codes are used to configure the memory address register - (control word 2) and the word count register (control word 3). The upper - select codes are used to configure the service select register (control word - 1) and to activate and terminate the transfer. - - There are differences in the implementations of the memory address and word - count registers among the various cards. The 12607B (2114) supports 14-bit - addresses and 13-bit word counts. The 12578A (2115/6) supports 15-bit - addresses and 14-bit word counts. The 12895A (2100) and 12897B (1000) - support 15-bit addresses and 16-bit word counts. - - Implementation notes: - - 1. Because the I/O bus floats to zero on 211x computers, an IOI (read word - count) returns zeros in the unused bit locations, even though the word - count is a negative value. - - 2. Select codes 2 and 3 cannot interrupt, so there is no SIR handler. -*/ - -uint32 dmasio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ -uint16 data; -IOSIGNAL signal; -IOCYCLE working_set = signal_set; /* no SIR handler needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioIOI: /* I/O data input */ - if (UNIT_CPU_MODEL == UNIT_2114) /* 2114? */ - data = dma [ch].cw3 & 0017777; /* only 13-bit count */ - else if (UNIT_CPU_TYPE == UNIT_TYPE_211X) /* 2115/2116? */ - data = dma [ch].cw3 & 0037777; /* only 14-bit count */ - else /* other models */ - data = (uint16) dma [ch].cw3; /* rest use full value */ - - stat_data = IORETURN (SCPE_OK, data); /* merge status and remaining word count */ - break; - - case ioIOO: /* I/O data output */ - if (dma [ch].select) /* word count selected? */ - dma [ch].cw3 = IODATA (stat_data); /* save count */ - else /* memory address selected */ - if (UNIT_CPU_MODEL == UNIT_2114) /* 2114? */ - dma [ch].cw2 = IODATA (stat_data) & 0137777; /* only 14-bit address */ - else /* other models */ - dma [ch].cw2 = IODATA (stat_data); /* full address stored */ - break; - - case ioCLC: /* clear control flip-flop */ - dma [ch].select = CLEAR; /* set for word count access */ - break; - - case ioSTC: /* set control flip-flop */ - dma [ch].select = SET; /* set for memory address access */ - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* DMA/DCPC primary (SC 6/7) I/O signal handler. - - The primary DMA control interface and the service select register are - manipulated through select codes 6 and 7. Each channel has transfer enable, - control, flag, and flag buffer flip-flops. Transfer enable must be set via - STC to start DMA. Control is used only to enable the DMA completion - interrupt; it is set by STC and cleared by CLC. Flag and flag buffer are set - at transfer completion to signal an interrupt. STF may be issued to abort a - transfer in progress. - - Again, there are hardware differences between the various DMA cards. The - 12607B (2114) stores only bits 2-0 of the select code and interprets them as - select codes 10-16 (SRQ17 is not decoded). The 12578A (2115/16), 12895A - (2100), and 12897B (1000) support the full range of select codes (10-77 - octal). - - Implementation notes: - - 1. An IOI reads the floating S-bus (high on the 1000, low on the 21xx). - - 2. The CRS signal on the DMA card resets the secondary (SC 2/3) select - flip-flops. Under simulation, ioCRS is dispatched to select codes 6 and - up, so we reset the flip-flop in our handler. - - 3. The 12578A supports byte-sized transfers by setting bit 14. Bit 14 is - ignored by all other DMA cards, which support word transfers only. - Under simulation, we use a byte-packing/unpacking register to hold one - byte while the other is read or written during the DMA cycle. -*/ - -uint32 dmapio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ -uint16 data; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - dma [ch].flag = dma [ch].flagbuf = CLEAR; /* clear flag and flag buffer */ - break; - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - dma [ch].flag = dma [ch].flagbuf = SET; /* set flag and flag buffer */ - dma [ch].xferen = CLEAR; /* clear transfer enable to abort transfer */ - break; - - case ioSFC: /* skip if flag is clear */ - setstdSKF (dma [ch]); /* skip if transfer in progress */ - break; - - case ioSFS: /* skip if flag is set */ - setstdSKF (dma [ch]); /* skip if transfer is complete */ - break; - - case ioIOI: /* I/O data input */ - if (UNIT_CPU_TYPE == UNIT_TYPE_1000) /* 1000? */ - stat_data = IORETURN (SCPE_OK, DMASK); /* return all ones */ - else /* other models */ - stat_data = IORETURN (SCPE_OK, 0); /* return all zeros */ - break; - - case ioIOO: /* I/O data output */ - data = IODATA (stat_data); /* clear supplied status */ - - if (UNIT_CPU_MODEL == UNIT_2114) /* 12607? */ - dma [ch].cw1 = (data & 0137707) | 010; /* mask SC, convert to 10-17 */ - else if (UNIT_CPU_TYPE == UNIT_TYPE_211X) /* 12578? */ - dma [ch].cw1 = data; /* store full select code, flags */ - else /* 12895, 12897 */ - dma [ch].cw1 = data & ~DMA1_PB; /* clip byte-packing flag */ - break; - - case ioPOPIO: /* power-on preset to I/O */ - dma [ch].flag = dma [ch].flagbuf = SET; /* set flag and flag buffer */ - break; - - case ioCRS: /* control reset */ - dma [ch].xferen = CLEAR; /* clear transfer enable */ - dma [ch].select = CLEAR; /* set secondary for word count access */ - /* fall into CLC handler */ - - case ioCLC: /* clear control flip-flop */ - dma [ch].control = CLEAR; /* clear control */ - break; - - case ioSTC: /* set control flip-flop */ - dma [ch].packer = 0; /* clear packing register */ - dma [ch].xferen = dma [ch].control = SET; /* set transfer enable and control */ - break; - - case ioSIR: /* set interrupt request */ - setstdPRL (dma [ch]); - setstdIRQ (dma [ch]); - break; - - case ioIAK: /* interrupt acknowledge */ - dma [ch].flagbuf = CLEAR; /* clear flag buffer */ - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Unassigned select code I/O signal handler. - - The 21xx/1000 I/O structure requires that no empty slots exist between - interface cards. This is due to the hardware priority chaining (PRH/PRL). - If it is necessary to leave unused I/O slots, HP 12777A Priority Jumper Cards - must be installed in them to maintain priority continuity. - - Under simulation, every unassigned I/O slot behaves as though a 12777A were - resident. - - Implementation notes: - - 1. For select codes < 10 octal, an IOI reads the floating S-bus (high on - the 1000, low on the 21xx). For select codes >= 10 octal, an IOI reads - the floating I/O bus (low on all machines). - - 2. If "stop_dev" is TRUE, then the simulator will stop when an unassigned - device is accessed. -*/ - -uint32 nullio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -uint16 data = 0; -IOSIGNAL signal; -IOCYCLE working_set = signal_set; /* no SIR handler needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioIOI: /* I/O data input */ - if ((dibptr->select_code < VARDEV) && /* internal device */ - (UNIT_CPU_TYPE == UNIT_TYPE_1000)) /* and 1000? */ - data = DMASK; /* return all ones */ - else /* external or other model */ - data = 0; /* return all zeros */ - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return IORETURN (stop_dev, data); /* flag missing device */ -} - - -/* DMA cycle routine. - - This routine performs one DMA input or output cycle using the indicated DMA - channel number and DMS map. When the transfer word count reaches zero, the - flag is set on the corresponding DMA channel to indicate completion. - - The 12578A card supports byte-packing. If bit 14 in control word 1 is set, - each transfer will involve one read/write from memory and two output/input - operations in order to transfer sequential bytes to/from the device. - - DMA I/O cycles differ from programmed I/O cycles in that multiple I/O control - backplane signals may be asserted simultaneously. With programmed I/O, only - CLF may be asserted with other signals, specifically with STC, CLC, SFS, SFC, - IOI, or IOO. With DMA, as many as five signals may be asserted concurrently. - - DMA I/O timing looks like this: - - ------------ 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 - - Under simulation, this routine dispatches one set of I/O signals per DMA - cycle to the target device's I/O signal handler. The signals correspond to - the table above, except that all signals for a given cycle are concurrent - (e.g., the last input cycle has IOI, EDT, and optionally CLC asserted, even - though IOI and EDT are not coincident in hardware). I/O signal handlers will - process these signals sequentially, in the order listed above, before - returning. - - Implementation notes: - - 1. The address increment and word count decrement is done only after the I/O - cycle has completed successfully. This allows a failed transfer to be - retried after correcting the I/O error. -*/ - -static t_stat dma_cycle (CHANNEL ch, uint32 map) -{ -const uint32 dev = dma [ch].cw1 & I_DEVMASK; /* device select code */ -const uint32 stc = dma [ch].cw1 & DMA1_STC; /* STC enable flag */ -const uint32 bytes = dma [ch].cw1 & DMA1_PB; /* pack bytes flag */ -const uint32 clc = dma [ch].cw1 & DMA1_CLC; /* CLC enable flag */ -const uint32 MA = dma [ch].cw2 & VAMASK; /* memory address */ -const uint32 input = dma [ch].cw2 & DMA2_OI; /* input flag */ -const uint32 even = dma [ch].packer & DMA_OE; /* odd/even packed byte flag */ -uint16 data; -t_stat status; -uint32 ioresult; -IOCYCLE signals; - -if (bytes && !even || dma [ch].cw3 != DMASK) { /* normal cycle? */ - if (input) /* input cycle? */ - signals = ioIOI | ioCLF; /* assert IOI and CLF */ - else /* output cycle */ - signals = ioIOO | ioCLF; /* assert IOO and CLF */ - - if (stc) /* STC wanted? */ - signals = signals | ioSTC; /* assert STC */ - } - -else { /* last cycle */ - if (input) /* input cycle? */ - signals = ioIOI | ioEDT; /* assert IOI and EDT */ - else { /* output cycle */ - signals = ioIOO | ioCLF | ioEDT; /* assert IOO and CLF and EDT */ - - if (stc) /* STC wanted? */ - signals = signals | ioSTC; /* assert STC */ - } - - if (clc) /* CLC wanted? */ - signals = signals | ioCLC; /* assert CLC */ - } - -if (input) { /* input cycle? */ - ioresult = devdisp (dev, signals, 0); /* do I/O input */ - - status = IOSTATUS (ioresult); /* get cycle status */ - - if (status == SCPE_OK) { /* good I/O cycle? */ - data = IODATA (ioresult); /* extract return data value */ - - if (bytes) { /* byte packing? */ - if (even) { /* second byte? */ - data = (uint16) (dma [ch].packer << 8) /* merge stored byte */ - | (data & DMASK8); - WriteIO (MA, data, map); /* store word data */ - } - else /* first byte */ - dma [ch].packer = (data & DMASK8); /* save it */ - - dma [ch].packer = dma [ch].packer ^ DMA_OE; /* flip odd/even bit */ - } - else /* no byte packing */ - WriteIO (MA, data, map); /* store word data */ - } - } - -else { /* output cycle */ - if (bytes) { /* byte packing? */ - if (even) /* second byte? */ - data = dma [ch].packer & DMASK8; /* retrieve it */ - - else { /* first byte */ - dma [ch].packer = ReadIO (MA, map); /* read word data */ - data = (dma [ch].packer >> 8) & DMASK8; /* get high byte */ - } - - dma [ch].packer = dma [ch].packer ^ DMA_OE; /* flip odd/even bit */ - } - else /* no byte packing */ - data = ReadIO (MA, map); /* read word data */ - - ioresult = devdisp (dev, signals, data); /* do I/O output */ - - status = IOSTATUS (ioresult); /* get cycle status */ - } - -if ((even || !bytes) && (status == SCPE_OK)) { /* new byte or no packing and good xfer? */ - dma [ch].cw2 = input | (dma [ch].cw2 + 1) & VAMASK; /* increment address */ - dma [ch].cw3 = (dma [ch].cw3 + 1) & DMASK; /* increment word count */ - - if (dma [ch].cw3 == 0) /* end of transfer? */ - dmapio (dtab [DMA1 + ch], ioENF, 0); /* set DMA channel flag */ - } - -return status; /* return I/O status */ -} - - -/* Reset routines. - - The reset routines are called to simulate either an initial power on - condition or a front-panel PRESET button press. For initial power on - (corresponds to PON, POPIO, and CRS signal assertion in the CPU), the "P" - command switch will be set. For PRESET (corresponds to POPIO and CRS - assertion), the switch will be clear. - - SCP delivers a power-on reset to all devices when the simulator is started. - A RUN, BOOT, RESET, or RESET ALL command delivers a PRESET to all devices. A - RESET delivers a PRESET to a specific device. -*/ - - -/* CPU reset. - - If this is the first call after simulator startup, allocate the initial - memory array, set the default CPU model, and install the default BBL. -*/ - -t_stat cpu_reset (DEVICE *dptr) -{ -if (M == NULL) { /* initial call after startup? */ - pcq_r = find_reg ("PCQ", NULL, dptr); /* get PC queue pointer */ - - if (pcq_r) /* defined? */ - pcq_r->qptr = 0; /* initialize queue */ - else /* not defined */ - return SCPE_IERR; /* internal error */ - - M = (uint16 *) calloc (PASIZE, sizeof (uint16)); /* alloc mem */ - - if (M == NULL) /* alloc fail? */ - return SCPE_MEM; - else { /* do one-time init */ - MEMSIZE = 32768; /* set initial memory size */ - cpu_set_model (NULL, UNIT_2116, NULL, NULL); /* set initial CPU model */ - SR = 001000; /* select PTR boot ROM at SC 10 */ - cpu_boot (0, NULL); /* install loader for 2116 */ - cpu_set_ldr (NULL, FALSE, NULL, NULL); /* disable loader (was enabled) */ - SR = 0; /* clear S */ - sim_vm_post = &hp_post_cmd; /* set cmd post proc */ - sim_vm_fprint_stopped = &hp_fprint_stopped; /* set sim stop printer */ - sim_brk_types = ALL_BKPTS; /* register allowed breakpoint types */ - } - } - -if (sim_switches & SWMASK ('P')) /* PON reset? */ - IOPOWERON (&cpu_dib); -else /* PRESET */ - IOPRESET (&cpu_dib); - -sim_brk_dflt = SWMASK ('N'); /* type is nomap as DMS is off */ - -return SCPE_OK; -} - - -/* Memory protect reset */ - -t_stat mp_reset (DEVICE *dptr) -{ -IOPRESET (&mp_dib); /* PRESET device (does not use PON) */ - -mp_fence = 0; /* clear fence register */ -mp_viol = 0; /* clear violation register */ - -return SCPE_OK; -} - - -/* DMA reset */ - -t_stat dma_reset (DEVICE *dptr) -{ -DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ -const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ - -if (UNIT_CPU_MODEL != UNIT_2114) /* 2114 has only one channel */ - hp_enbdis_pair (dma_dptrs [ch], /* make specified channel */ - dma_dptrs [ch ^ 1]); /* consistent with other channel */ - -if (sim_switches & SWMASK ('P')) { /* power-on reset? */ - dma [ch].cw1 = 0; /* clear control word registers */ - dma [ch].cw2 = 0; - dma [ch].cw3 = 0; - } - -IOPRESET (dibptr); /* PRESET device (does not use PON) */ - -dma [ch].packer = 0; /* clear byte packer */ - -return SCPE_OK; -} - - -/* Memory examine */ - -t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) -{ -uint16 d; - -if ((sw & ALL_MAPMODES) && (dms_enb == 0)) /* req map with DMS off? */ - return SCPE_NOFNC; /* command not allowed */ - -addr = dms_cons (addr, sw); /* translate address as indicated */ - -if (addr >= MEMSIZE) /* beyond memory limits? */ - return SCPE_NXM; /* non-existent memory */ - -if ((sw & SIM_SW_REST) || (addr >= 2)) /* restoring or memory access? */ - d = M[addr]; /* return memory value */ -else /* not restoring and A/B access */ - d = ABREG[addr]; /* return A/B register value */ - -if (vptr != NULL) - *vptr = d & DMASK; /* store return value */ -return SCPE_OK; -} - -/* Memory deposit */ - -t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw) -{ -if ((sw & ALL_MAPMODES) && (dms_enb == 0)) /* req map with DMS off? */ - return SCPE_NOFNC; /* command not allowed */ - -addr = dms_cons (addr, sw); /* translate address as indicated */ - -if (addr >= MEMSIZE) /* beyond memory limits? */ - return SCPE_NXM; /* non-existent memory */ - -if ((sw & SIM_SW_REST) || (addr >= 2)) /* restoring or memory access? */ - M[addr] = val & DMASK; /* store memory value */ -else /* not restoring and A/B access */ - ABREG[addr] = val & DMASK; /* store A/B register value */ - -return SCPE_OK; -} - - -/* Make a pair of devices consistent */ - -void hp_enbdis_pair (DEVICE *ccp, DEVICE *dcp) -{ -if (ccp->flags & DEV_DIS) - dcp->flags = dcp->flags | DEV_DIS; -else - dcp->flags = dcp->flags & ~DEV_DIS; - -return; -} - - -/* VM command post-processor - - Update T register to contents of memory addressed by M register - if M register has changed. -*/ - -void hp_post_cmd (t_bool from_scp) -{ -if (MR != saved_MR) { /* M changed since last update? */ - saved_MR = MR; - TR = ReadTAB (MR); /* sync T with new M */ - } -return; -} - - -/* Test for device conflict */ - -static t_bool dev_conflict (void) -{ -DEVICE *dptr; -DIB *dibptr; -uint32 i, j, k; -t_bool is_conflict = FALSE; -uint32 conflicts [MAXDEV + 1] = { 0 }; - -for (i = 0; sim_devices [i] != NULL; i++) { - dptr = sim_devices [i]; - dibptr = (DIB *) dptr->ctxt; - if (dibptr && !(dptr->flags & DEV_DIS)) - if (++conflicts [dibptr->select_code] > 1) - is_conflict = TRUE; - } - -if (is_conflict) { - sim_ttcmd(); - for (i = 0; i <= MAXDEV; i++) { - if (conflicts [i] > 1) { - k = conflicts [i]; - - printf ("Select code %o conflict:", i); - - if (sim_log) - fprintf (sim_log, "Select code %o conflict:", i); - - for (j = 0; sim_devices [j] != NULL; j++) { - dptr = sim_devices [j]; - dibptr = (DIB *) dptr->ctxt; - if (dibptr && !(dptr->flags & DEV_DIS) && i == dibptr->select_code) { - if (k < conflicts [i]) { - printf (" and"); - - if (sim_log) - fputs (" and", sim_log); - } - - printf (" %s", sim_dname (dptr)); - - if (sim_log) - fprintf (sim_log, " %s", sim_dname (dptr)); - - k = k - 1; - - if (k == 0) { - putchar ('\n'); - - if (sim_log) - fputc ('\n', sim_log); - break; - } - } - } - } - } - } -return is_conflict; -} - - -/* Change CPU memory size. - - On a 21xx, move the current loader to the top of the new memory size. Then - clear "non-existent memory" so that reads return zero, per spec. - - Validation: - - New size <= maximum size for current CPU. - - New size a positive multiple of 4K (progamming error if not). - - If new size < old size, truncation accepted. -*/ - -t_stat cpu_set_size (UNIT *uptr, int32 new_size, char *cptr, void *desc) -{ -int32 mc = 0; -uint32 i; -uint32 model = CPU_MODEL_INDEX; /* current CPU model index */ -uint32 old_size = (uint32) MEMSIZE; /* current memory size */ - -if ((uint32) new_size > cpu_features[model].maxmem) - return SCPE_NOFNC; /* mem size unsupported */ - -if ((new_size <= 0) || (new_size > PASIZE) || ((new_size & 07777) != 0)) - return SCPE_NXM; /* invalid size (prog err) */ - -if (!(sim_switches & SWMASK ('F'))) { /* force truncation? */ - for (i = new_size; i < MEMSIZE; i++) /* check truncated memory */ - mc = mc | M[i]; /* for content */ - - if ((mc != 0) && (!get_yn ("Really truncate memory [N]?", FALSE))) - return SCPE_INCOMP; - } - -if (UNIT_CPU_FAMILY == UNIT_FAMILY_21XX) { /* 21xx CPU? */ - cpu_set_ldr (uptr, FALSE, NULL, NULL); /* save loader to shadow RAM */ - MEMSIZE = new_size; /* set new memory size */ - fwanxm = (uint32) MEMSIZE - IBL_LNT; /* reserve memory for loader */ - } -else /* loader unsupported */ - MEMSIZE = fwanxm = new_size; /* set new memory size */ - -for (i = fwanxm; i < old_size; i++) /* zero non-existent memory */ - M[i] = 0; - -return SCPE_OK; -} - - -/* Change CPU models. - - For convenience, MP and DMA are typically enabled if available; they may be - disabled subsequently if desired. Note that the 2114 supports only one DMA - channel (channel 1). All other models support two channels. - - Validation: - - Sets standard equipment and convenience features. - - Changes DMA device name to DCPC if 1000 is selected. - - Enforces maximum memory allowed (doesn't change otherwise). - - Disables loader on 21xx machines. -*/ - -t_stat cpu_set_model (UNIT *uptr, int32 new_model, char *cptr, void *desc) -{ -uint32 old_family = UNIT_CPU_FAMILY; /* current CPU type */ -uint32 new_family = new_model & UNIT_FAMILY_MASK; /* new CPU family */ -uint32 new_index = new_model >> UNIT_V_CPU; /* new CPU model index */ -uint32 new_memsize; -t_stat result; - -cpu_unit.flags = cpu_unit.flags & ~UNIT_OPTS | /* set typical features */ - cpu_features[new_index].typ & UNIT_OPTS; /* mask pseudo-opts */ - - -if (cpu_features[new_index].typ & UNIT_MP) /* MP in typ config? */ - mp_dev.flags = mp_dev.flags & ~DEV_DIS; /* enable it */ -else - mp_dev.flags = mp_dev.flags | DEV_DIS; /* disable it */ - -if (cpu_features[new_index].opt & UNIT_MP) /* MP an option? */ - mp_dev.flags = mp_dev.flags | DEV_DISABLE; /* make it alterable */ -else - mp_dev.flags = mp_dev.flags & ~DEV_DISABLE; /* make it unalterable */ - - -if (cpu_features[new_index].typ & UNIT_DMA) { /* DMA in typ config? */ - dma1_dev.flags = dma1_dev.flags & ~DEV_DIS; /* enable DMA channel 1 */ - - if (new_model == UNIT_2114) /* 2114 has only one channel */ - dma2_dev.flags = dma2_dev.flags | DEV_DIS; /* disable channel 2 */ - else /* all others have two channels */ - dma2_dev.flags = dma2_dev.flags & ~DEV_DIS; /* enable it */ - } -else { - dma1_dev.flags = dma1_dev.flags | DEV_DIS; /* disable channel 1 */ - dma2_dev.flags = dma2_dev.flags | DEV_DIS; /* disable channel 2 */ - } - -if (cpu_features[new_index].opt & UNIT_DMA) { /* DMA an option? */ - dma1_dev.flags = dma1_dev.flags | DEV_DISABLE; /* make it alterable */ - - if (new_model == UNIT_2114) /* 2114 has only one channel */ - dma2_dev.flags = dma2_dev.flags & ~DEV_DISABLE; /* make it unalterable */ - else /* all others have two channels */ - dma2_dev.flags = dma2_dev.flags | DEV_DISABLE; /* make it alterable */ - } -else { - dma1_dev.flags = dma1_dev.flags & ~DEV_DISABLE; /* make it unalterable */ - dma2_dev.flags = dma2_dev.flags & ~DEV_DISABLE; /* make it unalterable */ - } - - -if ((old_family == UNIT_FAMILY_1000) && /* if current family is 1000 */ - (new_family == UNIT_FAMILY_21XX)) { /* and new family is 21xx */ - deassign_device (&dma1_dev); /* delete DCPC names */ - deassign_device (&dma2_dev); - } -else if ((old_family == UNIT_FAMILY_21XX) && /* if current family is 21xx */ - (new_family == UNIT_FAMILY_1000)) { /* and new family is 1000 */ - assign_device (&dma1_dev, "DCPC1"); /* change DMA device name */ - assign_device (&dma2_dev, "DCPC2"); /* to DCPC for familiarity */ - } - -if ((MEMSIZE == 0) || /* current mem size not set? */ - (MEMSIZE > cpu_features[new_index].maxmem)) /* current mem size too large? */ - new_memsize = cpu_features[new_index].maxmem; /* set it to max supported */ -else - new_memsize = (uint32) MEMSIZE; /* or leave it unchanged */ - -result = cpu_set_size (uptr, new_memsize, NULL, NULL); /* set memory size */ - -if (result == SCPE_OK) /* memory change OK? */ - if (new_family == UNIT_FAMILY_21XX) /* 21xx CPU? */ - fwanxm = (uint32) MEMSIZE - IBL_LNT; /* reserve memory for loader */ - else - fwanxm = (uint32) MEMSIZE; /* loader reserved only for 21xx */ - -return result; -} - - -/* Display the CPU model and optional loader status. - - Loader status is displayed for 21xx models and suppressed for 1000 models. -*/ - -t_stat cpu_show_model (FILE *st, UNIT *uptr, int32 val, void *desc) -{ -fputs ((char *) desc, st); /* write model name */ - -if (UNIT_CPU_FAMILY == UNIT_FAMILY_21XX) /* valid only for 21xx */ - if (fwanxm < MEMSIZE) /* loader area non-existent? */ - fputs (", loader disabled", st); /* yes, so access disabled */ - else - fputs (", loader enabled", st); /* no, so access enabled */ -return SCPE_OK; -} - - -/* Set a CPU option. - - Validation: - - Checks that the current CPU model supports the option selected. - - If CPU is 1000-F, ensures that VIS and IOP are mutually exclusive. - - If CPU is 2100, ensures that FP/FFP and IOP are mutually exclusive. - - If CPU is 2100, ensures that FP is enabled if FFP enabled - (FP is required for FFP installation). -*/ - -t_stat cpu_set_opt (UNIT *uptr, int32 option, char *cptr, void *desc) -{ -uint32 model = CPU_MODEL_INDEX; /* current CPU model index */ - -if ((cpu_features[model].opt & option) == 0) /* option supported? */ - return SCPE_NOFNC; /* no */ - -if (UNIT_CPU_TYPE == UNIT_TYPE_2100) { - if ((option == UNIT_FP) || (option == UNIT_FFP)) /* 2100 IOP and FP/FFP options */ - uptr->flags = uptr->flags & ~UNIT_IOP; /* are mutually exclusive */ - else if (option == UNIT_IOP) - uptr->flags = uptr->flags & ~(UNIT_FP | UNIT_FFP); - - if (option == UNIT_FFP) /* 2100 FFP option requires FP */ - uptr->flags = uptr->flags | UNIT_FP; - } - -else if (UNIT_CPU_MODEL == UNIT_1000_F) - if (option == UNIT_VIS) /* 1000-F IOP and VIS options */ - uptr->flags = uptr->flags & ~UNIT_IOP; /* are mutually exclusive */ - else if (option == UNIT_IOP) - uptr->flags = uptr->flags & ~UNIT_VIS; - -return SCPE_OK; -} - - -/* Clear a CPU option. - - Validation: - - Checks that the current CPU model supports the option selected. - - Clears flag from unit structure (we are processing MTAB_XTD entries). - - If CPU is 2100, ensures that FFP is disabled if FP disabled - (FP is required for FFP installation). -*/ - -t_bool cpu_clr_opt (UNIT *uptr, int32 option, char *cptr, void *desc) -{ -uint32 model = CPU_MODEL_INDEX; /* current CPU model index */ - -if ((cpu_features[model].opt & option) == 0) /* option supported? */ - return SCPE_NOFNC; /* no */ - -uptr->flags = uptr->flags & ~option; /* disable option */ - -if ((UNIT_CPU_TYPE == UNIT_TYPE_2100) && /* disabling 2100 FP? */ - (option == UNIT_FP)) - uptr->flags = uptr->flags & ~UNIT_FFP; /* yes, so disable FFP too */ - -return SCPE_OK; -} - - -/* 21xx loader enable/disable function. - - The 21xx CPUs store their initial binary loaders in the last 64 words of - available memory. This memory is protected by a LOADER ENABLE switch on the - front panel. When the switch is off (disabled), main memory effectively ends - 64 locations earlier, i.e., the loader area is treated as non-existent. - Because these are core machines, the loader is retained when system power is - off. - - 1000 CPUs do not have a protected loader feature. Instead, loaders are - stored in PROMs and are copied into main memory for execution by the IBL - switch. - - Under simulation, we keep both a total configured memory size (MEMSIZE) and a - current configured memory size (fwanxm = "first word address of non-existent - memory"). When the two are equal, the loader is enabled. When the current - size is less than the total size, the loader is disabled. - - Disabling the loader copies the last 64 words to a shadow array, zeros the - corresponding memory, and decreases the last word of addressable memory by - 64. Enabling the loader reverses this process. - - Disabling may be done manually by user command or automatically when a halt - instruction is executed. Enabling occurs only by user command. This differs - slightly from actual machine operation, which additionally disables the - loader when a manual halt is performed. We do not do this to allow - breakpoints within and single-stepping through the loaders. -*/ - -t_stat cpu_set_ldr (UNIT *uptr, int32 enable, char *cptr, void *desc) -{ -static BOOT_ROM loader; -int32 i; -t_bool is_enabled = (fwanxm == MEMSIZE); - -if ((UNIT_CPU_FAMILY != UNIT_FAMILY_21XX) || /* valid only for 21xx */ - (MEMSIZE == 0)) /* and for initialized memory */ - return SCPE_NOFNC; - -if (is_enabled && (enable == 0)) { /* disable loader? */ - fwanxm = (uint32) MEMSIZE - IBL_LNT; /* decrease available memory */ - for (i = 0; i < IBL_LNT; i++) { /* copy loader */ - loader[i] = M[fwanxm + i]; /* from memory */ - M[fwanxm + i] = 0; /* and zero location */ - } - } - -else if ((!is_enabled) && (enable == 1)) { /* enable loader? */ - for (i = 0; i < IBL_LNT; i++) /* copy loader */ - M[fwanxm + i] = loader[i]; /* to memory */ - fwanxm = (uint32) MEMSIZE; /* increase available memory */ - } - -return SCPE_OK; -} - - -/* Idle enable/disable */ - -t_stat cpu_set_idle (UNIT *uptr, int32 option, char *cptr, void *desc) -{ - if (option) - return sim_set_idle (uptr, 10, NULL, NULL); - else - return sim_clr_idle (uptr, 0, NULL, NULL); -} - - -/* Idle display */ - -t_stat cpu_show_idle (FILE *st, UNIT *uptr, int32 val, void *desc) -{ - return sim_show_idle (st, uptr, val, desc); -} - - -/* IBL routine (CPU boot) */ - -t_stat cpu_boot (int32 unitno, DEVICE *dptr) -{ -int32 dev = (SR >> IBL_V_DEV) & I_DEVMASK; -int32 sel = (SR >> IBL_V_SEL) & IBL_M_SEL; - -if (dev < 010) - return SCPE_NOFNC; - -switch (sel) { - - case 0: /* PTR boot */ - ibl_copy (ptr_rom, dev, IBL_S_NOCLR, IBL_S_NOSET); - break; - - case 1: /* DP/DQ boot */ - ibl_copy (dq_rom, dev, IBL_S_NOCLR, IBL_S_NOSET); - break; - - case 2: /* MS boot */ - ibl_copy (ms_rom, dev, IBL_S_NOCLR, IBL_S_NOSET); - break; - - case 3: /* DS boot */ - ibl_copy (ds_rom, dev, IBL_S_NOCLR, IBL_S_NOSET); - break; - } - -return SCPE_OK; -} - - -/* IBL boot ROM copy - - - Use memory size to set the initial PC and base of the boot area - - Copy boot ROM to memory, updating I/O instructions - - Place 2s complement of boot base in last location - - Modify S register as indicated - - Notes: - - Boot ROMs must be assembled with a device code of 10 (10 and 11 for - devices requiring two codes) -*/ - -t_stat ibl_copy (const BOOT_ROM rom, int32 dev, uint32 sr_clear, uint32 sr_set) -{ -int32 i; -uint16 wd; - -cpu_set_ldr (NULL, TRUE, NULL, NULL); /* enable loader (ignore errors) */ - -if (dev < 010) /* valid device? */ - return SCPE_ARG; - -PC = ((MEMSIZE - 1) & ~IBL_MASK) & VAMASK; /* start at mem top */ - -for (i = 0; i < IBL_LNT; i++) { /* copy bootstrap */ - wd = rom[i]; /* get word */ - - if (((wd & I_NMRMASK) == I_IO) && /* IO instruction? */ - ((wd & I_DEVMASK) >= 010) && /* dev >= 10? */ - (I_GETIOOP (wd) != soHLT)) /* not a HALT? */ - M[PC + i] = (wd + (dev - 010)) & DMASK; /* change dev code */ - - else /* leave unchanged */ - M[PC + i] = wd; - } - -M[PC + IBL_DPC] = (M[PC + IBL_DPC] + (dev - 010)) & DMASK; /* patch DMA ctrl */ -M[PC + IBL_END] = (~PC + 1) & DMASK; /* fill in start of boot */ - -SR = (SR & sr_clear) | sr_set; /* modify the S register as indicated */ - -return SCPE_OK; -} +/* hp2100_cpu.c: HP 21xx/1000 CPU simulator + + Copyright (c) 1993-2016, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + CPU 2114C/2115A/2116C/2100A/1000-M/E/F central processing unit + 12731A memory expansion module + MP 12581A/12892B memory protect + DMA1,DMA2 12607B/12578A/12895A direct memory access controller + DCPC1,DCPC2 12897B dual channel port controller + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 13-May-16 JDB Modified for revised SCP API function parameter types + 31-Dec-14 JDB Corrected devdisp data parameters + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 24-Dec-14 JDB Added casts for explicit downward conversions + 18-Mar-13 JDB Removed redundant extern declarations + 05-Feb-13 JDB HLT instruction handler now relies on sim_vm_fprint_stopped + 09-May-12 JDB Separated assignments from conditional expressions + 13-Jan-12 JDB Minor speedup in "is_mapped" + Added casts to cpu_mod, dmasio, dmapio, cpu_reset, dma_reset + 07-Apr-11 JDB Fixed I/O return status bug for DMA cycles + Failed I/O cycles now stop on failing instruction + 28-Mar-11 JDB Tidied up signal handling + 29-Oct-10 JDB Revised DMA for new multi-card paradigm + Consolidated DMA reset routines + DMA channels renamed from 0,1 to 1,2 to match documentation + 27-Oct-10 JDB Changed I/O instructions, handlers, and DMA for revised signal model + Changed I/O dispatch table to use DIB pointers + 19-Oct-10 JDB Removed DMA latency counter + 13-Oct-10 JDB Fixed DMA requests to enable stealing every cycle + Fixed DMA priority for channel 1 over channel 2 + Corrected comments for "cpu_set_idle" + 30-Sep-08 JDB Breakpoints on interrupt trap cells now work + 05-Sep-08 JDB VIS and IOP are now mutually exclusive on 1000-F + 11-Aug-08 JDB Removed A/B shadow register variables + 07-Aug-08 JDB Moved hp_setdev, hp_showdev to hp2100_sys.c + Moved non-existent memory checks to WritePW + 05-Aug-08 JDB Fixed mp_dms_jmp to accept lower bound, check write protection + 30-Jul-08 JDB Corrected DMS violation register set conditions + Refefined ABORT to pass address, moved def to hp2100_cpu.h + Combined dms and dms_io routines + 29-Jul-08 JDB JSB to 0/1 with W5 out and fence = 0 erroneously causes MP abort + 11-Jul-08 JDB Unified I/O slot dispatch by adding DIBs for CPU, MP, and DMA + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + EDT no longer passes DMA channel + 30-Apr-08 JDB Enabled SIGNAL instructions, SIG debug flag + 28-Apr-08 JDB Added SET CPU IDLE/NOIDLE, idle detection for DOS/RTE + 24-Apr-08 JDB Fixed single stepping through interrupts + 20-Apr-08 JDB Enabled EMA and VIS, added EMA, VIS, and SIGNAL debug flags + 03-Dec-07 JDB Memory ex/dep and bkpt type default to current map mode + 26-Nov-07 JDB Added SET CPU DEBUG and OS/VMA flags, enabled OS/VMA + 15-Nov-07 JDB Corrected MP W5 (JSB) jumper action, SET/SHOW reversal, + mp_mevff clear on interrupt with I/O instruction in trap cell + 04-Nov-07 JDB Removed DBI support from 1000-M (was temporary for RTE-6/VM) + 28-Apr-07 RMS Removed clock initialization + 02-Mar-07 JDB EDT passes input flag and DMA channel in dat parameter + 11-Jan-07 JDB Added 12578A DMA byte packing + 28-Dec-06 JDB CLC 0 now sends CRS instead of CLC to devices + 26-Dec-06 JDB Fixed improper IRQ deferral for 21xx CPUs + Fixed improper interrupt servicing in resolve + 21-Dec-06 JDB Added 21xx loader enable/disable support + 16-Dec-06 JDB Added 2114 and 2115 CPU options. + Added support for 12607B (2114) and 12578A (2115/6) DMA + 01-Dec-06 JDB Added 1000-F CPU option (requires HAVE_INT64) + SHOW CPU displays 1000-M/E instead of 21MX-M/E + 16-Oct-06 JDB Moved ReadF to hp2100_cpu1.c + 12-Oct-06 JDB Fixed INDMAX off-by-one error in resolve + 26-Sep-06 JDB Added iotrap parameter to UIG dispatchers for RTE microcode + 12-Sep-06 JDB iogrp returns NOTE_IOG to recalc interrupts + resolve returns NOTE_INDINT to service held-off interrupt + 16-Aug-06 JDB Added support for future microcode options, future F-Series + 09-Aug-06 JDB Added double integer microcode, 1000-M/E synonyms + Enhanced CPU option validity checking + Added DCPC as a synonym for DMA for 21MX simulations + 26-Dec-05 JDB Improved reporting in dev_conflict + 22-Sep-05 RMS Fixed declarations (from Sterling Garwood) + 21-Jan-05 JDB Reorganized CPU option flags + 15-Jan-05 RMS Split out EAU and MAC instructions + 26-Dec-04 RMS DMA reset doesn't clear alternate CTL flop (from Dave Bryan) + DMA reset shouldn't clear control words (from Dave Bryan) + Alternate CTL flop not visible as register (from Dave Bryan) + Fixed CBS, SBS, TBS to perform virtual reads + Separated A/B from M[0/1] for DMA IO (from Dave Bryan) + Fixed bug in JPY (from Dave Bryan) + 25-Dec-04 JDB Added SET CPU 21MX-M, 21MX-E (21MX defaults to MX-E) + TIMER/EXECUTE/DIAG instructions disabled for 21MX-M + T-register reflects changes in M-register when halted + 25-Sep-04 JDB Moved MP into its own device; added MP option jumpers + Modified DMA to allow disabling + Modified SET CPU 2100/2116 to truncate memory > 32K + Added -F switch to SET CPU to force memory truncation + Fixed S-register behavior on 2116 + Fixed LIx/MIx behavior for DMA on 2116 and 2100 + Fixed LIx/MIx behavior for empty I/O card slots + Modified WRU to be REG_HRO + Added BRK and DEL to save console settings + Fixed use of "unsigned int16" in cpu_reset + Modified memory size routine to return SCPE_INCOMP if + memory size truncation declined + 20-Jul-04 RMS Fixed bug in breakpoint test (reported by Dave Bryan) + Back up PC on instruction errors (from Dave Bryan) + 14-May-04 RMS Fixed bugs and added features from Dave Bryan + - SBT increments B after store + - DMS console map must check dms_enb + - SFS x,C and SFC x,C work + - MP violation clears automatically on interrupt + - SFS/SFC 5 is not gated by protection enabled + - DMS enable does not disable mem prot checks + - DMS status inconsistent at simulator halt + - Examine/deposit are checking wrong addresses + - Physical addresses are 20b not 15b + - Revised DMS to use memory rather than internal format + - Added instruction printout to HALT message + - Added M and T internal registers + - Added N, S, and U breakpoints + Revised IBL facility to conform to microcode + Added DMA EDT I/O pseudo-opcode + Separated DMA SRQ (service request) from FLG + 12-Mar-03 RMS Added logical name support + 02-Feb-03 RMS Fixed last cycle bug in DMA output (found by Mike Gemeny) + 22-Nov-02 RMS Added 21MX IOP support + 24-Oct-02 RMS Fixed bugs in IOP and extended instructions + Fixed bugs in memory protection and DMS + Added clock calibration + 25-Sep-02 RMS Fixed bug in DMS decode (found by Robert Alan Byer) + 26-Jul-02 RMS Restructured extended instructions, added IOP support + 22-Mar-02 RMS Changed to allocate memory array dynamically + 11-Mar-02 RMS Cleaned up setjmp/auto variable interaction + 17-Feb-02 RMS Added DMS support + Fixed bugs in extended instructions + 03-Feb-02 RMS Added terminal multiplexor support + Changed PCQ macro to use unmodified PC + Fixed flop restore logic (found by Bill McDermith) + Fixed SZx,SLx,RSS bug (found by Bill McDermith) + Added floating point support + 16-Jan-02 RMS Added additional device support + 07-Jan-02 RMS Fixed DMA register tables (found by Bill McDermith) + 07-Dec-01 RMS Revised to use breakpoint package + 03-Dec-01 RMS Added extended SET/SHOW support + 10-Aug-01 RMS Removed register in declarations + 26-Nov-00 RMS Fixed bug in dual device number routine + 21-Nov-00 RMS Fixed bug in reset routine + 15-Oct-00 RMS Added dynamic device number support + + References: + - 2100A Computer Reference Manual (02100-90001, Dec-1971) + - Model 2100A Computer Installation and Maintenance Manual + (02100-90002, Aug-1972) + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - HP 1000 M/E/F-Series Computers I/O Interfacing Guide + (02109-90006, Sep-1980) + - 12607A Direct Memory Access Operating and Service Manual + (12607-90002, Jan-1970) + - 12578A/12578A-01 Direct Memory Access Operating and Service Manual + (12578-9001, Mar-1972) + - 12892B Memory Protect Installation Manual (12892-90007, Jun-1978) + + + The register state for the HP 2116 CPU is: + + AR<15:0> A register - addressable as location 0 + BR<15:0> B register - addressable as location 1 + PR<14:0> P register - program counter + SR<15:0> S register - switch register + MR<14:0> M register - memory address + TR<15:0> T register - memory data + E extend flag (carry out) + O overflow flag + + The 2100 adds memory protection logic: + + mp_fence<14:0> memory fence register + mp_viol<15:0> memory protection violation register (F register) + + The 21MX adds a pair of index registers and memory expansion logic: + + XR<15:0> X register + YR<15:0> Y register + dms_sr<15:0> dynamic memory system status register + dms_vr<15:0> dynamic memory system violation register + + The original HP 2116 has four instruction formats: memory reference, + shift, alter/skip, and I/O. The HP 2100 added extended memory reference + and extended arithmetic. The HP21MX added extended byte, bit, and word + instructions as well as extended memory. + + The memory reference format is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |in| op |cp| offset | memory reference + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + <14:11> mnemonic action + + 0010 AND A = A & M[MA] + 0011 JSB M[MA] = P, P = MA + 1 + 0100 XOR A = A ^ M[MA] + 0101 JMP P = MA + 0110 IOR A = A | M[MA] + 0111 ISZ M[MA] = M[MA] + 1, skip if M[MA] == 0 + 1000 ADA A = A + M[MA] + 1001 ADB B = B + M[MA] + 1010 CPA skip if A != M[MA] + 1011 CPB skip if B != M[MA] + 1100 LDA A = M[MA] + 1101 LDB B = M[MA] + 1110 STA M[MA] = A + 1111 STB M[MA] = B + + <15,10> mode action + + 0,0 page zero direct MA = IR<9:0> + 0,1 current page direct MA = PR<14:0>'IR,9:0> + 1,0 page zero indirect MA = M[IR<9:0>] + 1,1 current page indirect MA = M[PR<14:10>'IR<9:0>] + + Memory reference instructions can access an address space of 32K words. + An instruction can directly reference the first 1024 words of memory + (called page zero), as well as 1024 words of the current page; it can + indirectly access all 32K. + + The shift format is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 0 0 0 0|ab| 0|s1| op1 |ce|s2|sl| op2 | shift + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | | \---+---/ | | | \---+---/ + | | | | | | | + | | | | | | +---- shift 2 opcode + | | | | | +---------- skip if low bit == 0 + | | | | +------------- shift 2 enable + | | | +---------------- clear Extend + | | +---------------------- shift 1 opcode + | +---------------------------- shift 1 enable + +---------------------------------- A/B select + + The alter/skip format is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 0 0 0 0|ab| 1|regop| e op|se|ss|sl|in|sz|rs| alter/skip + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | \-+-/ \-+-/ | | | | | | + | | | | | | | | +- reverse skip sense + | | | | | | | +---- skip if register == 0 + | | | | | | +------- increment register + | | | | | +---------- skip if low bit == 0 + | | | | +------------- skip if sign bit == 0 + | | | +---------------- skip if Extend == 0 + | | +--------------------- clr/com/set Extend + | +--------------------------- clr/com/set register + +---------------------------------- A/B select + + The I/O transfer format is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 1 0 0 0|ab| 1|hc| opcode | device | I/O transfer + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | | \---+---/\-------+-------/ + | | | | + | | | +--------- device select + | | +---------------------- opcode + | +---------------------------- hold/clear flag + +---------------------------------- A/B select + + The IO transfer instruction controls the specified device. + Depending on the opcode, the instruction may set or clear + the device flag, start or stop I/O, or read or write data. + + The 2100 added an extended memory reference instruction; + the 21MX added extended arithmetic, operate, byte, word, + and bit instructions. Note that the HP 21xx is, despite + the right-to-left bit numbering, a big endian system. + Bits <15:8> are byte 0, and bits <7:0> are byte 1. + + + The extended memory reference format (HP 2100) is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 1| 0 0 0|op| 0| opcode | extended mem ref + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |in| operand address | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + The extended arithmetic format (HP 2100) is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 1| 0 0 0 0 0|dr| 0 0| opcode |shift count| extended arithmetic + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + The extended operate format (HP 21MX) is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 1| 0 0 0|op| 0| 1 1 1 1 1| opcode | extended operate + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + The extended byte and word format (HP 21MX) is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 1| 0 0 0 1 0 1 1 1 1 1 1| opcode | extended byte/word + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |in| operand address | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0| + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + The extended bit operate format (HP 21MX) is: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | 1| 0 0 0 1 0 1 1 1 1 1 1 1| opcode | extended bit operate + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |in| operand address | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |in| operand address | + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + + General notes: + + 1. Reasons to stop. The simulator can be stopped by: + + HALT instruction + breakpoint encountered + infinite indirection loop + unimplemented instruction and stop_inst flag set + unknown I/O device and stop_dev flag set + I/O error in I/O simulator + + 2. Interrupts. I/O devices are modelled by substituting software states for + I/O backplane signals. Signals generated by I/O instructions and DMA + cycles are dispatched to the target device for action. Backplane signals + are processed sequentially, except for the "clear flag" signal, which may + be generated in parallel with another signal. For example, the "STC sc,C" + instruction generates the "set control" and the "clear flag" signals + concurrently. + + CPU interrupt signals are modelled as three parallel arrays: + + - device request priority as bit vector dev_prl [2] [31..0] + - device interrupt requests as bit vector dev_irq [2] [31..0] + - device service requests as bit vector dev_srq [2] [31..0] + + Each array forms a 64-bit vector, with bits 0-31 of the first element + corresponding to select codes 00-37 octal, and bits 0-31 of the second + element corresponding to select codes 40-77 octal. + + The HP 2100 interrupt structure is based on the PRH, PRL, IRQ, and IAK + signals. PRH indicates that no higher-priority device is interrupting. + PRL indicates to lower-priority devices that a given device is not + interrupting. IRQ indicates that a given device is requesting an + interrupt. IAK indicates that the given device's interrupt request is + being acknowledged. + + PRH and PRL form a hardware priority chain that extends from interface to + interface on the backplane. We model just PRL, as PRH is calculated from + the PRLs of higher-priority devices. + + Typical I/O devices have a flag, flag buffer, and control flip-flop. If a + device's flag, flag buffer, and control bits are set, and the device is + the highest priority on the interrupt chain, it requests an interrupt by + asserting IRQ. When the interrupt is acknowledged with IAK, the flag + buffer is cleared, preventing further interrupt requests from that device. + The combination of flag and control set blocks interrupts from lower + priority devices. + + Service requests are used to trigger the DMA service logic. Setting the + device flag typically also sets SRQ, although SRQ may be calculated + independently. + + 3. Non-existent memory. On the HP 2100, reads to non-existent memory + return zero, and writes are ignored. In the simulator, the + largest possible memory is instantiated and initialized to zero. + Thus, only writes need be checked against memory size. + + On the 21xx machines, doing SET CPU LOADERDISABLE decreases available + memory size by 64 words. + + 4. Adding I/O devices. These modules must be modified: + + hp2100_defs.h add interrupt request definition + hp2100_sys.c add sim_devices table entry + + 5. Instruction interruptibility. The simulator is fast enough, compared + to the run-time of the longest instructions, for interruptibility not + to matter. But the HP diagnostics explicitly test interruptibility in + EIS and DMS instructions, and long indirect address chains. Accordingly, + the simulator does "just enough" to pass these tests. In particular, if + an interrupt is pending but deferred at the beginning of an interruptible + instruction, the interrupt is taken at the appropriate point; but there + is no testing for new interrupts during execution (that is, the event + timer is not called). + + 6. Interrupt deferral. At instruction fetch time, a pending interrupt + request will be deferred if the previous instruction was a JMP indirect, + JSB indirect, STC, CLC, STF, CLF, or was executing from an interrupt trap + cell. In addition, the following instructions will cause deferral on the + 1000 series: SFS, SFC, JRS, DJP, DJS, SJP, SJS, UJP, and UJS. + + On the HP 1000, the request is always deferred until after the current + instruction completes. On the 21xx, the request is deferred unless the + current instruction is an MRG instruction other than JMP or JMP,I or + JSB,I. Note that for the 21xx, SFS and SFC are not included in the + deferral criteria. + + 7. Terminology. The 1000 series of computers was originally called the 21MX + at introduction. The 21MX (occasionally, 21MXM) corresponds to the 1000 + M-Series, and the 21MXE (occasionally, 21XE) corresponds to the 1000 + E-Series. The model numbers were changed before the introduction of the + 1000 F-Series, although some internal HP documentation refers to a 21MXF. + + The terms MEM (Memory Expansion Module), MEU (Memory Expansion Unit), DMI + (Dynamic Mapping Instructions), and DMS (Dynamic Mapping System) are used + somewhat interchangeably to refer to the logical-to-physical memory + address translation option provided on the 1000-Series. DMS consists of + the MEM card (12731A) and the DMI firmware (13307A). However, MEM and MEU + have been used interchangeably to refer to the mapping card, as have DMI + and DMS to refer to the firmware instructions. +*/ + + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + + +/* Memory protect constants */ + +#define UNIT_V_MP_JSB (UNIT_V_UF + 0) /* MP jumper W5 */ +#define UNIT_V_MP_INT (UNIT_V_UF + 1) /* MP jumper W6 */ +#define UNIT_V_MP_SEL1 (UNIT_V_UF + 2) /* MP jumper W7 */ +#define UNIT_MP_JSB (1 << UNIT_V_MP_JSB) /* 1 = W5 is out */ +#define UNIT_MP_INT (1 << UNIT_V_MP_INT) /* 1 = W6 is out */ +#define UNIT_MP_SEL1 (1 << UNIT_V_MP_SEL1) /* 1 = W7 is out */ + +/* DMA channels */ + +typedef enum { ch1, ch2 } CHANNEL; /* channel number */ + +#define DMA_CHAN_COUNT 2 /* number of DMA channels */ + +#define DMA_OE 020000000000 /* byte packing odd/even flag */ +#define DMA1_STC 0100000 /* DMA - issue STC */ +#define DMA1_PB 0040000 /* DMA - pack bytes */ +#define DMA1_CLC 0020000 /* DMA - issue CLC */ +#define DMA2_OI 0100000 /* DMA - output/input */ + +typedef struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + FLIP_FLOP xferen; /* transfer enable flip-flop */ + FLIP_FLOP select; /* register select flip-flop */ + + uint32 cw1; /* device select */ + uint32 cw2; /* direction, address */ + uint32 cw3; /* word count */ + uint32 packer; /* byte-packer holding reg */ + } DMA_STATE; + +#define DMA_1_REQ (1 << ch1) /* channel 1 request */ +#define DMA_2_REQ (1 << ch2) /* channel 2 request */ + + +/* Command line switches */ + +#define ALL_BKPTS (SWMASK('E')|SWMASK('N')|SWMASK('S')|SWMASK('U')) +#define ALL_MAPMODES (SWMASK('S')|SWMASK('U')|SWMASK('P')|SWMASK('Q')) + + +/* RTE base-page addresses. */ + +static const uint32 xeqt = 0001717; /* XEQT address */ +static const uint32 tbg = 0001674; /* TBG address */ + +/* DOS base-page addresses. */ + +static const uint32 m64 = 0000040; /* constant -64 address */ +static const uint32 p64 = 0000067; /* constant +64 address */ + +/* CPU local data */ + +static uint32 jsb_plb = 2; /* protected lower bound for JSB */ +static uint32 saved_MR = 0; /* between executions */ +static uint32 fwanxm = 0; /* first word addr of nx mem */ + +/* CPU global data */ + +uint16 *M = NULL; /* memory */ +uint16 ABREG[2]; /* A/B registers */ +uint32 PR = 0; /* P register */ +uint32 SR = 0; /* S register */ +uint32 MR = 0; /* M register */ +uint32 TR = 0; /* T register */ +uint32 XR = 0; /* X register */ +uint32 YR = 0; /* Y register */ +uint32 E = 0; /* E register */ +uint32 O = 0; /* O register */ +FLIP_FLOP ion = CLEAR; /* interrupt enable */ +t_bool ion_defer = FALSE; /* interrupt defer */ +uint32 intaddr = 0; /* interrupt addr */ +uint32 stop_inst = 1; /* stop on ill inst */ +uint32 stop_dev = 0; /* stop on ill dev */ +uint16 pcq[PCQ_SIZE] = { 0 }; /* PC queue */ +uint32 pcq_p = 0; /* PC queue ptr */ +REG *pcq_r = NULL; /* PC queue reg ptr */ + +uint32 dev_prl [2] = { ~(uint32) 0, ~(uint32) 0 }; /* device priority low bit vector */ +uint32 dev_irq [2] = { 0, 0 }; /* device interrupt request bit vector */ +uint32 dev_srq [2] = { 0, 0 }; /* device service request bit vector */ + +/* Memory protect global data */ + +FLIP_FLOP mp_control = CLEAR; /* MP control flip-flop */ +FLIP_FLOP mp_flag = CLEAR; /* MP flag flip-flop */ +FLIP_FLOP mp_flagbuf = CLEAR; /* MP flag buffer flip-flop */ +FLIP_FLOP mp_mevff = CLEAR; /* memory expansion violation flip-flop */ +FLIP_FLOP mp_evrff = SET; /* enable violation register flip-flop */ + +uint32 mp_fence = 0; /* MP fence register */ +uint32 mp_viol = 0; /* MP violation register */ + +uint32 iop_sp = 0; /* iop stack reg */ +uint32 ind_max = 16; /* iadr nest limit */ +uint32 err_PC = 0; /* error PC */ +jmp_buf save_env; /* MP abort handler */ + +/* DMA global data */ + +DMA_STATE dma [DMA_CHAN_COUNT]; /* per-channel state */ + +/* Dynamic mapping system global data */ + +uint32 dms_enb = 0; /* dms enable */ +uint32 dms_ump = 0; /* dms user map */ +uint32 dms_sr = 0; /* dms status reg */ +uint32 dms_vr = 0; /* dms violation reg */ +uint16 dms_map[MAP_NUM * MAP_LNT] = { 0 }; /* dms maps */ + +/* External data */ + +extern DIB clk_dib; /* CLK DIB for idle check */ +extern const BOOT_ROM ptr_rom, dq_rom, ms_rom, ds_rom; /* boot ROMs for cpu_boot routine */ + +/* CPU local routines */ + +static t_stat Ea (uint32 IR, uint32 *addr, uint32 irq); +static uint16 ReadTAB (uint32 va); +static uint32 dms (uint32 va, uint32 map, uint32 prot); +static uint32 shift (uint32 inval, uint32 flag, uint32 oper); +static t_stat dma_cycle (CHANNEL chan, uint32 map); +static uint32 calc_dma (void); +static t_bool dev_conflict (void); +static uint32 devdisp (uint32 select_code, IOCYCLE signal_set, uint16 data); + +/* CPU global routines */ + +t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); +t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); +t_stat cpu_reset (DEVICE *dptr); +t_stat cpu_boot (int32 unitno, DEVICE *dptr); +t_stat mp_reset (DEVICE *dptr); +t_stat dma_reset (DEVICE *dptr); +t_stat cpu_set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc); +t_stat cpu_set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc); +t_stat cpu_show_model (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat cpu_set_opt (UNIT *uptr, int32 option, CONST char *cptr, void *desc); +t_stat cpu_clr_opt (UNIT *uptr, int32 option, CONST char *cptr, void *desc); +t_stat cpu_set_ldr (UNIT *uptr, int32 enable, CONST char *cptr, void *desc); +t_stat cpu_set_idle (UNIT *uptr, int32 option, CONST char *cptr, void *desc); +t_stat cpu_show_idle (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +void hp_post_cmd (t_bool from_scp); + +IOHANDLER cpuio; +IOHANDLER ovflio; +IOHANDLER pwrfio; +IOHANDLER protio; +IOHANDLER dmapio; +IOHANDLER dmasio; +IOHANDLER nullio; + + +/* Table of CPU features by model. + + Fields: + - typ: standard features plus typically configured options. + - opt: complete list of optional features. + - maxmem: maximum configurable memory in 16-bit words. + + Features in the "typical" list are enabled when the CPU model is selected. + If a feature appears in the "typical" list but NOT in the "optional" list, + then it is standard equipment and cannot be disabled. If a feature appears + in the "optional" list, then it may be enabled or disabled as desired by the + user. +*/ + +struct FEATURE_TABLE { /* CPU model feature table: */ + uint32 typ; /* - typical features */ + uint32 opt; /* - optional features */ + uint32 maxmem; /* - maximum memory */ + }; + +static struct FEATURE_TABLE cpu_features[] = { /* features in UNIT_xxxx order*/ + { UNIT_DMA | UNIT_MP, /* UNIT_2116 */ + UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_EAU, + 32768 }, + { UNIT_DMA, /* UNIT_2115 */ + UNIT_PFAIL | UNIT_DMA | UNIT_EAU, + 8192 }, + { UNIT_DMA, /* UNIT_2114 */ + UNIT_PFAIL | UNIT_DMA, + 16384 }, + { 0, 0, 0 }, + { UNIT_PFAIL | UNIT_MP | UNIT_DMA | UNIT_EAU, /* UNIT_2100 */ + UNIT_DMA | UNIT_FP | UNIT_IOP | UNIT_FFP, + 32768 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | UNIT_DMS, /* UNIT_1000_M */ + UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_DMS | + UNIT_IOP | UNIT_FFP | UNIT_DS, + 1048576 }, + { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | UNIT_DMS, /* UNIT_1000_E */ + UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_DMS | + UNIT_IOP | UNIT_FFP | UNIT_DBI | UNIT_DS | UNIT_EMA_VMA, + 1048576 }, + { UNIT_MP | UNIT_DMA | UNIT_EAU | UNIT_FP | /* UNIT_1000_F */ + UNIT_FFP | UNIT_DBI | UNIT_DMS, + UNIT_PFAIL | UNIT_DMA | UNIT_MP | UNIT_VIS | + UNIT_IOP | UNIT_DS | UNIT_SIGNAL | UNIT_EMA_VMA, + 1048576 } + }; + + +/* Null device information block */ + +DIB null_dib = { &nullio, 0 }; + +/* CPU data structures + + cpu_dib CPU device information block + cpu_dev CPU device descriptor + cpu_unit CPU unit descriptor + cpu_reg CPU register list + cpu_mod CPU modifiers list + cpu_deb CPU debug flags +*/ + +DIB cpu_dib = { &cpuio, CPU }; + +UNIT cpu_unit = { UDATA (NULL, UNIT_FIX + UNIT_BINK, 0) }; + +REG cpu_reg[] = { + { ORDATA (P, PR, 15) }, + { ORDATA (A, AR, 16), REG_FIT }, + { ORDATA (B, BR, 16), REG_FIT }, + { ORDATA (M, MR, 15) }, + { ORDATA (T, TR, 16), REG_RO }, + { ORDATA (X, XR, 16) }, + { ORDATA (Y, YR, 16) }, + { ORDATA (S, SR, 16) }, + { FLDATA (E, E, 0) }, + { FLDATA (O, O, 0) }, + { FLDATA (ION, ion, 0) }, + { FLDATA (ION_DEFER, ion_defer, 0) }, + { ORDATA (CIR, intaddr, 6) }, + { FLDATA (DMSENB, dms_enb, 0) }, + { FLDATA (DMSCUR, dms_ump, VA_N_PAG) }, + { ORDATA (DMSSR, dms_sr, 16) }, + { ORDATA (DMSVR, dms_vr, 16) }, + { BRDATA (DMSMAP, dms_map, 8, 16, MAP_NUM * MAP_LNT) }, + { ORDATA (IOPSP, iop_sp, 16) }, + { FLDATA (STOP_INST, stop_inst, 0) }, + { FLDATA (STOP_DEV, stop_dev, 1) }, + { DRDATA (INDMAX, ind_max, 16), REG_NZ + PV_LEFT }, + { BRDATA (PCQ, pcq, 8, 15, PCQ_SIZE), REG_RO+REG_CIRC }, + { ORDATA (PCQP, pcq_p, 6), REG_HRO }, + { ORDATA (JSBPLB, jsb_plb, 32), REG_HRO }, + { ORDATA (SAVEDMR, saved_MR, 32), REG_HRO }, + { ORDATA (FWANXM, fwanxm, 32), REG_HRO }, + { ORDATA (WRU, sim_int_char, 8), REG_HRO }, + { ORDATA (BRK, sim_brk_char, 8), REG_HRO }, + { ORDATA (DEL, sim_del_char, 8), REG_HRO }, + { BRDATA (PRL, dev_prl, 8, 32, 2), REG_HRO }, + { BRDATA (IRQ, dev_irq, 8, 32, 2), REG_HRO }, + { BRDATA (SRQ, dev_srq, 8, 32, 2), REG_HRO }, + { NULL } + }; + +/* CPU modifier table. + + The 21MX monikers are deprecated in favor of the 1000 designations. See the + "HP 1000 Series Naming History" on the back inside cover of the Technical + Reference Handbook. */ + +MTAB cpu_mod[] = { + { UNIT_MODEL_MASK, UNIT_2116, "", "2116", &cpu_set_model, &cpu_show_model, (void *) "2116" }, + { UNIT_MODEL_MASK, UNIT_2115, "", "2115", &cpu_set_model, &cpu_show_model, (void *) "2115" }, + { UNIT_MODEL_MASK, UNIT_2114, "", "2114", &cpu_set_model, &cpu_show_model, (void *) "2114" }, + { UNIT_MODEL_MASK, UNIT_2100, "", "2100", &cpu_set_model, &cpu_show_model, (void *) "2100" }, + { UNIT_MODEL_MASK, UNIT_1000_E, "", "1000-E", &cpu_set_model, &cpu_show_model, (void *) "1000-E" }, + { UNIT_MODEL_MASK, UNIT_1000_E, NULL, "21MX-E", &cpu_set_model, &cpu_show_model, (void *) "1000-E" }, + { UNIT_MODEL_MASK, UNIT_1000_M, "", "1000-M", &cpu_set_model, &cpu_show_model, (void *) "1000-M" }, + { UNIT_MODEL_MASK, UNIT_1000_M, NULL, "21MX-M", &cpu_set_model, &cpu_show_model, (void *) "1000-M" }, + +#if defined (HAVE_INT64) + { UNIT_MODEL_MASK, UNIT_1000_F, "", "1000-F", &cpu_set_model, &cpu_show_model, (void *) "1000-F" }, +#endif + + { MTAB_XTD | MTAB_VDV, 1, "IDLE", "IDLE", &cpu_set_idle, &cpu_show_idle, NULL }, + { MTAB_XTD | MTAB_VDV, 0, NULL, "NOIDLE", &cpu_set_idle, NULL, NULL }, + + { MTAB_XTD | MTAB_VDV, 1, NULL, "LOADERENABLE", &cpu_set_ldr, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 0, NULL, "LOADERDISABLE", &cpu_set_ldr, NULL, NULL }, + + { UNIT_EAU, UNIT_EAU, "EAU", "EAU", &cpu_set_opt, NULL, NULL }, + { UNIT_EAU, 0, "no EAU", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_EAU, NULL, "NOEAU", &cpu_clr_opt, NULL, NULL }, + + { UNIT_FP, UNIT_FP, "FP", "FP", &cpu_set_opt, NULL, NULL }, + { UNIT_FP, 0, "no FP", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_FP, NULL, "NOFP", &cpu_clr_opt, NULL, NULL }, + + { UNIT_IOP, UNIT_IOP, "IOP", "IOP", &cpu_set_opt, NULL, NULL }, + { UNIT_IOP, 0, "no IOP", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_IOP, NULL, "NOIOP", &cpu_clr_opt, NULL, NULL }, + + { UNIT_DMS, UNIT_DMS, "DMS", "DMS", &cpu_set_opt, NULL, NULL }, + { UNIT_DMS, 0, "no DMS", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_DMS, NULL, "NODMS", &cpu_clr_opt, NULL, NULL }, + + { UNIT_FFP, UNIT_FFP, "FFP", "FFP", &cpu_set_opt, NULL, NULL }, + { UNIT_FFP, 0, "no FFP", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_FFP, NULL, "NOFFP", &cpu_clr_opt, NULL, NULL }, + + { UNIT_DBI, UNIT_DBI, "DBI", "DBI", &cpu_set_opt, NULL, NULL }, + { UNIT_DBI, 0, "no DBI", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_DBI, NULL, "NODBI", &cpu_clr_opt, NULL, NULL }, + + { UNIT_EMA_VMA, UNIT_EMA, "EMA", "EMA", &cpu_set_opt, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_EMA, NULL, "NOEMA", &cpu_clr_opt, NULL, NULL }, + + { UNIT_EMA_VMA, UNIT_VMAOS, "VMA", "VMA", &cpu_set_opt, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_VMAOS, NULL, "NOVMA", &cpu_clr_opt, NULL, NULL }, + + { UNIT_EMA_VMA, 0, "no EMA/VMA", NULL, &cpu_set_opt, NULL, NULL }, + +#if defined (HAVE_INT64) + { UNIT_VIS, UNIT_VIS, "VIS", "VIS", &cpu_set_opt, NULL, NULL }, + { UNIT_VIS, 0, "no VIS", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_VIS, NULL, "NOVIS", &cpu_clr_opt, NULL, NULL }, + + { UNIT_SIGNAL, UNIT_SIGNAL,"SIGNAL", "SIGNAL", &cpu_set_opt, NULL, NULL }, + { UNIT_SIGNAL, 0, "no SIGNAL", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_SIGNAL, NULL, "NOSIGNAL", &cpu_clr_opt, NULL, NULL }, +#endif + +/* Future microcode support. + { UNIT_DS, UNIT_DS, "DS", "DS", &cpu_set_opt, NULL, NULL }, + { UNIT_DS, 0, "no DS", NULL, NULL, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, UNIT_DS, NULL, "NODS", &cpu_clr_opt, NULL, NULL }, +*/ + + { MTAB_XTD | MTAB_VDV, 4096, NULL, "4K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 8192, NULL, "8K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 12288, NULL, "12K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 16384, NULL, "16K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 24576, NULL, "24K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 32768, NULL, "32K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 65536, NULL, "64K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 131072, NULL, "128K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 262144, NULL, "256K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 524288, NULL, "512K", &cpu_set_size, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 1048576, NULL, "1024K", &cpu_set_size, NULL, NULL }, + { 0 } + }; + +DEBTAB cpu_deb[] = { + { "OS", DEB_OS }, + { "OSTBG", DEB_OSTBG }, + { "VMA", DEB_VMA }, + { "EMA", DEB_EMA }, + { "VIS", DEB_VIS }, + { "SIG", DEB_SIG }, + { NULL, 0 } + }; + +DEVICE cpu_dev = { + "CPU", /* device name */ + &cpu_unit, /* unit array */ + cpu_reg, /* register array */ + cpu_mod, /* modifier array */ + 1, /* number of units */ + 8, /* address radix */ + PA_N_SIZE, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 16, /* data width */ + &cpu_ex, /* examine routine */ + &cpu_dep, /* deposit routine */ + &cpu_reset, /* reset routine */ + &cpu_boot, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &cpu_dib, /* device information block */ + DEV_DEBUG, /* device flags */ + 0, /* debug control flags */ + cpu_deb, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + +/* Overflow device information block */ + +DIB ovfl_dib = { &ovflio, OVF }; + +/* Powerfail device information block */ + +DIB pwrf_dib = { &pwrfio, PWR }; + +/* Memory protect data structures + + mp_dib MP device information block + mp_dev MP device descriptor + mp_unit MP unit descriptor + mp_reg MP register list + mp_mod MP modifiers list +*/ + +DIB mp_dib = { &protio, PRO }; + +UNIT mp_unit = { UDATA (NULL, UNIT_MP_SEL1, 0) }; /* default is JSB in, INT in, SEL1 out */ + +REG mp_reg[] = { + { FLDATA (CTL, mp_control, 0) }, + { FLDATA (FLG, mp_flag, 0) }, + { FLDATA (FBF, mp_flagbuf, 0) }, + { ORDATA (FR, mp_fence, 15) }, + { ORDATA (VR, mp_viol, 16) }, + { FLDATA (EVR, mp_evrff, 0) }, + { FLDATA (MEV, mp_mevff, 0) }, + { NULL } + }; + +MTAB mp_mod[] = { + { UNIT_MP_JSB, UNIT_MP_JSB, "JSB (W5) out", "JSBOUT", NULL }, + { UNIT_MP_JSB, 0, "JSB (W5) in", "JSBIN", NULL }, + { UNIT_MP_INT, UNIT_MP_INT, "INT (W6) out", "INTOUT", NULL }, + { UNIT_MP_INT, 0, "INT (W6) in", "INTIN", NULL }, + { UNIT_MP_SEL1, UNIT_MP_SEL1, "SEL1 (W7) out", "SEL1OUT", NULL }, + { UNIT_MP_SEL1, 0, "SEL1 (W7) in", "SEL1IN", NULL }, + { 0 } + }; + +DEVICE mp_dev = { + "MP", /* device name */ + &mp_unit, /* unit array */ + mp_reg, /* register array */ + mp_mod, /* modifier array */ + 1, /* number of units */ + 8, /* address radix */ + 1, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 16, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &mp_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &mp_dib, /* device information block */ + DEV_DISABLE | DEV_DIS, /* device flags */ + 0, /* debug control flags */ + NULL, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + +/* DMA controller data structures + + dmax_dib DMAx device information block + dmax_dev DMAx device descriptor + dmax_reg DMAx register list +*/ + +DIB dmap1_dib = { &dmapio, DMA1, ch1 }; +DIB dmas1_dib = { &dmasio, DMALT1, ch1 }; + +UNIT dma1_unit = { UDATA (NULL, 0, 0) }; + +REG dma1_reg[] = { + { FLDATA (XFR, dma [ch1].xferen, 0) }, + { FLDATA (CTL, dma [ch1].control, 0) }, + { FLDATA (FLG, dma [ch1].flag, 0) }, + { FLDATA (FBF, dma [ch1].flagbuf, 0) }, + { FLDATA (CTL2, dma [ch1].select, 0) }, + { ORDATA (CW1, dma [ch1].cw1, 16) }, + { ORDATA (CW2, dma [ch1].cw2, 16) }, + { ORDATA (CW3, dma [ch1].cw3, 16) }, + { FLDATA (BYTE, dma [ch1].packer, 31) }, + { ORDATA (PACKER, dma [ch1].packer, 8) }, + { NULL } + }; + +DEVICE dma1_dev = { + "DMA1", /* device name */ + &dma1_unit, /* unit array */ + dma1_reg, /* register array */ + NULL, /* modifier array */ + 1, /* number of units */ + 8, /* address radix */ + 1, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 16, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &dma_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &dmap1_dib, /* device information block */ + DEV_DISABLE, /* device flags */ + 0, /* debug control flags */ + NULL, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + +DIB dmap2_dib = { &dmapio, DMA2, ch2 }; +DIB dmas2_dib = { &dmasio, DMALT2, ch2 }; + +UNIT dma2_unit = { UDATA (NULL, 0, 0) }; + +REG dma2_reg[] = { + { FLDATA (XFR, dma [ch2].xferen, 0) }, + { FLDATA (CTL, dma [ch2].control, 0) }, + { FLDATA (FLG, dma [ch2].flag, 0) }, + { FLDATA (FBF, dma [ch2].flagbuf, 0) }, + { FLDATA (CTL2, dma [ch2].select, 0) }, + { ORDATA (CW1, dma [ch2].cw1, 16) }, + { ORDATA (CW2, dma [ch2].cw2, 16) }, + { ORDATA (CW3, dma [ch2].cw3, 16) }, + { FLDATA (BYTE, dma [ch2].packer, 31) }, + { ORDATA (PACKER, dma [ch2].packer, 8) }, + { NULL } + }; + +DEVICE dma2_dev = { + "DMA2", /* device name */ + &dma2_unit, /* unit array */ + dma2_reg, /* register array */ + NULL, /* modifier array */ + 1, /* number of units */ + 8, /* address radix */ + 1, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 16, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &dma_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &dmap2_dib, /* device information block */ + DEV_DISABLE, /* device flags */ + 0, /* debug control flags */ + NULL, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + +static DEVICE *dma_dptrs [] = { &dma1_dev, &dma2_dev }; + + +/* Interrupt deferral table (1000 version) */ +/* Deferral for I/O subops: soHLT, soFLG, soSFC, soSFS, soMIX, soLIX, soOTX, soCTL */ +static t_bool defer_tab [] = { FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE }; + +/* Device I/O dispatch table */ + +DIB *dtab [64] = { &cpu_dib, &ovfl_dib }; /* init with immutable devices */ + + + +/* Execute CPU instructions. + + This routine is the instruction decode routine for the HP 2100. It is called + from the simulator control program to execute instructions in simulated + memory, starting at the simulated PR. It runs until 'reason' is set to a + status other than SCPE_OK. +*/ + +t_stat sim_instr (void) +{ +uint32 intrq, dmarq; /* set after setjmp */ +uint32 iotrap = 0; /* set after setjmp */ +t_stat reason = SCPE_OK; /* set after setjmp */ +int32 i; /* temp */ +DEVICE *dptr; /* temp */ +DIB *dibptr; /* temp */ +int abortval; + +/* Restore register state */ + +if (dev_conflict ()) /* check device assignment consistency */ + return SCPE_STOP; /* conflict; stop execution */ + +err_PC = PR = PR & VAMASK; /* load local P register */ + +/* Restore I/O state */ + +dev_prl [0] = dev_prl [1] = ~(uint32) 0; /* set all priority lows */ +dev_irq [0] = dev_irq [1] = 0; /* clear all interrupt requests */ +dev_srq [0] = dev_srq [1] = 0; /* clear all service requests */ + +for (i = OPTDEV; i <= MAXDEV; i++) /* default optional devices */ + dtab [i] = &null_dib; + +dtab [PWR] = &pwrf_dib; /* for now, powerfail is always present */ + +for (i = 0; sim_devices [i] != NULL; i++) { /* loop thru dev */ + dptr = sim_devices [i]; + dibptr = (DIB *) dptr->ctxt; /* get DIB */ + + if (dibptr && !(dptr->flags & DEV_DIS)) { /* handler exists and device is enabled? */ + dtab [dibptr->select_code] = dibptr; /* set DIB pointer into dispatch table */ + dibptr->io_handler (dibptr, ioSIR, 0); /* set interrupt request state */ + } + } + +if (dtab [DMA1] != &null_dib) /* first DMA channel enabled? */ + dtab [DMALT1] = &dmas1_dib; /* set up secondary device handler */ + +if (dtab [DMA2] != &null_dib) /* second DMA channel enabled? */ + dtab [DMALT2] = &dmas2_dib; /* set up secondary device handler */ + +/* Configure interrupt deferral table */ + +if (UNIT_CPU_FAMILY == UNIT_FAMILY_21XX) /* 21xx series? */ + defer_tab [soSFC] = defer_tab [soSFS] = FALSE; /* SFC/S doesn't defer */ +else /* 1000 series */ + defer_tab [soSFC] = defer_tab [soSFS] = TRUE; /* SFC/S does defer */ + + +/* Set MP abort handling. + + If an abort occurs in memory protection, the relocation routine executes a + longjmp to this area OUTSIDE the main simulation loop. Memory protection + errors are the only sources of aborts in the HP 2100. All referenced + variables must be globals, and all sim_instr scoped automatics must be set + after the setjmp. + + To initiate an MP abort, use the MP_ABORT macro and pass the violation + address. MP_ABORT should only be called if "mp_control" is SET, as aborts do + not occur if MP is turned off. + + An MP interrupt (SC 05) is qualified by "ion" but not by "ion_defer". If the + interrupt system is off when an MP violation is detected, the violating + instruction will be aborted, even though no interrupt occurs. In this case, + neither the flag nor flag buffer are set, and EVR is not cleared. + + Implementation notes: + + 1. The protected lower bound address for the JSB instruction depends on the + W5 jumper setting. If W5 is in, then the lower bound is 2, allowing JSBs + to the A and B registers. If W5 is out, then the lower bound is 0, just + as with JMP. + + 2. The violation address is passed to enable the MEM violation register to + be updated. The "longjmp" routine will not pass a value of 0; it is + converted internally to 1. This is OK, because only the page number + of the address value is used, and locations 0 and 1 are both on page 0. + + 3. This routine is used both for MP and MEM violations. The MEV flip-flop + will be clear for the former and set for the latter. The MEV violation + register will be updated by "dms_upd_vr" only if the call is NOT for an + MEM violation; if it is, then the VR has already been set and should not + be disturbed. +*/ + +jsb_plb = (mp_unit.flags & UNIT_MP_JSB) ? 0 : 2; /* set protected lower bound for JSB */ + +abortval = setjmp (save_env); /* set abort hdlr */ + +if (abortval) { /* memory protect abort? */ + dms_upd_vr (abortval); /* update violation register (if not MEV) */ + + if (ion) /* interrupt system on? */ + protio (dtab [PRO], ioENF, 0); /* set flag */ + } + +dmarq = calc_dma (); /* initial recalc of DMA masks */ +intrq = calc_int (); /* initial recalc of interrupts */ + + +/* Main instruction fetch/decode loop */ + +while (reason == SCPE_OK) { /* loop until halted */ + uint32 IR, MA, absel, v1, t, skip; + + err_PC = PR; /* save P for error recovery */ + + if (sim_interval <= 0) { /* event timeout? */ + reason = sim_process_event (); /* process event service */ + + if (reason != SCPE_OK) /* service failed? */ + break; /* stop execution */ + + dmarq = calc_dma (); /* recalc DMA reqs */ + intrq = calc_int (); /* recalc interrupts */ + } + +/* DMA cycles are requested by an I/O card asserting its SRQ signal. If a DMA + channel is programmed to respond to that card's select code, a DMA cycle will + be initiated. A DMA cycle consists of a memory cycle and an I/O cycle. + These cycles are synchronized with the control processor on the 21xx CPUs. + On the 1000s, memory cycles are asynchronous, while I/O cycles are + synchronous. Memory cycle time is about 40% of the I/O cycle time. + + With properly designed interface cards, DMA is capable of taking consecutive + I/O cycles. On all machines except the 1000 M-Series, a DMA cycle freezes + the CPU for the duration of the cycle. On the M-Series, a DMA cycle freezes + the CPU if it attempts an I/O cycle (including IAK) or a directly-interfering + memory cycle. An interleaved memory cycle is allowed. Otherwise, the + control processor is allowed to run. Therefore, during consecutive DMA + cycles, the M-Series CPU will run until an IOG instruction is attempted, + whereas the other CPUs will freeze completely. + + All DMA cards except the 12607B provide two independent channels. If both + channels are active simultaneously, channel 1 has priority for I/O cycles + over channel 2. + + Most I/O cards assert SRQ no more than 50% of the time. A few buffered + cards, such as the 12821A and 13175A Disc Interfaces, are capable of + asserting SRQ continuously while filling or emptying the buffer. If SRQ for + channel 1 is asserted continuously when both channels are active, then no + channel 2 cycles will occur until channel 1 completes. + + Implementation notes: + + 1. CPU freeze is simulated by skipping instruction execution during the + current loop cycle. + + 2. If both channels have SRQ asserted, DMA priority is simulated by skipping + the channel 2 cycle if channel 1's SRQ is still asserted at the end of + its cycle. If it is not, then channel 2 steals the next cycle from the + CPU. + + 3. The 1000 M-Series allows some CPU processing concurrently with + continuous DMA cycles, whereas all other CPUs freeze. The processor + freezes if an I/O cycle is attempted, including an interrupt + acknowledgement. Because some microcode extensions (e.g., Access IOP, + RTE-6/VM OS) perform I/O cycles, advance detection of I/O cycles is + difficult. Therefore, we freeze all processing for the M-Series as well. +*/ + + if (dmarq) { + if (dmarq & DMA_1_REQ) { /* DMA channel 1 request? */ + reason = dma_cycle (ch1, PAMAP); /* do one DMA cycle using port A map */ + + if (reason == SCPE_OK) /* cycle OK? */ + dmarq = calc_dma (); /* recalc DMA requests */ + else + break; /* cycle failed, so stop */ + } + + if ((dmarq & (DMA_1_REQ | DMA_2_REQ)) == DMA_2_REQ) { /* DMA channel 1 idle and channel 2 request? */ + reason = dma_cycle (ch2, PBMAP); /* do one DMA cycle using port B map */ + + if (reason == SCPE_OK) /* cycle OK? */ + dmarq = calc_dma (); /* recalc DMA requests */ + else + break; /* cycle failed, so stop */ + } + + if (dmarq) /* DMA request still pending? */ + continue; /* service it before instruction execution */ + + intrq = calc_int (); /* recalc interrupts */ + } + + if (intrq && ion_defer) /* interrupt pending but deferred? */ + ion_defer = calc_defer (); /* confirm deferral */ + +/* Check for pending interrupt request. + + Interrupt recognition is controlled by three state variables: "ion", + "ion_defer", and "intrq". "ion" corresponds to the INTSYS flip-flop in the + 1000 CPU, "ion_defer" corresponds to the INTEN flip-flop, and "intrq" + corresponds to the NRMINT flip-flop. STF 00 and CLF 00 set and clear INTSYS, + turning the interrupt system on and off. Micro-orders ION and IOFF set and + clear INTEN, deferring or allowing certain interrupts. An IRQ signal from a + device, qualified by the corresponding PRL signal, will set NRMINT to request + a normal interrupt; an IOFF or IAK will clear it. + + Under simulation, "ion" is controlled by STF/CLF 00. "ion_defer" is set or + cleared as appropriate by the individual instruction simulators. "intrq" is + set to the successfully interrupting device's select code, or to zero if + there is no qualifying interrupt request. + + Presuming PRL is set to allow priority to an interrupting device: + + 1. Power fail (SC 04) may interrupt if "ion_defer" is clear; this is not + conditional on "ion" being set. + + 2. Memory protect (SC 05) may interrupt if "ion" is set; this is not + conditional on "ion_defer" being clear. + + 3. Parity error (SC 05) may interrupt always; this is not conditional on + "ion" being set or "ion_defer" being clear. + + 4. All other devices (SC 06 and up) may interrupt if "ion" is set and + "ion_defer" is clear. + + Qualification with "ion" is performed by "calc_int", except for case 2, which + is qualified by the MP abort handler above (because qualification occurs on + the MP card, rather than in the CPU). Therefore, we need only qualify by + "ion_defer" here. +*/ + + if (intrq && ((intrq == PRO) || !ion_defer)) { /* interrupt request? */ + if (sim_brk_summ && /* any breakpoints? */ + sim_brk_test (intrq, SWMASK ('E') | /* unconditional or right type for DMS? */ + (dms_enb ? SWMASK ('S') : SWMASK ('N')))) { + reason = STOP_IBKPT; /* stop simulation */ + break; + } + + intaddr = intrq; /* save int addr in CIR */ + intrq = 0; /* clear request */ + ion_defer = TRUE; /* defer interrupts */ + iotrap = 1; /* mark as I/O trap cell instr */ + + if (dms_enb) /* dms enabled? */ + dms_sr = dms_sr | MST_ENBI; /* set in status */ + else /* not enabled */ + dms_sr = dms_sr & ~MST_ENBI; /* clear in status */ + + if (dms_ump) { /* user map enabled at interrupt? */ + dms_sr = dms_sr | MST_UMPI; /* set in status */ + dms_ump = SMAP; /* switch to system map */ + } + else /* system map enabled at interrupt */ + dms_sr = dms_sr & ~MST_UMPI; /* clear in status */ + + IR = ReadW (intaddr); /* get trap cell instruction */ + + devdisp (intaddr, ioIAK, (uint16) IR); /* acknowledge interrupt */ + + if (intaddr != PRO) /* not MP interrupt? */ + protio (dtab [intaddr], ioIAK, IR); /* send IAK for device to MP too */ + } + + else { /* normal instruction */ + iotrap = 0; /* not a trap cell instruction */ + + if (sim_brk_summ && /* any breakpoints? */ + sim_brk_test (PR, SWMASK ('E') | /* unconditional or */ + (dms_enb ? /* correct type for DMS state? */ + (dms_ump ? + SWMASK ('U') : SWMASK ('S')) : + SWMASK ('N')))) { + reason = STOP_IBKPT; /* stop simulation */ + break; + } + + if (mp_evrff) /* violation register enabled */ + mp_viol = PR; /* update with current P */ + + IR = ReadW (PR); /* fetch instr */ + PR = (PR + 1) & VAMASK; + ion_defer = FALSE; + } + + sim_interval = sim_interval - 1; /* count instruction */ + +/* Instruction decode. The 21MX does a 256-way decode on IR<15:8> + + 15 14 13 12 11 10 09 08 instruction + + x <-!= 0-> x x x x memory reference + 0 0 0 0 x 0 x x shift + 0 0 0 0 x 0 x x alter-skip + 1 0 0 0 x 1 x x IO + 1 0 0 0 0 0 x 0 extended arithmetic + 1 0 0 0 0 0 0 1 divide (decoded as 100400) + 1 0 0 0 1 0 0 0 double load (decoded as 104000) + 1 0 0 0 1 0 0 1 double store (decoded as 104400) + 1 0 0 0 1 0 1 0 extended instr group 0 (A/B must be set) + 1 0 0 0 x 0 1 1 extended instr group 1 (A/B ignored) */ + + absel = (IR & I_AB) ? 1 : 0; /* get A/B select */ + + switch ((IR >> 8) & 0377) { /* decode IR<15:8> */ + +/* Memory reference instructions */ + + case 0020:case 0021:case 0022:case 0023: + case 0024:case 0025:case 0026:case 0027: + case 0220:case 0221:case 0222:case 0223: + case 0224:case 0225:case 0226:case 0227: + reason = Ea (IR, &MA, intrq); /* AND */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + AR = AR & ReadW (MA); + break; + +/* JSB is a little tricky. It is possible to generate both an MP and a DM + violation simultaneously, as the MP and MEM cards validate in parallel. + Consider a JSB to a location under the MP fence and on a write-protected + page. This situation must be reported as a DM violation, because it has + priority (SFS 5 and SFC 5 check only the MEVFF, which sets independently of + the MP fence violation). Under simulation, this means that DM violations + must be checked, and the MEVFF must be set, before an MP abort is taken. + This is done by the "mp_dms_jmp" routine. +*/ + + case 0230:case 0231:case 0232:case 0233: + case 0234:case 0235:case 0236:case 0237: + ion_defer = TRUE; /* defer if JSB,I */ + + case 0030:case 0031:case 0032:case 0033: + case 0034:case 0035:case 0036:case 0037: + reason = Ea (IR, &MA, intrq); /* JSB */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + mp_dms_jmp (MA, jsb_plb); /* validate jump address */ + + WriteW (MA, PR); /* store P */ + PCQ_ENTRY; + PR = (MA + 1) & VAMASK; /* jump */ + break; + + case 0040:case 0041:case 0042:case 0043: + case 0044:case 0045:case 0046:case 0047: + case 0240:case 0241:case 0242:case 0243: + case 0244:case 0245:case 0246:case 0247: + reason = Ea (IR, &MA, intrq); /* XOR */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + AR = AR ^ ReadW (MA); + break; + +/* CPU idle processing. + + The 21xx/1000 CPUs have no "wait for interrupt" instruction. Idling in HP + operating systems consists of sitting in "idle loops" that end with JMP + instructions. We test for certain known patterns when a JMP instruction is + executed to decide if the simulator should idle. + + Idling must not occur if an interrupt is pending. As mentioned in the + "General Notes" above, HP CPUs will defer interrupts if certain instructions + are executed. OS interrupt handlers exit via such deferring instructions. + If there is a pending interrupt when the OS is otherwise idle, the idle loop + will execute one instruction before reentering the interrupt handler. If we + call sim_idle() in this case, we will lose interrupts. + + Consider the situation in RTE. Under simulation, the TTY and CLK events are + co-scheduled, with the CLK expiring one instruction after the TTY. When the + TTY interrupts, $CIC in RTE is entered. One instruction later, the CLK + expires and posts its interrupt, but it is not immediately handled, because + the JSB $CIC,I / JMP $CIC0,I / SFS 0,C instruction entry sequence continually + defers interrupts until the interrupt system is turned off. When $CIC + returns via $IRT, one instruction of the idle loop is executed, even though + the CLK interrupt is still pending, because the UJP instruction used to + return also defers interrupts. + + If sim_idle() is called at this point, the simulator will sleep when it + should be handling the pending CLK interrupt. When it awakes, TTY expiration + will be moved forward to the next instruction. The still-pending CLK + interrupt will then be recognized, and $CIC will be entered. But the TTY and + then the CLK will then expire and attempt to interrupt again, although they + are deferred by the $CIC entry sequence. This causes the second CLK + interrupt to be missed, as processing of the first one is just now being + started. + + Similarly, at the end of the CLK handling, the TTY interrupt is still + pending. When $IRT returns to the idle loop, sim_idle() would be called + again, so the TTY and then CLK interrupt a third time. Because the second + TTY interrupt is still pending, $CIC is entered, but the third TTY interrupt + is lost. + + We solve this problem by testing for a pending interrupt before calling + sim_idle(). The system isn't really quiescent if it is just about to handle + an interrupt. +*/ + + case 0250:case 0251:case 0252:case 0253: + case 0254:case 0255:case 0256:case 0257: + ion_defer = TRUE; /* defer if JMP,I */ + + case 0050:case 0051:case 0052:case 0053: + case 0054:case 0055:case 0056:case 0057: + reason = Ea (IR, &MA, intrq); /* JMP */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + mp_dms_jmp (MA, 0); /* validate jump addr */ + PCQ_ENTRY; + PR = MA; /* jump */ + +/* Idle conditions by operating system: + + RTE-6/VM: + - ISZ / JMP *-1 + - mp_fence = 0 + - XEQT (address 1717B) = 0 + - DMS on with system map enabled + - RTE verification: TBG (address 1674B) = CLK select code + + RTE though RTE-IVB: + - JMP * + - mp_fence = 0 + - XEQT (address 1717B) = 0 + - DMS on with user map enabled (RTE-III through RTE-IVB only) + - RTE verification: TBG (address 1674B) = CLK select code + + DOS through DOS-III: + - STF 0 / CCA / CCB / JMP *-3 + - DOS verification: A = B = -1, address 40B = -64, address 67B = +64 + - Note that in DOS, the TBG is set to 100 milliseconds +*/ + + if ((sim_idle_enab) && (intrq == 0)) /* idle enabled w/o pending irq? */ + if (((PR == err_PC) || /* RTE through RTE-IVB */ + ((PR == (err_PC - 1)) && /* RTE-6/VM */ + ((ReadW (PR) & I_MRG) == I_ISZ))) && /* RTE jump target */ + (mp_fence == CLEAR) && (M [xeqt] == 0) && /* RTE idle indications */ + (M [tbg] == clk_dib.select_code) || /* RTE verification */ + + (PR == (err_PC - 3)) && /* DOS through DOS-III */ + (ReadW (PR) == I_STF) && /* DOS jump target */ + (AR == 0177777) && (BR == 0177777) && /* DOS idle indication */ + (M [m64] == 0177700) && /* DOS verification */ + (M [p64] == 0000100)) /* DOS verification */ + + sim_idle (TMR_POLL, FALSE); /* idle the simulator */ + break; + + case 0060:case 0061:case 0062:case 0063: + case 0064:case 0065:case 0066:case 0067: + case 0260:case 0261:case 0262:case 0263: + case 0264:case 0265:case 0266:case 0267: + reason = Ea (IR, &MA, intrq); /* IOR */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + AR = AR | ReadW (MA); + break; + + case 0070:case 0071:case 0072:case 0073: + case 0074:case 0075:case 0076:case 0077: + case 0270:case 0271:case 0272:case 0273: + case 0274:case 0275:case 0276:case 0277: + reason = Ea (IR, &MA, intrq); /* ISZ */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + t = (ReadW (MA) + 1) & DMASK; + WriteW (MA, t); + + if (t == 0) + PR = (PR + 1) & VAMASK; + break; + + case 0100:case 0101:case 0102:case 0103: + case 0104:case 0105:case 0106:case 0107: + case 0300:case 0301:case 0302:case 0303: + case 0304:case 0305:case 0306:case 0307: + reason = Ea (IR, &MA, intrq); /* ADA */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + v1 = ReadW (MA); + t = AR + v1; + + if (t > DMASK) + E = 1; + + if (((~AR ^ v1) & (AR ^ t)) & SIGN) + O = 1; + + AR = t & DMASK; + break; + + case 0110:case 0111:case 0112:case 0113: + case 0114:case 0115:case 0116:case 0117: + case 0310:case 0311:case 0312:case 0313: + case 0314:case 0315:case 0316:case 0317: + reason = Ea (IR, &MA, intrq); /* ADB */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + v1 = ReadW (MA); + t = BR + v1; + + if (t > DMASK) + E = 1; + + if (((~BR ^ v1) & (BR ^ t)) & SIGN) + O = 1; + + BR = t & DMASK; + break; + + case 0120:case 0121:case 0122:case 0123: + case 0124:case 0125:case 0126:case 0127: + case 0320:case 0321:case 0322:case 0323: + case 0324:case 0325:case 0326:case 0327: + reason = Ea (IR, &MA, intrq); /* CPA */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + if (AR != ReadW (MA)) + PR = (PR + 1) & VAMASK; + break; + + case 0130:case 0131:case 0132:case 0133: + case 0134:case 0135:case 0136:case 0137: + case 0330:case 0331:case 0332:case 0333: + case 0334:case 0335:case 0336:case 0337: + reason = Ea (IR, &MA, intrq); /* CPB */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + if (BR != ReadW (MA)) + PR = (PR + 1) & VAMASK; + break; + + case 0140:case 0141:case 0142:case 0143: + case 0144:case 0145:case 0146:case 0147: + case 0340:case 0341:case 0342:case 0343: + case 0344:case 0345:case 0346:case 0347: + reason = Ea (IR, &MA, intrq); /* LDA */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + AR = ReadW (MA); + break; + + case 0150:case 0151:case 0152:case 0153: + case 0154:case 0155:case 0156:case 0157: + case 0350:case 0351:case 0352:case 0353: + case 0354:case 0355:case 0356:case 0357: + reason = Ea (IR, &MA, intrq); /* LDB */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + BR = ReadW (MA); + break; + + case 0160:case 0161:case 0162:case 0163: + case 0164:case 0165:case 0166:case 0167: + case 0360:case 0361:case 0362:case 0363: + case 0364:case 0365:case 0366:case 0367: + reason = Ea (IR, &MA, intrq); /* STA */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + WriteW (MA, AR); + break; + + case 0170:case 0171:case 0172:case 0173: + case 0174:case 0175:case 0176:case 0177: + case 0370:case 0371:case 0372:case 0373: + case 0374:case 0375:case 0376:case 0377: + reason = Ea (IR, &MA, intrq); /* STB */ + + if (reason != SCPE_OK) /* address failed to resolve? */ + break; /* stop execution */ + + WriteW (MA, BR); + break; + +/* Alter/skip instructions */ + + case 0004:case 0005:case 0006:case 0007: + case 0014:case 0015:case 0016:case 0017: + skip = 0; /* no skip */ + + if (IR & 000400) /* CLx */ + t = 0; + else + t = ABREG[absel]; + + if (IR & 001000) /* CMx */ + t = t ^ DMASK; + + if (IR & 000001) { /* RSS? */ + if ((IR & 000040) && (E != 0)) /* SEZ,RSS */ + skip = 1; + + if (IR & 000100) /* CLE */ + E = 0; + + if (IR & 000200) /* CME */ + E = E ^ 1; + + if (((IR & 000030) == 000030) && /* SSx,SLx,RSS */ + ((t & 0100001) == 0100001)) + skip = 1; + + if (((IR & 000030) == 000020) && /* SSx,RSS */ + ((t & SIGN) != 0)) + skip = 1; + + if (((IR & 000030) == 000010) && /* SLx,RSS */ + ((t & 1) != 0)) + skip = 1; + + if (IR & 000004) { /* INx */ + t = (t + 1) & DMASK; + + if (t == 0) + E = 1; + + if (t == SIGN) + O = 1; + } + + if ((IR & 000002) && (t != 0)) /* SZx,RSS */ + skip = 1; + + if ((IR & 000072) == 0) /* RSS */ + skip = 1; + } /* end if RSS */ + + else { + if ((IR & 000040) && (E == 0)) /* SEZ */ + skip = 1; + + if (IR & 000100) /* CLE */ + E = 0; + + if (IR & 000200) /* CME */ + E = E ^ 1; + + if ((IR & 000020) && /* SSx */ + ((t & SIGN) == 0)) + skip = 1; + + if ((IR & 000010) && /* SLx */ + ((t & 1) == 0)) + skip = 1; + + if (IR & 000004) { /* INx */ + t = (t + 1) & DMASK; + + if (t == 0) + E = 1; + + if (t == SIGN) + O = 1; + } + if ((IR & 000002) && (t == 0)) /* SZx */ + skip = 1; + } /* end if ~RSS */ + + ABREG[absel] = (uint16) t; /* store result */ + PR = (PR + skip) & VAMASK; /* add in skip */ + break; /* end if alter/skip */ + +/* Shift instructions */ + + case 0000:case 0001:case 0002:case 0003: + case 0010:case 0011:case 0012:case 0013: + t = shift (ABREG[absel], IR & 01000, IR >> 6); /* do first shift */ + + if (IR & 000040) /* CLE */ + E = 0; + + if ((IR & 000010) && ((t & 1) == 0)) /* SLx */ + PR = (PR + 1) & VAMASK; + + ABREG[absel] = (uint16) shift (t, IR & 00020, IR); /* do second shift */ + break; /* end if shift */ + +/* I/O instructions */ + + case 0204:case 0205:case 0206:case 0207: + case 0214:case 0215:case 0216:case 0217: + reason = iogrp (IR, iotrap); /* execute instr */ + break; /* end if I/O */ + +/* Extended arithmetic */ + + case 0200: /* EAU group 0 */ + case 0201: /* divide */ + case 0202: /* EAU group 2 */ + case 0210: /* DLD */ + case 0211: /* DST */ + reason = cpu_eau (IR, intrq); /* extended arith */ + break; + +/* Extended instructions */ + + case 0212: /* UIG 0 extension */ + reason = cpu_uig_0 (IR, intrq, iotrap); /* extended opcode */ + break; + + case 0203: /* UIG 1 extension */ + case 0213: + reason = cpu_uig_1 (IR, intrq, iotrap); /* extended opcode */ + break; + } /* end case IR */ + + if (reason == NOTE_IOG) { /* I/O instr exec? */ + dmarq = calc_dma (); /* recalc DMA masks */ + intrq = calc_int (); /* recalc interrupts */ + reason = SCPE_OK; /* continue */ + } + + else if (reason == NOTE_INDINT) { /* intr pend during indir? */ + PR = err_PC; /* back out of inst */ + reason = SCPE_OK; /* continue */ + } + } /* end while */ + +/* Simulation halted */ + +if (iotrap && (reason == STOP_HALT)) /* HLT in trap cell? */ + MR = intaddr; /* M = interrupt address */ +else /* normal HLT */ + MR = (PR - 1) & VAMASK; /* M = P - 1 */ + +TR = ReadTAB (MR); /* T = last word fetched */ +saved_MR = MR; /* save for T cmd update */ + +if (reason == STOP_HALT) /* programmed halt? */ + cpu_set_ldr (NULL, FALSE, NULL, NULL); /* disable loader (after T is read) */ +else /* simulation stop */ + PR = err_PC; /* back out instruction */ + +dms_upd_sr (); /* update dms_sr */ +dms_upd_vr (MR); /* update dms_vr */ +pcq_r->qptr = pcq_p; /* update pc q ptr */ + +if (dms_enb) /* DMS enabled? */ + if (dms_ump) /* set default */ + sim_brk_dflt = SWMASK ('U'); /* breakpoint type */ + else /* to current */ + sim_brk_dflt = SWMASK ('S'); /* map mode */ + +else /* DMS disabled */ + sim_brk_dflt = SWMASK ('N'); /* set breakpoint type to non-DMS */ + +return reason; /* return status code */ +} + + +/* Resolve indirect addresses. + + An indirect chain is followed until a direct address is obtained. Under + simulation, a maximum number of indirect levels are allowed (typically 16), + after which the instruction will be aborted. + + If the memory protect feature is present, an indirect counter is used that + allows a pending interrupt to be serviced if more than three levels of + indirection are encountered. If MP jumper W6 ("INT") is out and MP is + enabled, then pending interrupts are serviced immediately. When employing + the indirect counter, the hardware clears a pending interrupt deferral after + the third indirection and aborts the instruction after the fourth. +*/ + +t_stat resolve (uint32 MA, uint32 *addr, uint32 irq) +{ +uint32 i; +t_bool pending = (irq && !(mp_unit.flags & DEV_DIS)); +t_bool int_enable = ((mp_unit.flags & UNIT_MP_INT) && mp_control); + +for (i = 0; (i < ind_max) && (MA & I_IA); i++) { /* resolve multilevel */ + if (pending) { /* interrupt pending and MP enabled? */ + if ((i == 2) || int_enable) /* 3rd level indirect or INT out? */ + ion_defer = FALSE; /* reenable interrrupts */ + if ((i > 2) || int_enable) /* 4th or higher or INT out? */ + return NOTE_INDINT; /* break out now */ + } + + MA = ReadW (MA & VAMASK); /* follow address chain */ + } + +if (MA & I_IA) /* indirect loop? */ + return STOP_IND; /* stop simulation */ + +*addr = MA; +return SCPE_OK; +} + + +/* Get effective address from IR */ + +static t_stat Ea (uint32 IR, uint32 *addr, uint32 irq) +{ +uint32 MA; + +MA = IR & (I_IA | I_DISP); /* ind + disp */ + +if (IR & I_CP) /* current page? */ + MA = ((PR - 1) & I_PAGENO) | MA; /* merge in page from P */ + +return resolve (MA, addr, irq); /* resolve indirects */ +} + + +/* Shift micro operation */ + +static uint32 shift (uint32 t, uint32 flag, uint32 op) +{ +uint32 oldE; + +op = op & 07; /* get shift op */ +if (flag) { /* enabled? */ + switch (op) { /* case on operation */ + + case 00: /* signed left shift */ + return ((t & SIGN) | ((t << 1) & 077777)); + + case 01: /* signed right shift */ + return ((t & SIGN) | (t >> 1)); + + case 02: /* rotate left */ + return (((t << 1) | (t >> 15)) & DMASK); + + case 03: /* rotate right */ + return (((t >> 1) | (t << 15)) & DMASK); + + case 04: /* left shift, 0 sign */ + return ((t << 1) & 077777); + + case 05: /* ext right rotate */ + oldE = E; + E = t & 1; + return ((t >> 1) | (oldE << 15)); + + case 06: /* ext left rotate */ + oldE = E; + E = (t >> 15) & 1; + return (((t << 1) | oldE) & DMASK); + + case 07: /* rotate left four */ + return (((t << 4) | (t >> 12)) & DMASK); + } /* end case */ + } /* end if */ + +if (op == 05) /* disabled ext rgt rot */ + E = t & 1; + +if (op == 06) /* disabled ext lft rot */ + E = (t >> 15) & 1; + +return t; /* input unchanged */ +} + + +/* I/O instruction decode. + + If memory protect is enabled, and the instruction is not in a trap cell, then + HLT instructions are illegal and will cause a memory protect violation. If + jumper W7 (SEL1) is in, then all other I/O instructions are legal; if W7 is + out, then only I/O instructions to select code 1 are legal, and I/O to other + select codes will cause a violation. + + If the instruction is allowed, then the I/O signal corresponding to the + instruction is determined, the state of the interrupt deferral flag is set. + Then the signal is dispatched to the device simulator indicated by the target + select code. The return value is split into status and data values, with the + latter containing the SKF signal state or data to be returned in the A or B + registers. + + Implementation notes: + + 1. If the H/C (hold/clear flag) bit is set, then the ioCLF signal is added + (not ORed) to the base signal derived from the I/O instruction. + + 2. ioNONE is dispatched for HLT instructions because although HLT does not + assert any backplane signals, the H/C bit may be set. If it is, then the + result will be to dispatch ioCLF. + + 3. Device simulators return either ioSKF or ioNONE in response to an SFC or + SFS signal. ioSKF means that the instruction should skip. Because + device simulators return the "data" parameter value by default, we + initialize that parameter to ioNONE to ensure that a simulator that does + not implement SFC or SFS does not skip, which is the correct action for + an interface that does not drive the SKF signal. + + 4. STF/CLF and STC/CLC share sub-opcode values and must be further decoded + by the state of instruction register bits 9 and 11, respectively. + + 5. We return NOTE_IOG for normal status instead of SCPE_OK to request that + interrupts be recalculated at the end of the instruction (execution of + the I/O group instructions can change the interrupt priority chain). +*/ + +t_stat iogrp (uint32 ir, uint32 iotrap) +{ + +/* Translation for I/O subopcodes: soHLT, soFLG, soSFC, soSFS, soMIX, soLIX, soOTX, soCTL */ +static const IOSIGNAL generate_signal [] = { ioNONE, ioSTF, ioSFC, ioSFS, ioIOI, ioIOI, ioIOO, ioSTC }; + +const uint32 dev = ir & I_DEVMASK; /* device select code */ +const uint32 sop = I_GETIOOP (ir); /* I/O subopcode */ +const uint32 ab = (ir & I_AB) != 0; /* A/B register select */ +const t_bool clf = (ir & I_HC) != 0; /* H/C flag select */ +uint16 iodata = (uint16) ioNONE; /* initialize for SKF test */ +uint32 ioreturn; +t_stat iostat; +IOCYCLE signal_set; + +if (!iotrap && mp_control && /* instr not in trap cell and MP on? */ + ((sop == soHLT) || /* and is HLT? */ + ((dev != OVF) && (mp_unit.flags & UNIT_MP_SEL1)))) { /* or is not SC 01 and SEL1 out? */ + if (sop == soLIX) /* MP violation; is LIA/B instruction? */ + ABREG [ab] = 0; /* A/B writes anyway */ + + MP_ABORT (err_PC); /* MP abort */ + } + +signal_set = generate_signal [sop]; /* generate I/O signal from instruction */ +ion_defer = defer_tab [sop]; /* defer depending on instruction */ + +if (sop == soOTX) /* OTA/B instruction? */ + iodata = ABREG [ab]; /* pass A/B register value */ + +else if ((sop == soCTL) && (ir & I_CTL)) /* CLC instruction? */ + signal_set = ioCLC; /* change STC to CLC signal */ + +if ((sop == soFLG) && clf) /* CLF instruction? */ + signal_set = ioCLF; /* change STF to CLF signal */ + +else if (clf) /* CLF with another instruction? */ + signal_set = signal_set | ioCLF; /* add CLF signal */ + +ioreturn = devdisp (dev, signal_set, iodata); /* dispatch I/O signal */ + +iostat = IOSTATUS (ioreturn); /* extract status */ +iodata = IODATA (ioreturn); /* extract return data value */ + +if (((sop == soSFC) || (sop == soSFS)) && /* testing flag state? */ + ((IOSIGNAL) iodata == ioSKF)) /* and SKF asserted? */ + PR = (PR + 1) & VAMASK; /* bump P to skip next instruction */ + +else if (sop == soLIX) /* LIA/B instruction? */ + ABREG [ab] = iodata; /* load returned data */ + +else if (sop == soMIX) /* MIA/B instruction? */ + ABREG [ab] = ABREG [ab] | iodata; /* merge returned data */ + +else if (sop == soHLT) { /* HLT instruction? */ + return STOP_HALT; /* return halt status */ + } + +if (iostat == SCPE_OK) /* normal status? */ + return NOTE_IOG; /* request interrupt recalc */ +else /* abnormal status */ + return iostat; /* return it */ +} + + +/* Device I/O signal dispatcher */ + +static uint32 devdisp (uint32 select_code, IOCYCLE signal_set, uint16 data) +{ +return dtab [select_code]->io_handler (dtab [select_code], + signal_set, + IORETURN (SCPE_OK, data)); +} + + +/* Calculate DMA requests */ + +static uint32 calc_dma (void) +{ +uint32 r = 0; + +if (dma [ch1].xferen && SRQ (dma [ch1].cw1 & I_DEVMASK)) /* check DMA1 cycle */ + r = r | DMA_1_REQ; +if (dma [ch2].xferen && SRQ (dma [ch2].cw1 & I_DEVMASK)) /* check DMA2 cycle */ + r = r | DMA_2_REQ; +return r; +} + + +/* Determine whether a pending interrupt deferral should be inhibited. + + Execution of certain instructions generally cause a pending interrupt to be + deferred until the succeeding instruction completes. However, the interrupt + deferral rules differ on the 21xx vs. the 1000. + + The 1000 always defers until the completion of the instruction following a + deferring instruction. The 21xx defers unless the following instruction is + an MRG instruction other than JMP or JMP,I or JSB,I. If it is, then the + deferral is inhibited, i.e., the pending interrupt will be serviced. + + See the "Set Phase Logic Flowchart," transition from phase 1A to phase 1B, + and the "Theory of Operation," "Control Section Detailed Theory," "Phase + Control Logic," "Phase 1B" paragraph in the Model 2100A Computer Installation + and Maintenance Manual for details. +*/ + +t_bool calc_defer (void) +{ +uint16 IR; + +if (UNIT_CPU_FAMILY == UNIT_FAMILY_21XX) { /* 21xx series? */ + IR = ReadW (PR); /* prefetch next instr */ + + if (((IR & I_MRG & ~I_AB) != 0000000) && /* is MRG instruction? */ + ((IR & I_MRG_I) != I_JSB_I) && /* but not JSB,I? */ + ((IR & I_MRG) != I_JMP)) /* and not JMP or JMP,I? */ + return FALSE; /* yes, so inhibit deferral */ + else + return TRUE; /* no, so allow deferral */ + } +else + return TRUE; /* 1000 always allows deferral */ +} + + +/* Calculate interrupt requests. + + The interrupt request (IRQ) of the highest-priority device for which all + higher-priority PRL bits are set is granted. That is, there must be an + unbroken chain of priority to a device requesting an interrupt for that + request to be granted. + + A device sets its IRQ bit to request an interrupt, and it clears its PRL bit + to prevent lower-priority devices from interrupting. IRQ is cleared by an + interrupt acknowledge (IAK) signal. PRL generally remains low while a + device's interrupt service routine is executing to prevent preemption. + + IRQ and PRL indicate one of four possible states for a device: + + IRQ PRL Device state + --- --- ---------------------- + 0 1 Not interrupting + 1 0 Interrupt requested + 0 0 Interrupt acknowledged + 1 1 (not allowed) + + Note that PRL must be dropped when requesting an interrupt (IRQ set). This + is a hardware requirement of the 1000 series. The IRQ lines from the + backplane are not priority encoded. Instead, the PRL chain expresses the + priority by allowing only one IRQ line to be active at a time. This allows a + simple pull-down encoding of the CIR inputs. + + The end of priority chain is marked by the highest-priority (lowest-order) + bit that is clear. The device corresponding to that bit is the only device + that may interrupt (a higher priority device that had IRQ set would also have + had PRL set, which is a state violation). We calculate a priority mask by + ANDing the complement of the PRL bits with an increment of the PRL bits. + Only the lowest-order bit will differ. For example: + + dev_prl : ...1 1 0 1 1 0 1 1 1 1 1 1 (PRL denied for SC 06 and 11) + + dev_prl + 1 : ...1 1 0 1 1 1 0 0 0 0 0 0 + ~dev_prl : ...0 0 1 0 0 1 0 0 0 0 0 0 + ANDed value : ...0 0 0 0 0 1 0 0 0 0 0 0 (break is at SC 06) + + The interrupt requests are then ANDed with the priority mask to determine if + a request is pending: + + pri mask : ...0 0 0 0 0 1 0 0 0 0 0 0 (allowed interrupt source) + dev_irq : ...0 0 1 0 0 1 0 0 0 0 0 0 (devices requesting interrupts) + ANDed value : ...0 0 0 0 0 1 0 0 0 0 0 0 (request to grant) + + The select code corresponding to the granted request is then returned to the + caller. + + If ION is clear, only power fail (SC 04) and parity error (SC 05) are + eligible to interrupt (memory protect shares SC 05, but qualification occurs + in the MP abort handler, so if SC 05 is interrupting when ION is clear, it + must be a parity error interrupt). +*/ + +uint32 calc_int (void) +{ +uint32 sc, pri_mask [2], req_grant [2]; + +pri_mask [0] = ~dev_prl [0] & (dev_prl [0] + 1); /* calculate lower priority mask */ +req_grant [0] = pri_mask [0] & dev_irq [0]; /* calculate lower request to grant */ + +if (ion) /* interrupt system on? */ + if ((req_grant [0] == 0) && (pri_mask [0] == 0)) { /* no requests in lower set and PRL unbroken? */ + pri_mask [1] = ~dev_prl [1] & (dev_prl [1] + 1); /* calculate upper priority mask */ + req_grant [1] = pri_mask [1] & dev_irq [1]; /* calculate upper request to grant */ + } + else /* lower set has request */ + req_grant [1] = 0; /* no grants to upper set */ + +else { /* interrupt system off */ + req_grant [0] = req_grant [0] & /* only PF and PE can interrupt */ + (BIT_M (PWR) | BIT_M (PRO)); + req_grant [1] = 0; + } + +if (req_grant [0]) /* device in lower half? */ + for (sc = 0; sc <= 31; sc++) /* determine interrupting select code */ + if (req_grant [0] & 1) /* grant this request? */ + return sc; /* return this select code */ + else /* not this one */ + req_grant [0] = req_grant [0] >> 1; /* position next request */ + +else if (req_grant [1]) /* device in upper half */ + for (sc = 32; sc <= 63; sc++) /* determine interrupting select code */ + if (req_grant [1] & 1) /* grant this request? */ + return sc; /* return this select code */ + else /* not this one */ + req_grant [1] = req_grant [1] >> 1; /* position next request */ + +return 0; /* no interrupt granted */ +} + + +/* Memory access routines. + + These routines access memory for reads and writes. They validate the + accesses for MP and MEM violations, if enabled. The following routines are + provided: + + - 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 + + - WritePW : Write a word using a physical address + - WriteB : Write a byte using the current map + - WriteBA : Write a byte using the alternate map + - WriteW : Write a word using the current map + - WriteWA : Write a word using the alternate map + - WriteIO : Write a word using the specified map without protection + + The memory protect (MP) and memory expansion module (MEM) accessories provide + a protected mode that guards against improper accesses by user programs. + They may be enabled or disabled independently, although protection requires + that both be enabled. MP checks that memory writes do not fall below the + Memory Protect Fence Register (MPFR) value, and MEM checks that read/write + protection rules on the target page are compatible with the access desired. + If either check fails, and MP is enabled, then the request is aborted. + + Each mapped routine calls "dms" if DMS is enabled to translate the logical + address supplied to a physical address. "dms" performs a protection check + and aborts without returning if the check fails. The write routines perform + an additional memory-protect check and abort if a violation occurs (so, to + pass, a page must be writable AND the target must be above the MP fence). + + Note that MP uses a lower bound of 2 for memory writes, allowing unrestricted + access to the A and B registers (addressed as locations 0 and 1). +*/ + +#define MP_TEST(va) (mp_control && ((va) >= 2) && ((va) < mp_fence)) + + +/* Read a word using a physical address */ + +uint16 ReadPW (uint32 pa) +{ +if (pa <= 1) /* read locations 0 or 1? */ + return ABREG[pa]; /* return A/B register */ +else /* location >= 2 */ + return M[pa]; /* return physical memory value */ +} + + +/* Read a byte using the current map */ + +uint8 ReadB (uint32 va) +{ +int32 pa; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va >> 1, dms_ump, RDPROT); /* translate address */ +else /* MEM disabled */ + pa = va >> 1; /* use logical as physical address */ + +if (va & 1) /* low byte addressed? */ + return (ReadPW (pa) & 0377); /* mask to lower byte */ +else /* high byte addressed */ + return ((ReadPW (pa) >> 8) & 0377); /* position higher byte and mask */ +} + + +/* Read a byte using the alternate map */ + +uint8 ReadBA (uint32 va) +{ +uint32 pa; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va >> 1, dms_ump ^ MAP_LNT, RDPROT); /* translate address using alternate map */ +else /* MEM disabled */ + pa = va >> 1; /* use logical as physical address */ + +if (va & 1) /* low byte addressed? */ + return (ReadPW (pa) & 0377); /* mask to lower byte */ +else /* high byte addressed */ + return ((ReadPW (pa) >> 8) & 0377); /* position higher byte and mask */ +} + + +/* Read a word using the current map */ + +uint16 ReadW (uint32 va) +{ +uint32 pa; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va, dms_ump, RDPROT); /* translate address */ +else /* MEM disabled */ + pa = va; /* use logical as physical address */ + +return ReadPW (pa); /* return word */ +} + + +/* Read a word using the alternate map */ + +uint16 ReadWA (uint32 va) +{ +uint32 pa; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va, dms_ump ^ MAP_LNT, RDPROT); /* translate address using alternate map */ +else /* MEM disabled */ + pa = va; /* use logical as physical address */ + +return ReadPW (pa); /* return word */ +} + + +/* Read a word using the specified map without protection */ + +uint16 ReadIO (uint32 va, uint32 map) +{ +uint32 pa; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va, map, NOPROT); /* translate address with no protection */ +else /* MEM disabled */ + pa = va; /* use logical as physical address */ + +return M[pa]; /* return word without A/B interception */ +} + + +/* Read a word using the current map without protection */ + +static uint16 ReadTAB (uint32 va) +{ +uint32 pa; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va, dms_ump, NOPROT); /* translate address with no protection */ +else /* MEM disabled */ + pa = va; /* use logical as physical address */ + +return ReadPW (pa); /* return word */ +} + + +/* Write a word using a physical address */ + +void WritePW (uint32 pa, uint32 dat) +{ +if (pa <= 1) /* write locations 0 or 1? */ + ABREG[pa] = dat & DMASK; /* store A/B register */ +else if (pa < fwanxm) /* 2 <= location <= LWA memory? */ + M[pa] = dat & DMASK; /* store physical memory value */ + +return; +} + + +/* Write a byte using the current map */ + +void WriteB (uint32 va, uint32 dat) +{ +uint32 pa, t; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va >> 1, dms_ump, WRPROT); /* translate address */ +else /* MEM disabled */ + pa = va >> 1; /* use logical as physical address */ + +if (MP_TEST (va >> 1)) /* MPCK? */ + MP_ABORT (va >> 1); /* MP violation */ + +t = ReadPW (pa); /* get word */ + +if (va & 1) /* low byte addressed? */ + t = (t & 0177400) | (dat & 0377); /* merge in lower byte */ +else /* high byte addressed */ + t = (t & 0377) | ((dat & 0377) << 8); /* position higher byte and merge */ + +WritePW (pa, t); /* store word */ +return; +} + + +/* Write a byte using the alternate map */ + +void WriteBA (uint32 va, uint32 dat) +{ +uint32 pa, t; + +if (dms_enb) { /* MEM enabled? */ + dms_viol (va >> 1, MVI_WPR); /* always a violation if protected */ + pa = dms (va >> 1, dms_ump ^ MAP_LNT, WRPROT); /* translate address using alternate map */ + } +else /* MEM disabled */ + pa = va >> 1; /* use logical as physical address */ + +if (MP_TEST (va >> 1)) /* MPCK? */ + MP_ABORT (va >> 1); /* MP violation */ + +t = ReadPW (pa); /* get word */ + +if (va & 1) /* low byte addressed? */ + t = (t & 0177400) | (dat & 0377); /* merge in lower byte */ +else /* high byte addressed */ + t = (t & 0377) | ((dat & 0377) << 8); /* position higher byte and merge */ + +WritePW (pa, t); /* store word */ +return; +} + + +/* Write a word using the current map */ + +void WriteW (uint32 va, uint32 dat) +{ +uint32 pa; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va, dms_ump, WRPROT); /* translate address */ +else /* MEM disabled */ + pa = va; /* use logical as physical address */ + +if (MP_TEST (va)) /* MPCK? */ + MP_ABORT (va); /* MP violation */ + +WritePW (pa, dat); /* store word */ +return; +} + + +/* Write a word using the alternate map */ + +void WriteWA (uint32 va, uint32 dat) +{ +int32 pa; + +if (dms_enb) { /* MEM enabled? */ + dms_viol (va, MVI_WPR); /* always a violation if protected */ + pa = dms (va, dms_ump ^ MAP_LNT, WRPROT); /* translate address using alternate map */ + } +else /* MEM disabled */ + pa = va; /* use logical as physical address */ + +if (MP_TEST (va)) /* MPCK? */ + MP_ABORT (va); /* MP violation */ + +WritePW (pa, dat); /* store word */ +return; +} + + +/* Write a word using the specified map without protection */ + +void WriteIO (uint32 va, uint32 dat, uint32 map) +{ +uint32 pa; + +if (dms_enb) /* MEM enabled? */ + pa = dms (va, map, NOPROT); /* translate address with no protection */ +else /* MEM disabled */ + pa = va; /* use logical as physical address */ + +if (pa < fwanxm) + M[pa] = dat & DMASK; /* store word without A/B interception */ +return; +} + + +/* Mapped access check. + + Returns TRUE if the address will be mapped (presuming MEM is enabled). +*/ + +static t_bool is_mapped (uint32 va) +{ +uint32 dms_fence; + +if (va >= 02000) /* above the base bage? */ + return TRUE; /* always mapped */ +else { + dms_fence = dms_sr & MST_FENCE; /* get BP fence value */ + return (dms_sr & MST_FLT) ? (va < dms_fence) : /* below BP fence and lower portion mapped? */ + (va >= dms_fence); /* or above BP fence and upper portion mapped? */ + } +} + + +/* DMS relocation. + + This routine translates logical into physical addresses. It must be called + only when DMS is enabled, as that condition is not checked. The logical + address, desired map, and desired access type are supplied. If the access is + legal, the mapped physical address is returned; if it is not, then a MEM + violation is indicated. + + The current map may be specified by passing "dms_ump" as the "map" parameter, + or a specific map may be used. Normally, read and write accesses pass RDPROT + or WRPROT as the "prot" parameter to request access checking. For DMA + accesses, NOPROT must be passed to inhibit access checks. + + This routine checks for read, write, and base-page violations and will call + "dms_viol" as appropriate. The latter routine will abort if MP is enabled, + or will return if protection is off. +*/ + +static uint32 dms (uint32 va, uint32 map, uint32 prot) +{ +uint32 pgn, mpr; + +if (va <= 1) /* reference to A/B register? */ + return va; /* use address */ + +if (!is_mapped (va)) { /* unmapped? */ + if ((va >= 2) && (prot == WRPROT)) /* base page write access? */ + dms_viol (va, MVI_BPG); /* signal a base page violation */ + return va; /* use unmapped address */ + } + +pgn = VA_GETPAG (va); /* get page num */ +mpr = dms_map[map + pgn]; /* get map reg */ + +if (mpr & prot) /* desired access disallowed? */ + dms_viol (va, prot); /* signal protection violation */ + +return (MAP_GETPAG (mpr) | VA_GETOFF (va)); /* return mapped address */ +} + + +/* DMS relocation for console access. + + Console access allows the desired map to be specified by switches on the + command line. All protection checks are off for console access. + + This routine is called to restore a saved configuration, and mapping is not + used for restoration. +*/ + +static uint32 dms_cons (t_addr va, int32 sw) +{ +uint32 map_sel; + +if ((dms_enb == 0) || /* DMS off? */ + (sw & (SWMASK ('N') | SIM_SW_REST))) /* no mapping rqst or save/rest? */ + return (uint32) va; /* use physical address */ + +else if (sw & SWMASK ('S')) + map_sel = SMAP; + +else if (sw & SWMASK ('U')) + map_sel = UMAP; + +else if (sw & SWMASK ('P')) + map_sel = PAMAP; + +else if (sw & SWMASK ('Q')) + map_sel = PBMAP; + +else /* dflt to log addr, cur map */ + map_sel = dms_ump; + +if (va >= VASIZE) /* virtual, must be 15b */ + return (uint32) MEMSIZE; + +else if (dms_enb) /* DMS on? go thru map */ + return dms ((uint32) va, map_sel, NOPROT); + +else /* else return virtual */ + return (uint32) va; +} + + +/* Memory protect and DMS validation for jumps. + + Jumps are a special case of write validation. The target address is treated + as a write, even when no physical write takes place, so jumping to a + write-protected page causes a MEM violation. In addition, a MEM violation is + indicated if the jump is to the unmapped portion of the base page. Finally, + jumping to a location under the memory-protect fence causes an MP violation. + + Because the MP and MEM hardware works in parallel, all three violations may + exist concurrently. For example, a JMP to the unmapped portion of the base + page that is write protected and under the MP fence will indicate a + base-page, write, and MP violation, whereas a JMP to the mapped portion will + indicate a write and MP violation (BPV is inhibited by the MEBEN signal). If + MEM and MP violations occur concurrently, the MEM violation takes precedence, + as the SFS and SFC instructions test the MEV flip-flop. + + The lower bound of protected memory is passed in the "plb" argument. This + must be either 0 or 2. All violations are qualified by the MPCND signal, + which responds to the lower bound. Therefore, if the lower bound is 2, and + if the part below the base-page fence is unmapped, or if the base page is + write-protected, then a MEM violation will occur only if the access is not to + locations 0 or 1. The instruction set firmware uses a lower bound of 0 for + JMP, JLY, and JPY (and for JSB with W5 out), and of 2 for DJP, SJP, UJP, JRS, + and .GOTO (and JSB with W5 in). + + Finally, all violations are inhibited if MP is off (mp_control is CLEAR), and + MEM violations are inhibited if the MEM is disabled. +*/ + +void mp_dms_jmp (uint32 va, uint32 plb) +{ +uint32 violation = 0; +uint32 pgn = VA_GETPAG (va); /* get page number */ + +if (mp_control) { /* MP on? */ + if (dms_enb) { /* MEM on? */ + if (dms_map [dms_ump + pgn] & WRPROT) /* page write protected? */ + violation = MVI_WPR; /* write violation occured */ + + if (!is_mapped (va) && (va >= plb)) /* base page target? */ + violation = violation | MVI_BPG; /* base page violation occured */ + + if (violation) /* any violation? */ + dms_viol (va, violation); /* signal MEM violation */ + } + + if ((va >= plb) && (va < mp_fence)) /* jump under fence? */ + MP_ABORT (va); /* signal MP violation */ + } + +return; +} + + +/* DMS read and write map registers */ + +uint16 dms_rmap (uint32 mapi) +{ +mapi = mapi & MAP_MASK; +return (dms_map[mapi] & ~MAP_RSVD); +} + +void dms_wmap (uint32 mapi, uint32 dat) +{ +mapi = mapi & MAP_MASK; +dms_map[mapi] = (uint16) (dat & ~MAP_RSVD); +return; +} + + +/* Process a MEM violation. + + A MEM violation will report the cause in the violation register. This occurs + even if the MEM is not in the protected mode (i.e., MP is not enabled). If + MP is enabled, an MP abort is taken with the MEV flip-flop set. Otherwise, + we return to the caller. +*/ + +void dms_viol (uint32 va, uint32 st) +{ +dms_vr = st | dms_upd_vr (va); /* set violation cause in register */ + +if (mp_control) { /* memory protect on? */ + mp_mevff = SET; /* record memory expansion violation */ + MP_ABORT (va); /* abort */ + } +return; +} + + +/* Update the MEM violation register. + + In hardware, the MEM violation register (VR) is clocked on every memory read, + every memory write above the lower bound of protected memory, and every + execution of a privileged DMS instruction. The register is not clocked when + MP is disabled by an MP or MEM error (i.e., when MEVFF sets or CTL5FF + clears), in order to capture the state of the MEM. In other words, the VR + continually tracks the memory map register accessed plus the MEM state + (MEBEN, MAPON, and USR) until a violation occurs, and then it's "frozen." + + Under simulation, we do not have to update the VR on every memory access, + because the visible state is only available via a programmed RVA/B + instruction or via the SCP interface. Therefore, it is sufficient if the + register is updated: + + - at a MEM violation (when freezing) + - at an MP violation (when freezing) + - during RVA/B execution (if not frozen) + - before returning to SCP after a simulator stop (if not frozen) +*/ + +uint32 dms_upd_vr (uint32 va) +{ +if (mp_control && (mp_mevff == CLEAR)) { /* violation register unfrozen? */ + dms_vr = VA_GETPAG (va) | /* set map address */ + (dms_enb ? MVI_MEM : 0) | /* and MEM enabled */ + (dms_ump ? MVI_UMP : 0); /* and user map enabled */ + + if (is_mapped (va)) /* is addressed mapped? */ + dms_vr = dms_vr | MVI_MEB; /* ME bus is enabled */ + } + +return dms_vr; +} + + +/* Update the MEM status register */ + +uint32 dms_upd_sr (void) +{ +dms_sr = dms_sr & ~(MST_ENB | MST_UMP | MST_PRO); + +if (dms_enb) + dms_sr = dms_sr | MST_ENB; + +if (dms_ump) + dms_sr = dms_sr | MST_UMP; + +if (mp_control) + dms_sr = dms_sr | MST_PRO; + +return dms_sr; +} + + +/* CPU (SC 0) I/O signal handler. + + I/O instructions for select code 0 manipulate the interrupt system. STF and + CLF turn the interrupt system on and off, and SFS and SFC test the state of + the interrupt system. When the interrupt system is off, only power fail and + parity error interrupts are allowed. + + A PON reset initializes certain CPU registers. The 1000 series does a + microcoded memory clear and leaves the T and P registers set as a result. + + Front-panel PRESET performs additional initialization. We also handle MEM + preset here. + + Implementation notes: + + 1. An IOI signal reads the floating I/O bus (0 on all machines). + + 2. A CLC 0 issues CRS to all devices, not CLC. While most cards react + identically to CRS and CLC, some do not, e.g., the 12566B when used as an + I/O diagnostic target. + + 3. RTE uses the undocumented SFS 0,C instruction to both test and turn off + the interrupt system. This is confirmed in the "RTE-6/VM Technical + Specifications" manual (HP 92084-90015), section 2.3.1 "Process the + Interrupt", subsection "A.1 $CIC": + + "Test to see if the interrupt system is on or off. This is done with + the SFS 0,C instruction. In either case, turn it off (the ,C does + it)." + + ...and in section 5.8, "Parity Error Detection": + + "Because parity error interrupts can occur even when the interrupt + system is off, the code at $CIC must be able to save the complete + system status. The major hole in being able to save the complete state + is in saving the interrupt system state. In order to do this in both + the 21MX and the 21XE the instruction 103300 was used to both test the + interrupt system and turn it off." + + 4. Select code 0 cannot interrupt, so there is no SIR handler. +*/ + +uint32 cpuio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +uint32 sc; +IOSIGNAL signal; +IOCYCLE working_set = signal_set; /* no SIR handler needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + ion = CLEAR; /* turn interrupt system off */ + break; + + case ioSTF: /* set flag flip-flop */ + ion = SET; /* turn interrupt system on */ + break; + + case ioSFC: /* skip if flag is clear */ + setSKF (!ion); /* skip if interrupt system is off */ + break; + + case ioSFS: /* skip if flag is set */ + setSKF (ion); /* skip if interupt system is on */ + break; + + case ioIOI: /* I/O input */ + stat_data = IORETURN (SCPE_OK, 0); /* returns 0 */ + break; + + case ioPON: /* power on normal */ + AR = 0; /* clear A register */ + BR = 0; /* clear B register */ + SR = 0; /* clear S register */ + TR = 0; /* clear T register */ + E = 1; /* set E register */ + + if (UNIT_CPU_FAMILY == UNIT_FAMILY_1000) { /* 1000 series? */ + memset (M, 0, (uint32) MEMSIZE * 2); /* zero allocated memory */ + MR = 0077777; /* set M register */ + PR = 0100000; /* set P register */ + } + + else { /* 21xx series */ + MR = 0; /* clear M register */ + PR = 0; /* clear P register */ + } + break; + + case ioPOPIO: /* power-on preset to I/O */ + O = 0; /* clear O register */ + ion = CLEAR; /* turn off interrupt system */ + ion_defer = FALSE; /* clear interrupt deferral */ + + dms_enb = 0; /* turn DMS off */ + dms_ump = 0; /* init to system map */ + dms_sr = 0; /* clear status register and BP fence */ + dms_vr = 0; /* clear violation register */ + break; + + case ioCLC: /* clear control flip-flop */ + for (sc = CRSDEV; sc <= MAXDEV; sc++) /* send CRS to devices */ + devdisp (sc, ioCRS, 0); /* from select code 6 and up */ + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Overflow/S-register (SC 1) I/O signal handler. + + Flag instructions directed to select code 1 manipulate the overflow (O) + register. Input and output instructions access the switch (S) register. On + the 2115 and 2116, there is no S-register indicator, so it is effectively + read-only. On the other machines, a front-panel display of the S-register is + provided. On all machines, front-panel switches are provided to set the + contents of the S register. + + Implementation notes: + + 1. Select code 1 cannot interrupt, so there is no SIR handler. +*/ + +uint32 ovflio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = signal_set; /* no SIR handler needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + O = 0; /* clear overflow */ + break; + + case ioSTF: /* set flag flip-flop */ + O = 1; /* set overflow */ + break; + + case ioSFC: /* skip if flag is clear */ + setSKF (!O); /* skip if overflow is clear */ + break; + + case ioSFS: /* skip if flag is set */ + setSKF (O); /* skip if overflow is set */ + break; + + case ioIOI: /* I/O input */ + stat_data = IORETURN (SCPE_OK, SR); /* read switch register value */ + break; + + case ioIOO: /* I/O output */ + if ((UNIT_CPU_MODEL != UNIT_2116) && /* no S register display on */ + (UNIT_CPU_MODEL != UNIT_2115)) /* 2116 and 2115 machines */ + SR = IODATA (stat_data); /* write S register value */ + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Power fail (SC 4) I/O signal handler. + + Power fail detection is standard on 2100 and 1000 systems and is optional on + 21xx systems. Power fail recovery is standard on the 2100 and optional on + the others. Power failure or restoration will cause an interrupt on select + code 4. The direction of power change (down or up) can be tested by SFC. + + We do not implement power fail under simulation. However, the central + interrupt register (CIR) is always read by an IOI directed to select code 4. +*/ + +uint32 pwrfio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioSTC: /* set control flip-flop */ + break; /* reinitializes power fail */ + + case ioCLC: /* clear control flip-flop */ + break; /* reinitializes power fail */ + + case ioSFC: /* skip if flag is clear */ + break; /* skips if power fail occurred */ + + case ioIOI: /* I/O input */ + stat_data = IORETURN (SCPE_OK, intaddr); /* input CIR value */ + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Memory protect/parity error (SC 5) I/O signal handler. + + The memory protect card has a number of non-standard features: + + - CLF and STF affect the parity error enable flip-flop, not the flag + - SFC and SFS test the memory expansion violation flip-flop, not the flag + - POPIO clears control, flag, and flag buffer instead of setting the flags + - CLC does not clear control (the only way to turn off MP is to cause a + violation) + - PRL and IRQ are a function of the flag only, not flag and control + - IAK is used unqualified by IRQ + + The IAK backplane signal is asserted when any interrupt is acknowledged by + the CPU. Normally, an interface qualifies IAK with its own IRQ to ensure + that it responds only to an acknowledgement of its own request. The MP card + does this to reset its flag buffer and flag flip-flops, and to reset the + parity error indication. However, it also responds to an unqualified IAK + (i.e., for any interface) as follows: + + - clears the MPV flip-flop + - clears the indirect counter + - clears the control flip-flop + - sets the INTPT flip-flop + + The INTPT flip-flop indicates an occurrence of an interrupt. If the trap + cell of the interrupting device contains an I/O instruction that is not a + HLT, action equivalent to STC 05 is taken, i.e.: + + - sets the control flip-flop + - set the EVR flip-flop + - clears the MEV flip-flop + - clears the PARERR flip-flop + + In other words, an interrupt for any device will disable MP unless the trap + cell contains an I/O instruction other than a HLT. + + Implementation notes: + + 1. Because the card uses IAK unqualified, this routine is called whenever + any interrupt occurs. If the MP card itself is not interrupting, the + select code passed will not be SC 05. In either case, the trap cell + instruction is passed in the data portion of the "stat_data" parameter. + + 2. The MEV flip-flop records memory expansion (a.k.a. dynamic mapping) + violations. It is set when an DM violation is encountered and can be + tested via SFC/SFS. + + 3. MP cannot be turned off in hardware, except by causing a violation. + Microcode typically does this by executing an IOG micro-order with select + code /= 1, followed by an IAK to clear the interrupt and a FTCH to clear + the INTPT flip-flop. Under simulation, mp_control may be set to CLEAR to + produce the same effect. + + 4. Parity error logic is not implemented. +*/ + +uint32 protio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +uint16 data; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + break; /* turns off PE interrupt */ + + case ioSTF: /* set flag flip-flop */ + break; /* turns on PE interrupt */ + + case ioENF: /* enable flag */ + mp_flag = mp_flagbuf = SET; /* set flag buffer and flag flip-flops */ + mp_evrff = CLEAR; /* inhibit violation register updates */ + break; + + case ioSFC: /* skip if flag is clear */ + setSKF (!mp_mevff); /* skip if MP interrupt */ + break; + + case ioSFS: /* skip if flag is set */ + setSKF (mp_mevff); /* skip if DMS interrupt */ + break; + + case ioIOI: /* I/O input */ + stat_data = IORETURN (SCPE_OK, mp_viol); /* read MP violation register */ + break; + + case ioIOO: /* I/O output */ + mp_fence = IODATA (stat_data) & VAMASK; /* write to MP fence register */ + + if (cpu_unit.flags & UNIT_2100) /* 2100 IOP uses MP fence */ + iop_sp = mp_fence; /* as a stack pointer */ + break; + + case ioPOPIO: /* power-on preset to I/O */ + mp_control = CLEAR; /* clear control flip-flop */ + mp_flag = mp_flagbuf = CLEAR; /* clear flag and flag buffer flip-flops */ + mp_mevff = CLEAR; /* clear memory expansion violation flip-flop */ + mp_evrff = SET; /* set enable violation register flip-flop */ + break; + + case ioSTC: /* set control flip-flop */ + mp_control = SET; /* turn on MP */ + mp_mevff = CLEAR; /* clear memory expansion violation flip-flop */ + mp_evrff = SET; /* set enable violation register flip-flop */ + break; + + case ioSIR: /* set interrupt request */ + setPRL (PRO, !mp_flag); /* set PRL signal */ + setIRQ (PRO, mp_flag); /* set IRQ signal */ + break; + + case ioIAK: /* interrupt acknowledge */ + if (dibptr->select_code == PRO) /* MP interrupt acknowledgement? */ + mp_flag = mp_flagbuf = CLEAR; /* clear flag and flag buffer */ + + data = IODATA (stat_data); /* get trap cell instruction */ + + if (((data & I_NMRMASK) != I_IO) || /* trap cell instruction not I/O */ + (I_GETIOOP (data) == soHLT)) /* or is halt? */ + mp_control = CLEAR; /* turn protection off */ + else { /* non-HLT I/O instruction leaves MP on */ + mp_mevff = CLEAR; /* but clears MEV flip-flop */ + mp_evrff = SET; /* and reenables violation register flip-flop */ + } + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* DMA/DCPC secondary (SC 2/3) I/O signal handler. + + DMA consists of one (12607B) or two (12578A/12895A/12897B) channels. Each + channel uses two select codes: 2 and 6 for channel 1, and 3 and 7 for channel + 2. The lower select codes are used to configure the memory address register + (control word 2) and the word count register (control word 3). The upper + select codes are used to configure the service select register (control word + 1) and to activate and terminate the transfer. + + There are differences in the implementations of the memory address and word + count registers among the various cards. The 12607B (2114) supports 14-bit + addresses and 13-bit word counts. The 12578A (2115/6) supports 15-bit + addresses and 14-bit word counts. The 12895A (2100) and 12897B (1000) + support 15-bit addresses and 16-bit word counts. + + Implementation notes: + + 1. Because the I/O bus floats to zero on 211x computers, an IOI (read word + count) returns zeros in the unused bit locations, even though the word + count is a negative value. + + 2. Select codes 2 and 3 cannot interrupt, so there is no SIR handler. +*/ + +uint32 dmasio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ +uint16 data; +IOSIGNAL signal; +IOCYCLE working_set = signal_set; /* no SIR handler needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioIOI: /* I/O data input */ + if (UNIT_CPU_MODEL == UNIT_2114) /* 2114? */ + data = dma [ch].cw3 & 0017777; /* only 13-bit count */ + else if (UNIT_CPU_TYPE == UNIT_TYPE_211X) /* 2115/2116? */ + data = dma [ch].cw3 & 0037777; /* only 14-bit count */ + else /* other models */ + data = (uint16) dma [ch].cw3; /* rest use full value */ + + stat_data = IORETURN (SCPE_OK, data); /* merge status and remaining word count */ + break; + + case ioIOO: /* I/O data output */ + if (dma [ch].select) /* word count selected? */ + dma [ch].cw3 = IODATA (stat_data); /* save count */ + else /* memory address selected */ + if (UNIT_CPU_MODEL == UNIT_2114) /* 2114? */ + dma [ch].cw2 = IODATA (stat_data) & 0137777; /* only 14-bit address */ + else /* other models */ + dma [ch].cw2 = IODATA (stat_data); /* full address stored */ + break; + + case ioCLC: /* clear control flip-flop */ + dma [ch].select = CLEAR; /* set for word count access */ + break; + + case ioSTC: /* set control flip-flop */ + dma [ch].select = SET; /* set for memory address access */ + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* DMA/DCPC primary (SC 6/7) I/O signal handler. + + The primary DMA control interface and the service select register are + manipulated through select codes 6 and 7. Each channel has transfer enable, + control, flag, and flag buffer flip-flops. Transfer enable must be set via + STC to start DMA. Control is used only to enable the DMA completion + interrupt; it is set by STC and cleared by CLC. Flag and flag buffer are set + at transfer completion to signal an interrupt. STF may be issued to abort a + transfer in progress. + + Again, there are hardware differences between the various DMA cards. The + 12607B (2114) stores only bits 2-0 of the select code and interprets them as + select codes 10-16 (SRQ17 is not decoded). The 12578A (2115/16), 12895A + (2100), and 12897B (1000) support the full range of select codes (10-77 + octal). + + Implementation notes: + + 1. An IOI reads the floating S-bus (high on the 1000, low on the 21xx). + + 2. The CRS signal on the DMA card resets the secondary (SC 2/3) select + flip-flops. Under simulation, ioCRS is dispatched to select codes 6 and + up, so we reset the flip-flop in our handler. + + 3. The 12578A supports byte-sized transfers by setting bit 14. Bit 14 is + ignored by all other DMA cards, which support word transfers only. + Under simulation, we use a byte-packing/unpacking register to hold one + byte while the other is read or written during the DMA cycle. +*/ + +uint32 dmapio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ +uint16 data; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + dma [ch].flag = dma [ch].flagbuf = CLEAR; /* clear flag and flag buffer */ + break; + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + dma [ch].flag = dma [ch].flagbuf = SET; /* set flag and flag buffer */ + dma [ch].xferen = CLEAR; /* clear transfer enable to abort transfer */ + break; + + case ioSFC: /* skip if flag is clear */ + setstdSKF (dma [ch]); /* skip if transfer in progress */ + break; + + case ioSFS: /* skip if flag is set */ + setstdSKF (dma [ch]); /* skip if transfer is complete */ + break; + + case ioIOI: /* I/O data input */ + if (UNIT_CPU_TYPE == UNIT_TYPE_1000) /* 1000? */ + stat_data = IORETURN (SCPE_OK, DMASK); /* return all ones */ + else /* other models */ + stat_data = IORETURN (SCPE_OK, 0); /* return all zeros */ + break; + + case ioIOO: /* I/O data output */ + data = IODATA (stat_data); /* clear supplied status */ + + if (UNIT_CPU_MODEL == UNIT_2114) /* 12607? */ + dma [ch].cw1 = (data & 0137707) | 010; /* mask SC, convert to 10-17 */ + else if (UNIT_CPU_TYPE == UNIT_TYPE_211X) /* 12578? */ + dma [ch].cw1 = data; /* store full select code, flags */ + else /* 12895, 12897 */ + dma [ch].cw1 = data & ~DMA1_PB; /* clip byte-packing flag */ + break; + + case ioPOPIO: /* power-on preset to I/O */ + dma [ch].flag = dma [ch].flagbuf = SET; /* set flag and flag buffer */ + break; + + case ioCRS: /* control reset */ + dma [ch].xferen = CLEAR; /* clear transfer enable */ + dma [ch].select = CLEAR; /* set secondary for word count access */ + /* fall into CLC handler */ + + case ioCLC: /* clear control flip-flop */ + dma [ch].control = CLEAR; /* clear control */ + break; + + case ioSTC: /* set control flip-flop */ + dma [ch].packer = 0; /* clear packing register */ + dma [ch].xferen = dma [ch].control = SET; /* set transfer enable and control */ + break; + + case ioSIR: /* set interrupt request */ + setstdPRL (dma [ch]); + setstdIRQ (dma [ch]); + break; + + case ioIAK: /* interrupt acknowledge */ + dma [ch].flagbuf = CLEAR; /* clear flag buffer */ + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Unassigned select code I/O signal handler. + + The 21xx/1000 I/O structure requires that no empty slots exist between + interface cards. This is due to the hardware priority chaining (PRH/PRL). + If it is necessary to leave unused I/O slots, HP 12777A Priority Jumper Cards + must be installed in them to maintain priority continuity. + + Under simulation, every unassigned I/O slot behaves as though a 12777A were + resident. + + Implementation notes: + + 1. For select codes < 10 octal, an IOI reads the floating S-bus (high on + the 1000, low on the 21xx). For select codes >= 10 octal, an IOI reads + the floating I/O bus (low on all machines). + + 2. If "stop_dev" is TRUE, then the simulator will stop when an unassigned + device is accessed. +*/ + +uint32 nullio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +uint16 data = 0; +IOSIGNAL signal; +IOCYCLE working_set = signal_set; /* no SIR handler needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioIOI: /* I/O data input */ + if ((dibptr->select_code < VARDEV) && /* internal device */ + (UNIT_CPU_TYPE == UNIT_TYPE_1000)) /* and 1000? */ + data = DMASK; /* return all ones */ + else /* external or other model */ + data = 0; /* return all zeros */ + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return IORETURN (stop_dev, data); /* flag missing device */ +} + + +/* DMA cycle routine. + + This routine performs one DMA input or output cycle using the indicated DMA + channel number and DMS map. When the transfer word count reaches zero, the + flag is set on the corresponding DMA channel to indicate completion. + + The 12578A card supports byte-packing. If bit 14 in control word 1 is set, + each transfer will involve one read/write from memory and two output/input + operations in order to transfer sequential bytes to/from the device. + + DMA I/O cycles differ from programmed I/O cycles in that multiple I/O control + backplane signals may be asserted simultaneously. With programmed I/O, only + CLF may be asserted with other signals, specifically with STC, CLC, SFS, SFC, + IOI, or IOO. With DMA, as many as five signals may be asserted concurrently. + + DMA I/O timing looks like this: + + ------------ 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 + + Under simulation, this routine dispatches one set of I/O signals per DMA + cycle to the target device's I/O signal handler. The signals correspond to + the table above, except that all signals for a given cycle are concurrent + (e.g., the last input cycle has IOI, EDT, and optionally CLC asserted, even + though IOI and EDT are not coincident in hardware). I/O signal handlers will + process these signals sequentially, in the order listed above, before + returning. + + Implementation notes: + + 1. The address increment and word count decrement is done only after the I/O + cycle has completed successfully. This allows a failed transfer to be + retried after correcting the I/O error. +*/ + +static t_stat dma_cycle (CHANNEL ch, uint32 map) +{ +const uint32 dev = dma [ch].cw1 & I_DEVMASK; /* device select code */ +const uint32 stc = dma [ch].cw1 & DMA1_STC; /* STC enable flag */ +const uint32 bytes = dma [ch].cw1 & DMA1_PB; /* pack bytes flag */ +const uint32 clc = dma [ch].cw1 & DMA1_CLC; /* CLC enable flag */ +const uint32 MA = dma [ch].cw2 & VAMASK; /* memory address */ +const uint32 input = dma [ch].cw2 & DMA2_OI; /* input flag */ +const uint32 even = dma [ch].packer & DMA_OE; /* odd/even packed byte flag */ +uint16 data; +t_stat status; +uint32 ioresult; +IOCYCLE signals; + +if (bytes && !even || dma [ch].cw3 != DMASK) { /* normal cycle? */ + if (input) /* input cycle? */ + signals = ioIOI | ioCLF; /* assert IOI and CLF */ + else /* output cycle */ + signals = ioIOO | ioCLF; /* assert IOO and CLF */ + + if (stc) /* STC wanted? */ + signals = signals | ioSTC; /* assert STC */ + } + +else { /* last cycle */ + if (input) /* input cycle? */ + signals = ioIOI | ioEDT; /* assert IOI and EDT */ + else { /* output cycle */ + signals = ioIOO | ioCLF | ioEDT; /* assert IOO and CLF and EDT */ + + if (stc) /* STC wanted? */ + signals = signals | ioSTC; /* assert STC */ + } + + if (clc) /* CLC wanted? */ + signals = signals | ioCLC; /* assert CLC */ + } + +if (input) { /* input cycle? */ + ioresult = devdisp (dev, signals, 0); /* do I/O input */ + + status = IOSTATUS (ioresult); /* get cycle status */ + + if (status == SCPE_OK) { /* good I/O cycle? */ + data = IODATA (ioresult); /* extract return data value */ + + if (bytes) { /* byte packing? */ + if (even) { /* second byte? */ + data = (uint16) (dma [ch].packer << 8) /* merge stored byte */ + | (data & DMASK8); + WriteIO (MA, data, map); /* store word data */ + } + else /* first byte */ + dma [ch].packer = (data & DMASK8); /* save it */ + + dma [ch].packer = dma [ch].packer ^ DMA_OE; /* flip odd/even bit */ + } + else /* no byte packing */ + WriteIO (MA, data, map); /* store word data */ + } + } + +else { /* output cycle */ + if (bytes) { /* byte packing? */ + if (even) /* second byte? */ + data = dma [ch].packer & DMASK8; /* retrieve it */ + + else { /* first byte */ + dma [ch].packer = ReadIO (MA, map); /* read word data */ + data = (dma [ch].packer >> 8) & DMASK8; /* get high byte */ + } + + dma [ch].packer = dma [ch].packer ^ DMA_OE; /* flip odd/even bit */ + } + else /* no byte packing */ + data = ReadIO (MA, map); /* read word data */ + + ioresult = devdisp (dev, signals, data); /* do I/O output */ + + status = IOSTATUS (ioresult); /* get cycle status */ + } + +if ((even || !bytes) && (status == SCPE_OK)) { /* new byte or no packing and good xfer? */ + dma [ch].cw2 = input | (dma [ch].cw2 + 1) & VAMASK; /* increment address */ + dma [ch].cw3 = (dma [ch].cw3 + 1) & DMASK; /* increment word count */ + + if (dma [ch].cw3 == 0) /* end of transfer? */ + dmapio (dtab [DMA1 + ch], ioENF, 0); /* set DMA channel flag */ + } + +return status; /* return I/O status */ +} + + +/* Reset routines. + + The reset routines are called to simulate either an initial power on + condition or a front-panel PRESET button press. For initial power on + (corresponds to PON, POPIO, and CRS signal assertion in the CPU), the "P" + command switch will be set. For PRESET (corresponds to POPIO and CRS + assertion), the switch will be clear. + + SCP delivers a power-on reset to all devices when the simulator is started. + A RUN, BOOT, RESET, or RESET ALL command delivers a PRESET to all devices. A + RESET delivers a PRESET to a specific device. +*/ + + +/* CPU reset. + + If this is the first call after simulator startup, allocate the initial + memory array, set the default CPU model, and install the default BBL. +*/ + +t_stat cpu_reset (DEVICE *dptr) +{ +if (M == NULL) { /* initial call after startup? */ + pcq_r = find_reg ("PCQ", NULL, dptr); /* get PC queue pointer */ + + if (pcq_r) /* defined? */ + pcq_r->qptr = 0; /* initialize queue */ + else /* not defined */ + return SCPE_IERR; /* internal error */ + + M = (uint16 *) calloc (PASIZE, sizeof (uint16)); /* alloc mem */ + + if (M == NULL) /* alloc fail? */ + return SCPE_MEM; + else { /* do one-time init */ + MEMSIZE = 32768; /* set initial memory size */ + cpu_set_model (NULL, UNIT_2116, NULL, NULL); /* set initial CPU model */ + SR = 001000; /* select PTR boot ROM at SC 10 */ + cpu_boot (0, NULL); /* install loader for 2116 */ + cpu_set_ldr (NULL, FALSE, NULL, NULL); /* disable loader (was enabled) */ + SR = 0; /* clear S */ + sim_vm_post = &hp_post_cmd; /* set cmd post proc */ + sim_vm_fprint_stopped = &hp_fprint_stopped; /* set sim stop printer */ + sim_brk_types = ALL_BKPTS; /* register allowed breakpoint types */ + } + } + +if (sim_switches & SWMASK ('P')) /* PON reset? */ + IOPOWERON (&cpu_dib); +else /* PRESET */ + IOPRESET (&cpu_dib); + +sim_brk_dflt = SWMASK ('N'); /* type is nomap as DMS is off */ + +return SCPE_OK; +} + + +/* Memory protect reset */ + +t_stat mp_reset (DEVICE *dptr) +{ +IOPRESET (&mp_dib); /* PRESET device (does not use PON) */ + +mp_fence = 0; /* clear fence register */ +mp_viol = 0; /* clear violation register */ + +return SCPE_OK; +} + + +/* DMA reset */ + +t_stat dma_reset (DEVICE *dptr) +{ +DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ +const CHANNEL ch = (CHANNEL) dibptr->card_index; /* DMA channel number */ + +if (UNIT_CPU_MODEL != UNIT_2114) /* 2114 has only one channel */ + hp_enbdis_pair (dma_dptrs [ch], /* make specified channel */ + dma_dptrs [ch ^ 1]); /* consistent with other channel */ + +if (sim_switches & SWMASK ('P')) { /* power-on reset? */ + dma [ch].cw1 = 0; /* clear control word registers */ + dma [ch].cw2 = 0; + dma [ch].cw3 = 0; + } + +IOPRESET (dibptr); /* PRESET device (does not use PON) */ + +dma [ch].packer = 0; /* clear byte packer */ + +return SCPE_OK; +} + + +/* Memory examine */ + +t_stat cpu_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) +{ +uint16 d; + +if ((sw & ALL_MAPMODES) && (dms_enb == 0)) /* req map with DMS off? */ + return SCPE_NOFNC; /* command not allowed */ + +addr = dms_cons (addr, sw); /* translate address as indicated */ + +if (addr >= MEMSIZE) /* beyond memory limits? */ + return SCPE_NXM; /* non-existent memory */ + +if ((sw & SIM_SW_REST) || (addr >= 2)) /* restoring or memory access? */ + d = M[addr]; /* return memory value */ +else /* not restoring and A/B access */ + d = ABREG[addr]; /* return A/B register value */ + +if (vptr != NULL) + *vptr = d & DMASK; /* store return value */ +return SCPE_OK; +} + +/* Memory deposit */ + +t_stat cpu_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw) +{ +if ((sw & ALL_MAPMODES) && (dms_enb == 0)) /* req map with DMS off? */ + return SCPE_NOFNC; /* command not allowed */ + +addr = dms_cons (addr, sw); /* translate address as indicated */ + +if (addr >= MEMSIZE) /* beyond memory limits? */ + return SCPE_NXM; /* non-existent memory */ + +if ((sw & SIM_SW_REST) || (addr >= 2)) /* restoring or memory access? */ + M[addr] = val & DMASK; /* store memory value */ +else /* not restoring and A/B access */ + ABREG[addr] = val & DMASK; /* store A/B register value */ + +return SCPE_OK; +} + + +/* Make a pair of devices consistent */ + +void hp_enbdis_pair (DEVICE *ccp, DEVICE *dcp) +{ +if (ccp->flags & DEV_DIS) + dcp->flags = dcp->flags | DEV_DIS; +else + dcp->flags = dcp->flags & ~DEV_DIS; + +return; +} + + +/* VM command post-processor + + Update T register to contents of memory addressed by M register + if M register has changed. +*/ + +void hp_post_cmd (t_bool from_scp) +{ +if (MR != saved_MR) { /* M changed since last update? */ + saved_MR = MR; + TR = ReadTAB (MR); /* sync T with new M */ + } +return; +} + + +/* Test for device conflict */ + +static t_bool dev_conflict (void) +{ +DEVICE *dptr; +DIB *dibptr; +uint32 i, j, k; +t_bool is_conflict = FALSE; +uint32 conflicts [MAXDEV + 1] = { 0 }; + +for (i = 0; sim_devices [i] != NULL; i++) { + dptr = sim_devices [i]; + dibptr = (DIB *) dptr->ctxt; + if (dibptr && !(dptr->flags & DEV_DIS)) + if (++conflicts [dibptr->select_code] > 1) + is_conflict = TRUE; + } + +if (is_conflict) { + sim_ttcmd(); + for (i = 0; i <= MAXDEV; i++) { + if (conflicts [i] > 1) { + k = conflicts [i]; + + printf ("Select code %o conflict:", i); + + if (sim_log) + fprintf (sim_log, "Select code %o conflict:", i); + + for (j = 0; sim_devices [j] != NULL; j++) { + dptr = sim_devices [j]; + dibptr = (DIB *) dptr->ctxt; + if (dibptr && !(dptr->flags & DEV_DIS) && i == dibptr->select_code) { + if (k < conflicts [i]) { + printf (" and"); + + if (sim_log) + fputs (" and", sim_log); + } + + printf (" %s", sim_dname (dptr)); + + if (sim_log) + fprintf (sim_log, " %s", sim_dname (dptr)); + + k = k - 1; + + if (k == 0) { + putchar ('\n'); + + if (sim_log) + fputc ('\n', sim_log); + break; + } + } + } + } + } + } +return is_conflict; +} + + +/* Change CPU memory size. + + On a 21xx, move the current loader to the top of the new memory size. Then + clear "non-existent memory" so that reads return zero, per spec. + + Validation: + - New size <= maximum size for current CPU. + - New size a positive multiple of 4K (progamming error if not). + - If new size < old size, truncation accepted. +*/ + +t_stat cpu_set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc) +{ +int32 mc = 0; +uint32 i; +uint32 model = CPU_MODEL_INDEX; /* current CPU model index */ +uint32 old_size = (uint32) MEMSIZE; /* current memory size */ + +if ((uint32) new_size > cpu_features[model].maxmem) + return SCPE_NOFNC; /* mem size unsupported */ + +if ((new_size <= 0) || (new_size > PASIZE) || ((new_size & 07777) != 0)) + return SCPE_NXM; /* invalid size (prog err) */ + +if (!(sim_switches & SWMASK ('F'))) { /* force truncation? */ + for (i = new_size; i < MEMSIZE; i++) /* check truncated memory */ + mc = mc | M[i]; /* for content */ + + if ((mc != 0) && (!get_yn ("Really truncate memory [N]?", FALSE))) + return SCPE_INCOMP; + } + +if (UNIT_CPU_FAMILY == UNIT_FAMILY_21XX) { /* 21xx CPU? */ + cpu_set_ldr (uptr, FALSE, NULL, NULL); /* save loader to shadow RAM */ + MEMSIZE = new_size; /* set new memory size */ + fwanxm = (uint32) MEMSIZE - IBL_LNT; /* reserve memory for loader */ + } +else /* loader unsupported */ + MEMSIZE = fwanxm = new_size; /* set new memory size */ + +for (i = fwanxm; i < old_size; i++) /* zero non-existent memory */ + M[i] = 0; + +return SCPE_OK; +} + + +/* Change CPU models. + + For convenience, MP and DMA are typically enabled if available; they may be + disabled subsequently if desired. Note that the 2114 supports only one DMA + channel (channel 1). All other models support two channels. + + Validation: + - Sets standard equipment and convenience features. + - Changes DMA device name to DCPC if 1000 is selected. + - Enforces maximum memory allowed (doesn't change otherwise). + - Disables loader on 21xx machines. +*/ + +t_stat cpu_set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc) +{ +uint32 old_family = UNIT_CPU_FAMILY; /* current CPU type */ +uint32 new_family = new_model & UNIT_FAMILY_MASK; /* new CPU family */ +uint32 new_index = new_model >> UNIT_V_CPU; /* new CPU model index */ +uint32 new_memsize; +t_stat result; + +cpu_unit.flags = cpu_unit.flags & ~UNIT_OPTS | /* set typical features */ + cpu_features[new_index].typ & UNIT_OPTS; /* mask pseudo-opts */ + + +if (cpu_features[new_index].typ & UNIT_MP) /* MP in typ config? */ + mp_dev.flags = mp_dev.flags & ~DEV_DIS; /* enable it */ +else + mp_dev.flags = mp_dev.flags | DEV_DIS; /* disable it */ + +if (cpu_features[new_index].opt & UNIT_MP) /* MP an option? */ + mp_dev.flags = mp_dev.flags | DEV_DISABLE; /* make it alterable */ +else + mp_dev.flags = mp_dev.flags & ~DEV_DISABLE; /* make it unalterable */ + + +if (cpu_features[new_index].typ & UNIT_DMA) { /* DMA in typ config? */ + dma1_dev.flags = dma1_dev.flags & ~DEV_DIS; /* enable DMA channel 1 */ + + if (new_model == UNIT_2114) /* 2114 has only one channel */ + dma2_dev.flags = dma2_dev.flags | DEV_DIS; /* disable channel 2 */ + else /* all others have two channels */ + dma2_dev.flags = dma2_dev.flags & ~DEV_DIS; /* enable it */ + } +else { + dma1_dev.flags = dma1_dev.flags | DEV_DIS; /* disable channel 1 */ + dma2_dev.flags = dma2_dev.flags | DEV_DIS; /* disable channel 2 */ + } + +if (cpu_features[new_index].opt & UNIT_DMA) { /* DMA an option? */ + dma1_dev.flags = dma1_dev.flags | DEV_DISABLE; /* make it alterable */ + + if (new_model == UNIT_2114) /* 2114 has only one channel */ + dma2_dev.flags = dma2_dev.flags & ~DEV_DISABLE; /* make it unalterable */ + else /* all others have two channels */ + dma2_dev.flags = dma2_dev.flags | DEV_DISABLE; /* make it alterable */ + } +else { + dma1_dev.flags = dma1_dev.flags & ~DEV_DISABLE; /* make it unalterable */ + dma2_dev.flags = dma2_dev.flags & ~DEV_DISABLE; /* make it unalterable */ + } + + +if ((old_family == UNIT_FAMILY_1000) && /* if current family is 1000 */ + (new_family == UNIT_FAMILY_21XX)) { /* and new family is 21xx */ + deassign_device (&dma1_dev); /* delete DCPC names */ + deassign_device (&dma2_dev); + } +else if ((old_family == UNIT_FAMILY_21XX) && /* if current family is 21xx */ + (new_family == UNIT_FAMILY_1000)) { /* and new family is 1000 */ + assign_device (&dma1_dev, "DCPC1"); /* change DMA device name */ + assign_device (&dma2_dev, "DCPC2"); /* to DCPC for familiarity */ + } + +if ((MEMSIZE == 0) || /* current mem size not set? */ + (MEMSIZE > cpu_features[new_index].maxmem)) /* current mem size too large? */ + new_memsize = cpu_features[new_index].maxmem; /* set it to max supported */ +else + new_memsize = (uint32) MEMSIZE; /* or leave it unchanged */ + +result = cpu_set_size (uptr, new_memsize, NULL, NULL); /* set memory size */ + +if (result == SCPE_OK) /* memory change OK? */ + if (new_family == UNIT_FAMILY_21XX) /* 21xx CPU? */ + fwanxm = (uint32) MEMSIZE - IBL_LNT; /* reserve memory for loader */ + else + fwanxm = (uint32) MEMSIZE; /* loader reserved only for 21xx */ + +return result; +} + + +/* Display the CPU model and optional loader status. + + Loader status is displayed for 21xx models and suppressed for 1000 models. +*/ + +t_stat cpu_show_model (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +fputs ((const char *) desc, st); /* write model name */ + +if (UNIT_CPU_FAMILY == UNIT_FAMILY_21XX) /* valid only for 21xx */ + if (fwanxm < MEMSIZE) /* loader area non-existent? */ + fputs (", loader disabled", st); /* yes, so access disabled */ + else + fputs (", loader enabled", st); /* no, so access enabled */ +return SCPE_OK; +} + + +/* Set a CPU option. + + Validation: + - Checks that the current CPU model supports the option selected. + - If CPU is 1000-F, ensures that VIS and IOP are mutually exclusive. + - If CPU is 2100, ensures that FP/FFP and IOP are mutually exclusive. + - If CPU is 2100, ensures that FP is enabled if FFP enabled + (FP is required for FFP installation). +*/ + +t_stat cpu_set_opt (UNIT *uptr, int32 option, CONST char *cptr, void *desc) +{ +uint32 model = CPU_MODEL_INDEX; /* current CPU model index */ + +if ((cpu_features[model].opt & option) == 0) /* option supported? */ + return SCPE_NOFNC; /* no */ + +if (UNIT_CPU_TYPE == UNIT_TYPE_2100) { + if ((option == UNIT_FP) || (option == UNIT_FFP)) /* 2100 IOP and FP/FFP options */ + uptr->flags = uptr->flags & ~UNIT_IOP; /* are mutually exclusive */ + else if (option == UNIT_IOP) + uptr->flags = uptr->flags & ~(UNIT_FP | UNIT_FFP); + + if (option == UNIT_FFP) /* 2100 FFP option requires FP */ + uptr->flags = uptr->flags | UNIT_FP; + } + +else if (UNIT_CPU_MODEL == UNIT_1000_F) + if (option == UNIT_VIS) /* 1000-F IOP and VIS options */ + uptr->flags = uptr->flags & ~UNIT_IOP; /* are mutually exclusive */ + else if (option == UNIT_IOP) + uptr->flags = uptr->flags & ~UNIT_VIS; + +return SCPE_OK; +} + + +/* Clear a CPU option. + + Validation: + - Checks that the current CPU model supports the option selected. + - Clears flag from unit structure (we are processing MTAB_XTD entries). + - If CPU is 2100, ensures that FFP is disabled if FP disabled + (FP is required for FFP installation). +*/ + +t_bool cpu_clr_opt (UNIT *uptr, int32 option, CONST char *cptr, void *desc) +{ +uint32 model = CPU_MODEL_INDEX; /* current CPU model index */ + +if ((cpu_features[model].opt & option) == 0) /* option supported? */ + return SCPE_NOFNC; /* no */ + +uptr->flags = uptr->flags & ~option; /* disable option */ + +if ((UNIT_CPU_TYPE == UNIT_TYPE_2100) && /* disabling 2100 FP? */ + (option == UNIT_FP)) + uptr->flags = uptr->flags & ~UNIT_FFP; /* yes, so disable FFP too */ + +return SCPE_OK; +} + + +/* 21xx loader enable/disable function. + + The 21xx CPUs store their initial binary loaders in the last 64 words of + available memory. This memory is protected by a LOADER ENABLE switch on the + front panel. When the switch is off (disabled), main memory effectively ends + 64 locations earlier, i.e., the loader area is treated as non-existent. + Because these are core machines, the loader is retained when system power is + off. + + 1000 CPUs do not have a protected loader feature. Instead, loaders are + stored in PROMs and are copied into main memory for execution by the IBL + switch. + + Under simulation, we keep both a total configured memory size (MEMSIZE) and a + current configured memory size (fwanxm = "first word address of non-existent + memory"). When the two are equal, the loader is enabled. When the current + size is less than the total size, the loader is disabled. + + Disabling the loader copies the last 64 words to a shadow array, zeros the + corresponding memory, and decreases the last word of addressable memory by + 64. Enabling the loader reverses this process. + + Disabling may be done manually by user command or automatically when a halt + instruction is executed. Enabling occurs only by user command. This differs + slightly from actual machine operation, which additionally disables the + loader when a manual halt is performed. We do not do this to allow + breakpoints within and single-stepping through the loaders. +*/ + +t_stat cpu_set_ldr (UNIT *uptr, int32 enable, CONST char *cptr, void *desc) +{ +static BOOT_ROM loader; +int32 i; +t_bool is_enabled = (fwanxm == MEMSIZE); + +if ((UNIT_CPU_FAMILY != UNIT_FAMILY_21XX) || /* valid only for 21xx */ + (MEMSIZE == 0)) /* and for initialized memory */ + return SCPE_NOFNC; + +if (is_enabled && (enable == 0)) { /* disable loader? */ + fwanxm = (uint32) MEMSIZE - IBL_LNT; /* decrease available memory */ + for (i = 0; i < IBL_LNT; i++) { /* copy loader */ + loader[i] = M[fwanxm + i]; /* from memory */ + M[fwanxm + i] = 0; /* and zero location */ + } + } + +else if ((!is_enabled) && (enable == 1)) { /* enable loader? */ + for (i = 0; i < IBL_LNT; i++) /* copy loader */ + M[fwanxm + i] = loader[i]; /* to memory */ + fwanxm = (uint32) MEMSIZE; /* increase available memory */ + } + +return SCPE_OK; +} + + +/* Idle enable/disable */ + +t_stat cpu_set_idle (UNIT *uptr, int32 option, CONST char *cptr, void *desc) +{ +if (option) + return sim_set_idle (uptr, 10, NULL, NULL); +else + return sim_clr_idle (uptr, 0, NULL, NULL); +} + + +/* Idle display */ + +t_stat cpu_show_idle (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +return sim_show_idle (st, uptr, val, desc); +} + + +/* IBL routine (CPU boot) */ + +t_stat cpu_boot (int32 unitno, DEVICE *dptr) +{ +int32 dev = (SR >> IBL_V_DEV) & I_DEVMASK; +int32 sel = (SR >> IBL_V_SEL) & IBL_M_SEL; + +if (dev < 010) + return SCPE_NOFNC; + +switch (sel) { + + case 0: /* PTR boot */ + ibl_copy (ptr_rom, dev, IBL_S_NOCLR, IBL_S_NOSET); + break; + + case 1: /* DP/DQ boot */ + ibl_copy (dq_rom, dev, IBL_S_NOCLR, IBL_S_NOSET); + break; + + case 2: /* MS boot */ + ibl_copy (ms_rom, dev, IBL_S_NOCLR, IBL_S_NOSET); + break; + + case 3: /* DS boot */ + ibl_copy (ds_rom, dev, IBL_S_NOCLR, IBL_S_NOSET); + break; + } + +return SCPE_OK; +} + + +/* IBL boot ROM copy + + - Use memory size to set the initial P and base of the boot area + - Copy boot ROM to memory, updating I/O instructions + - Place 2s complement of boot base in last location + - Modify S register as indicated + + Notes: + - Boot ROMs must be assembled with a device code of 10 (10 and 11 for + devices requiring two codes) +*/ + +t_stat ibl_copy (const BOOT_ROM rom, int32 dev, uint32 sr_clear, uint32 sr_set) +{ +int32 i; +uint16 wd; + +cpu_set_ldr (NULL, TRUE, NULL, NULL); /* enable loader (ignore errors) */ + +if (dev < 010) /* valid device? */ + return SCPE_ARG; + +PR = ((MEMSIZE - 1) & ~IBL_MASK) & VAMASK; /* start at mem top */ + +for (i = 0; i < IBL_LNT; i++) { /* copy bootstrap */ + wd = rom[i]; /* get word */ + + if (((wd & I_NMRMASK) == I_IO) && /* IO instruction? */ + ((wd & I_DEVMASK) >= 010) && /* dev >= 10? */ + (I_GETIOOP (wd) != soHLT)) /* not a HALT? */ + M[PR + i] = (wd + (dev - 010)) & DMASK; /* change dev code */ + + else /* leave unchanged */ + M[PR + i] = wd; + } + +M[PR + IBL_DPC] = (M[PR + IBL_DPC] + (dev - 010)) & DMASK; /* patch DMA ctrl */ +M[PR + IBL_END] = (~PR + 1) & DMASK; /* fill in start of boot */ + +SR = (SR & sr_clear) | sr_set; /* modify the S register as indicated */ + +return SCPE_OK; +} diff --git a/HP2100/hp2100_cpu.h b/HP2100/hp2100_cpu.h index 74b28d8b..35343f5d 100644 --- a/HP2100/hp2100_cpu.h +++ b/HP2100/hp2100_cpu.h @@ -1,335 +1,336 @@ -/* hp2100_cpu.h: HP 2100 CPU definitions - - Copyright (c) 2005-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not - be used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - 24-Dec-14 JDB Added casts for explicit downward conversions - 18-Mar-13 JDB Added declarations for the MP abort handler and CPU registers - 14-Mar-13 MP Changed guard macro name to avoid reserved namespace - 03-Jan-10 RMS Changed declarations of mp_control, mp_mefvv, for VMS compiler - 15-Jul-08 JDB Rearranged declarations with hp2100_cpu.c and hp2100_defs.h - 26-Jun-08 JDB Added mp_control to CPU state externals - 24-Apr-08 JDB Added calc_defer() prototype - 20-Apr-08 JDB Added DEB_VIS and DEB_SIG debug flags - 26-Nov-07 JDB Added extern sim_deb, cpu_dev, DEB flags for debug printouts - 05-Nov-07 JDB Added extern intaddr, mp_viol, mp_mevff, calc_int, dev_ctl, - ReadIO, WriteIO for RTE-6/VM microcode support - 16-Dec-06 JDB Added UNIT_2115 and UNIT_2114 - 16-Oct-06 JDB Moved ReadF to hp2100_cpu1.c - 26-Sep-06 JDB Added CPU externs for microcode simulators - 16-Aug-06 JDB Added UNIT_EMA for future RTE-4 EMA microcode - Added UNIT_VMA for future RTE-6 VMA and OS microcode - Added UNIT_1000_F for future F-Series support - 09-Aug-06 JDB Added UNIT_DBI for double integer microcode - 21-Jan-05 JDB Reorganized CPU option flags - 14-Jan-05 RMS Cloned from hp2100_cpu.c - - CPU models are broken down into family, type, and series to facilitate option - validation. Bit 3 encodes the family, bit 2 encodes the type, and bits 1:0 - encode the series within the type. -*/ - -#ifndef HP2100_CPU_H_ -#define HP2100_CPU_H_ 0 - -#include - - -/* CPU model definition flags */ - -#define CPU_V_SERIES 0 -#define CPU_V_TYPE 2 -#define CPU_V_FAMILY 3 - -#define FAMILY_21XX (0 << CPU_V_FAMILY) -#define FAMILY_1000 (1 << CPU_V_FAMILY) - -#define TYPE_211X (0 << CPU_V_TYPE) /* 2114, 2115, 2116 */ -#define TYPE_2100 (1 << CPU_V_TYPE) /* 2100A, 2100S */ -#define TYPE_1000MEF (0 << CPU_V_TYPE) /* 1000-M, 1000-E, 1000-F */ -#define TYPE_1000AL (1 << CPU_V_TYPE) /* 1000-L, A600, A700, A900, A990 */ - -#define SERIES_16 (0 << CPU_V_SERIES) /* 211X */ -#define SERIES_15 (1 << CPU_V_SERIES) /* 211X */ -#define SERIES_14 (2 << CPU_V_SERIES) /* 211X */ -#define SERIES_00 (0 << CPU_V_SERIES) /* 2100 */ -#define SERIES_M (0 << CPU_V_SERIES) /* 1000 */ -#define SERIES_E (1 << CPU_V_SERIES) /* 1000 */ -#define SERIES_F (2 << CPU_V_SERIES) /* 1000 */ - -/* CPU unit flags */ - -#define UNIT_M_CPU 017 /* CPU model mask [3:0] */ -#define UNIT_M_TYPE 014 /* CPU type mask [3:2] */ -#define UNIT_M_FAMILY 010 /* CPU family mask [3:3] */ - -#define UNIT_V_CPU (UNIT_V_UF + 0) /* CPU model bits 0-3 */ -#define UNIT_V_EAU (UNIT_V_UF + 4) /* EAU installed */ -#define UNIT_V_FP (UNIT_V_UF + 5) /* FP installed */ -#define UNIT_V_IOP (UNIT_V_UF + 6) /* IOP installed */ -#define UNIT_V_DMS (UNIT_V_UF + 7) /* DMS installed */ -#define UNIT_V_FFP (UNIT_V_UF + 8) /* FFP installed */ -#define UNIT_V_DBI (UNIT_V_UF + 9) /* DBI installed */ -#define UNIT_V_EMA (UNIT_V_UF + 10) /* RTE-4 EMA installed */ -#define UNIT_V_VMAOS (UNIT_V_UF + 11) /* RTE-6 VMA/OS installed */ -#define UNIT_V_VIS (UNIT_V_UF + 12) /* VIS installed */ -#define UNIT_V_SIGNAL (UNIT_V_UF + 13) /* SIGNAL/1000 installed */ -/* Future microcode expansion; reuse flags bottom-up if needed */ -#define UNIT_V_DS (UNIT_V_UF + 14) /* DS installed */ - -/* Unit models */ - -#define UNIT_MODEL_MASK (UNIT_M_CPU << UNIT_V_CPU) - -#define UNIT_2116 ((FAMILY_21XX | TYPE_211X | SERIES_16) << UNIT_V_CPU) -#define UNIT_2115 ((FAMILY_21XX | TYPE_211X | SERIES_15) << UNIT_V_CPU) -#define UNIT_2114 ((FAMILY_21XX | TYPE_211X | SERIES_14) << UNIT_V_CPU) -#define UNIT_2100 ((FAMILY_21XX | TYPE_2100 | SERIES_00) << UNIT_V_CPU) -#define UNIT_1000_M ((FAMILY_1000 | TYPE_1000MEF | SERIES_M) << UNIT_V_CPU) -#define UNIT_1000_E ((FAMILY_1000 | TYPE_1000MEF | SERIES_E) << UNIT_V_CPU) -#define UNIT_1000_F ((FAMILY_1000 | TYPE_1000MEF | SERIES_F) << UNIT_V_CPU) - -/* Unit types */ - -#define UNIT_TYPE_MASK (UNIT_M_TYPE << UNIT_V_CPU) - -#define UNIT_TYPE_211X ((FAMILY_21XX | TYPE_211X) << UNIT_V_CPU) -#define UNIT_TYPE_2100 ((FAMILY_21XX | TYPE_2100) << UNIT_V_CPU) -#define UNIT_TYPE_1000 ((FAMILY_1000 | TYPE_1000MEF) << UNIT_V_CPU) - -/* Unit families */ - -#define UNIT_FAMILY_MASK (UNIT_M_FAMILY << UNIT_V_CPU) - -#define UNIT_FAMILY_21XX (FAMILY_21XX << UNIT_V_CPU) -#define UNIT_FAMILY_1000 (FAMILY_1000 << UNIT_V_CPU) - -/* Unit accessors */ - -#define UNIT_CPU_MODEL (cpu_unit.flags & UNIT_MODEL_MASK) -#define UNIT_CPU_TYPE (cpu_unit.flags & UNIT_TYPE_MASK) -#define UNIT_CPU_FAMILY (cpu_unit.flags & UNIT_FAMILY_MASK) - -#define CPU_MODEL_INDEX (UNIT_CPU_MODEL >> UNIT_V_CPU) - -/* Unit features */ - -#define UNIT_EAU (1 << UNIT_V_EAU) -#define UNIT_FP (1 << UNIT_V_FP) -#define UNIT_IOP (1 << UNIT_V_IOP) -#define UNIT_DMS (1 << UNIT_V_DMS) -#define UNIT_FFP (1 << UNIT_V_FFP) -#define UNIT_DBI (1 << UNIT_V_DBI) -#define UNIT_EMA (1 << UNIT_V_EMA) -#define UNIT_VMAOS (1 << UNIT_V_VMAOS) -#define UNIT_VIS (1 << UNIT_V_VIS) -#define UNIT_DS (1 << UNIT_V_DS) -#define UNIT_SIGNAL (1 << UNIT_V_SIGNAL) - -#define UNIT_EMA_VMA (UNIT_EMA | UNIT_VMAOS) - -#define UNIT_OPTS (UNIT_EAU | UNIT_FP | UNIT_IOP | \ - UNIT_DMS | UNIT_FFP | UNIT_DBI | \ - UNIT_EMA | UNIT_VMAOS | \ - UNIT_VIS | UNIT_DS | UNIT_SIGNAL) - -/* "Pseudo-option" flags used only for option testing; never set into UNIT structure. */ - -#define UNIT_V_PFAIL (UNIT_V_UF - 1) /* Power fail installed */ -#define UNIT_V_DMA (UNIT_V_UF - 2) /* DMA installed */ -#define UNIT_V_MP (UNIT_V_UF - 3) /* Memory protect installed */ - -#define UNIT_PFAIL (1 << UNIT_V_PFAIL) -#define UNIT_DMA (1 << UNIT_V_DMA) -#define UNIT_MP (1 << UNIT_V_MP) - -#define UNIT_NONE 0 /* no options */ - -/* Debug flags */ - -#define DEB_OS (1 << 0) /* RTE-6/VM OS firmware non-TBG processing */ -#define DEB_OSTBG (1 << 1) /* RTE-6/VM OS firmware TBG processing */ -#define DEB_VMA (1 << 2) /* RTE-6/VM VMA firmware instructions */ -#define DEB_EMA (1 << 3) /* RTE-6/VM EMA firmware instructions */ -#define DEB_VIS (1 << 4) /* E/F-Series VIS firmware instructions */ -#define DEB_SIG (1 << 5) /* F-Series SIGNAL/1000 firmware instructions */ - -/* PC queue. */ - -#define PCQ_SIZE 64 /* must be 2**n */ -#define PCQ_MASK (PCQ_SIZE - 1) -#define PCQ_ENTRY pcq[pcq_p = (pcq_p - 1) & PCQ_MASK] = (uint16) err_PC - -/* Memory reference instructions */ - -#define I_IA 0100000 /* indirect address */ -#define I_AB 0004000 /* A/B select */ -#define I_CP 0002000 /* current page */ -#define I_DISP 0001777 /* page displacement */ -#define I_PAGENO 0076000 /* page number */ - -/* Other instructions */ - -#define I_NMRMASK 0172000 /* non-mrf opcode */ -#define I_ASKP 0002000 /* alter/skip */ -#define I_IO 0102000 /* I/O */ -#define I_CTL 0004000 /* CTL on/off */ -#define I_HC 0001000 /* hold/clear */ -#define I_DEVMASK 0000077 /* device select code mask */ -#define I_GETIOOP(x) (((x) >> 6) & 07) /* I/O sub op */ - -/* Instruction masks */ - -#define I_MRG 0074000 /* MRG instructions */ -#define I_MRG_I (I_MRG | I_IA) /* MRG indirect instruction group */ -#define I_JSB 0014000 /* JSB instruction */ -#define I_JSB_I (I_JSB | I_IA) /* JSB,I instruction */ -#define I_JMP 0024000 /* JMP instruction */ -#define I_ISZ 0034000 /* ISZ instruction */ - -#define I_IOG 0107700 /* I/O group instruction */ -#define I_SFS 0102300 /* SFS instruction */ -#define I_STF 0102100 /* STF instruction */ - -/* Memory management */ - -#define VA_N_OFF 10 /* offset width */ -#define VA_M_OFF ((1 << VA_N_OFF) - 1) /* offset mask */ -#define VA_GETOFF(x) ((x) & VA_M_OFF) -#define VA_N_PAG (VA_N_SIZE - VA_N_OFF) /* page width */ -#define VA_V_PAG (VA_N_OFF) /* page offset */ -#define VA_M_PAG ((1 << VA_N_PAG) - 1) /* page mask */ -#define VA_GETPAG(x) (((x) >> VA_V_PAG) & VA_M_PAG) - -/* Maps */ - -#define MAP_NUM 4 /* num maps */ -#define MAP_LNT (1 << VA_N_PAG) /* map length */ -#define MAP_MASK ((MAP_NUM * MAP_LNT) - 1) -#define SMAP 0 /* system map */ -#define UMAP (SMAP + MAP_LNT) /* user map */ -#define PAMAP (UMAP + MAP_LNT) /* port A map */ -#define PBMAP (PAMAP + MAP_LNT) /* port B map */ - -/* DMS map entries */ - -#define MAP_V_RPR 15 /* read prot */ -#define MAP_V_WPR 14 /* write prot */ -#define RDPROT (1 << MAP_V_RPR) /* read access check */ -#define WRPROT (1 << MAP_V_WPR) /* write access check */ -#define NOPROT 0 /* no access check */ -#define MAP_RSVD 0036000 /* reserved bits */ -#define MAP_N_PAG (PA_N_SIZE - VA_N_OFF) /* page width */ -#define MAP_V_PAG (VA_N_OFF) -#define MAP_M_PAG ((1 << MAP_N_PAG) - 1) -#define MAP_GETPAG(x) (((x) & MAP_M_PAG) << MAP_V_PAG) - -/* MEM status register */ - -#define MST_ENBI 0100000 /* MEM enabled at interrupt */ -#define MST_UMPI 0040000 /* User map selected at inerrupt */ -#define MST_ENB 0020000 /* MEM enabled currently */ -#define MST_UMP 0010000 /* User map selected currently */ -#define MST_PRO 0004000 /* Protected mode enabled currently */ -#define MST_FLT 0002000 /* Base page portion mapped */ -#define MST_FENCE 0001777 /* Base page fence */ - -/* MEM violation register */ - -#define MVI_V_RPR 15 /* must be same as */ -#define MVI_V_WPR 14 /* MAP_V_xPR */ -#define MVI_RPR (1 << MVI_V_RPR) /* rd viol */ -#define MVI_WPR (1 << MVI_V_WPR) /* wr viol */ -#define MVI_BPG 0020000 /* base page viol */ -#define MVI_PRV 0010000 /* priv viol */ -#define MVI_MEB 0000200 /* me bus enb @ viol */ -#define MVI_MEM 0000100 /* mem enb @ viol */ -#define MVI_UMP 0000040 /* usr map @ viol */ -#define MVI_PAG 0000037 /* pag sel */ - -/* CPU registers */ - -#define AR ABREG[0] /* A = reg 0 */ -#define BR ABREG[1] /* B = reg 1 */ - -extern uint16 ABREG[2]; /* A/B regs (use AR/BR) */ -extern uint32 PC; /* P register */ -extern uint32 SR; /* S register */ -extern uint32 MR; /* M register */ -extern uint32 TR; /* T register */ -extern uint32 XR; /* X register */ -extern uint32 YR; /* Y register */ -extern uint32 E; /* E register */ -extern uint32 O; /* O register */ - -/* CPU state */ - -extern uint32 err_PC; -extern uint32 dms_enb; -extern uint32 dms_ump; -extern uint32 dms_sr; -extern uint32 dms_vr; -extern FLIP_FLOP mp_control; -extern uint32 mp_fence; -extern uint32 mp_viol; -extern FLIP_FLOP mp_mevff; -extern uint32 iop_sp; -extern t_bool ion_defer; -extern uint32 intaddr; -extern uint16 pcq [PCQ_SIZE]; -extern uint32 pcq_p; -extern uint32 stop_inst; -extern UNIT cpu_unit; -extern DEVICE cpu_dev; -extern REG cpu_reg []; -extern jmp_buf save_env; - - -/* CPU functions */ - -#define MP_ABORT(va) longjmp (save_env, (va)) - -extern t_stat resolve (uint32 MA, uint32 *addr, uint32 irq); -extern uint16 ReadPW (uint32 pa); -extern uint8 ReadB (uint32 va); -extern uint8 ReadBA (uint32 va); -extern uint16 ReadW (uint32 va); -extern uint16 ReadWA (uint32 va); -extern uint16 ReadIO (uint32 va, uint32 map); -extern void WritePW (uint32 pa, uint32 dat); -extern void WriteB (uint32 va, uint32 dat); -extern void WriteBA (uint32 va, uint32 dat); -extern void WriteW (uint32 va, uint32 dat); -extern void WriteWA (uint32 va, uint32 dat); -extern void WriteIO (uint32 va, uint32 dat, uint32 map); -extern t_stat iogrp (uint32 ir, uint32 iotrap); -extern uint32 calc_int (void); -extern t_bool calc_defer (void); -extern void mp_dms_jmp (uint32 va, uint32 plb); -extern uint16 dms_rmap (uint32 mapi); -extern void dms_wmap (uint32 mapi, uint32 dat); -extern void dms_viol (uint32 va, uint32 st); -extern uint32 dms_upd_vr (uint32 va); -extern uint32 dms_upd_sr (void); - -#endif +/* hp2100_cpu.h: HP 2100 CPU definitions + + Copyright (c) 2005-2016, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 24-Dec-14 JDB Added casts for explicit downward conversions + 18-Mar-13 JDB Added declarations for the MP abort handler and CPU registers + 14-Mar-13 MP Changed guard macro name to avoid reserved namespace + 03-Jan-10 RMS Changed declarations of mp_control, mp_mefvv, for VMS compiler + 15-Jul-08 JDB Rearranged declarations with hp2100_cpu.c and hp2100_defs.h + 26-Jun-08 JDB Added mp_control to CPU state externals + 24-Apr-08 JDB Added calc_defer() prototype + 20-Apr-08 JDB Added DEB_VIS and DEB_SIG debug flags + 26-Nov-07 JDB Added extern sim_deb, cpu_dev, DEB flags for debug printouts + 05-Nov-07 JDB Added extern intaddr, mp_viol, mp_mevff, calc_int, dev_ctl, + ReadIO, WriteIO for RTE-6/VM microcode support + 16-Dec-06 JDB Added UNIT_2115 and UNIT_2114 + 16-Oct-06 JDB Moved ReadF to hp2100_cpu1.c + 26-Sep-06 JDB Added CPU externs for microcode simulators + 16-Aug-06 JDB Added UNIT_EMA for future RTE-4 EMA microcode + Added UNIT_VMA for future RTE-6 VMA and OS microcode + Added UNIT_1000_F for future F-Series support + 09-Aug-06 JDB Added UNIT_DBI for double integer microcode + 21-Jan-05 JDB Reorganized CPU option flags + 14-Jan-05 RMS Cloned from hp2100_cpu.c + + CPU models are broken down into family, type, and series to facilitate option + validation. Bit 3 encodes the family, bit 2 encodes the type, and bits 1:0 + encode the series within the type. +*/ + +#ifndef HP2100_CPU_H_ +#define HP2100_CPU_H_ 0 + +#include + + +/* CPU model definition flags */ + +#define CPU_V_SERIES 0 +#define CPU_V_TYPE 2 +#define CPU_V_FAMILY 3 + +#define FAMILY_21XX (0 << CPU_V_FAMILY) +#define FAMILY_1000 (1 << CPU_V_FAMILY) + +#define TYPE_211X (0 << CPU_V_TYPE) /* 2114, 2115, 2116 */ +#define TYPE_2100 (1 << CPU_V_TYPE) /* 2100A, 2100S */ +#define TYPE_1000MEF (0 << CPU_V_TYPE) /* 1000-M, 1000-E, 1000-F */ +#define TYPE_1000AL (1 << CPU_V_TYPE) /* 1000-L, A600, A700, A900, A990 */ + +#define SERIES_16 (0 << CPU_V_SERIES) /* 211X */ +#define SERIES_15 (1 << CPU_V_SERIES) /* 211X */ +#define SERIES_14 (2 << CPU_V_SERIES) /* 211X */ +#define SERIES_00 (0 << CPU_V_SERIES) /* 2100 */ +#define SERIES_M (0 << CPU_V_SERIES) /* 1000 */ +#define SERIES_E (1 << CPU_V_SERIES) /* 1000 */ +#define SERIES_F (2 << CPU_V_SERIES) /* 1000 */ + +/* CPU unit flags */ + +#define UNIT_M_CPU 017 /* CPU model mask [3:0] */ +#define UNIT_M_TYPE 014 /* CPU type mask [3:2] */ +#define UNIT_M_FAMILY 010 /* CPU family mask [3:3] */ + +#define UNIT_V_CPU (UNIT_V_UF + 0) /* CPU model bits 0-3 */ +#define UNIT_V_EAU (UNIT_V_UF + 4) /* EAU installed */ +#define UNIT_V_FP (UNIT_V_UF + 5) /* FP installed */ +#define UNIT_V_IOP (UNIT_V_UF + 6) /* IOP installed */ +#define UNIT_V_DMS (UNIT_V_UF + 7) /* DMS installed */ +#define UNIT_V_FFP (UNIT_V_UF + 8) /* FFP installed */ +#define UNIT_V_DBI (UNIT_V_UF + 9) /* DBI installed */ +#define UNIT_V_EMA (UNIT_V_UF + 10) /* RTE-4 EMA installed */ +#define UNIT_V_VMAOS (UNIT_V_UF + 11) /* RTE-6 VMA/OS installed */ +#define UNIT_V_VIS (UNIT_V_UF + 12) /* VIS installed */ +#define UNIT_V_SIGNAL (UNIT_V_UF + 13) /* SIGNAL/1000 installed */ +/* Future microcode expansion; reuse flags bottom-up if needed */ +#define UNIT_V_DS (UNIT_V_UF + 14) /* DS installed */ + +/* Unit models */ + +#define UNIT_MODEL_MASK (UNIT_M_CPU << UNIT_V_CPU) + +#define UNIT_2116 ((FAMILY_21XX | TYPE_211X | SERIES_16) << UNIT_V_CPU) +#define UNIT_2115 ((FAMILY_21XX | TYPE_211X | SERIES_15) << UNIT_V_CPU) +#define UNIT_2114 ((FAMILY_21XX | TYPE_211X | SERIES_14) << UNIT_V_CPU) +#define UNIT_2100 ((FAMILY_21XX | TYPE_2100 | SERIES_00) << UNIT_V_CPU) +#define UNIT_1000_M ((FAMILY_1000 | TYPE_1000MEF | SERIES_M) << UNIT_V_CPU) +#define UNIT_1000_E ((FAMILY_1000 | TYPE_1000MEF | SERIES_E) << UNIT_V_CPU) +#define UNIT_1000_F ((FAMILY_1000 | TYPE_1000MEF | SERIES_F) << UNIT_V_CPU) + +/* Unit types */ + +#define UNIT_TYPE_MASK (UNIT_M_TYPE << UNIT_V_CPU) + +#define UNIT_TYPE_211X ((FAMILY_21XX | TYPE_211X) << UNIT_V_CPU) +#define UNIT_TYPE_2100 ((FAMILY_21XX | TYPE_2100) << UNIT_V_CPU) +#define UNIT_TYPE_1000 ((FAMILY_1000 | TYPE_1000MEF) << UNIT_V_CPU) + +/* Unit families */ + +#define UNIT_FAMILY_MASK (UNIT_M_FAMILY << UNIT_V_CPU) + +#define UNIT_FAMILY_21XX (FAMILY_21XX << UNIT_V_CPU) +#define UNIT_FAMILY_1000 (FAMILY_1000 << UNIT_V_CPU) + +/* Unit accessors */ + +#define UNIT_CPU_MODEL (cpu_unit.flags & UNIT_MODEL_MASK) +#define UNIT_CPU_TYPE (cpu_unit.flags & UNIT_TYPE_MASK) +#define UNIT_CPU_FAMILY (cpu_unit.flags & UNIT_FAMILY_MASK) + +#define CPU_MODEL_INDEX (UNIT_CPU_MODEL >> UNIT_V_CPU) + +/* Unit features */ + +#define UNIT_EAU (1 << UNIT_V_EAU) +#define UNIT_FP (1 << UNIT_V_FP) +#define UNIT_IOP (1 << UNIT_V_IOP) +#define UNIT_DMS (1 << UNIT_V_DMS) +#define UNIT_FFP (1 << UNIT_V_FFP) +#define UNIT_DBI (1 << UNIT_V_DBI) +#define UNIT_EMA (1 << UNIT_V_EMA) +#define UNIT_VMAOS (1 << UNIT_V_VMAOS) +#define UNIT_VIS (1 << UNIT_V_VIS) +#define UNIT_DS (1 << UNIT_V_DS) +#define UNIT_SIGNAL (1 << UNIT_V_SIGNAL) + +#define UNIT_EMA_VMA (UNIT_EMA | UNIT_VMAOS) + +#define UNIT_OPTS (UNIT_EAU | UNIT_FP | UNIT_IOP | \ + UNIT_DMS | UNIT_FFP | UNIT_DBI | \ + UNIT_EMA | UNIT_VMAOS | \ + UNIT_VIS | UNIT_DS | UNIT_SIGNAL) + +/* "Pseudo-option" flags used only for option testing; never set into UNIT structure. */ + +#define UNIT_V_PFAIL (UNIT_V_UF - 1) /* Power fail installed */ +#define UNIT_V_DMA (UNIT_V_UF - 2) /* DMA installed */ +#define UNIT_V_MP (UNIT_V_UF - 3) /* Memory protect installed */ + +#define UNIT_PFAIL (1 << UNIT_V_PFAIL) +#define UNIT_DMA (1 << UNIT_V_DMA) +#define UNIT_MP (1 << UNIT_V_MP) + +#define UNIT_NONE 0 /* no options */ + +/* Debug flags */ + +#define DEB_OS (1 << 0) /* RTE-6/VM OS firmware non-TBG processing */ +#define DEB_OSTBG (1 << 1) /* RTE-6/VM OS firmware TBG processing */ +#define DEB_VMA (1 << 2) /* RTE-6/VM VMA firmware instructions */ +#define DEB_EMA (1 << 3) /* RTE-6/VM EMA firmware instructions */ +#define DEB_VIS (1 << 4) /* E/F-Series VIS firmware instructions */ +#define DEB_SIG (1 << 5) /* F-Series SIGNAL/1000 firmware instructions */ + +/* PC queue. */ + +#define PCQ_SIZE 64 /* must be 2**n */ +#define PCQ_MASK (PCQ_SIZE - 1) +#define PCQ_ENTRY pcq[pcq_p = (pcq_p - 1) & PCQ_MASK] = (uint16) err_PC + +/* Memory reference instructions */ + +#define I_IA 0100000 /* indirect address */ +#define I_AB 0004000 /* A/B select */ +#define I_CP 0002000 /* current page */ +#define I_DISP 0001777 /* page displacement */ +#define I_PAGENO 0076000 /* page number */ + +/* Other instructions */ + +#define I_NMRMASK 0172000 /* non-mrf opcode */ +#define I_ASKP 0002000 /* alter/skip */ +#define I_IO 0102000 /* I/O */ +#define I_CTL 0004000 /* CTL on/off */ +#define I_HC 0001000 /* hold/clear */ +#define I_DEVMASK 0000077 /* device select code mask */ +#define I_GETIOOP(x) (((x) >> 6) & 07) /* I/O sub op */ + +/* Instruction masks */ + +#define I_MRG 0074000 /* MRG instructions */ +#define I_MRG_I (I_MRG | I_IA) /* MRG indirect instruction group */ +#define I_JSB 0014000 /* JSB instruction */ +#define I_JSB_I (I_JSB | I_IA) /* JSB,I instruction */ +#define I_JMP 0024000 /* JMP instruction */ +#define I_ISZ 0034000 /* ISZ instruction */ + +#define I_IOG 0107700 /* I/O group instruction */ +#define I_SFS 0102300 /* SFS instruction */ +#define I_STF 0102100 /* STF instruction */ + +/* Memory management */ + +#define VA_N_OFF 10 /* offset width */ +#define VA_M_OFF ((1 << VA_N_OFF) - 1) /* offset mask */ +#define VA_GETOFF(x) ((x) & VA_M_OFF) +#define VA_N_PAG (VA_N_SIZE - VA_N_OFF) /* page width */ +#define VA_V_PAG (VA_N_OFF) /* page offset */ +#define VA_M_PAG ((1 << VA_N_PAG) - 1) /* page mask */ +#define VA_GETPAG(x) (((x) >> VA_V_PAG) & VA_M_PAG) + +/* Maps */ + +#define MAP_NUM 4 /* num maps */ +#define MAP_LNT (1 << VA_N_PAG) /* map length */ +#define MAP_MASK ((MAP_NUM * MAP_LNT) - 1) +#define SMAP 0 /* system map */ +#define UMAP (SMAP + MAP_LNT) /* user map */ +#define PAMAP (UMAP + MAP_LNT) /* port A map */ +#define PBMAP (PAMAP + MAP_LNT) /* port B map */ + +/* DMS map entries */ + +#define MAP_V_RPR 15 /* read prot */ +#define MAP_V_WPR 14 /* write prot */ +#define RDPROT (1 << MAP_V_RPR) /* read access check */ +#define WRPROT (1 << MAP_V_WPR) /* write access check */ +#define NOPROT 0 /* no access check */ +#define MAP_RSVD 0036000 /* reserved bits */ +#define MAP_N_PAG (PA_N_SIZE - VA_N_OFF) /* page width */ +#define MAP_V_PAG (VA_N_OFF) +#define MAP_M_PAG ((1 << MAP_N_PAG) - 1) +#define MAP_GETPAG(x) (((x) & MAP_M_PAG) << MAP_V_PAG) + +/* MEM status register */ + +#define MST_ENBI 0100000 /* MEM enabled at interrupt */ +#define MST_UMPI 0040000 /* User map selected at inerrupt */ +#define MST_ENB 0020000 /* MEM enabled currently */ +#define MST_UMP 0010000 /* User map selected currently */ +#define MST_PRO 0004000 /* Protected mode enabled currently */ +#define MST_FLT 0002000 /* Base page portion mapped */ +#define MST_FENCE 0001777 /* Base page fence */ + +/* MEM violation register */ + +#define MVI_V_RPR 15 /* must be same as */ +#define MVI_V_WPR 14 /* MAP_V_xPR */ +#define MVI_RPR (1 << MVI_V_RPR) /* rd viol */ +#define MVI_WPR (1 << MVI_V_WPR) /* wr viol */ +#define MVI_BPG 0020000 /* base page viol */ +#define MVI_PRV 0010000 /* priv viol */ +#define MVI_MEB 0000200 /* me bus enb @ viol */ +#define MVI_MEM 0000100 /* mem enb @ viol */ +#define MVI_UMP 0000040 /* usr map @ viol */ +#define MVI_PAG 0000037 /* pag sel */ + +/* CPU registers */ + +#define AR ABREG[0] /* A = reg 0 */ +#define BR ABREG[1] /* B = reg 1 */ + +extern uint16 ABREG[2]; /* A/B regs (use AR/BR) */ +extern uint32 PR; /* P register */ +extern uint32 SR; /* S register */ +extern uint32 MR; /* M register */ +extern uint32 TR; /* T register */ +extern uint32 XR; /* X register */ +extern uint32 YR; /* Y register */ +extern uint32 E; /* E register */ +extern uint32 O; /* O register */ + +/* CPU state */ + +extern uint32 err_PC; +extern uint32 dms_enb; +extern uint32 dms_ump; +extern uint32 dms_sr; +extern uint32 dms_vr; +extern FLIP_FLOP mp_control; +extern uint32 mp_fence; +extern uint32 mp_viol; +extern FLIP_FLOP mp_mevff; +extern uint32 iop_sp; +extern t_bool ion_defer; +extern uint32 intaddr; +extern uint16 pcq [PCQ_SIZE]; +extern uint32 pcq_p; +extern uint32 stop_inst; +extern UNIT cpu_unit; +extern DEVICE cpu_dev; +extern REG cpu_reg []; +extern jmp_buf save_env; + + +/* CPU functions */ + +#define MP_ABORT(va) longjmp (save_env, (va)) + +extern t_stat resolve (uint32 MA, uint32 *addr, uint32 irq); +extern uint16 ReadPW (uint32 pa); +extern uint8 ReadB (uint32 va); +extern uint8 ReadBA (uint32 va); +extern uint16 ReadW (uint32 va); +extern uint16 ReadWA (uint32 va); +extern uint16 ReadIO (uint32 va, uint32 map); +extern void WritePW (uint32 pa, uint32 dat); +extern void WriteB (uint32 va, uint32 dat); +extern void WriteBA (uint32 va, uint32 dat); +extern void WriteW (uint32 va, uint32 dat); +extern void WriteWA (uint32 va, uint32 dat); +extern void WriteIO (uint32 va, uint32 dat, uint32 map); +extern t_stat iogrp (uint32 ir, uint32 iotrap); +extern uint32 calc_int (void); +extern t_bool calc_defer (void); +extern void mp_dms_jmp (uint32 va, uint32 plb); +extern uint16 dms_rmap (uint32 mapi); +extern void dms_wmap (uint32 mapi, uint32 dat); +extern void dms_viol (uint32 va, uint32 st); +extern uint32 dms_upd_vr (uint32 va); +extern uint32 dms_upd_sr (void); + +#endif diff --git a/HP2100/hp2100_cpu0.c b/HP2100/hp2100_cpu0.c index 2208707e..a7769901 100644 --- a/HP2100/hp2100_cpu0.c +++ b/HP2100/hp2100_cpu0.c @@ -1,271 +1,271 @@ -/* hp2100_cpu0.c: HP 1000 user microcode and unimplemented instruction set stubs - - Copyright (c) 2006-2012, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - CPU0 User microcode and unimplemented firmware options - - 09-May-12 JDB Separated assignments from conditional expressions - 04-Nov-10 JDB Removed DS note regarding PIF card (is now implemented) - 18-Sep-08 JDB .FLUN and self-tests for VIS and SIGNAL are NOP if not present - 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h - 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) - Added "user microcode" dispatcher for unclaimed instructions - 26-Feb-08 HV Removed and implemented "cpu_vis" and "cpu_signal" - 22-Nov-07 JDB Removed and implemented "cpu_rte_ema" - 12-Nov-07 JDB Removed and implemented "cpu_rte_vma" and "cpu_rte_os" - 01-Dec-06 JDB Removed and implemented "cpu_sis". - 26-Sep-06 JDB Created - - Primary references: - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - Macro/1000 Reference Manual (92059-90001, Dec-1992) - - Additional references are listed with the associated firmware - implementations, as are the HP option model numbers pertaining to the - applicable CPUs. - - - This file contains template simulations for the firmware options that have - not yet been implemented. When a given firmware option is implemented, it - should be moved out of this file and into another (or its own, depending on - complexity). - - It also contains a user-microprogram dispatcher to allow simulation of - site-specific firmware. All UIG instructions unclaimed by installed firmware - options are directed here and may be simulated by writing the appropriate - code. -*/ - - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - - -/* Distributed System. - - Distributed System firmware was provided with the HP 91740A DS/1000 product - for use with the HP 12771A (12665A) Serial Interface and 12773A Modem - Interface system interconnection kits. Firmware permitted high-speed - transfers with minimum impact to the processor. The advent of the - "intelligent" 12794A and 12825A HDLC cards, the 12793A and 12834A Bisync - cards, and the 91750A DS-1000/IV software obviated the need for CPU firmware, - as essentially the firmware was moved onto the I/O cards. - - Primary documentation for the DS instructions has not been located. However, - examination of the DS/1000 sources reveals that two instruction were used by - the DVA65 Serial Interface driver (91740-18071) and placed in the trap cells - of the communications interfaces. Presumably they handled interrupts from - the cards. - - Implementation of the DS instructions will also require simulation of the - 12665A Hardwired Serial Data Interface Card. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A 91740A 91740B 91740B - - The routines are mapped to instruction codes as follows: - - Instr. 1000-M 1000-E/F Description - ------ ------ -------- ---------------------------------------------- - 105520 105300 "Open loop" (trap cell handler) - 105521 105301 "Closed loop" (trap cell handler) - 105522 105302 [unknown] - [test] 105524 105304 [self test] - -- 105310 7974 boot loader ROM extension - - Notes: - - 1. The E/F-Series opcodes were moved from 105340-357 to 105300-317 at - revision 1813. - - 2. DS/1000 ROM data are available from Bitsavers. - - Additional references (documents unavailable): - - HP 91740A M-Series Distributed System (DS/1000) Firmware Installation - Manual (91740-90007). - - HP 91740B Distributed System (DS/1000) Firmware Installation Manual - (91740-90009). -*/ - -static const OP_PAT op_ds[16] = { - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N /* --- --- --- --- */ - }; - -t_stat cpu_ds (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -uint32 entry; - -entry = IR & 017; /* mask to entry point */ - -if (op_ds [entry] != OP_N) { - reason = cpu_ops (op_ds[entry], op, intrq); /* get instruction operands */ - if (reason != SCPE_OK) /* did the evaluation fail? */ - return reason; /* return the reason for failure */ - } - -switch (entry) { /* decode IR<3:0> */ - - default: /* others undefined */ - reason = stop_inst; - } - -return reason; -} - - -/* User firmware dispatcher. - - All UIG instructions unclaimed by installed firmware options are directed - here. User- or site-specific firmware may be simulated by dispatching to the - appropriate simulator routine. Unimplemented instructions should return - "stop_inst" to cause a simulator stop if enabled. - - Implementation notes: - - 1. This routine may be passed any opcode in the ranges 101400-101737 and - 105000-105737. The 10x740-777 range is dedicated to the EIG instructions - and is unavailable for user microprograms. - - 2. HP operating systems and subsystems depend on the following instructions - to execute as NOP and return success if the corresponding firmware is not - installed: - - 105226 -- Fast FORTRAN Processor .FLUN instruction - 105355 -- RTE-6/VM OS self-test instruction - 105477 -- Vector Instruction Set self-test - 105617 -- SIGNAL/1000 self-test - - These instructions are executed to determine firmware configuration - dynamically. If you use any of these opcodes for your own use, be aware - that certain HP programs may fail. - - 3. User microprograms occupied one or more firmware modules, each containing - 16 potential instruction entry points. A skeleton dispatcher for the 32 - possible modules is implemented below, along with a sample module. -*/ - -t_stat cpu_user (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; - -if (UNIT_CPU_TYPE == UNIT_TYPE_211X) /* 2116/15/14 CPU? */ - return stop_inst; /* user microprograms not supported */ - -switch (IR) { - case 0105226: /* firmware detection: FFP .FLUN */ - case 0105355: /* firmware detection: RTE-6/VM OS self-test */ - case 0105477: /* firmware detection: VIS self-test */ - case 0105617: /* firmware detection: SIGNAL/1000 self-test */ - return SCPE_OK; /* execute as NOP */ - } - -switch ((IR >> 4) & 037) { /* decode IR<8:4> */ - -/* case 000: ** 105000-105017 */ -/* return cpu_user_00 (IR, intrq); ** uncomment to handle instruction */ - -/* case 001: ** 105020-105037 */ -/* return cpu_user_01 (IR, intrq); ** uncomment to handle instruction */ - -/* case 0nn: ** other cases as needed */ -/* return cpu_user_nn (IR, intrq); ** uncomment to handle instruction */ - - case 020: /* 10x400-10x417 */ - return cpu_user_20 (IR, intrq); /* call sample dispatcher */ - -/* case 021: ** 10x420-10x437 */ -/* return cpu_user_21 (IR, intrq); ** uncomment to handle instruction */ - -/* case 0nn: ** other cases as needed */ -/* return cpu_user_nn (IR, intrq); ** uncomment to handle instruction */ - - default: /* others undefined */ - reason = stop_inst; - } - -return reason; -} - - -/* Example user microprogram simulator. - - User- or site-specific firmware may be simulated by writing the appropriate - code below. Unimplemented instructions should return "stop_inst" to cause a - simulator stop if enabled. - - For information on the operand patterns used in the "op_user" array, see the - comments preceding the "cpu_ops" routine in "hp2100_cpu1.c" and the "operand - processing encoding" constants in "hp2100_cpu1.h". -*/ - -static const OP_PAT op_user_20[16] = { - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N /* --- --- --- --- */ - }; - -t_stat cpu_user_20 (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -uint32 entry; - -entry = IR & 017; /* mask to entry point */ - -if (op_user_20 [entry] != OP_N) { - reason = cpu_ops (op_user_20 [entry], op, intrq); /* get instruction operands */ - if (reason != SCPE_OK) /* did the evaluation fail? */ - return reason; /* return the reason for failure */ - } - -switch (entry) { /* decode IR<4:0> */ - - case 000: /* 10x400 */ -/* break; ** uncomment to handle instruction */ - - case 001: /* 10x401 */ -/* break; ** uncomment to handle instruction */ - -/* case 0nn: ** other cases as needed */ -/* break; ** uncomment to handle instruction */ - - default: /* others undefined */ - reason = stop_inst; - } - -return reason; -} +/* hp2100_cpu0.c: HP 1000 user microcode and unimplemented instruction set stubs + + Copyright (c) 2006-2012, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + CPU0 User microcode and unimplemented firmware options + + 09-May-12 JDB Separated assignments from conditional expressions + 04-Nov-10 JDB Removed DS note regarding PIF card (is now implemented) + 18-Sep-08 JDB .FLUN and self-tests for VIS and SIGNAL are NOP if not present + 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h + 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) + Added "user microcode" dispatcher for unclaimed instructions + 26-Feb-08 HV Removed and implemented "cpu_vis" and "cpu_signal" + 22-Nov-07 JDB Removed and implemented "cpu_rte_ema" + 12-Nov-07 JDB Removed and implemented "cpu_rte_vma" and "cpu_rte_os" + 01-Dec-06 JDB Removed and implemented "cpu_sis". + 26-Sep-06 JDB Created + + Primary references: + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - Macro/1000 Reference Manual (92059-90001, Dec-1992) + + Additional references are listed with the associated firmware + implementations, as are the HP option model numbers pertaining to the + applicable CPUs. + + + This file contains template simulations for the firmware options that have + not yet been implemented. When a given firmware option is implemented, it + should be moved out of this file and into another (or its own, depending on + complexity). + + It also contains a user-microprogram dispatcher to allow simulation of + site-specific firmware. All UIG instructions unclaimed by installed firmware + options are directed here and may be simulated by writing the appropriate + code. +*/ + + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + + +/* Distributed System. + + Distributed System firmware was provided with the HP 91740A DS/1000 product + for use with the HP 12771A (12665A) Serial Interface and 12773A Modem + Interface system interconnection kits. Firmware permitted high-speed + transfers with minimum impact to the processor. The advent of the + "intelligent" 12794A and 12825A HDLC cards, the 12793A and 12834A Bisync + cards, and the 91750A DS-1000/IV software obviated the need for CPU firmware, + as essentially the firmware was moved onto the I/O cards. + + Primary documentation for the DS instructions has not been located. However, + examination of the DS/1000 sources reveals that two instruction were used by + the DVA65 Serial Interface driver (91740-18071) and placed in the trap cells + of the communications interfaces. Presumably they handled interrupts from + the cards. + + Implementation of the DS instructions will also require simulation of the + 12665A Hardwired Serial Data Interface Card. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A 91740A 91740B 91740B + + The routines are mapped to instruction codes as follows: + + Instr. 1000-M 1000-E/F Description + ------ ------ -------- ---------------------------------------------- + 105520 105300 "Open loop" (trap cell handler) + 105521 105301 "Closed loop" (trap cell handler) + 105522 105302 [unknown] + [test] 105524 105304 [self test] + -- 105310 7974 boot loader ROM extension + + Notes: + + 1. The E/F-Series opcodes were moved from 105340-357 to 105300-317 at + revision 1813. + + 2. DS/1000 ROM data are available from Bitsavers. + + Additional references (documents unavailable): + - HP 91740A M-Series Distributed System (DS/1000) Firmware Installation + Manual (91740-90007). + - HP 91740B Distributed System (DS/1000) Firmware Installation Manual + (91740-90009). +*/ + +static const OP_PAT op_ds[16] = { + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N /* --- --- --- --- */ + }; + +t_stat cpu_ds (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +uint32 entry; + +entry = IR & 017; /* mask to entry point */ + +if (op_ds [entry] != OP_N) { + reason = cpu_ops (op_ds[entry], op, intrq); /* get instruction operands */ + if (reason != SCPE_OK) /* did the evaluation fail? */ + return reason; /* return the reason for failure */ + } + +switch (entry) { /* decode IR<3:0> */ + + default: /* others undefined */ + reason = stop_inst; + } + +return reason; +} + + +/* User firmware dispatcher. + + All UIG instructions unclaimed by installed firmware options are directed + here. User- or site-specific firmware may be simulated by dispatching to the + appropriate simulator routine. Unimplemented instructions should return + "stop_inst" to cause a simulator stop if enabled. + + Implementation notes: + + 1. This routine may be passed any opcode in the ranges 101400-101737 and + 105000-105737. The 10x740-777 range is dedicated to the EIG instructions + and is unavailable for user microprograms. + + 2. HP operating systems and subsystems depend on the following instructions + to execute as NOP and return success if the corresponding firmware is not + installed: + + 105226 -- Fast FORTRAN Processor .FLUN instruction + 105355 -- RTE-6/VM OS self-test instruction + 105477 -- Vector Instruction Set self-test + 105617 -- SIGNAL/1000 self-test + + These instructions are executed to determine firmware configuration + dynamically. If you use any of these opcodes for your own use, be aware + that certain HP programs may fail. + + 3. User microprograms occupied one or more firmware modules, each containing + 16 potential instruction entry points. A skeleton dispatcher for the 32 + possible modules is implemented below, along with a sample module. +*/ + +t_stat cpu_user (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; + +if (UNIT_CPU_TYPE == UNIT_TYPE_211X) /* 2116/15/14 CPU? */ + return stop_inst; /* user microprograms not supported */ + +switch (IR) { + case 0105226: /* firmware detection: FFP .FLUN */ + case 0105355: /* firmware detection: RTE-6/VM OS self-test */ + case 0105477: /* firmware detection: VIS self-test */ + case 0105617: /* firmware detection: SIGNAL/1000 self-test */ + return SCPE_OK; /* execute as NOP */ + } + +switch ((IR >> 4) & 037) { /* decode IR<8:4> */ + +/* case 000: ** 105000-105017 */ +/* return cpu_user_00 (IR, intrq); ** uncomment to handle instruction */ + +/* case 001: ** 105020-105037 */ +/* return cpu_user_01 (IR, intrq); ** uncomment to handle instruction */ + +/* case 0nn: ** other cases as needed */ +/* return cpu_user_nn (IR, intrq); ** uncomment to handle instruction */ + + case 020: /* 10x400-10x417 */ + return cpu_user_20 (IR, intrq); /* call sample dispatcher */ + +/* case 021: ** 10x420-10x437 */ +/* return cpu_user_21 (IR, intrq); ** uncomment to handle instruction */ + +/* case 0nn: ** other cases as needed */ +/* return cpu_user_nn (IR, intrq); ** uncomment to handle instruction */ + + default: /* others undefined */ + reason = stop_inst; + } + +return reason; +} + + +/* Example user microprogram simulator. + + User- or site-specific firmware may be simulated by writing the appropriate + code below. Unimplemented instructions should return "stop_inst" to cause a + simulator stop if enabled. + + For information on the operand patterns used in the "op_user" array, see the + comments preceding the "cpu_ops" routine in "hp2100_cpu1.c" and the "operand + processing encoding" constants in "hp2100_cpu1.h". +*/ + +static const OP_PAT op_user_20[16] = { + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N /* --- --- --- --- */ + }; + +t_stat cpu_user_20 (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +uint32 entry; + +entry = IR & 017; /* mask to entry point */ + +if (op_user_20 [entry] != OP_N) { + reason = cpu_ops (op_user_20 [entry], op, intrq); /* get instruction operands */ + if (reason != SCPE_OK) /* did the evaluation fail? */ + return reason; /* return the reason for failure */ + } + +switch (entry) { /* decode IR<4:0> */ + + case 000: /* 10x400 */ +/* break; ** uncomment to handle instruction */ + + case 001: /* 10x401 */ +/* break; ** uncomment to handle instruction */ + +/* case 0nn: ** other cases as needed */ +/* break; ** uncomment to handle instruction */ + + default: /* others undefined */ + reason = stop_inst; + } + +return reason; +} diff --git a/HP2100/hp2100_cpu1.c b/HP2100/hp2100_cpu1.c index 4bfa632e..54911fe3 100644 --- a/HP2100/hp2100_cpu1.c +++ b/HP2100/hp2100_cpu1.c @@ -1,928 +1,1008 @@ -/* hp2100_cpu1.c: HP 2100/1000 EAU simulator and UIG dispatcher - - Copyright (c) 2005-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - CPU1 Extended arithmetic and optional microcode dispatchers - - 24-Dec-14 JDB Added casts for explicit downward conversions - 05-Apr-14 JDB Corrected typo in comments for cpu_ops - 09-May-12 JDB Separated assignments from conditional expressions - 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h - 05-Sep-08 JDB Moved option-present tests to UIG dispatchers - Call "user microcode" dispatcher for unclaimed UIG instructions - 20-Apr-08 JDB Fixed VIS and SIGNAL to depend on the FPP and HAVE_INT64 - 28-Nov-07 JDB Added fprint_ops, fprint_regs for debug printouts - 17-Nov-07 JDB Enabled DIAG as NOP on 1000 F-Series - 04-Jan-07 JDB Added special DBI dispatcher for non-INT64 diagnostic - 29-Dec-06 JDB Allows RRR as NOP if 2114 (diag config test) - 01-Dec-06 JDB Substitutes FPP for firmware FP if HAVE_INT64 - 16-Oct-06 JDB Generalized operands for F-Series FP types - 26-Sep-06 JDB Split hp2100_cpu1.c into multiple modules to simplify extensions - Added iotrap parameter to UIG dispatchers for RTE microcode - 22-Feb-05 JDB Removed EXECUTE instruction (is NOP in actual microcode) - 21-Jan-05 JDB Reorganized CPU option and operand processing flags - Split code along microcode modules - 15-Jan-05 RMS Cloned from hp2100_cpu.c - - Primary references: - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - Macro/1000 Reference Manual (92059-90001, Dec-1992) - - HP 93585A Double Integer Firmware Package Installation and Programming - Manual (93585-90007, Feb-1984) - - Additional references are listed with the associated firmware - implementations, as are the HP option model numbers pertaining to the - applicable CPUs. - - - This source file contains the Extended Arithmetic Unit simulator and the User - Instruction Group (a.k.a. "Macro") dispatcher for the 2100 and 1000 (21MX) - CPUs. The UIG simulators reside in separate source files, due to the large - number of firmware options available for these machines. Unit flags indicate - which options are present in the current system. - - This module also provides generalized instruction operand processing. - - The 2100 and 1000 machines were microprogrammable; the 2116/15/14 machines - were not. Both user- and HP-written microprograms were supported. The - microcode address space of the 2100 encompassed four modules of 256 words - each. The 1000 M-series expanded that to sixteen modules, and the 1000 - E/F-series expanded that still further to sixty-four modules. Each CPU had - its own microinstruction set, although the micromachines of the various 1000 - models were similar internally. - - The UIG instructions were divided into ranges assigned to HP firmware - options, reserved for future HP use, and reserved for user microprograms. - User microprograms could occupy any range not already used on a given - machine, but in practice, some effort was made to avoid the HP-reserved - ranges. - - User microprogram simulation is supported by routing any UIG instruction not - allocated to an installed firmware option to a user-firmware dispatcher. - Site-specific microprograms may be simulated there. In the absence of such a - simulation, an unimplemented instruction stop will occur. - - Regarding option instruction sets, there was some commonality across CPU - types. EAU instructions were identical across all models, and the floating - point set was the same on the 2100 and 1000. Other options implemented - proper instruction supersets (e.g., the Fast FORTRAN Processor from 2100 to - 1000-M to 1000-E to 1000-F) or functional equivalence with differing code - points (the 2000 I/O Processor from 2100 to 1000, and the extended-precision - floating-point instructions from 1000-E to 1000-F). - - The 2100 decoded the EAU and UIG sets separately in hardware and supported - only the UIG 0 code points. Bits 7-4 of a UIG instruction decoded one of - sixteen entry points in the lowest-numbered module after module 0. Those - entry points could be used directly (as for the floating-point instructions), - or additional decoding based on bits 3-0 could be implemented. - - The 1000 generalized the instruction decoding to a series of microcoded - jumps, based on the bits in the instruction. Bits 15-8 indicated the group - of the current instruction: EAU (200, 201, 202, 210, and 211), UIG 0 (212), - or UIG 1 (203 and 213). UIG 0, UIG 1, and some EAU instructions were decoded - further by selecting one of sixteen modules within the group via bits 7-4. - Finally, each UIG module decoded up to sixteen instruction entry points via - bits 3-0. Jump tables for all firmware options were contained in the base - set, so modules needed only to be concerned with decoding their individual - entry points within the module. - - While the 2100 and 1000 hardware decoded these instruction sets differently, - the decoding mechanism of the simulation follows that of the 1000 E/F-series. - Where needed, CPU type- or model-specific behavior is simulated. - - The design of the 1000 microinstruction set was such that executing an - instruction for which no microcode was present (e.g., executing a FFP - instruction when the FFP firmware was not installed) resulted in a NOP. - Under simulation, such execution causes an undefined instruction stop if - "stop_inst" is non-zero and a NOP otherwise. -*/ - - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - - -/* EAU - - The Extended Arithmetic Unit (EAU) adds ten instructions with double-word - operands, including multiply, divide, shifts, and rotates. Option - implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A 12579A 12579A std std std std - - The instruction codes are mapped to routines as follows: - - Instr. Bits - Code 15-8 7-4 2116 2100 1000-M 1000-E 1000-F Note - ------ ---- --- ------ ------ ------ ------ ------ --------------------- - 100000 200 00 [diag] [diag] [self test] - 100020 200 01 ASL ASL ASL ASL ASL Bits 3-0 encode shift - 100040 200 02 LSL LSL LSL LSL LSL Bits 3-0 encode shift - 100060 200 03 TIMER TIMER [deterministic delay] - 100100 200 04 RRL RRL RRL RRL RRL Bits 3-0 encode shift - 100200 200 10 MPY MPY MPY MPY MPY - 100400 201 xx DIV DIV DIV DIV DIV - 101020 202 01 ASR ASR ASR ASR ASR Bits 3-0 encode shift - 101040 202 02 LSR LSR LSR LSR LSR Bits 3-0 encode shift - 101100 202 04 RRR RRR RRR RRR RRR Bits 3-0 encode shift - 104200 210 xx DLD DLD DLD DLD DLD - 104400 211 xx DST DST DST DST DST - - The remaining codes for bits 7-4 are undefined and will cause a simulator - stop if enabled. On a real 1000-M, all undefined instructions in the 200 - group decode as MPY, and all in the 202 group decode as NOP. On a real - 1000-E, instruction patterns 200/05 through 200/07 and 202/03 decode as NOP; - all others cause erroneous execution. - - EAU instruction decoding on the 1000 M-series is convoluted. The JEAU - microorder maps IR bits 11, 9-7 and 5-4 to bits 2-0 of the microcode jump - address. The map is detailed on page IC-84 of the ERD. - - The 1000 E/F-series add two undocumented instructions to the 200 group: TIMER - and DIAG. These are described in the ERD on page IA 5-5, paragraph 5-7. The - M-series executes these as MPY and RRL, respectively. A third instruction, - EXECUTE (100120), is also described but was never implemented, and the - E/F-series microcode execute a NOP for this instruction code. - - Notes: - - 1. Under simulation, TIMER, DIAG, and EXECUTE cause undefined instruction - stops if the CPU is set to 21xx. DIAG and EXECUTE also cause stops on - the 1000-M. TIMER does not, because it is used by several HP programs - to differentiate between M- and E/F-series machines. - - 2. DIAG is not implemented under simulation. On the E/F, it performs a - destructive test of all installed memory. Because of this, it is only - functional if the machine is halted, i.e., if the instruction is - executed with the INSTR STEP button. If it is executed in a program, - the result is NOP. - - 3. RRR is permitted and executed as NOP if the CPU is a 2114, as the - presence of the EAU is tested by the diagnostic configurator to - differentiate between 2114 and 2100/1000 CPUs. -*/ - -t_stat cpu_eau (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -uint32 rs, qs, sc, v1, v2, t; -int32 sop1, sop2; - -if ((cpu_unit.flags & UNIT_EAU) == 0) /* option installed? */ - if ((UNIT_CPU_MODEL == UNIT_2114) && (IR == 0101100)) /* 2114 and RRR 16? */ - return SCPE_OK; /* allowed as NOP */ - else - return stop_inst; /* fail */ - -switch ((IR >> 8) & 0377) { /* decode IR<15:8> */ - - case 0200: /* EAU group 0 */ - switch ((IR >> 4) & 017) { /* decode IR<7:4> */ - - case 000: /* DIAG 100000 */ - if ((UNIT_CPU_MODEL != UNIT_1000_E) && /* must be 1000 E-series */ - (UNIT_CPU_MODEL != UNIT_1000_F)) /* or 1000 F-series */ - return stop_inst; /* trap if not */ - break; /* DIAG is NOP unless halted */ - - case 001: /* ASL 100020-100037 */ - sc = (IR & 017)? (IR & 017): 16; /* get sc */ - O = 0; /* clear ovflo */ - while (sc-- != 0) { /* bit by bit */ - t = BR << 1; /* shift B */ - BR = (BR & SIGN) | (t & 077777) | (AR >> 15); - AR = (AR << 1) & DMASK; - if ((BR ^ t) & SIGN) O = 1; - } - break; - - case 002: /* LSL 100040-100057 */ - sc = (IR & 017)? (IR & 017): 16; /* get sc */ - BR = ((BR << sc) | (AR >> (16 - sc))) & DMASK; - AR = (AR << sc) & DMASK; /* BR'AR lsh left */ - break; - - case 003: /* TIMER 100060 */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ - return stop_inst; /* trap if not */ - if (UNIT_CPU_MODEL == UNIT_1000_M) /* 1000 M-series? */ - goto MPY; /* decode as MPY */ - BR = (BR + 1) & DMASK; /* increment B */ - if (BR) PC = err_PC; /* if !=0, repeat */ - break; - - case 004: /* RRL 100100-100117 */ - sc = (IR & 017)? (IR & 017): 16; /* get sc */ - t = BR; /* BR'AR rot left */ - BR = ((BR << sc) | (AR >> (16 - sc))) & DMASK; - AR = ((AR << sc) | (t >> (16 - sc))) & DMASK; - break; - - case 010: /* MPY 100200 (OP_K) */ - MPY: - reason = cpu_ops (OP_K, op, intrq); /* get operand */ - if (reason == SCPE_OK) { /* successful eval? */ - sop1 = SEXT (AR); /* sext AR */ - sop2 = SEXT (op[0].word); /* sext mem */ - sop1 = sop1 * sop2; /* signed mpy */ - BR = (sop1 >> 16) & DMASK; /* to BR'AR */ - AR = sop1 & DMASK; - O = 0; /* no overflow */ - } - break; - - default: /* others undefined */ - return stop_inst; - } - - break; - - case 0201: /* DIV 100400 (OP_K) */ - reason = cpu_ops (OP_K, op, intrq); /* get operand */ - if (reason != SCPE_OK) /* eval failed? */ - break; - rs = qs = BR & SIGN; /* save divd sign */ - if (rs) { /* neg? */ - AR = (~AR + 1) & DMASK; /* make B'A pos */ - BR = (~BR + (AR == 0)) & DMASK; /* make divd pos */ - } - v2 = op[0].word; /* divr = mem */ - if (v2 & SIGN) { /* neg? */ - v2 = (~v2 + 1) & DMASK; /* make divr pos */ - qs = qs ^ SIGN; /* sign of quotient */ - } - if (BR >= v2) O = 1; /* divide work? */ - else { /* maybe... */ - O = 0; /* assume ok */ - v1 = (BR << 16) | AR; /* 32b divd */ - AR = (v1 / v2) & DMASK; /* quotient */ - BR = (v1 % v2) & DMASK; /* remainder */ - if (AR) { /* quotient > 0? */ - if (qs) AR = (~AR + 1) & DMASK; /* apply quo sign */ - if ((AR ^ qs) & SIGN) O = 1; /* still wrong? ovflo */ - } - if (rs) BR = (~BR + 1) & DMASK; /* apply rem sign */ - } - break; - - case 0202: /* EAU group 2 */ - switch ((IR >> 4) & 017) { /* decode IR<7:4> */ - - case 001: /* ASR 101020-101037 */ - sc = (IR & 017)? (IR & 017): 16; /* get sc */ - AR = ((BR << (16 - sc)) | (AR >> sc)) & DMASK; - BR = (SEXT (BR) >> sc) & DMASK; /* BR'AR ash right */ - O = 0; - break; - - case 002: /* LSR 101040-101057 */ - sc = (IR & 017)? (IR & 017): 16; /* get sc */ - AR = ((BR << (16 - sc)) | (AR >> sc)) & DMASK; - BR = BR >> sc; /* BR'AR log right */ - break; - - case 004: /* RRR 101100-101117 */ - sc = (IR & 017)? (IR & 017): 16; /* get sc */ - t = AR; /* BR'AR rot right */ - AR = ((AR >> sc) | (BR << (16 - sc))) & DMASK; - BR = ((BR >> sc) | (t << (16 - sc))) & DMASK; - break; - - default: /* others undefined */ - return stop_inst; - } - - break; - - case 0210: /* DLD 104200 (OP_D) */ - reason = cpu_ops (OP_D, op, intrq); /* get operand */ - if (reason == SCPE_OK) { /* successful eval? */ - AR = (op[0].dword >> 16) & DMASK; /* load AR */ - BR = op[0].dword & DMASK; /* load BR */ - } - break; - - case 0211: /* DST 104400 (OP_A) */ - reason = cpu_ops (OP_A, op, intrq); /* get operand */ - if (reason == SCPE_OK) { /* successful eval? */ - WriteW (op[0].word, AR); /* store AR */ - WriteW ((op[0].word + 1) & VAMASK, BR); /* store BR */ - } - break; - - default: /* should never get here */ - return SCPE_IERR; /* bad call from cpu_instr */ - } - -return reason; -} - - -/* UIG 0 - - The first User Instruction Group (UIG) encodes firmware options for the 2100 - and 1000. Instruction codes 105000-105377 are assigned to microcode options - as follows: - - Instructions Option Name 2100 1000-M 1000-E 1000-F - ------------- -------------------------- ------ ------ ------ ------ - 105000-105362 2000 I/O Processor opt - - - - 105000-105137 Floating Point opt std std std - 105200-105237 Fast FORTRAN Processor opt opt opt std - 105240-105257 RTE-IVA/B Extended Memory - - opt opt - 105240-105257 RTE-6/VM Virtual Memory - - opt opt - 105300-105317 Distributed System - - opt opt - 105320-105337 Double Integer - - opt - - 105320-105337 Scientific Instruction Set - - - std - 105340-105357 RTE-6/VM Operating System - - opt opt - - If the 2100 IOP is installed, the only valid UIG instructions are IOP - instructions, as the IOP used the full 2100 microcode addressing space. The - IOP dispatcher remaps the 2100 codes to 1000 codes for execution. - - The F-Series moved the three-word extended real instructions from the FFP - range to the base floating-point range and added four-word double real and - two-word double integer instructions. The double integer instructions - occupied some of the vacated extended real instruction codes in the FFP, with - the rest assigned to the floating-point range. Consequently, many - instruction codes for the F-Series are different from the E-Series. - - Implementation notes: - - 1. Product 93585A, available from the "Specials" group, added double integer - microcode to the E-Series. The instruction codes were different from - those in the F-Series to avoid conflicting with the E-Series FFP. - - 2. To run the double-integer instructions diagnostic in the absence of - 64-bit integer support (and therefore of F-Series simulation), a special - DBI dispatcher may be enabled by defining ENABLE_DIAG during compilation. - This dispatcher will remap the F-Series DBI instructions to the E-Series - codes, so that the F-Series diagnostic may be run. Because several of - the F-Series DBI instruction codes replace M/E-Series FFP codes, this - dispatcher will only operate if FFP is disabled. - - Note that enabling the dispatcher will produce non-standard FP behavior. - For example, any code in the range 105000-105017 normally would execute a - FAD instruction. With the dispatcher enabled, 105014 would execute a - .DAD, while the other codes would execute a FAD. Therefore, ENABLE_DIAG - should only be used to run the diagnostic and is not intended for general - use. - - 3. Any instruction not claimed by an installed option will be sent to the - user microcode dispatcher. -*/ - -t_stat cpu_uig_0 (uint32 IR, uint32 intrq, uint32 iotrap) -{ -if ((cpu_unit.flags & UNIT_IOP) && /* I/O Processor? */ - (UNIT_CPU_TYPE == UNIT_TYPE_2100)) /* and 2100 CPU? */ - return cpu_iop (IR, intrq); /* dispatch to IOP */ - - -#if !defined (HAVE_INT64) && defined (ENABLE_DIAG) /* special DBI diagnostic dispatcher */ - -if (((cpu_unit.flags & UNIT_FFP) == 0) && /* FFP absent? */ - (cpu_unit.flags & UNIT_DBI)) /* and DBI present? */ - switch (IR & 0377) { - case 0014: /* .DAD 105014 */ - return cpu_dbi (0105321, intrq); - - case 0034: /* .DSB 105034 */ - return cpu_dbi (0105327, intrq); - - case 0054: /* .DMP 105054 */ - return cpu_dbi (0105322, intrq); - - case 0074: /* .DDI 105074 */ - return cpu_dbi (0105325, intrq); - - case 0114: /* .DSBR 105114 */ - return cpu_dbi (0105334, intrq); - - case 0134: /* .DDIR 105134 */ - return cpu_dbi (0105326, intrq); - - case 0203: /* .DNG 105203 */ - return cpu_dbi (0105323, intrq); - - case 0204: /* .DCO 105204 */ - return cpu_dbi (0105324, intrq); - - case 0210: /* .DIN 105210 */ - return cpu_dbi (0105330, intrq); - - case 0211: /* .DDE 105211 */ - return cpu_dbi (0105331, intrq); - - case 0212: /* .DIS 105212 */ - return cpu_dbi (0105332, intrq); - - case 0213: /* .DDS 105213 */ - return cpu_dbi (0105333, intrq); - } /* otherwise, continue */ - -#endif /* end of special DBI dispatcher */ - - -switch ((IR >> 4) & 017) { /* decode IR<7:4> */ - - case 000: /* 105000-105017 */ - case 001: /* 105020-105037 */ - case 002: /* 105040-105057 */ - case 003: /* 105060-105077 */ - case 004: /* 105100-105117 */ - case 005: /* 105120-105137 */ - if (cpu_unit.flags & UNIT_FP) /* FP option installed? */ -#if defined (HAVE_INT64) /* int64 support available */ - return cpu_fpp (IR, intrq); /* Floating Point Processor */ -#else /* int64 support unavailable */ - return cpu_fp (IR, intrq); /* Firmware Floating Point */ -#endif /* end of int64 support */ - else - break; - - case 010: /* 105200-105217 */ - case 011: /* 105220-105237 */ - if (cpu_unit.flags & UNIT_FFP) /* FFP option installed? */ - return cpu_ffp (IR, intrq); /* Fast FORTRAN Processor */ - else - break; - - case 012: /* 105240-105257 */ - if (cpu_unit.flags & UNIT_VMAOS) /* VMA/OS option installed? */ - return cpu_rte_vma (IR, intrq); /* RTE-6 VMA */ - else if (cpu_unit.flags & UNIT_EMA) /* EMA option installed? */ - return cpu_rte_ema (IR, intrq); /* RTE-4 EMA */ - else - break; - - case 014: /* 105300-105317 */ - if (cpu_unit.flags & UNIT_DS) /* DS option installed? */ - return cpu_ds (IR, intrq); /* Distributed System */ - else - break; - - case 015: /* 105320-105337 */ -#if defined (HAVE_INT64) /* int64 support available */ - if (UNIT_CPU_MODEL == UNIT_1000_F) /* F-series? */ - return cpu_sis (IR, intrq); /* Scientific Instruction is standard */ - else /* M/E-series */ -#endif /* end of int64 support */ - if (cpu_unit.flags & UNIT_DBI) /* DBI option installed? */ - return cpu_dbi (IR, intrq); /* Double integer */ - else - break; - - case 016: /* 105340-105357 */ - if (cpu_unit.flags & UNIT_VMAOS) /* VMA/OS option installed? */ - return cpu_rte_os (IR, intrq, iotrap); /* RTE-6 OS */ - else - break; - } - -return cpu_user (IR, intrq); /* try user microcode */ -} - - -/* UIG 1 - - The second User Instruction Group (UIG) encodes firmware options for the - 1000. Instruction codes 101400-101777 and 105400-105777 are assigned to - microcode options as follows ("x" is "1" or "5" below): - - Instructions Option Name 1000-M 1000-E 1000-F - ------------- ---------------------------- ------ ------ ------ - 10x400-10x437 2000 IOP opt opt opt - 10x460-10x477 2000 IOP opt opt opt - 10x460-10x477 Vector Instruction Set - - opt - 10x520-10x537 Distributed System opt - - - 10x600-10x617 SIGNAL/1000 Instruction Set - - opt - 10x700-10x737 Dynamic Mapping System opt opt std - 10x740-10x777 Extended Instruction Group std std std - - Only 1000 systems execute these instructions. - - Implementation notes: - - 1. The Distributed System (DS) microcode was mapped to different instruction - ranges for the M-Series and the E/F-Series. The sequence of instructions - was identical, though, so we remap the former range to the latter before - dispatching. - - 2. Any instruction not claimed by an installed option will be sent to the - user microcode dispatcher. -*/ - -t_stat cpu_uig_1 (uint32 IR, uint32 intrq, uint32 iotrap) -{ -if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* 1000 execution? */ - return stop_inst; /* no, so trap */ - -switch ((IR >> 4) & 017) { /* decode IR<7:4> */ - - case 000: /* 105400-105417 */ - case 001: /* 105420-105437 */ - if (cpu_unit.flags & UNIT_IOP) /* IOP option installed? */ - return cpu_iop (IR, intrq); /* 2000 I/O Processor */ - else - break; - - case 003: /* 105460-105477 */ -#if defined (HAVE_INT64) /* int64 support available */ - if (cpu_unit.flags & UNIT_VIS) /* VIS option installed? */ - return cpu_vis (IR, intrq); /* Vector Instruction Set */ - else -#endif /* end of int64 support */ - if (cpu_unit.flags & UNIT_IOP) /* IOP option installed? */ - return cpu_iop (IR, intrq); /* 2000 I/O Processor */ - else - break; - - case 005: /* 105520-105537 */ - if (cpu_unit.flags & UNIT_DS) { /* DS option installed? */ - IR = IR ^ 0000620; /* remap to 105300-105317 */ - return cpu_ds (IR, intrq); /* Distributed System */ - } - else - break; - -#if defined (HAVE_INT64) /* int64 support available */ - case 010: /* 105600-105617 */ - if (cpu_unit.flags & UNIT_SIGNAL) /* SIGNAL option installed? */ - return cpu_signal (IR, intrq); /* SIGNAL/1000 Instructions */ - else - break; -#endif /* end of int64 support */ - - case 014: /* 105700-105717 */ - case 015: /* 105720-105737 */ - if (cpu_unit.flags & UNIT_DMS) /* DMS option installed? */ - return cpu_dms (IR, intrq); /* Dynamic Mapping System */ - else - break; - - case 016: /* 105740-105757 */ - case 017: /* 105760-105777 */ - return cpu_eig (IR, intrq); /* Extended Instruction Group */ - } - -return cpu_user (IR, intrq); /* try user microcode */ -} - - -/* Read a multiple-precision operand value. */ - -OP ReadOp (uint32 va, OPSIZE precision) -{ -OP operand; -uint32 i; - -if (precision == in_s) - operand.word = ReadW (va); /* read single integer */ - -else if (precision == in_d) - operand.dword = ReadW (va) << 16 | /* read double integer */ - ReadW ((va + 1) & VAMASK); /* merge high and low words */ - -else - for (i = 0; i < (uint32) precision; i++) { /* read fp 2 to 5 words */ - operand.fpk[i] = ReadW (va); - va = (va + 1) & VAMASK; - } -return operand; -} - -/* Write a multiple-precision operand value. */ - -void WriteOp (uint32 va, OP operand, OPSIZE precision) -{ -uint32 i; - -if (precision == in_s) - WriteW (va, operand.word); /* write single integer */ - -else if (precision == in_d) { - WriteW (va, (operand.dword >> 16) & DMASK); /* write double integer */ - WriteW ((va + 1) & VAMASK, operand.dword & DMASK); /* high word, then low word */ - } - -else - for (i = 0; i < (uint32) precision; i++) { /* write fp 2 to 5 words */ - WriteW (va, operand.fpk[i]); - va = (va + 1) & VAMASK; - } -return; -} - - -/* Get instruction operands. - - Operands for a given instruction are specifed by an "operand pattern" - consisting of flags indicating the types and storage methods. The pattern - directs how each operand is to be retrieved and whether the operand value or - address is returned in the operand array. - - Typically, a microcode simulation handler will define an OP_PAT array, with - each element containing an operand pattern corresponding to the simulated - instruction. Operand patterns are defined in the header file accompanying - this source file. After calling this function with the appropriate operand - pattern and a pointer to an array of OPs, operands are decoded and stored - sequentially in the array. - - The following operand encodings are defined: - - Code Operand Description Example Return - ------ ---------------------------------------- ----------- ------------ - OP_NUL No operand present [inst] None - - OP_IAR Integer constant in A register LDA I Value of I - [inst] - ... - I DEC 0 - - OP_JAB Double integer constant in A/B registers DLD J Value of J - [inst] - ... - J DEC 0,0 - - OP_FAB 2-word FP constant in A/B registers DLD F Value of F - [inst] - ... - F DEC 0.0 - - OP_CON Inline 1-word constant [inst] Value of C - C DEC 0 - ... - - OP_VAR Inline 1-word variable [inst] Address of V - V BSS 1 - ... - - OP_ADR Inline address [inst] Address of A - DEF A - ... - A EQU * - - OP_ADK Address of integer constant [inst] Value of K - DEF K - ... - K DEC 0 - - OP_ADD Address of double integer constant [inst] Value of D - DEF D - ... - D DEC 0,0 - - OP_ADF Address of 2-word FP constant [inst] Value of F - DEF F - ... - F DEC 0.0 - - OP_ADX Address of 3-word FP constant [inst] Value of X - DEF X - ... - X DEX 0.0 - - OP_ADT Address of 4-word FP constant [inst] Value of T - DEF T - ... - T DEY 0.0 - - OP_ADE Address of 5-word FP constant [inst] Value of E - DEF E - ... - E DEC 0,0,0,0,0 - - Address operands, i.e., those having a DEF to the operand, will be resolved - to direct addresses. If an interrupt is pending and more than three levels - of indirection are used, the routine returns without completing operand - retrieval (the instruction will be retried after interrupt servicing). - Addresses are always resolved in the current DMS map. - - An operand pattern consists of one or more operand encodings, corresponding - to the operands required by a given instruction. Values are returned in - sequence to the operand array. -*/ - -t_stat cpu_ops (OP_PAT pattern, OPS op, uint32 irq) -{ -t_stat reason = SCPE_OK; -OP_PAT flags; -uint32 i, MA; - -for (i = 0; i < OP_N_F; i++) { - flags = pattern & OP_M_FLAGS; /* get operand pattern */ - - if (flags >= OP_ADR) { /* address operand? */ - reason = resolve (ReadW (PC), &MA, irq); /* resolve indirects */ - if (reason != SCPE_OK) /* resolution failed? */ - return reason; - } - - switch (flags) { - case OP_NUL: /* null operand */ - return reason; /* no more, so quit */ - - case OP_IAR: /* int in A */ - (*op++).word = AR; /* get one-word value */ - break; - - case OP_JAB: /* dbl-int in A/B */ - (*op++).dword = (AR << 16) | BR; /* get two-word value */ - break; - - case OP_FAB: /* 2-word FP in A/B */ - (*op).fpk[0] = AR; /* get high FP word */ - (*op++).fpk[1] = BR; /* get low FP word */ - break; - - case OP_CON: /* inline constant operand */ - *op++ = ReadOp (PC, in_s); /* get value */ - break; - - case OP_VAR: /* inline variable operand */ - (*op++).word = (uint16) PC; /* get pointer to variable */ - break; - - case OP_ADR: /* inline address operand */ - (*op++).word = (uint16) MA; /* get address (set by "resolve" above) */ - break; - - case OP_ADK: /* address of int constant */ - *op++ = ReadOp (MA, in_s); /* get value */ - break; - - case OP_ADD: /* address of dbl-int constant */ - *op++ = ReadOp (MA, in_d); /* get value */ - break; - - case OP_ADF: /* address of 2-word FP const */ - *op++ = ReadOp (MA, fp_f); /* get value */ - break; - - case OP_ADX: /* address of 3-word FP const */ - *op++ = ReadOp (MA, fp_x); /* get value */ - break; - - case OP_ADT: /* address of 4-word FP const */ - *op++ = ReadOp (MA, fp_t); /* get value */ - break; - - case OP_ADE: /* address of 5-word FP const */ - *op++ = ReadOp (MA, fp_e); /* get value */ - break; - - default: - return SCPE_IERR; /* not implemented */ - } - - if (flags >= OP_CON) /* operand after instruction? */ - PC = (PC + 1) & VAMASK; /* yes, so bump to next */ - pattern = pattern >> OP_N_FLAGS; /* move next pattern into place */ - } -return reason; -} - - -/* Print operands to the debug device. - - The values of an operand array are printed to the debug device. The types of - the operands are specified by an operand pattern. Typically, the operand - pattern is the same one that was used to fill the array originally. -*/ - -void fprint_ops (OP_PAT pattern, OPS op) -{ -OP_PAT flags; -uint32 i; - -for (i = 0; i < OP_N_F; i++) { - flags = pattern & OP_M_FLAGS; /* get operand pattern */ - - switch (flags) { - case OP_NUL: /* null operand */ - return; /* no more, so quit */ - - case OP_IAR: /* int in A */ - case OP_CON: /* inline constant operand */ - case OP_VAR: /* inline variable operand */ - case OP_ADR: /* inline address operand */ - case OP_ADK: /* address of int constant */ - fprintf (sim_deb, - ", op[%d] = %06o", - i, op[i].word); - break; - - case OP_JAB: /* dbl-int in A/B */ - case OP_ADD: /* address of dbl-int constant */ - fprintf (sim_deb, - ", op[%d] = %011o", - i, op[i].dword); - break; - - case OP_FAB: /* 2-word FP in A/B */ - case OP_ADF: /* address of 2-word FP const */ - fprintf (sim_deb, - ", op[%d] = (%06o, %06o)", - i, op[i].fpk[0], op[i].fpk[1]); - break; - - case OP_ADX: /* address of 3-word FP const */ - fprintf (sim_deb, - ", op[%d] = (%06o, %06o, %06o)", - i, op[i].fpk[0], op[i].fpk[1], - op[i].fpk[2]); - break; - - case OP_ADT: /* address of 4-word FP const */ - fprintf (sim_deb, - ", op[%d] = (%06o, %06o, %06o, %06o)", - i, op[i].fpk[0], op[i].fpk[1], - op[i].fpk[2], op[i].fpk[3]); - break; - - case OP_ADE: /* address of 5-word FP const */ - fprintf (sim_deb, - ", op[%d] = (%06o, %06o, %06o, %06o, %06o)", - i, op[i].fpk[0], op[i].fpk[1], - op[i].fpk[2], op[i].fpk[3], op[i].fpk[4]); - break; - - default: - fprintf (sim_deb, "UNKNOWN OPERAND TYPE"); /* not implemented */ - } - - pattern = pattern >> OP_N_FLAGS; /* move next pattern into place */ - } -} - - -/* Print CPU registers to the debug device. - - One or more CPU registers may be printed to the debug output device, which - must be valid before calling. -*/ - -void fprint_regs (char *caption, uint32 regs, uint32 base) -{ -static uint32 ARX, BRX, PRL; /* static so addresses are constant */ - -static const char *reg_names[] = { "CIR", "A", "B", "E", "X", "Y", "O", "P", "return" }; -static const uint32 *reg_ptrs[] = { &intaddr, &ARX, &BRX, &E, &XR, &YR, &O, &PC, &PRL }; -static const char *formats[] = { "%02o", "%06o", "%06o", "%01o", "%06o", "%06o", "%01o", "%06o", "P+%d" }; - -static char format[20] = " %s = "; /* base format string */ -static const int eos = 6; /* length of base format string */ - -uint32 i; -t_bool first = TRUE; /* first-time through flag */ - -ARX = AR; /* copy 16-bit value to static variable */ -BRX = BR; /* copy 16-bit value to static variable */ -PRL = PC - base; /* compute value in static variable */ - -for (i = 0; i < REG_COUNT; i++) { - if (regs & 1) { /* register requested? */ - if (first) /* first time? */ - fputs (caption, sim_deb); /* print caption */ - else - fputc (',', sim_deb); /* print separator */ - - strcpy (&format[eos], formats[i]); /* copy format specifier */ - fprintf (sim_deb, format, reg_names[i], *reg_ptrs[i]); - - first = FALSE; - } - - regs = regs >> 1; /* align next register flag */ - } -return; -} +/* hp2100_cpu1.c: HP 2100/1000 EAU simulator and UIG dispatcher + + Copyright (c) 2005-2016, Robert M. Supnik + Copyright (c) 2017 J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. + + CPU1 Extended arithmetic and optional microcode dispatchers + + 22-Apr-17 JDB Improved the EAU shift/rotate instructions + 21-Mar-17 JDB Fixed UIG 1 comment regarding 2000 IOP and F-Series + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 24-Dec-14 JDB Added casts for explicit downward conversions + 05-Apr-14 JDB Corrected typo in comments for cpu_ops + 09-May-12 JDB Separated assignments from conditional expressions + 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h + 05-Sep-08 JDB Moved option-present tests to UIG dispatchers + Call "user microcode" dispatcher for unclaimed UIG instructions + 20-Apr-08 JDB Fixed VIS and SIGNAL to depend on the FPP and HAVE_INT64 + 28-Nov-07 JDB Added fprint_ops, fprint_regs for debug printouts + 17-Nov-07 JDB Enabled DIAG as NOP on 1000 F-Series + 04-Jan-07 JDB Added special DBI dispatcher for non-INT64 diagnostic + 29-Dec-06 JDB Allows RRR as NOP if 2114 (diag config test) + 01-Dec-06 JDB Substitutes FPP for firmware FP if HAVE_INT64 + 16-Oct-06 JDB Generalized operands for F-Series FP types + 26-Sep-06 JDB Split hp2100_cpu1.c into multiple modules to simplify extensions + Added iotrap parameter to UIG dispatchers for RTE microcode + 22-Feb-05 JDB Removed EXECUTE instruction (is NOP in actual microcode) + 21-Jan-05 JDB Reorganized CPU option and operand processing flags + Split code along microcode modules + 15-Jan-05 RMS Cloned from hp2100_cpu.c + + Primary references: + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - Macro/1000 Reference Manual (92059-90001, Dec-1992) + - HP 93585A Double Integer Firmware Package Installation and Programming + Manual (93585-90007, Feb-1984) + + Additional references are listed with the associated firmware + implementations, as are the HP option model numbers pertaining to the + applicable CPUs. + + + This source file contains the Extended Arithmetic Unit simulator and the User + Instruction Group (a.k.a. "Macro") dispatcher for the 2100 and 1000 (21MX) + CPUs. The UIG simulators reside in separate source files, due to the large + number of firmware options available for these machines. Unit flags indicate + which options are present in the current system. + + This module also provides generalized instruction operand processing. + + The 2100 and 1000 machines were microprogrammable; the 2116/15/14 machines + were not. Both user- and HP-written microprograms were supported. The + microcode address space of the 2100 encompassed four modules of 256 words + each. The 1000 M-series expanded that to sixteen modules, and the 1000 + E/F-series expanded that still further to sixty-four modules. Each CPU had + its own microinstruction set, although the micromachines of the various 1000 + models were similar internally. + + The UIG instructions were divided into ranges assigned to HP firmware + options, reserved for future HP use, and reserved for user microprograms. + User microprograms could occupy any range not already used on a given + machine, but in practice, some effort was made to avoid the HP-reserved + ranges. + + User microprogram simulation is supported by routing any UIG instruction not + allocated to an installed firmware option to a user-firmware dispatcher. + Site-specific microprograms may be simulated there. In the absence of such a + simulation, an unimplemented instruction stop will occur. + + Regarding option instruction sets, there was some commonality across CPU + types. EAU instructions were identical across all models, and the floating + point set was the same on the 2100 and 1000. Other options implemented + proper instruction supersets (e.g., the Fast FORTRAN Processor from 2100 to + 1000-M to 1000-E to 1000-F) or functional equivalence with differing code + points (the 2000 I/O Processor from 2100 to 1000, and the extended-precision + floating-point instructions from 1000-E to 1000-F). + + The 2100 decoded the EAU and UIG sets separately in hardware and supported + only the UIG 0 code points. Bits 7-4 of a UIG instruction decoded one of + sixteen entry points in the lowest-numbered module after module 0. Those + entry points could be used directly (as for the floating-point instructions), + or additional decoding based on bits 3-0 could be implemented. + + The 1000 generalized the instruction decoding to a series of microcoded + jumps, based on the bits in the instruction. Bits 15-8 indicated the group + of the current instruction: EAU (200, 201, 202, 210, and 211), UIG 0 (212), + or UIG 1 (203 and 213). UIG 0, UIG 1, and some EAU instructions were decoded + further by selecting one of sixteen modules within the group via bits 7-4. + Finally, each UIG module decoded up to sixteen instruction entry points via + bits 3-0. Jump tables for all firmware options were contained in the base + set, so modules needed only to be concerned with decoding their individual + entry points within the module. + + While the 2100 and 1000 hardware decoded these instruction sets differently, + the decoding mechanism of the simulation follows that of the 1000 E/F-series. + Where needed, CPU type- or model-specific behavior is simulated. + + The design of the 1000 microinstruction set was such that executing an + instruction for which no microcode was present (e.g., executing a FFP + instruction when the FFP firmware was not installed) resulted in a NOP. + Under simulation, such execution causes an undefined instruction stop if + "stop_inst" is non-zero and a NOP otherwise. +*/ + + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + + +/* EAU + + The Extended Arithmetic Unit (EAU) adds ten instructions with double-word + operands, including multiply, divide, shifts, and rotates. Option + implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A 12579A 12579A std std std std + + The instruction codes are mapped to routines as follows: + + Instr. Bits + Code 15-8 7-4 2116 2100 1000-M 1000-E 1000-F Note + ------ ---- --- ------ ------ ------ ------ ------ --------------------- + 100000 200 00 [diag] [diag] [self test] + 100020 200 01 ASL ASL ASL ASL ASL Bits 3-0 encode shift + 100040 200 02 LSL LSL LSL LSL LSL Bits 3-0 encode shift + 100060 200 03 TIMER TIMER [deterministic delay] + 100100 200 04 RRL RRL RRL RRL RRL Bits 3-0 encode shift + 100200 200 10 MPY MPY MPY MPY MPY + 100400 201 xx DIV DIV DIV DIV DIV + 101020 202 01 ASR ASR ASR ASR ASR Bits 3-0 encode shift + 101040 202 02 LSR LSR LSR LSR LSR Bits 3-0 encode shift + 101100 202 04 RRR RRR RRR RRR RRR Bits 3-0 encode shift + 104200 210 xx DLD DLD DLD DLD DLD + 104400 211 xx DST DST DST DST DST + + The remaining codes for bits 7-4 are undefined and will cause a simulator + stop if enabled. On a real 1000-M, all undefined instructions in the 200 + group decode as MPY, and all in the 202 group decode as NOP. On a real + 1000-E, instruction patterns 200/05 through 200/07 and 202/03 decode as NOP; + all others cause erroneous execution. + + EAU instruction decoding on the 1000 M-series is convoluted. The JEAU + microorder maps IR bits 11, 9-7 and 5-4 to bits 2-0 of the microcode jump + address. The map is detailed on page IC-84 of the ERD. + + The 1000 E/F-series add two undocumented instructions to the 200 group: TIMER + and DIAG. These are described in the ERD on page IA 5-5, paragraph 5-7. The + M-series executes these as MPY and RRL, respectively. A third instruction, + EXECUTE (100120), is also described but was never implemented, and the + E/F-series microcode execute a NOP for this instruction code. + + + Implementation notes: + + 1. Under simulation, TIMER, DIAG, and EXECUTE cause undefined instruction + stops if the CPU is set to 21xx. DIAG and EXECUTE also cause stops on + the 1000-M. TIMER does not, because it is used by several HP programs + to differentiate between M- and E/F-series machines. + + 2. DIAG is not implemented under simulation. On the E/F, it performs a + destructive test of all installed memory. Because of this, it is only + functional if the machine is halted, i.e., if the instruction is + executed with the INSTR STEP button. If it is executed in a program, + the result is NOP. + + 3. RRR is permitted and executed as NOP if the CPU is a 2114, as the + presence of the EAU is tested by the diagnostic configurator to + differentiate between 2114 and 2100/1000 CPUs. + + 4. The shift count is calculated unconditionally, as six of the ten + instructions will be using the value. + + 5. An arithmetic left shift must be handled as a special case because the + shifted operand bits "skip over" the sign bit. That is, the bits are + lost from the next-most-significant bit while preserving the MSB. For + all other shifts, including the arithmetic right shift, the operand may + be shifted and then merged with the appropriate fill bits. + + 6. The C standard specifies that the results of bitwise shifts with negative + signed operands are undefined (for left shifts) or implementation-defined + (for right shifts). Therefore, we must use unsigned operands and handle + arithmetic shifts explicitly. +*/ + +t_stat cpu_eau (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +uint32 rs, qs, v1, v2, operand, fill, mask, shift; +int32 sop1, sop2; + +if ((cpu_unit.flags & UNIT_EAU) == 0) /* option installed? */ + if (UNIT_CPU_MODEL == UNIT_2114 && IR == 0101100) /* 2114 and RRR 16? */ + return SCPE_OK; /* allowed as NOP */ + else + return stop_inst; /* fail */ + +if (IR & 017) /* if the shift count is 1-15 */ + shift = IR & 017; /* then use it verbatim */ +else /* otherwise the count iz zero */ + shift = 16; /* so use a shift count of 16 */ + +switch ((IR >> 8) & 0377) { /* decode IR<15:8> */ + + case 0200: /* EAU group 0 */ + switch ((IR >> 4) & 017) { /* decode IR<7:4> */ + + case 000: /* DIAG 100000 */ + if ((UNIT_CPU_MODEL != UNIT_1000_E) && /* must be 1000 E-series */ + (UNIT_CPU_MODEL != UNIT_1000_F)) /* or 1000 F-series */ + return stop_inst; /* trap if not */ + break; /* DIAG is NOP unless halted */ + + + case 001: /* ASL 100020-100037 */ + operand = TO_DWORD (BR, AR); /* form the double-word operand */ + + mask = D32_UMAX << 31 - shift; /* form a mask for the bits that will be lost */ + + if (operand & D32_SIGN) /* if the operand is negative */ + O = (~operand & mask & D32_MASK) != 0; /* then set overflow if any of the lost bits are zeros */ + else /* otherwise it's positive */ + O = (operand & mask & D32_MASK) != 0; /* so set overflow if any of the lost bits are ones */ + + operand = operand << shift & D32_SMAX /* shift the operand left */ + | operand & D32_SIGN; /* while keeping the original sign bit */ + + BR = UPPER_WORD (operand); /* split the operand */ + AR = LOWER_WORD (operand); /* into its constituent parts */ + break; + + + case 002: /* LSL 100040-100057 */ + operand = TO_DWORD (BR, AR) << shift; /* shift the double-word operand left */ + + BR = UPPER_WORD (operand); /* split the operand */ + AR = LOWER_WORD (operand); /* into its constituent parts */ + break; + + + case 004: /* RRL 100100-100117 */ + operand = TO_DWORD (BR, AR); /* form the double-word operand */ + fill = operand; /* and fill with operand bits */ + + operand = operand << shift /* rotate the operand left */ + | fill >> 32 - shift; /* while filling in on the right */ + + BR = UPPER_WORD (operand); /* split the operand */ + AR = LOWER_WORD (operand); /* into its constituent parts */ + break; + + + case 003: /* TIMER 100060 */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ + return stop_inst; /* trap if not */ + + if (UNIT_CPU_MODEL != UNIT_1000_M) { /* 1000 E/F-series? */ + BR = (BR + 1) & DMASK; /* increment B */ + + if (BR) /* if !=0, repeat */ + PR = err_PC; + break; + } + + /* fall into the MPY case if 1000 M-Series */ + + case 010: /* MPY 100200 (OP_K) */ + reason = cpu_ops (OP_K, op, intrq); /* get operand */ + + if (reason == SCPE_OK) { /* successful eval? */ + sop1 = SEXT (AR); /* sext AR */ + sop2 = SEXT (op[0].word); /* sext mem */ + sop1 = sop1 * sop2; /* signed mpy */ + BR = UPPER_WORD (sop1); /* to BR'AR */ + AR = LOWER_WORD (sop1); + O = 0; /* no overflow */ + } + break; + + + default: /* others undefined */ + return stop_inst; + } + + break; + + + case 0201: /* DIV 100400 (OP_K) */ + reason = cpu_ops (OP_K, op, intrq); /* get operand */ + + if (reason != SCPE_OK) /* eval failed? */ + break; + + rs = qs = BR & SIGN; /* save divd sign */ + + if (rs) { /* neg? */ + AR = (~AR + 1) & DMASK; /* make B'A pos */ + BR = (~BR + (AR == 0)) & DMASK; /* make divd pos */ + } + + v2 = op[0].word; /* divr = mem */ + + if (v2 & SIGN) { /* neg? */ + v2 = (~v2 + 1) & DMASK; /* make divr pos */ + qs = qs ^ SIGN; /* sign of quotient */ + } + + if (BR >= v2) /* if the divisor is too small */ + O = 1; /* then set overflow */ + + else { /* maybe... */ + O = 0; /* assume ok */ + v1 = (BR << 16) | AR; /* 32b divd */ + AR = (v1 / v2) & DMASK; /* quotient */ + BR = (v1 % v2) & DMASK; /* remainder */ + + if (AR) { /* quotient > 0? */ + if (qs) /* apply quo sign */ + AR = NEG16 (AR); + + if ((AR ^ qs) & SIGN) /* still wrong? ovflo */ + O = 1; + } + + if (rs) + BR = NEG16 (BR); /* apply rem sign */ + } + break; + + + case 0202: /* EAU group 2 */ + switch ((IR >> 4) & 017) { /* decode IR<7:4> */ + + case 001: /* ASR 101020-101037 */ + O = 0; /* clear ovflo */ + + operand = TO_DWORD (BR, AR); /* form the double-word operand */ + fill = (operand & D32_SIGN ? ~0 : 0); /* and fill with copies of the sign bit */ + + operand = operand >> shift /* shift the operand right */ + | fill << 32 - shift; /* while filling in with sign bits */ + + BR = UPPER_WORD (operand); /* split the operand */ + AR = LOWER_WORD (operand); /* into its constituent parts */ + break; + + + case 002: /* LSR 101040-101057 */ + operand = TO_DWORD (BR, AR) >> shift; /* shift the double-word operand right */ + + BR = UPPER_WORD (operand); /* split the operand */ + AR = LOWER_WORD (operand); /* into its constituent parts */ + break; + + + case 004: /* RRR 101100-101117 */ + operand = TO_DWORD (BR, AR); /* form the double-word operand */ + fill = operand; /* and fill with operand bits */ + + operand = operand >> shift /* rotate the operand right */ + | fill << 32 - shift; /* while filling in on the left */ + + BR = UPPER_WORD (operand); /* split the operand */ + AR = LOWER_WORD (operand); /* into its constituent parts */ + break; + + + default: /* others undefined */ + return stop_inst; + } + + break; + + + case 0210: /* DLD 104200 (OP_D) */ + reason = cpu_ops (OP_D, op, intrq); /* get operand */ + + if (reason == SCPE_OK) { /* successful eval? */ + AR = UPPER_WORD (op[0].dword); /* load AR */ + BR = LOWER_WORD (op[0].dword); /* load BR */ + } + break; + + + case 0211: /* DST 104400 (OP_A) */ + reason = cpu_ops (OP_A, op, intrq); /* get operand */ + + if (reason == SCPE_OK) { /* successful eval? */ + WriteW (op[0].word, AR); /* store AR */ + WriteW ((op[0].word + 1) & VAMASK, BR); /* store BR */ + } + break; + + + default: /* should never get here */ + return SCPE_IERR; /* bad call from cpu_instr */ + } + +return reason; +} + + +/* UIG 0 + + The first User Instruction Group (UIG) encodes firmware options for the 2100 + and 1000. Instruction codes 105000-105377 are assigned to microcode options + as follows: + + Instructions Option Name 2100 1000-M 1000-E 1000-F + ------------- -------------------------- ------ ------ ------ ------ + 105000-105362 2000 I/O Processor opt - - - + 105000-105137 Floating Point opt std std std + 105200-105237 Fast FORTRAN Processor opt opt opt std + 105240-105257 RTE-IVA/B Extended Memory - - opt opt + 105240-105257 RTE-6/VM Virtual Memory - - opt opt + 105300-105317 Distributed System - - opt opt + 105320-105337 Double Integer - - opt - + 105320-105337 Scientific Instruction Set - - - std + 105340-105357 RTE-6/VM Operating System - - opt opt + + If the 2100 IOP is installed, the only valid UIG instructions are IOP + instructions, as the IOP used the full 2100 microcode addressing space. The + IOP dispatcher remaps the 2100 codes to 1000 codes for execution. + + The F-Series moved the three-word extended real instructions from the FFP + range to the base floating-point range and added four-word double real and + two-word double integer instructions. The double integer instructions + occupied some of the vacated extended real instruction codes in the FFP, with + the rest assigned to the floating-point range. Consequently, many + instruction codes for the F-Series are different from the E-Series. + + Implementation notes: + + 1. Product 93585A, available from the "Specials" group, added double integer + microcode to the E-Series. The instruction codes were different from + those in the F-Series to avoid conflicting with the E-Series FFP. + + 2. To run the double-integer instructions diagnostic in the absence of + 64-bit integer support (and therefore of F-Series simulation), a special + DBI dispatcher may be enabled by defining ENABLE_DIAG during compilation. + This dispatcher will remap the F-Series DBI instructions to the E-Series + codes, so that the F-Series diagnostic may be run. Because several of + the F-Series DBI instruction codes replace M/E-Series FFP codes, this + dispatcher will only operate if FFP is disabled. + + Note that enabling the dispatcher will produce non-standard FP behavior. + For example, any code in the range 105000-105017 normally would execute a + FAD instruction. With the dispatcher enabled, 105014 would execute a + .DAD, while the other codes would execute a FAD. Therefore, ENABLE_DIAG + should only be used to run the diagnostic and is not intended for general + use. + + 3. Any instruction not claimed by an installed option will be sent to the + user microcode dispatcher. +*/ + +t_stat cpu_uig_0 (uint32 IR, uint32 intrq, uint32 iotrap) +{ +if ((cpu_unit.flags & UNIT_IOP) && /* I/O Processor? */ + (UNIT_CPU_TYPE == UNIT_TYPE_2100)) /* and 2100 CPU? */ + return cpu_iop (IR, intrq); /* dispatch to IOP */ + + +#if !defined (HAVE_INT64) && defined (ENABLE_DIAG) /* special DBI diagnostic dispatcher */ + +if (((cpu_unit.flags & UNIT_FFP) == 0) && /* FFP absent? */ + (cpu_unit.flags & UNIT_DBI)) /* and DBI present? */ + switch (IR & 0377) { + case 0014: /* .DAD 105014 */ + return cpu_dbi (0105321, intrq); + + case 0034: /* .DSB 105034 */ + return cpu_dbi (0105327, intrq); + + case 0054: /* .DMP 105054 */ + return cpu_dbi (0105322, intrq); + + case 0074: /* .DDI 105074 */ + return cpu_dbi (0105325, intrq); + + case 0114: /* .DSBR 105114 */ + return cpu_dbi (0105334, intrq); + + case 0134: /* .DDIR 105134 */ + return cpu_dbi (0105326, intrq); + + case 0203: /* .DNG 105203 */ + return cpu_dbi (0105323, intrq); + + case 0204: /* .DCO 105204 */ + return cpu_dbi (0105324, intrq); + + case 0210: /* .DIN 105210 */ + return cpu_dbi (0105330, intrq); + + case 0211: /* .DDE 105211 */ + return cpu_dbi (0105331, intrq); + + case 0212: /* .DIS 105212 */ + return cpu_dbi (0105332, intrq); + + case 0213: /* .DDS 105213 */ + return cpu_dbi (0105333, intrq); + } /* otherwise, continue */ + +#endif /* end of special DBI dispatcher */ + + +switch ((IR >> 4) & 017) { /* decode IR<7:4> */ + + case 000: /* 105000-105017 */ + case 001: /* 105020-105037 */ + case 002: /* 105040-105057 */ + case 003: /* 105060-105077 */ + case 004: /* 105100-105117 */ + case 005: /* 105120-105137 */ + if (cpu_unit.flags & UNIT_FP) /* FP option installed? */ +#if defined (HAVE_INT64) /* int64 support available */ + return cpu_fpp (IR, intrq); /* Floating Point Processor */ +#else /* int64 support unavailable */ + return cpu_fp (IR, intrq); /* Firmware Floating Point */ +#endif /* end of int64 support */ + else + break; + + case 010: /* 105200-105217 */ + case 011: /* 105220-105237 */ + if (cpu_unit.flags & UNIT_FFP) /* FFP option installed? */ + return cpu_ffp (IR, intrq); /* Fast FORTRAN Processor */ + else + break; + + case 012: /* 105240-105257 */ + if (cpu_unit.flags & UNIT_VMAOS) /* VMA/OS option installed? */ + return cpu_rte_vma (IR, intrq); /* RTE-6 VMA */ + else if (cpu_unit.flags & UNIT_EMA) /* EMA option installed? */ + return cpu_rte_ema (IR, intrq); /* RTE-4 EMA */ + else + break; + + case 014: /* 105300-105317 */ + if (cpu_unit.flags & UNIT_DS) /* DS option installed? */ + return cpu_ds (IR, intrq); /* Distributed System */ + else + break; + + case 015: /* 105320-105337 */ +#if defined (HAVE_INT64) /* int64 support available */ + if (UNIT_CPU_MODEL == UNIT_1000_F) /* F-series? */ + return cpu_sis (IR, intrq); /* Scientific Instruction is standard */ + else /* M/E-series */ +#endif /* end of int64 support */ + if (cpu_unit.flags & UNIT_DBI) /* DBI option installed? */ + return cpu_dbi (IR, intrq); /* Double integer */ + else + break; + + case 016: /* 105340-105357 */ + if (cpu_unit.flags & UNIT_VMAOS) /* VMA/OS option installed? */ + return cpu_rte_os (IR, intrq, iotrap); /* RTE-6 OS */ + else + break; + } + +return cpu_user (IR, intrq); /* try user microcode */ +} + + +/* UIG 1 + + The second User Instruction Group (UIG) encodes firmware options for the + 1000. Instruction codes 101400-101777 and 105400-105777 are assigned to + microcode options as follows ("x" is "1" or "5" below): + + Instructions Option Name 1000-M 1000-E 1000-F + ------------- ---------------------------- ------ ------ ------ + 10x400-10x437 2000 IOP opt opt - + 10x460-10x477 2000 IOP opt opt - + 10x460-10x477 Vector Instruction Set - - opt + 10x520-10x537 Distributed System opt - - + 10x600-10x617 SIGNAL/1000 Instruction Set - - opt + 10x700-10x737 Dynamic Mapping System opt opt std + 10x740-10x777 Extended Instruction Group std std std + + Only 1000 systems execute these instructions. + + Implementation notes: + + 1. The Distributed System (DS) microcode was mapped to different instruction + ranges for the M-Series and the E/F-Series. The sequence of instructions + was identical, though, so we remap the former range to the latter before + dispatching. + + 2. Any instruction not claimed by an installed option will be sent to the + user microcode dispatcher. +*/ + +t_stat cpu_uig_1 (uint32 IR, uint32 intrq, uint32 iotrap) +{ +if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* 1000 execution? */ + return stop_inst; /* no, so trap */ + +switch ((IR >> 4) & 017) { /* decode IR<7:4> */ + + case 000: /* 105400-105417 */ + case 001: /* 105420-105437 */ + if (cpu_unit.flags & UNIT_IOP) /* IOP option installed? */ + return cpu_iop (IR, intrq); /* 2000 I/O Processor */ + else + break; + + case 003: /* 105460-105477 */ +#if defined (HAVE_INT64) /* int64 support available */ + if (cpu_unit.flags & UNIT_VIS) /* VIS option installed? */ + return cpu_vis (IR, intrq); /* Vector Instruction Set */ + else +#endif /* end of int64 support */ + if (cpu_unit.flags & UNIT_IOP) /* IOP option installed? */ + return cpu_iop (IR, intrq); /* 2000 I/O Processor */ + else + break; + + case 005: /* 105520-105537 */ + if (cpu_unit.flags & UNIT_DS) { /* DS option installed? */ + IR = IR ^ 0000620; /* remap to 105300-105317 */ + return cpu_ds (IR, intrq); /* Distributed System */ + } + else + break; + +#if defined (HAVE_INT64) /* int64 support available */ + case 010: /* 105600-105617 */ + if (cpu_unit.flags & UNIT_SIGNAL) /* SIGNAL option installed? */ + return cpu_signal (IR, intrq); /* SIGNAL/1000 Instructions */ + else + break; +#endif /* end of int64 support */ + + case 014: /* 105700-105717 */ + case 015: /* 105720-105737 */ + if (cpu_unit.flags & UNIT_DMS) /* DMS option installed? */ + return cpu_dms (IR, intrq); /* Dynamic Mapping System */ + else + break; + + case 016: /* 105740-105757 */ + case 017: /* 105760-105777 */ + return cpu_eig (IR, intrq); /* Extended Instruction Group */ + } + +return cpu_user (IR, intrq); /* try user microcode */ +} + + +/* Read a multiple-precision operand value. */ + +OP ReadOp (uint32 va, OPSIZE precision) +{ +OP operand; +uint32 i; + +if (precision == in_s) + operand.word = ReadW (va); /* read single integer */ + +else if (precision == in_d) + operand.dword = ReadW (va) << 16 | /* read double integer */ + ReadW ((va + 1) & VAMASK); /* merge high and low words */ + +else + for (i = 0; i < (uint32) precision; i++) { /* read fp 2 to 5 words */ + operand.fpk[i] = ReadW (va); + va = (va + 1) & VAMASK; + } +return operand; +} + +/* Write a multiple-precision operand value. */ + +void WriteOp (uint32 va, OP operand, OPSIZE precision) +{ +uint32 i; + +if (precision == in_s) + WriteW (va, operand.word); /* write single integer */ + +else if (precision == in_d) { + WriteW (va, (operand.dword >> 16) & DMASK); /* write double integer */ + WriteW ((va + 1) & VAMASK, operand.dword & DMASK); /* high word, then low word */ + } + +else + for (i = 0; i < (uint32) precision; i++) { /* write fp 2 to 5 words */ + WriteW (va, operand.fpk[i]); + va = (va + 1) & VAMASK; + } +return; +} + + +/* Get instruction operands. + + Operands for a given instruction are specifed by an "operand pattern" + consisting of flags indicating the types and storage methods. The pattern + directs how each operand is to be retrieved and whether the operand value or + address is returned in the operand array. + + Typically, a microcode simulation handler will define an OP_PAT array, with + each element containing an operand pattern corresponding to the simulated + instruction. Operand patterns are defined in the header file accompanying + this source file. After calling this function with the appropriate operand + pattern and a pointer to an array of OPs, operands are decoded and stored + sequentially in the array. + + The following operand encodings are defined: + + Code Operand Description Example Return + ------ ---------------------------------------- ----------- ------------ + OP_NUL No operand present [inst] None + + OP_IAR Integer constant in A register LDA I Value of I + [inst] + ... + I DEC 0 + + OP_JAB Double integer constant in A/B registers DLD J Value of J + [inst] + ... + J DEC 0,0 + + OP_FAB 2-word FP constant in A/B registers DLD F Value of F + [inst] + ... + F DEC 0.0 + + OP_CON Inline 1-word constant [inst] Value of C + C DEC 0 + ... + + OP_VAR Inline 1-word variable [inst] Address of V + V BSS 1 + ... + + OP_ADR Inline address [inst] Address of A + DEF A + ... + A EQU * + + OP_ADK Address of integer constant [inst] Value of K + DEF K + ... + K DEC 0 + + OP_ADD Address of double integer constant [inst] Value of D + DEF D + ... + D DEC 0,0 + + OP_ADF Address of 2-word FP constant [inst] Value of F + DEF F + ... + F DEC 0.0 + + OP_ADX Address of 3-word FP constant [inst] Value of X + DEF X + ... + X DEX 0.0 + + OP_ADT Address of 4-word FP constant [inst] Value of T + DEF T + ... + T DEY 0.0 + + OP_ADE Address of 5-word FP constant [inst] Value of E + DEF E + ... + E DEC 0,0,0,0,0 + + Address operands, i.e., those having a DEF to the operand, will be resolved + to direct addresses. If an interrupt is pending and more than three levels + of indirection are used, the routine returns without completing operand + retrieval (the instruction will be retried after interrupt servicing). + Addresses are always resolved in the current DMS map. + + An operand pattern consists of one or more operand encodings, corresponding + to the operands required by a given instruction. Values are returned in + sequence to the operand array. +*/ + +t_stat cpu_ops (OP_PAT pattern, OPS op, uint32 irq) +{ +t_stat reason = SCPE_OK; +OP_PAT flags; +uint32 i, MA; + +for (i = 0; i < OP_N_F; i++) { + flags = pattern & OP_M_FLAGS; /* get operand pattern */ + + if (flags >= OP_ADR) { /* address operand? */ + reason = resolve (ReadW (PR), &MA, irq); /* resolve indirects */ + if (reason != SCPE_OK) /* resolution failed? */ + return reason; + } + + switch (flags) { + case OP_NUL: /* null operand */ + return reason; /* no more, so quit */ + + case OP_IAR: /* int in A */ + (*op++).word = AR; /* get one-word value */ + break; + + case OP_JAB: /* dbl-int in A/B */ + (*op++).dword = (AR << 16) | BR; /* get two-word value */ + break; + + case OP_FAB: /* 2-word FP in A/B */ + (*op).fpk[0] = AR; /* get high FP word */ + (*op++).fpk[1] = BR; /* get low FP word */ + break; + + case OP_CON: /* inline constant operand */ + *op++ = ReadOp (PR, in_s); /* get value */ + break; + + case OP_VAR: /* inline variable operand */ + (*op++).word = (uint16) PR; /* get pointer to variable */ + break; + + case OP_ADR: /* inline address operand */ + (*op++).word = (uint16) MA; /* get address (set by "resolve" above) */ + break; + + case OP_ADK: /* address of int constant */ + *op++ = ReadOp (MA, in_s); /* get value */ + break; + + case OP_ADD: /* address of dbl-int constant */ + *op++ = ReadOp (MA, in_d); /* get value */ + break; + + case OP_ADF: /* address of 2-word FP const */ + *op++ = ReadOp (MA, fp_f); /* get value */ + break; + + case OP_ADX: /* address of 3-word FP const */ + *op++ = ReadOp (MA, fp_x); /* get value */ + break; + + case OP_ADT: /* address of 4-word FP const */ + *op++ = ReadOp (MA, fp_t); /* get value */ + break; + + case OP_ADE: /* address of 5-word FP const */ + *op++ = ReadOp (MA, fp_e); /* get value */ + break; + + default: + return SCPE_IERR; /* not implemented */ + } + + if (flags >= OP_CON) /* operand after instruction? */ + PR = (PR + 1) & VAMASK; /* yes, so bump to next */ + pattern = pattern >> OP_N_FLAGS; /* move next pattern into place */ + } +return reason; +} + + +/* Print operands to the debug device. + + The values of an operand array are printed to the debug device. The types of + the operands are specified by an operand pattern. Typically, the operand + pattern is the same one that was used to fill the array originally. +*/ + +void fprint_ops (OP_PAT pattern, OPS op) +{ +OP_PAT flags; +uint32 i; + +for (i = 0; i < OP_N_F; i++) { + flags = pattern & OP_M_FLAGS; /* get operand pattern */ + + switch (flags) { + case OP_NUL: /* null operand */ + return; /* no more, so quit */ + + case OP_IAR: /* int in A */ + case OP_CON: /* inline constant operand */ + case OP_VAR: /* inline variable operand */ + case OP_ADR: /* inline address operand */ + case OP_ADK: /* address of int constant */ + fprintf (sim_deb, + ", op[%d] = %06o", + i, op[i].word); + break; + + case OP_JAB: /* dbl-int in A/B */ + case OP_ADD: /* address of dbl-int constant */ + fprintf (sim_deb, + ", op[%d] = %011o", + i, op[i].dword); + break; + + case OP_FAB: /* 2-word FP in A/B */ + case OP_ADF: /* address of 2-word FP const */ + fprintf (sim_deb, + ", op[%d] = (%06o, %06o)", + i, op[i].fpk[0], op[i].fpk[1]); + break; + + case OP_ADX: /* address of 3-word FP const */ + fprintf (sim_deb, + ", op[%d] = (%06o, %06o, %06o)", + i, op[i].fpk[0], op[i].fpk[1], + op[i].fpk[2]); + break; + + case OP_ADT: /* address of 4-word FP const */ + fprintf (sim_deb, + ", op[%d] = (%06o, %06o, %06o, %06o)", + i, op[i].fpk[0], op[i].fpk[1], + op[i].fpk[2], op[i].fpk[3]); + break; + + case OP_ADE: /* address of 5-word FP const */ + fprintf (sim_deb, + ", op[%d] = (%06o, %06o, %06o, %06o, %06o)", + i, op[i].fpk[0], op[i].fpk[1], + op[i].fpk[2], op[i].fpk[3], op[i].fpk[4]); + break; + + default: + fprintf (sim_deb, "UNKNOWN OPERAND TYPE"); /* not implemented */ + } + + pattern = pattern >> OP_N_FLAGS; /* move next pattern into place */ + } +} + + +/* Print CPU registers to the debug device. + + One or more CPU registers may be printed to the debug output device, which + must be valid before calling. +*/ + +void fprint_regs (char *caption, uint32 regs, uint32 base) +{ +static uint32 ARX, BRX, PRL; /* static so addresses are constant */ + +static const char *reg_names[] = { "CIR", "A", "B", "E", "X", "Y", "O", "P", "return" }; +static const uint32 *reg_ptrs[] = { &intaddr, &ARX, &BRX, &E, &XR, &YR, &O, &PR, &PRL }; +static const char *formats[] = { "%02o", "%06o", "%06o", "%01o", "%06o", "%06o", "%01o", "%06o", "P+%d" }; + +static char format[20] = " %s = "; /* base format string */ +static const int eos = 6; /* length of base format string */ + +uint32 i; +t_bool first = TRUE; /* first-time through flag */ + +ARX = AR; /* copy 16-bit value to static variable */ +BRX = BR; /* copy 16-bit value to static variable */ +PRL = PR - base; /* compute value in static variable */ + +for (i = 0; i < REG_COUNT; i++) { + if (regs & 1) { /* register requested? */ + if (first) /* first time? */ + fputs (caption, sim_deb); /* print caption */ + else + fputc (',', sim_deb); /* print separator */ + + strcpy (&format[eos], formats[i]); /* copy format specifier */ + fprintf (sim_deb, format, reg_names[i], *reg_ptrs[i]); + + first = FALSE; + } + + regs = regs >> 1; /* align next register flag */ + } +return; +} diff --git a/HP2100/hp2100_cpu1.h b/HP2100/hp2100_cpu1.h index 2c954443..539fa83b 100644 --- a/HP2100/hp2100_cpu1.h +++ b/HP2100/hp2100_cpu1.h @@ -1,323 +1,323 @@ -/* hp2100_cpu1.h: HP 2100/1000 firmware dispatcher definitions - - Copyright (c) 2006-2013, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not - be used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - 18-Mar-13 JDB Added externs for microcode helper functions - 14-Mar-13 MP Changed guard macro name to avoid reserved namespace - 11-Sep-08 JDB Moved microcode function prototypes here - 30-Apr-08 JDB Corrected OP_AFF to OP_AAFF for SIGNAL/1000 - Removed unused operand patterns - 23-Feb-08 HV Added more OP_* for SIGNAL/1000 and VIS - 28-Nov-07 JDB Added fprint_ops, fprint_regs for debug printouts - 19-Oct-07 JDB Revised OP_KKKAKK operand profile to OP_CCCACC for $LOC - 16-Oct-06 JDB Generalized operands for F-Series FP types - 26-Sep-06 JDB Split from hp2100_cpu1.c -*/ - -#ifndef HP2100_CPU1_H_ -#define HP2100_CPU1_H_ - - -/* Register print encoding */ - -#define REG_COUNT 9 /* count of print flags */ - -#define REG_CIR (1 << 0) /* print central interrupt register */ -#define REG_A (1 << 1) /* print A register */ -#define REG_B (1 << 2) /* print B register */ -#define REG_E (1 << 3) /* print E register */ -#define REG_X (1 << 4) /* print X register */ -#define REG_Y (1 << 5) /* print Y register */ -#define REG_O (1 << 6) /* print O register */ -#define REG_P (1 << 7) /* print P register */ -#define REG_P_REL (1 << 8) /* print P register as relative */ - - -/* Operand processing encoding */ - -/* Base operand types. Note that all address encodings must be grouped together - after OP_ADR. -*/ - -#define OP_NUL 0 /* no operand */ -#define OP_IAR 1 /* 1-word int in A reg */ -#define OP_JAB 2 /* 2-word int in A/B regs */ -#define OP_FAB 3 /* 2-word FP const in A/B regs */ -#define OP_CON 4 /* inline 1-word constant */ -#define OP_VAR 5 /* inline 1-word variable */ - -#define OP_ADR 6 /* inline address */ -#define OP_ADK 7 /* addr of 1-word int const */ -#define OP_ADD 8 /* addr of 2-word int const */ -#define OP_ADF 9 /* addr of 2-word FP const */ -#define OP_ADX 10 /* addr of 3-word FP const */ -#define OP_ADT 11 /* addr of 4-word FP const */ -#define OP_ADE 12 /* addr of 5-word FP const */ - -#define OP_N_FLAGS 4 /* number of bits needed for flags */ -#define OP_M_FLAGS ((1 << OP_N_FLAGS) - 1) /* mask for flag bits */ - -#define OP_N_F (8 * sizeof (uint32) / OP_N_FLAGS) /* max number of op fields */ - -#define OP_V_F1 (0 * OP_N_FLAGS) /* 1st operand field */ -#define OP_V_F2 (1 * OP_N_FLAGS) /* 2nd operand field */ -#define OP_V_F3 (2 * OP_N_FLAGS) /* 3rd operand field */ -#define OP_V_F4 (3 * OP_N_FLAGS) /* 4th operand field */ -#define OP_V_F5 (4 * OP_N_FLAGS) /* 5th operand field */ -#define OP_V_F6 (5 * OP_N_FLAGS) /* 6th operand field */ -#define OP_V_F7 (6 * OP_N_FLAGS) /* 7th operand field */ -#define OP_V_F8 (7 * OP_N_FLAGS) /* 8th operand field */ - -/* Operand processing patterns */ - -#define OP_N (OP_NUL << OP_V_F1) -#define OP_I (OP_IAR << OP_V_F1) -#define OP_J (OP_JAB << OP_V_F1) -#define OP_R (OP_FAB << OP_V_F1) -#define OP_C (OP_CON << OP_V_F1) -#define OP_V (OP_VAR << OP_V_F1) -#define OP_A (OP_ADR << OP_V_F1) -#define OP_K (OP_ADK << OP_V_F1) -#define OP_D (OP_ADD << OP_V_F1) -#define OP_X (OP_ADX << OP_V_F1) -#define OP_T (OP_ADT << OP_V_F1) -#define OP_E (OP_ADE << OP_V_F1) - -#define OP_IA ((OP_IAR << OP_V_F1) | (OP_ADR << OP_V_F2)) -#define OP_JA ((OP_JAB << OP_V_F1) | (OP_ADR << OP_V_F2)) -#define OP_JD ((OP_JAB << OP_V_F1) | (OP_ADD << OP_V_F2)) -#define OP_RC ((OP_FAB << OP_V_F1) | (OP_CON << OP_V_F2)) -#define OP_RK ((OP_FAB << OP_V_F1) | (OP_ADK << OP_V_F2)) -#define OP_RF ((OP_FAB << OP_V_F1) | (OP_ADF << OP_V_F2)) -#define OP_CV ((OP_CON << OP_V_F1) | (OP_VAR << OP_V_F2)) -#define OP_AC ((OP_ADR << OP_V_F1) | (OP_CON << OP_V_F2)) -#define OP_AA ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2)) -#define OP_AK ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2)) -#define OP_AX ((OP_ADR << OP_V_F1) | (OP_ADX << OP_V_F2)) -#define OP_AT ((OP_ADR << OP_V_F1) | (OP_ADT << OP_V_F2)) -#define OP_KV ((OP_ADK << OP_V_F1) | (OP_VAR << OP_V_F2)) -#define OP_KA ((OP_ADK << OP_V_F1) | (OP_ADR << OP_V_F2)) -#define OP_KK ((OP_ADK << OP_V_F1) | (OP_ADK << OP_V_F2)) - -#define OP_IIF ((OP_IAR << OP_V_F1) | (OP_IAR << OP_V_F2) | \ - (OP_ADF << OP_V_F3)) - -#define OP_IAT ((OP_IAR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADT << OP_V_F3)) - -#define OP_CVA ((OP_CON << OP_V_F1) | (OP_VAR << OP_V_F2) | \ - (OP_ADR << OP_V_F3)) - -#define OP_AAA ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADR << OP_V_F3)) - -#define OP_AAF ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADF << OP_V_F3)) - -#define OP_AAX ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADX << OP_V_F3)) - -#define OP_AAT ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADT << OP_V_F3)) - -#define OP_AKA ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2) | \ - (OP_ADR << OP_V_F3)) - -#define OP_AKK ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2) | \ - (OP_ADK << OP_V_F3)) - -#define OP_AXX ((OP_ADR << OP_V_F1) | (OP_ADX << OP_V_F2) | \ - (OP_ADX << OP_V_F3)) - -#define OP_ATT ((OP_ADR << OP_V_F1) | (OP_ADT << OP_V_F2) | \ - (OP_ADT << OP_V_F3)) - -#define OP_AEE ((OP_ADR << OP_V_F1) | (OP_ADE << OP_V_F2) | \ - (OP_ADE << OP_V_F3)) - -#define OP_AAXX ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADX << OP_V_F3) | (OP_ADX << OP_V_F4)) - -#define OP_AAFF ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADF << OP_V_F3) | (OP_ADF << OP_V_F4)) - -#define OP_AAKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADK << OP_V_F3) | (OP_ADK << OP_V_F4)) - -#define OP_KKKK ((OP_ADK << OP_V_F1) | (OP_ADK << OP_V_F2) | \ - (OP_ADK << OP_V_F3) | (OP_ADK << OP_V_F4)) - -#define OP_AAAKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADR << OP_V_F3) | (OP_ADK << OP_V_F4) | \ - (OP_ADK << OP_V_F5)) - -#define OP_AKAKK ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2) | \ - (OP_ADR << OP_V_F3) | (OP_ADK << OP_V_F4) | \ - (OP_ADK << OP_V_F5)) - -#define OP_AAACCC ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADR << OP_V_F3) | (OP_CON << OP_V_F4) | \ - (OP_CON << OP_V_F5) | (OP_CON << OP_V_F6)) - -#define OP_AAFFKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADF << OP_V_F3) | (OP_ADF << OP_V_F4) | \ - (OP_ADK << OP_V_F5) | (OP_ADK << OP_V_F6)) - -#define OP_AAKAKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADK << OP_V_F3) | (OP_ADR << OP_V_F4) | \ - (OP_ADK << OP_V_F5) | (OP_ADK << OP_V_F6)) - -#define OP_CATAKK ((OP_CON << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADT << OP_V_F3) | (OP_ADR << OP_V_F4) | \ - (OP_ADK << OP_V_F5) | (OP_ADK << OP_V_F6)) - -#define OP_CCCACC ((OP_CON << OP_V_F1) | (OP_CON << OP_V_F2) | \ - (OP_CON << OP_V_F3) | (OP_ADR << OP_V_F4) | \ - (OP_CON << OP_V_F5) | (OP_CON << OP_V_F6)) - -#define OP_AAAFFKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADR << OP_V_F3) | (OP_ADF << OP_V_F4) | \ - (OP_ADF << OP_V_F5) | (OP_ADK << OP_V_F6) | \ - (OP_ADK << OP_V_F7)) - -#define OP_AKAKAKK ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2) | \ - (OP_ADR << OP_V_F3) | (OP_ADK << OP_V_F4) | \ - (OP_ADR << OP_V_F5) | (OP_ADK << OP_V_F6) | \ - (OP_ADK << OP_V_F7)) - -#define OP_AAKAKAKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ - (OP_ADK << OP_V_F3) | (OP_ADR << OP_V_F4) | \ - (OP_ADK << OP_V_F5) | (OP_ADR << OP_V_F6) | \ - (OP_ADK << OP_V_F7) | (OP_ADK << OP_V_F8)) - -#define OP_CCACACCA ((OP_CON << OP_V_F1) | (OP_CON << OP_V_F2) | \ - (OP_ADR << OP_V_F3) | (OP_CON << OP_V_F4) | \ - (OP_ADR << OP_V_F5) | (OP_CON << OP_V_F6) | \ - (OP_CON << OP_V_F7) | (OP_ADR << OP_V_F8)) - - -/* Operand precisions (compatible with F-Series FPP): - - - S = 1-word integer - - D = 2-word integer - - F = 2-word single-precision floating-point - - X = 3-word extended-precision floating-point - - T = 4-word double-precision floating-point - - E = 5-word expanded-exponent floating-point - - A = null operand (operand is in FPP accumulator) - - 5-word floating-point numbers are supported by the F-Series Floating-Point - Processor hardware, but the instruction codes are not documented. - - Note that ordering is important, as we depend on the "fp" type codes to - reflect the number of words needed. -*/ - -typedef enum { in_s, in_d, fp_f, fp_x, fp_t, fp_e, fp_a } OPSIZE; - - -/* Conversion from operand size to word count */ - -#define TO_COUNT(s) ((s == fp_a) ? 0 : (uint32) (s + (s < fp_f))) - - -/* HP in-memory representation of a packed floating-point number. - Actual value will use two, three, four, or five words, as needed. -*/ - -typedef uint16 FPK[5]; - - -/* Operand processing types. - - NOTE: Microsoft VC++ 6.0 does not support the C99 standard, so we cannot - initialize unions by arbitrary variant ("designated initializers"). - Therefore, we follow the C90 form of initializing via the first named - variant. The FPK variant must appear first in the OP structure, as we define - a number of FPK constants in other modules. -*/ - -typedef union { /* general operand */ - FPK fpk; /* floating-point value */ - uint16 word; /* 16-bit integer */ - uint32 dword; /* 32-bit integer */ - } OP; - -typedef OP OPS[OP_N_F]; /* operand array */ - -typedef uint32 OP_PAT; /* operand pattern */ - - -/* Microcode dispatcher functions (grouped by cpu module number) */ - -extern t_stat cpu_ds (uint32 IR, uint32 intrq); /* [0] Distributed System stub */ -extern t_stat cpu_user (uint32 IR, uint32 intrq); /* [0] User firmware dispatcher */ -extern t_stat cpu_user_20 (uint32 IR, uint32 intrq); /* [0] Module 20 user microprograms stub */ - -extern t_stat cpu_eau (uint32 IR, uint32 intrq); /* [1] EAU group simulator */ -extern t_stat cpu_uig_0 (uint32 IR, uint32 intrq, uint32 iotrap); /* [1] UIG group 0 dispatcher */ -extern t_stat cpu_uig_1 (uint32 IR, uint32 intrq, uint32 iotrap); /* [1] UIG group 1 dispatcher */ - -#if !defined (HAVE_INT64) /* int64 support unavailable */ -extern t_stat cpu_fp (uint32 IR, uint32 intrq); /* [2] Firmware Floating Point */ -#endif -extern t_stat cpu_dms (uint32 IR, uint32 intrq); /* [2] Dynamic mapping system */ -extern t_stat cpu_eig (uint32 IR, uint32 intrq); /* [2] Extended instruction group */ -extern t_stat cpu_iop (uint32 IR, uint32 intrq); /* [2] 2000 I/O Processor */ - -extern t_stat cpu_ffp (uint32 IR, uint32 intrq); /* [3] Fast FORTRAN Processor */ -extern t_stat cpu_dbi (uint32 IR, uint32 intrq); /* [3] Double-Integer instructions */ - -#if defined (HAVE_INT64) /* int64 support available */ -extern t_stat cpu_fpp (uint32 IR, uint32 intrq); /* [4] Floating Point Processor */ -extern t_stat cpu_sis (uint32 IR, uint32 intrq); /* [4] Scientific Instruction Set */ -#endif - -extern t_stat cpu_rte_vma (uint32 IR, uint32 intrq); /* [5] RTE-6 VMA */ -extern t_stat cpu_rte_ema (uint32 IR, uint32 intrq); /* [5] RTE-IV EMA */ - -extern t_stat cpu_rte_os (uint32 IR, uint32 intrq, uint32 iotrap); /* [6] RTE-6 OS */ - -#if defined (HAVE_INT64) /* int64 support available */ -extern t_stat cpu_vis (uint32 IR, uint32 intrq); /* [7] Vector Instruction Set */ -extern t_stat cpu_signal (uint32 IR, uint32 intrq); /* [7] SIGNAL/1000 Instructions */ -#endif - - -/* Microcode helper functions */ - -extern OP ReadOp (uint32 va, OPSIZE precision); /* generalized operand read */ -extern void WriteOp (uint32 va, OP operand, OPSIZE precision); /* generalized operand write */ -extern t_stat cpu_ops (OP_PAT pattern, OPS op, uint32 irq); /* operand processor */ - -extern void fprint_ops (OP_PAT pattern, OPS op); /* debug print operands */ -extern void fprint_regs (char *caption, uint32 regs, uint32 base); /* debug print CPU registers */ - -/* implemented in hp2100_cpu5.c (RTE-IV EMA functions) */ - -extern t_stat cpu_ema_eres (uint32 *rtn, uint32 dtbl, uint32 atbl, t_bool debug); -extern t_stat cpu_ema_eseg (uint32 *rtn, uint32 ir, uint32 tbl, t_bool debug); -extern t_stat cpu_ema_vset (uint32 *rtn, OPS op, t_bool debug); - -#endif +/* hp2100_cpu1.h: HP 2100/1000 firmware dispatcher definitions + + Copyright (c) 2006-2013, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + 18-Mar-13 JDB Added externs for microcode helper functions + 14-Mar-13 MP Changed guard macro name to avoid reserved namespace + 11-Sep-08 JDB Moved microcode function prototypes here + 30-Apr-08 JDB Corrected OP_AFF to OP_AAFF for SIGNAL/1000 + Removed unused operand patterns + 23-Feb-08 HV Added more OP_* for SIGNAL/1000 and VIS + 28-Nov-07 JDB Added fprint_ops, fprint_regs for debug printouts + 19-Oct-07 JDB Revised OP_KKKAKK operand profile to OP_CCCACC for $LOC + 16-Oct-06 JDB Generalized operands for F-Series FP types + 26-Sep-06 JDB Split from hp2100_cpu1.c +*/ + +#ifndef HP2100_CPU1_H_ +#define HP2100_CPU1_H_ + + +/* Register print encoding */ + +#define REG_COUNT 9 /* count of print flags */ + +#define REG_CIR (1 << 0) /* print central interrupt register */ +#define REG_A (1 << 1) /* print A register */ +#define REG_B (1 << 2) /* print B register */ +#define REG_E (1 << 3) /* print E register */ +#define REG_X (1 << 4) /* print X register */ +#define REG_Y (1 << 5) /* print Y register */ +#define REG_O (1 << 6) /* print O register */ +#define REG_P (1 << 7) /* print P register */ +#define REG_P_REL (1 << 8) /* print P register as relative */ + + +/* Operand processing encoding */ + +/* Base operand types. Note that all address encodings must be grouped together + after OP_ADR. +*/ + +#define OP_NUL 0 /* no operand */ +#define OP_IAR 1 /* 1-word int in A reg */ +#define OP_JAB 2 /* 2-word int in A/B regs */ +#define OP_FAB 3 /* 2-word FP const in A/B regs */ +#define OP_CON 4 /* inline 1-word constant */ +#define OP_VAR 5 /* inline 1-word variable */ + +#define OP_ADR 6 /* inline address */ +#define OP_ADK 7 /* addr of 1-word int const */ +#define OP_ADD 8 /* addr of 2-word int const */ +#define OP_ADF 9 /* addr of 2-word FP const */ +#define OP_ADX 10 /* addr of 3-word FP const */ +#define OP_ADT 11 /* addr of 4-word FP const */ +#define OP_ADE 12 /* addr of 5-word FP const */ + +#define OP_N_FLAGS 4 /* number of bits needed for flags */ +#define OP_M_FLAGS ((1 << OP_N_FLAGS) - 1) /* mask for flag bits */ + +#define OP_N_F (8 * sizeof (uint32) / OP_N_FLAGS) /* max number of op fields */ + +#define OP_V_F1 (0 * OP_N_FLAGS) /* 1st operand field */ +#define OP_V_F2 (1 * OP_N_FLAGS) /* 2nd operand field */ +#define OP_V_F3 (2 * OP_N_FLAGS) /* 3rd operand field */ +#define OP_V_F4 (3 * OP_N_FLAGS) /* 4th operand field */ +#define OP_V_F5 (4 * OP_N_FLAGS) /* 5th operand field */ +#define OP_V_F6 (5 * OP_N_FLAGS) /* 6th operand field */ +#define OP_V_F7 (6 * OP_N_FLAGS) /* 7th operand field */ +#define OP_V_F8 (7 * OP_N_FLAGS) /* 8th operand field */ + +/* Operand processing patterns */ + +#define OP_N (OP_NUL << OP_V_F1) +#define OP_I (OP_IAR << OP_V_F1) +#define OP_J (OP_JAB << OP_V_F1) +#define OP_R (OP_FAB << OP_V_F1) +#define OP_C (OP_CON << OP_V_F1) +#define OP_V (OP_VAR << OP_V_F1) +#define OP_A (OP_ADR << OP_V_F1) +#define OP_K (OP_ADK << OP_V_F1) +#define OP_D (OP_ADD << OP_V_F1) +#define OP_X (OP_ADX << OP_V_F1) +#define OP_T (OP_ADT << OP_V_F1) +#define OP_E (OP_ADE << OP_V_F1) + +#define OP_IA ((OP_IAR << OP_V_F1) | (OP_ADR << OP_V_F2)) +#define OP_JA ((OP_JAB << OP_V_F1) | (OP_ADR << OP_V_F2)) +#define OP_JD ((OP_JAB << OP_V_F1) | (OP_ADD << OP_V_F2)) +#define OP_RC ((OP_FAB << OP_V_F1) | (OP_CON << OP_V_F2)) +#define OP_RK ((OP_FAB << OP_V_F1) | (OP_ADK << OP_V_F2)) +#define OP_RF ((OP_FAB << OP_V_F1) | (OP_ADF << OP_V_F2)) +#define OP_CV ((OP_CON << OP_V_F1) | (OP_VAR << OP_V_F2)) +#define OP_AC ((OP_ADR << OP_V_F1) | (OP_CON << OP_V_F2)) +#define OP_AA ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2)) +#define OP_AK ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2)) +#define OP_AX ((OP_ADR << OP_V_F1) | (OP_ADX << OP_V_F2)) +#define OP_AT ((OP_ADR << OP_V_F1) | (OP_ADT << OP_V_F2)) +#define OP_KV ((OP_ADK << OP_V_F1) | (OP_VAR << OP_V_F2)) +#define OP_KA ((OP_ADK << OP_V_F1) | (OP_ADR << OP_V_F2)) +#define OP_KK ((OP_ADK << OP_V_F1) | (OP_ADK << OP_V_F2)) + +#define OP_IIF ((OP_IAR << OP_V_F1) | (OP_IAR << OP_V_F2) | \ + (OP_ADF << OP_V_F3)) + +#define OP_IAT ((OP_IAR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADT << OP_V_F3)) + +#define OP_CVA ((OP_CON << OP_V_F1) | (OP_VAR << OP_V_F2) | \ + (OP_ADR << OP_V_F3)) + +#define OP_AAA ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADR << OP_V_F3)) + +#define OP_AAF ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADF << OP_V_F3)) + +#define OP_AAX ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADX << OP_V_F3)) + +#define OP_AAT ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADT << OP_V_F3)) + +#define OP_AKA ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2) | \ + (OP_ADR << OP_V_F3)) + +#define OP_AKK ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2) | \ + (OP_ADK << OP_V_F3)) + +#define OP_AXX ((OP_ADR << OP_V_F1) | (OP_ADX << OP_V_F2) | \ + (OP_ADX << OP_V_F3)) + +#define OP_ATT ((OP_ADR << OP_V_F1) | (OP_ADT << OP_V_F2) | \ + (OP_ADT << OP_V_F3)) + +#define OP_AEE ((OP_ADR << OP_V_F1) | (OP_ADE << OP_V_F2) | \ + (OP_ADE << OP_V_F3)) + +#define OP_AAXX ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADX << OP_V_F3) | (OP_ADX << OP_V_F4)) + +#define OP_AAFF ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADF << OP_V_F3) | (OP_ADF << OP_V_F4)) + +#define OP_AAKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADK << OP_V_F3) | (OP_ADK << OP_V_F4)) + +#define OP_KKKK ((OP_ADK << OP_V_F1) | (OP_ADK << OP_V_F2) | \ + (OP_ADK << OP_V_F3) | (OP_ADK << OP_V_F4)) + +#define OP_AAAKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADR << OP_V_F3) | (OP_ADK << OP_V_F4) | \ + (OP_ADK << OP_V_F5)) + +#define OP_AKAKK ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2) | \ + (OP_ADR << OP_V_F3) | (OP_ADK << OP_V_F4) | \ + (OP_ADK << OP_V_F5)) + +#define OP_AAACCC ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADR << OP_V_F3) | (OP_CON << OP_V_F4) | \ + (OP_CON << OP_V_F5) | (OP_CON << OP_V_F6)) + +#define OP_AAFFKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADF << OP_V_F3) | (OP_ADF << OP_V_F4) | \ + (OP_ADK << OP_V_F5) | (OP_ADK << OP_V_F6)) + +#define OP_AAKAKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADK << OP_V_F3) | (OP_ADR << OP_V_F4) | \ + (OP_ADK << OP_V_F5) | (OP_ADK << OP_V_F6)) + +#define OP_CATAKK ((OP_CON << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADT << OP_V_F3) | (OP_ADR << OP_V_F4) | \ + (OP_ADK << OP_V_F5) | (OP_ADK << OP_V_F6)) + +#define OP_CCCACC ((OP_CON << OP_V_F1) | (OP_CON << OP_V_F2) | \ + (OP_CON << OP_V_F3) | (OP_ADR << OP_V_F4) | \ + (OP_CON << OP_V_F5) | (OP_CON << OP_V_F6)) + +#define OP_AAAFFKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADR << OP_V_F3) | (OP_ADF << OP_V_F4) | \ + (OP_ADF << OP_V_F5) | (OP_ADK << OP_V_F6) | \ + (OP_ADK << OP_V_F7)) + +#define OP_AKAKAKK ((OP_ADR << OP_V_F1) | (OP_ADK << OP_V_F2) | \ + (OP_ADR << OP_V_F3) | (OP_ADK << OP_V_F4) | \ + (OP_ADR << OP_V_F5) | (OP_ADK << OP_V_F6) | \ + (OP_ADK << OP_V_F7)) + +#define OP_AAKAKAKK ((OP_ADR << OP_V_F1) | (OP_ADR << OP_V_F2) | \ + (OP_ADK << OP_V_F3) | (OP_ADR << OP_V_F4) | \ + (OP_ADK << OP_V_F5) | (OP_ADR << OP_V_F6) | \ + (OP_ADK << OP_V_F7) | (OP_ADK << OP_V_F8)) + +#define OP_CCACACCA ((OP_CON << OP_V_F1) | (OP_CON << OP_V_F2) | \ + (OP_ADR << OP_V_F3) | (OP_CON << OP_V_F4) | \ + (OP_ADR << OP_V_F5) | (OP_CON << OP_V_F6) | \ + (OP_CON << OP_V_F7) | (OP_ADR << OP_V_F8)) + + +/* Operand precisions (compatible with F-Series FPP): + + - S = 1-word integer + - D = 2-word integer + - F = 2-word single-precision floating-point + - X = 3-word extended-precision floating-point + - T = 4-word double-precision floating-point + - E = 5-word expanded-exponent floating-point + - A = null operand (operand is in FPP accumulator) + + 5-word floating-point numbers are supported by the F-Series Floating-Point + Processor hardware, but the instruction codes are not documented. + + Note that ordering is important, as we depend on the "fp" type codes to + reflect the number of words needed. +*/ + +typedef enum { in_s, in_d, fp_f, fp_x, fp_t, fp_e, fp_a } OPSIZE; + + +/* Conversion from operand size to word count */ + +#define TO_COUNT(s) ((s == fp_a) ? 0 : (uint32) (s + (s < fp_f))) + + +/* HP in-memory representation of a packed floating-point number. + Actual value will use two, three, four, or five words, as needed. +*/ + +typedef uint16 FPK[5]; + + +/* Operand processing types. + + NOTE: Microsoft VC++ 6.0 does not support the C99 standard, so we cannot + initialize unions by arbitrary variant ("designated initializers"). + Therefore, we follow the C90 form of initializing via the first named + variant. The FPK variant must appear first in the OP structure, as we define + a number of FPK constants in other modules. +*/ + +typedef union { /* general operand */ + FPK fpk; /* floating-point value */ + uint16 word; /* 16-bit integer */ + uint32 dword; /* 32-bit integer */ + } OP; + +typedef OP OPS[OP_N_F]; /* operand array */ + +typedef uint32 OP_PAT; /* operand pattern */ + + +/* Microcode dispatcher functions (grouped by cpu module number) */ + +extern t_stat cpu_ds (uint32 IR, uint32 intrq); /* [0] Distributed System stub */ +extern t_stat cpu_user (uint32 IR, uint32 intrq); /* [0] User firmware dispatcher */ +extern t_stat cpu_user_20 (uint32 IR, uint32 intrq); /* [0] Module 20 user microprograms stub */ + +extern t_stat cpu_eau (uint32 IR, uint32 intrq); /* [1] EAU group simulator */ +extern t_stat cpu_uig_0 (uint32 IR, uint32 intrq, uint32 iotrap); /* [1] UIG group 0 dispatcher */ +extern t_stat cpu_uig_1 (uint32 IR, uint32 intrq, uint32 iotrap); /* [1] UIG group 1 dispatcher */ + +#if !defined (HAVE_INT64) /* int64 support unavailable */ +extern t_stat cpu_fp (uint32 IR, uint32 intrq); /* [2] Firmware Floating Point */ +#endif +extern t_stat cpu_dms (uint32 IR, uint32 intrq); /* [2] Dynamic mapping system */ +extern t_stat cpu_eig (uint32 IR, uint32 intrq); /* [2] Extended instruction group */ +extern t_stat cpu_iop (uint32 IR, uint32 intrq); /* [2] 2000 I/O Processor */ + +extern t_stat cpu_ffp (uint32 IR, uint32 intrq); /* [3] Fast FORTRAN Processor */ +extern t_stat cpu_dbi (uint32 IR, uint32 intrq); /* [3] Double-Integer instructions */ + +#if defined (HAVE_INT64) /* int64 support available */ +extern t_stat cpu_fpp (uint32 IR, uint32 intrq); /* [4] Floating Point Processor */ +extern t_stat cpu_sis (uint32 IR, uint32 intrq); /* [4] Scientific Instruction Set */ +#endif + +extern t_stat cpu_rte_vma (uint32 IR, uint32 intrq); /* [5] RTE-6 VMA */ +extern t_stat cpu_rte_ema (uint32 IR, uint32 intrq); /* [5] RTE-IV EMA */ + +extern t_stat cpu_rte_os (uint32 IR, uint32 intrq, uint32 iotrap); /* [6] RTE-6 OS */ + +#if defined (HAVE_INT64) /* int64 support available */ +extern t_stat cpu_vis (uint32 IR, uint32 intrq); /* [7] Vector Instruction Set */ +extern t_stat cpu_signal (uint32 IR, uint32 intrq); /* [7] SIGNAL/1000 Instructions */ +#endif + + +/* Microcode helper functions */ + +extern OP ReadOp (uint32 va, OPSIZE precision); /* generalized operand read */ +extern void WriteOp (uint32 va, OP operand, OPSIZE precision); /* generalized operand write */ +extern t_stat cpu_ops (OP_PAT pattern, OPS op, uint32 irq); /* operand processor */ + +extern void fprint_ops (OP_PAT pattern, OPS op); /* debug print operands */ +extern void fprint_regs (char *caption, uint32 regs, uint32 base); /* debug print CPU registers */ + +/* implemented in hp2100_cpu5.c (RTE-IV EMA functions) */ + +extern t_stat cpu_ema_eres (uint32 *rtn, uint32 dtbl, uint32 atbl, t_bool debug); +extern t_stat cpu_ema_eseg (uint32 *rtn, uint32 ir, uint32 tbl, t_bool debug); +extern t_stat cpu_ema_vset (uint32 *rtn, OPS op, t_bool debug); + +#endif diff --git a/HP2100/hp2100_cpu2.c b/HP2100/hp2100_cpu2.c index f303b8b6..12f4ab25 100644 --- a/HP2100/hp2100_cpu2.c +++ b/HP2100/hp2100_cpu2.c @@ -1,1130 +1,1131 @@ -/* hp2100_cpu2.c: HP 2100/1000 FP/DMS/EIG/IOP instructions - - Copyright (c) 2005-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - CPU2 Floating-point, dynamic mapping, extended, and I/O processor - instructions - - 24-Dec-14 JDB Added casts for explicit downward conversions - 09-May-12 JDB Separated assignments from conditional expressions - 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h - 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) - 05-Aug-08 JDB Updated mp_dms_jmp calling sequence - Fixed DJP, SJP, and UJP jump target validation - 30-Jul-08 JDB RVA/B conditionally updates dms_vr before returning value - 19-Dec-06 JDB DMS self-test now executes as NOP on 1000-M - 01-Dec-06 JDB Substitutes FPP for firmware FP if HAVE_INT64 - 26-Sep-06 JDB Moved from hp2100_cpu1.c to simplify extensions - 22-Feb-05 JDB Fixed missing MPCK on JRS target - 21-Jan-05 JDB Reorganized CPU option and operand processing flags - Split code along microcode modules - 15-Jan-05 RMS Cloned from hp2100_cpu.c - - Primary references: - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - Macro/1000 Reference Manual (92059-90001, Dec-1992) - - Additional references are listed with the associated firmware - implementations, as are the HP option model numbers pertaining to the - applicable CPUs. -*/ - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - -#if !defined (HAVE_INT64) /* int64 support unavailable */ - -#include "hp2100_fp.h" - - -/* Single-Precision Floating Point Instructions - - The 2100 and 1000 CPUs share the single-precision (two word) floating-point - instruction codes. Floating-point firmware was an option on the 2100 and was - standard on the 1000-M and E. The 1000-F had a standard hardware Floating - Point Processor that executed these six instructions and added extended- and - double-precision floating- point instructions, as well as double-integer - instructions (the FPP is simulated separately). - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A 12901A std std N/A - - The instruction codes for the 2100 and 1000-M/E systems are mapped to - routines as follows: - - Instr. 2100/1000-M/E Description - ------ ------------- ----------------------------------- - 105000 FAD Single real add - 105020 FSB Single real subtract - 105040 FMP Single real multiply - 105060 FDV Single real divide - 105100 FIX Single integer to single real fix - 105120 FLT Single real to single integer float - - Bits 3-0 are not decoded by these instructions, so FAD (e.g.) would be - executed by any instruction in the range 105000-105017. - - Implementation note: rather than have two simulators that each executes the - single-precision FP instruction set, we compile conditionally, based on the - availability of 64-bit integer support in the host compiler. 64-bit integers - are required for the FPP, so if they are available, then the FPP is used to - handle the six single-precision instructions for the 2100 and M/E-Series, and - this function is omitted. If support is unavailable, this function is used - instead. - - Implementation note: the operands to FAD, etc. are floating-point values, so - OP_F would normally be used. However, the firmware FP support routines want - floating-point operands as 32-bit integer values, so OP_D is used to achieve - this. -*/ - -static const OP_PAT op_fp[8] = { - OP_D, OP_D, OP_D, OP_D, /* FAD FSB FMP FDV */ - OP_N, OP_N, OP_N, OP_N /* FIX FLT --- --- */ - }; - -t_stat cpu_fp (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -uint32 entry; - -entry = (IR >> 4) & 017; /* mask to entry point */ - -if (op_fp [entry] != OP_N) { - reason = cpu_ops (op_fp [entry], op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -switch (entry) { /* decode IR<7:4> */ - - case 000: /* FAD 105000 (OP_D) */ - O = f_as (op[0].dword, 0); /* add, upd ovflo */ - break; - - case 001: /* FSB 105020 (OP_D) */ - O = f_as (op[0].dword, 1); /* sub, upd ovflo */ - break; - - case 002: /* FMP 105040 (OP_D) */ - O = f_mul (op[0].dword); /* mul, upd ovflo */ - break; - - case 003: /* FDV 105060 (OP_D) */ - O = f_div (op[0].dword); /* div, upd ovflo */ - break; - - case 004: /* FIX 105100 (OP_N) */ - O = f_fix (); /* fix, upd ovflo */ - break; - - case 005: /* FLT 105120 (OP_N) */ - O = f_flt (); /* float, upd ovflo */ - break; - - default: /* should be impossible */ - return SCPE_IERR; - } - -return reason; -} - -#endif /* int64 support unavailable */ - - -/* Dynamic Mapping System - - The 1000 Dynamic Mapping System (DMS) consisted of the 12731A Memory - Expansion Module (MEM) card and 38 instructions to expand the basic 32K - logical address space to a 1024K physical space. The MEM provided four maps - of 32 mapping registers each: a system map, a user map, and two DCPC maps. - DMS worked in conjunction with memory protect to provide a "protected mode" - in which memory read and write violations could be trapped, and that - inhibited "privileged" instruction execution that attempted to alter the - memory mapping. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A 12976B 13307B std - - The instruction codes are mapped to routines as follows: - - Instr. 1000-M 1000-E/F Instr. 1000-M 1000-E/F - ------ ------ -------- ------ ------ -------- - 10x700 [xmm] [xmm] 10x720 XMM XMM - 10x701 [nop] [test] 10x721 XMS XMS - 10x702 MBI MBI 10x722 XM* XM* - 10x703 MBF MBF 10x723 [nop] [nop] - 10x704 MBW MBW 10x724 XL* XL* - 10x705 MWI MWI 10x725 XS* XS* - 10x706 MWF MWF 10x726 XC* XC* - 10x707 MWW MWW 10x727 LF* LF* - 10x710 SY* SY* 10x730 RS* RS* - - 10x711 US* US* 10x731 RV* RV* - 10x712 PA* PA* 10x732 DJP DJP - 10x713 PB* PB* 10x733 DJS DJS - 10x714 SSM SSM 10x734 SJP SJP - 10x715 JRS JRS 10x735 SJS SJS - 10x716 [nop] [nop] 10x736 UJP UJP - 10x717 [nop] [nop] 10x737 UJS UJS - - Instructions that use IR bit 9 to select the A or B register are designated - with a * above (e.g., 101710 is SYA, and 105710 is SYB). For those that do - not use this feature, either the 101xxx or 105xxx code will execute the - corresponding instruction, although the 105xxx form is the documented - instruction code. - - Implementation notes: - - 1. Instruction code 10x700 will execute the XMM instruction, although 10x720 - is the documented instruction value. - - 2. Instruction code 10x701 will complement the A or B register, as - indicated, on 1000-E and F-Series machines. This instruction is a NOP on - M-Series machines. - - 3. The DMS privilege violation rules are: - - load map and CTL5 set (XMM, XMS, XM*, SY*, US*, PA*, PB*) - - load state or fence and UMAP set (JRS, DJP, DJS, SJP, SJS, UJP, UJS, LF*) - - 4. The 1000 manual is incorrect in stating that M*I, M*W, XS* are - privileged. - - 5. The protected memory lower bound for the DJP, SJP, UJP, and JRS - instructions is 2. -*/ - -static const OP_PAT op_dms[32] = { - OP_N, OP_N, OP_N, OP_N, /* [xmm] [test] MBI MBF */ - OP_N, OP_N, OP_N, OP_N, /* MBW MWI MWF MWW */ - OP_N, OP_N, OP_N, OP_N, /* SYA/B USA/B PAA/B PBA/B */ - OP_A, OP_KA, OP_N, OP_N, /* SSM JRS nop nop */ - OP_N, OP_N, OP_N, OP_N, /* XMM XMS XMA/B nop */ - OP_A, OP_A, OP_A, OP_N, /* XLA/B XSA/B XCA/B LFA/B */ - OP_N, OP_N, OP_A, OP_A, /* RSA/B RVA/B DJP DJS */ - OP_A, OP_A, OP_A, OP_A /* SJP SJS UJP UJS */ - }; - -t_stat cpu_dms (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -uint32 entry, absel; -uint32 i, t, mapi, mapj; - -absel = (IR & I_AB)? 1: 0; /* get A/B select */ -entry = IR & 037; /* mask to entry point */ - -if (op_dms [entry] != OP_N) { - reason = cpu_ops (op_dms [entry], op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -switch (entry) { /* decode IR<3:0> */ - -/* DMS module 1 */ - - case 000: /* [undefined] 105700 (OP_N) */ - goto XMM; /* decodes as XMM */ - - case 001: /* [self test] 105701 (OP_N) */ - if (UNIT_CPU_MODEL != UNIT_1000_M) /* executes as NOP on 1000-M */ - ABREG[absel] = ~ABREG[absel]; /* CMA or CMB */ - break; - - case 002: /* MBI 105702 (OP_N) */ - AR = AR & ~1; /* force A, B even */ - BR = BR & ~1; - while (XR != 0) { /* loop */ - t = ReadB (AR); /* read curr */ - WriteBA (BR, t); /* write alt */ - AR = (AR + 1) & DMASK; /* incr ptrs */ - BR = (BR + 1) & DMASK; - XR = (XR - 1) & DMASK; - if (XR && intrq && !(AR & 1)) { /* more, int, even? */ - PC = err_PC; /* stop for now */ - break; - } - } - break; - - case 003: /* MBF 105703 (OP_N) */ - AR = AR & ~1; /* force A, B even */ - BR = BR & ~1; - while (XR != 0) { /* loop */ - t = ReadBA (AR); /* read alt */ - WriteB (BR, t); /* write curr */ - AR = (AR + 1) & DMASK; /* incr ptrs */ - BR = (BR + 1) & DMASK; - XR = (XR - 1) & DMASK; - if (XR && intrq && !(AR & 1)) { /* more, int, even? */ - PC = err_PC; /* stop for now */ - break; - } - } - break; - - case 004: /* MBW 105704 (OP_N) */ - AR = AR & ~1; /* force A, B even */ - BR = BR & ~1; - while (XR != 0) { /* loop */ - t = ReadBA (AR); /* read alt */ - WriteBA (BR, t); /* write alt */ - AR = (AR + 1) & DMASK; /* incr ptrs */ - BR = (BR + 1) & DMASK; - XR = (XR - 1) & DMASK; - if (XR && intrq && !(AR & 1)) { /* more, int, even? */ - PC = err_PC; /* stop for now */ - break; - } - } - break; - - case 005: /* MWI 105705 (OP_N) */ - while (XR != 0) { /* loop */ - t = ReadW (AR & VAMASK); /* read curr */ - WriteWA (BR & VAMASK, t); /* write alt */ - AR = (AR + 1) & DMASK; /* incr ptrs */ - BR = (BR + 1) & DMASK; - XR = (XR - 1) & DMASK; - if (XR && intrq) { /* more and intr? */ - PC = err_PC; /* stop for now */ - break; - } - } - break; - - case 006: /* MWF 105706 (OP_N) */ - while (XR != 0) { /* loop */ - t = ReadWA (AR & VAMASK); /* read alt */ - WriteW (BR & VAMASK, t); /* write curr */ - AR = (AR + 1) & DMASK; /* incr ptrs */ - BR = (BR + 1) & DMASK; - XR = (XR - 1) & DMASK; - if (XR && intrq) { /* more and intr? */ - PC = err_PC; /* stop for now */ - break; - } - } - break; - - case 007: /* MWW 105707 (OP_N) */ - while (XR != 0) { /* loop */ - t = ReadWA (AR & VAMASK); /* read alt */ - WriteWA (BR & VAMASK, t); /* write alt */ - AR = (AR + 1) & DMASK; /* incr ptrs */ - BR = (BR + 1) & DMASK; - XR = (XR - 1) & DMASK; - if (XR && intrq) { /* more and intr? */ - PC = err_PC; /* stop for now */ - break; - } - } - break; - - case 010: /* SYA, SYB 10x710 (OP_N) */ - case 011: /* USA, USB 10x711 (OP_N) */ - case 012: /* PAA, PAB 10x712 (OP_N) */ - case 013: /* PBA, PBB 10x713 (OP_N) */ - mapi = (IR & 03) << VA_N_PAG; /* map base */ - if (ABREG[absel] & SIGN) { /* store? */ - for (i = 0; i < MAP_LNT; i++) { - t = dms_rmap (mapi + i); /* map to memory */ - WriteW ((ABREG[absel] + i) & VAMASK, t); - } - } - else { /* load */ - dms_viol (err_PC, MVI_PRV); /* priv if PRO */ - for (i = 0; i < MAP_LNT; i++) { - t = ReadW ((ABREG[absel] + i) & VAMASK); - dms_wmap (mapi + i, t); /* mem to map */ - } - } - ABREG[absel] = (ABREG[absel] + MAP_LNT) & DMASK; - break; - - case 014: /* SSM 105714 (OP_A) */ - WriteW (op[0].word, dms_upd_sr ()); /* store stat */ - break; - - case 015: /* JRS 105715 (OP_KA) */ - if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - dms_enb = 0; /* assume off */ - dms_ump = SMAP; - if (op[0].word & 0100000) { /* set enable? */ - dms_enb = 1; - if (op[0].word & 0040000) dms_ump = UMAP; /* set/clr usr */ - } - mp_dms_jmp (op[1].word, 2); /* mpck jmp target */ - PCQ_ENTRY; /* save old PC */ - PC = op[1].word; /* jump */ - ion_defer = 1; /* defer intr */ - break; - -/* DMS module 2 */ - - case 020: /* XMM 105720 (OP_N) */ - XMM: - if (XR == 0) break; /* nop? */ - while (XR != 0) { /* loop */ - if (XR & SIGN) { /* store? */ - t = dms_rmap (AR); /* map to mem */ - WriteW (BR & VAMASK, t); - XR = (XR + 1) & DMASK; - } - else { /* load */ - dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - t = ReadW (BR & VAMASK); /* mem to map */ - dms_wmap (AR, t); - XR = (XR - 1) & DMASK; - } - AR = (AR + 1) & DMASK; - BR = (BR + 1) & DMASK; - if (intrq && ((XR & 017) == 017)) { /* intr, grp of 16? */ - PC = err_PC; /* stop for now */ - break; - } - } - break; - - case 021: /* XMS 105721 (OP_N) */ - if ((XR & SIGN) || (XR == 0)) break; /* nop? */ - dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - while (XR != 0) { - dms_wmap (AR, BR); /* AR to map */ - XR = (XR - 1) & DMASK; - AR = (AR + 1) & DMASK; - BR = (BR + 1) & DMASK; - if (intrq && ((XR & 017) == 017)) { /* intr, grp of 16? */ - PC = err_PC; - break; - } - } - break; - - case 022: /* XMA, XMB 10x722 (OP_N) */ - dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - if (ABREG[absel] & 0100000) mapi = UMAP; - else mapi = SMAP; - if (ABREG[absel] & 0000001) mapj = PBMAP; - else mapj = PAMAP; - for (i = 0; i < MAP_LNT; i++) { - t = dms_rmap (mapi + i); /* read map */ - dms_wmap (mapj + i, t); /* write map */ - } - break; - - case 024: /* XLA, XLB 10x724 (OP_A) */ - ABREG[absel] = ReadWA (op[0].word); /* load alt */ - break; - - case 025: /* XSA, XSB 10x725 (OP_A) */ - WriteWA (op[0].word, ABREG[absel]); /* store alt */ - break; - - case 026: /* XCA, XCB 10x726 (OP_A) */ - if (ABREG[absel] != ReadWA (op[0].word)) /* compare alt */ - PC = (PC + 1) & VAMASK; - break; - - case 027: /* LFA, LFB 10x727 (OP_N) */ - if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - dms_sr = (dms_sr & ~(MST_FLT | MST_FENCE)) | - (ABREG[absel] & (MST_FLT | MST_FENCE)); - break; - - case 030: /* RSA, RSB 10x730 (OP_N) */ - ABREG[absel] = (uint16) dms_upd_sr (); /* save stat */ - break; - - case 031: /* RVA, RVB 10x731 (OP_N) */ - ABREG[absel] = (uint16) dms_upd_vr (err_PC); /* return updated violation register */ - break; - - case 032: /* DJP 105732 (OP_A) */ - if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - dms_enb = 0; /* disable map */ - dms_ump = SMAP; - mp_dms_jmp (op[0].word, 2); /* validate jump addr */ - PCQ_ENTRY; /* save curr PC */ - PC = op[0].word; /* new PC */ - ion_defer = 1; - break; - - case 033: /* DJS 105733 (OP_A) */ - if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - WriteW (op[0].word, PC); /* store ret addr */ - PCQ_ENTRY; /* save curr PC */ - PC = (op[0].word + 1) & VAMASK; /* new PC */ - dms_enb = 0; /* disable map */ - dms_ump = SMAP; - ion_defer = 1; /* defer intr */ - break; - - case 034: /* SJP 105734 (OP_A) */ - if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - dms_enb = 1; /* enable system */ - dms_ump = SMAP; - mp_dms_jmp (op[0].word, 2); /* validate jump addr */ - PCQ_ENTRY; /* save curr PC */ - PC = op[0].word; /* jump */ - ion_defer = 1; /* defer intr */ - break; - - case 035: /* SJS 105735 (OP_A) */ - if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - t = PC; /* save retn addr */ - PCQ_ENTRY; /* save curr PC */ - PC = (op[0].word + 1) & VAMASK; /* new PC */ - dms_enb = 1; /* enable system */ - dms_ump = SMAP; - WriteW (op[0].word, t); /* store ret addr */ - ion_defer = 1; /* defer intr */ - break; - - case 036: /* UJP 105736 (OP_A) */ - if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - dms_enb = 1; /* enable user */ - dms_ump = UMAP; - mp_dms_jmp (op[0].word, 2); /* validate jump addr */ - PCQ_ENTRY; /* save curr PC */ - PC = op[0].word; /* jump */ - ion_defer = 1; /* defer intr */ - break; - - case 037: /* UJS 105737 (OP_A) */ - if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ - t = PC; /* save retn addr */ - PCQ_ENTRY; /* save curr PC */ - PC = (op[0].word + 1) & VAMASK; /* new PC */ - dms_enb = 1; /* enable user */ - dms_ump = UMAP; - WriteW (op[0].word, t); /* store ret addr */ - ion_defer = 1; /* defer intr */ - break; - - default: /* others NOP */ - break; - } - -return reason; -} - - -/* Extended Instruction Group - - The Extended Instruction Group (EIG) adds 32 index and 10 bit/byte/word - manipulation instructions to the 1000 base set. These instructions - use the new X and Y index registers that were added to the 1000. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A std std std - - The instruction codes are mapped to routines as follows: - - Instr. 1000-M/E/F Instr. 1000-M/E/F - ------ ---------- ------ ---------- - 10x740 S*X 10x760 ISX - 10x741 C*X 10x761 DSX - 10x742 L*X 10x762 JLY - 10x743 STX 10x763 LBT - 10x744 CX* 10x764 SBT - 10x745 LDX 10x765 MBT - 10x746 ADX 10x766 CBT - 10x747 X*X 10x767 SFB - - 10x750 S*Y 10x770 ISY - 10x751 C*Y 10x771 DSY - 10x752 L*Y 10x772 JPY - 10x753 STY 10x773 SBS - 10x754 CY* 10x774 CBS - 10x755 LDY 10x775 TBS - 10x756 ADY 10x776 CMW - 10x757 X*Y 10x777 MVW - - Instructions that use IR bit 9 to select the A or B register are designated - with a * above (e.g., 101740 is SAX, and 105740 is SBX). For those that do - not use this feature, either the 101xxx or 105xxx code will execute the - corresponding instruction, although the 105xxx form is the documented - instruction code. - - Implementation notes: - - 1. The LBT, SBT, MBT, and MVW instructions are used as part of the 2100 IOP - implementation. When so called, the MBT and MVW instructions have the - additional restriction that the count must be positive. - - 2. The protected memory lower bound for the JLY and JPY instructions is 0. -*/ - -static const OP_PAT op_eig[32] = { - OP_A, OP_N, OP_A, OP_A, /* S*X C*X L*X STX */ - OP_N, OP_K, OP_K, OP_N, /* CX* LDX ADX X*X */ - OP_A, OP_N, OP_A, OP_A, /* S*Y C*Y L*Y STY */ - OP_N, OP_K, OP_K, OP_N, /* CY* LDY ADY X*Y */ - OP_N, OP_N, OP_A, OP_N, /* ISX DSX JLY LBT */ - OP_N, OP_KV, OP_KV, OP_N, /* SBT MBT CBT SFB */ - OP_N, OP_N, OP_C, OP_KA, /* ISY DSY JPY SBS */ - OP_KA, OP_KK, OP_KV, OP_KV /* CBS TBS CMW MVW */ - }; - -t_stat cpu_eig (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -uint32 entry, absel; -uint32 t, v1, v2, wc; -int32 sop1, sop2; - -absel = (IR & I_AB)? 1: 0; /* get A/B select */ -entry = IR & 037; /* mask to entry point */ - -if (op_eig [entry] != OP_N) { - reason = cpu_ops (op_eig [entry], op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -switch (entry) { /* decode IR<4:0> */ - -/* EIG module 1 */ - - case 000: /* SAX, SBX 10x740 (OP_A) */ - op[0].word = (op[0].word + XR) & VAMASK; /* indexed addr */ - WriteW (op[0].word, ABREG[absel]); /* store */ - break; - - case 001: /* CAX, CBX 10x741 (OP_N) */ - XR = ABREG[absel]; /* copy to XR */ - break; - - case 002: /* LAX, LBX 10x742 (OP_A) */ - op[0].word = (op[0].word + XR) & VAMASK; /* indexed addr */ - ABREG[absel] = ReadW (op[0].word); /* load */ - break; - - case 003: /* STX 105743 (OP_A) */ - WriteW (op[0].word, XR); /* store XR */ - break; - - case 004: /* CXA, CXB 10x744 (OP_N) */ - ABREG[absel] = (uint16) XR; /* copy from XR */ - break; - - case 005: /* LDX 105745 (OP_K)*/ - XR = op[0].word; /* load XR */ - break; - - case 006: /* ADX 105746 (OP_K) */ - t = XR + op[0].word; /* add to XR */ - if (t > DMASK) E = 1; /* set E, O */ - if (((~XR ^ op[0].word) & (XR ^ t)) & SIGN) O = 1; - XR = t & DMASK; - break; - - case 007: /* XAX, XBX 10x747 (OP_N) */ - t = XR; /* exchange XR */ - XR = ABREG[absel]; - ABREG[absel] = (uint16) t; - break; - - case 010: /* SAY, SBY 10x750 (OP_A) */ - op[0].word = (op[0].word + YR) & VAMASK; /* indexed addr */ - WriteW (op[0].word, ABREG[absel]); /* store */ - break; - - case 011: /* CAY, CBY 10x751 (OP_N) */ - YR = ABREG[absel]; /* copy to YR */ - break; - - case 012: /* LAY, LBY 10x752 (OP_A) */ - op[0].word = (op[0].word + YR) & VAMASK; /* indexed addr */ - ABREG[absel] = ReadW (op[0].word); /* load */ - break; - - case 013: /* STY 105753 (OP_A) */ - WriteW (op[0].word, YR); /* store YR */ - break; - - case 014: /* CYA, CYB 10x754 (OP_N) */ - ABREG[absel] = (uint16) YR; /* copy from YR */ - break; - - case 015: /* LDY 105755 (OP_K) */ - YR = op[0].word; /* load YR */ - break; - - case 016: /* ADY 105756 (OP_K) */ - t = YR + op[0].word; /* add to YR */ - if (t > DMASK) E = 1; /* set E, O */ - if (((~YR ^ op[0].word) & (YR ^ t)) & SIGN) O = 1; - YR = t & DMASK; - break; - - case 017: /* XAY, XBY 10x757 (OP_N) */ - t = YR; /* exchange YR */ - YR = ABREG[absel]; - ABREG[absel] = (uint16) t; - break; - -/* EIG module 2 */ - - case 020: /* ISX 105760 (OP_N) */ - XR = (XR + 1) & DMASK; /* incr XR */ - if (XR == 0) PC = (PC + 1) & VAMASK; /* skip if zero */ - break; - - case 021: /* DSX 105761 (OP_N) */ - XR = (XR - 1) & DMASK; /* decr XR */ - if (XR == 0) PC = (PC + 1) & VAMASK; /* skip if zero */ - break; - - case 022: /* JLY 105762 (OP_A) */ - mp_dms_jmp (op[0].word, 0); /* validate jump addr */ - PCQ_ENTRY; - YR = PC; /* ret addr to YR */ - PC = op[0].word; /* jump */ - break; - - case 023: /* LBT 105763 (OP_N) */ - AR = ReadB (BR); /* load byte */ - BR = (BR + 1) & DMASK; /* incr ptr */ - break; - - case 024: /* SBT 105764 (OP_N) */ - WriteB (BR, AR); /* store byte */ - BR = (BR + 1) & DMASK; /* incr ptr */ - break; - - case 025: /* MBT 105765 (OP_KV) */ - wc = ReadW (op[1].word); /* get continuation count */ - if (wc == 0) wc = op[0].word; /* none? get initiation count */ - if ((wc & SIGN) && - (UNIT_CPU_TYPE == UNIT_TYPE_2100)) - break; /* < 0 is NOP for 2100 IOP */ - while (wc != 0) { /* while count */ - WriteW (op[1].word, wc); /* for MP abort */ - t = ReadB (AR); /* move byte */ - WriteB (BR, t); - AR = (AR + 1) & DMASK; /* incr src */ - BR = (BR + 1) & DMASK; /* incr dst */ - wc = (wc - 1) & DMASK; /* decr cnt */ - if (intrq && wc) { /* intr, more to do? */ - PC = err_PC; /* back up PC */ - break; - } - } - WriteW (op[1].word, wc); /* clean up inline */ - break; - - case 026: /* CBT 105766 (OP_KV) */ - wc = ReadW (op[1].word); /* get continuation count */ - if (wc == 0) wc = op[0].word; /* none? get initiation count */ - while (wc != 0) { /* while count */ - WriteW (op[1].word, wc); /* for MP abort */ - v1 = ReadB (AR); /* get src1 */ - v2 = ReadB (BR); /* get src2 */ - if (v1 != v2) { /* compare */ - PC = (PC + 1 + (v1 > v2)) & VAMASK; - BR = (BR + wc) & DMASK; /* update BR */ - wc = 0; /* clr interim */ - break; - } - AR = (AR + 1) & DMASK; /* incr src1 */ - BR = (BR + 1) & DMASK; /* incr src2 */ - wc = (wc - 1) & DMASK; /* decr cnt */ - if (intrq && wc) { /* intr, more to do? */ - PC = err_PC; /* back up PC */ - break; - } - } - WriteW (op[1].word, wc); /* clean up inline */ - break; - - case 027: /* SFB 105767 (OP_N) */ - v1 = AR & 0377; /* test byte */ - v2 = (AR >> 8) & 0377; /* term byte */ - for (;;) { /* scan */ - t = ReadB (BR); /* read byte */ - if (t == v1) break; /* test match? */ - BR = (BR + 1) & DMASK; - if (t == v2) { /* term match? */ - PC = (PC + 1) & VAMASK; - break; - } - if (intrq) { /* int pending? */ - PC = err_PC; /* back up PC */ - break; - } - } - break; - - case 030: /* ISY 105770 (OP_N) */ - YR = (YR + 1) & DMASK; /* incr YR */ - if (YR == 0) PC = (PC + 1) & VAMASK; /* skip if zero */ - break; - - case 031: /* DSY 105771 (OP_N) */ - YR = (YR - 1) & DMASK; /* decr YR */ - if (YR == 0) PC = (PC + 1) & VAMASK; /* skip if zero */ - break; - - case 032: /* JPY 105772 (OP_C) */ - op[0].word = (op[0].word + YR) & VAMASK; /* index, no indir */ - mp_dms_jmp (op[0].word, 0); /* validate jump addr */ - PCQ_ENTRY; - PC = op[0].word; /* jump */ - break; - - case 033: /* SBS 105773 (OP_KA) */ - WriteW (op[1].word, /* set bits */ - ReadW (op[1].word) | op[0].word); - break; - - case 034: /* CBS 105774 (OP_KA) */ - WriteW (op[1].word, /* clear bits */ - ReadW (op[1].word) & ~op[0].word); - break; - - case 035: /* TBS 105775 (OP_KK) */ - if ((op[1].word & op[0].word) != op[0].word) /* test bits */ - PC = (PC + 1) & VAMASK; - break; - - case 036: /* CMW 105776 (OP_KV) */ - wc = ReadW (op[1].word); /* get continuation count */ - if (wc == 0) wc = op[0].word; /* none? get initiation count */ - while (wc != 0) { /* while count */ - WriteW (op[1].word, wc); /* for abort */ - v1 = ReadW (AR & VAMASK); /* first op */ - v2 = ReadW (BR & VAMASK); /* second op */ - sop1 = (int32) SEXT (v1); /* signed */ - sop2 = (int32) SEXT (v2); - if (sop1 != sop2) { /* compare */ - PC = (PC + 1 + (sop1 > sop2)) & VAMASK; - BR = (BR + wc) & DMASK; /* update BR */ - wc = 0; /* clr interim */ - break; - } - AR = (AR + 1) & DMASK; /* incr src1 */ - BR = (BR + 1) & DMASK; /* incr src2 */ - wc = (wc - 1) & DMASK; /* decr cnt */ - if (intrq && wc) { /* intr, more to do? */ - PC = err_PC; /* back up PC */ - break; - } - } - WriteW (op[1].word, wc); /* clean up inline */ - break; - - case 037: /* MVW 105777 (OP_KV) */ - wc = ReadW (op[1].word); /* get continuation count */ - if (wc == 0) wc = op[0].word; /* none? get initiation count */ - if ((wc & SIGN) && - (UNIT_CPU_TYPE == UNIT_TYPE_2100)) - break; /* < 0 is NOP for 2100 IOP */ - while (wc != 0) { /* while count */ - WriteW (op[1].word, wc); /* for abort */ - t = ReadW (AR & VAMASK); /* move word */ - WriteW (BR & VAMASK, t); - AR = (AR + 1) & DMASK; /* incr src */ - BR = (BR + 1) & DMASK; /* incr dst */ - wc = (wc - 1) & DMASK; /* decr cnt */ - if (intrq && wc) { /* intr, more to do? */ - PC = err_PC; /* back up PC */ - break; - } - } - WriteW (op[1].word, wc); /* clean up inline */ - break; - - } - -return reason; -} - - -/* 2000 I/O Processor - - The IOP accelerates certain operations of the HP 2000 Time-Share BASIC system - I/O processor. Most 2000 systems were delivered with 2100 CPUs, although IOP - microcode was developed for the 1000-M and 1000-E. As the I/O processors - were specific to the 2000 system, general compatibility with other CPU - microcode options was unnecessary, and indeed no other options were possible - for the 2100. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A 13206A 13207A 22702A N/A - - The routines are mapped to instruction codes as follows: - - Instr. 2100 1000-M/E Description - ------ ---------- ---------- -------------------------------------------- - SAI 105060-117 101400-037 Store A indexed by B (+/- offset in IR<4:0>) - LAI 105020-057 105400-037 Load A indexed by B (+/- offset in IR<4:0>) - CRC 105150 105460 Generate CRC - REST 105340 105461 Restore registers from stack - READF 105220 105462 Read F register (stack pointer) - INS -- 105463 Initialize F register (stack pointer) - ENQ 105240 105464 Enqueue - PENQ 105257 105465 Priority enqueue - DEQ 105260 105466 Dequeue - TRSLT 105160 105467 Translate character - ILIST 105000 105470 Indirect address list (similar to $SETP) - PRFEI 105222 105471 Power fail exit with I/O - PRFEX 105223 105472 Power fail exit - PRFIO 105221 105473 Power fail I/O - SAVE 105362 105474 Save registers to stack - - MBYTE 105120 105765 Move bytes (MBT) - MWORD 105200 105777 Move words (MVW) - SBYTE 105300 105764 Store byte (SBT) - LBYTE 105320 105763 Load byte (LBT) - - The INS instruction was not required in the 2100 implementation because the - stack pointer was actually the memory protect fence register and so could be - loaded directly with an OTA/B 05. Also, the 1000 implementation did not - offer the MBYTE, MWORD, SBYTE, and LBYTE instructions because the equivalent - instructions from the standard Extended Instruction Group were used instead. - - Note that the 2100 MBYTE and MWORD instructions operate slightly differently - from the 1000 MBT and MVW instructions. Specifically, the move count is - signed on the 2100 and unsigned on the 1000. A negative count on the 2100 - results in a NOP. - - The simulator remaps the 2100 instructions to the 1000 codes. The four EIG - equivalents are dispatched to the EIG simulator. The rest are handled here. - - Additional reference: - - HP 2000 Computer System Sources and Listings Documentation - (22687-90020, undated), section 3, pages 2-74 through 2-91. -*/ - -static const OP_PAT op_iop[16] = { - OP_V, OP_N, OP_N, OP_N, /* CRC RESTR READF INS */ - OP_N, OP_N, OP_N, OP_V, /* ENQ PENQ DEQ TRSLT */ - OP_AC, OP_CVA, OP_A, OP_CV, /* ILIST PRFEI PRFEX PRFIO */ - OP_N, OP_N, OP_N, OP_N /* SAVE --- --- --- */ - }; - -t_stat cpu_iop (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -uint32 entry; -uint32 hp, tp, i, t, wc, MA; - -if (UNIT_CPU_TYPE == UNIT_TYPE_2100) { /* 2100 IOP? */ - if ((IR >= 0105020) && (IR <= 0105057)) /* remap LAI */ - IR = 0105400 | (IR - 0105020); - else if ((IR >= 0105060) && (IR <= 0105117)) /* remap SAI */ - IR = 0101400 | (IR - 0105060); - else { - switch (IR) { /* remap others */ - case 0105000: IR = 0105470; break; /* ILIST */ - case 0105120: return cpu_eig (0105765, intrq); /* MBYTE (maps to MBT) */ - case 0105150: IR = 0105460; break; /* CRC */ - case 0105160: IR = 0105467; break; /* TRSLT */ - case 0105200: return cpu_eig (0105777, intrq); /* MWORD (maps to MVW) */ - case 0105220: IR = 0105462; break; /* READF */ - case 0105221: IR = 0105473; break; /* PRFIO */ - case 0105222: IR = 0105471; break; /* PRFEI */ - case 0105223: IR = 0105472; break; /* PRFEX */ - case 0105240: IR = 0105464; break; /* ENQ */ - case 0105257: IR = 0105465; break; /* PENQ */ - case 0105260: IR = 0105466; break; /* DEQ */ - case 0105300: return cpu_eig (0105764, intrq); /* SBYTE (maps to SBT) */ - case 0105320: return cpu_eig (0105763, intrq); /* LBYTE (maps to LBT) */ - case 0105340: IR = 0105461; break; /* REST */ - case 0105362: IR = 0105474; break; /* SAVE */ - - default: /* all others invalid */ - return stop_inst; - } - } - } - -entry = IR & 077; /* mask to entry point */ - -if (entry <= 037) { /* LAI/SAI 10x400-437 */ - MA = ((entry - 020) + BR) & VAMASK; /* +/- offset */ - if (IR & I_AB) AR = ReadW (MA); /* AB = 1 -> LAI */ - else WriteW (MA, AR); /* AB = 0 -> SAI */ - return reason; - } -else if (entry <= 057) /* IR = 10x440-457? */ - return stop_inst; /* not part of IOP */ - -entry = entry - 060; /* offset 10x460-477 */ - -if (op_iop [entry] != OP_N) { - reason = cpu_ops (op_iop [entry], op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -switch (entry) { /* decode IR<5:0> */ - - case 000: /* CRC 105460 (OP_V) */ - t = ReadW (op[0].word) ^ (AR & 0377); /* xor prev CRC and char */ - for (i = 0; i < 8; i++) { /* apply polynomial */ - t = (t >> 1) | ((t & 1) << 15); /* rotate right */ - if (t & SIGN) t = t ^ 020001; /* old t<0>? xor */ - } - WriteW (op[0].word, t); /* rewrite CRC */ - break; - - case 001: /* RESTR 105461 (OP_N) */ - iop_sp = (iop_sp - 1) & VAMASK; /* decr stack ptr */ - t = ReadW (iop_sp); /* get E and O */ - O = ((t >> 1) ^ 1) & 1; /* restore O */ - E = t & 1; /* restore E */ - iop_sp = (iop_sp - 1) & VAMASK; /* decr sp */ - BR = ReadW (iop_sp); /* restore B */ - iop_sp = (iop_sp - 1) & VAMASK; /* decr sp */ - AR = ReadW (iop_sp); /* restore A */ - if (UNIT_CPU_MODEL == UNIT_2100) - mp_fence = iop_sp; /* 2100 keeps sp in MP FR */ - break; - - case 002: /* READF 105462 (OP_N) */ - AR = (uint16) iop_sp; /* copy stk ptr */ - break; - - case 003: /* INS 105463 (OP_N) */ - iop_sp = AR; /* init stk ptr */ - break; - - case 004: /* ENQ 105464 (OP_N) */ - hp = ReadW (AR & VAMASK); /* addr of head */ - tp = ReadW ((AR + 1) & VAMASK); /* addr of tail */ - WriteW ((BR - 1) & VAMASK, 0); /* entry link */ - WriteW ((tp - 1) & VAMASK, BR); /* tail link */ - WriteW ((AR + 1) & VAMASK, BR); /* queue tail */ - if (hp != 0) PC = (PC + 1) & VAMASK; /* q not empty? skip */ - break; - - case 005: /* PENQ 105465 (OP_N) */ - hp = ReadW (AR & VAMASK); /* addr of head */ - WriteW ((BR - 1) & VAMASK, hp); /* becomes entry link */ - WriteW (AR & VAMASK, BR); /* queue head */ - if (hp == 0) /* q empty? */ - WriteW ((AR + 1) & VAMASK, BR); /* queue tail */ - else PC = (PC + 1) & VAMASK; /* skip */ - break; - - case 006: /* DEQ 105466 (OP_N) */ - BR = ReadW (AR & VAMASK); /* addr of head */ - if (BR) { /* queue not empty? */ - hp = ReadW ((BR - 1) & VAMASK); /* read hd entry link */ - WriteW (AR & VAMASK, hp); /* becomes queue head */ - if (hp == 0) /* q now empty? */ - WriteW ((AR + 1) & VAMASK, (AR + 1) & DMASK); - PC = (PC + 1) & VAMASK; /* skip */ - } - break; - - case 007: /* TRSLT 105467 (OP_V) */ - wc = ReadW (op[0].word); /* get count */ - if (wc & SIGN) break; /* cnt < 0? */ - while (wc != 0) { /* loop */ - MA = (AR + AR + ReadB (BR)) & VAMASK; - t = ReadB (MA); /* xlate */ - WriteB (BR, t); /* store char */ - BR = (BR + 1) & DMASK; /* incr ptr */ - wc = (wc - 1) & DMASK; /* decr cnt */ - if (wc && intrq) { /* more and intr? */ - WriteW (op[0].word, wc); /* save count */ - PC = err_PC; /* stop for now */ - break; - } - } - break; - - case 010: /* ILIST 105470 (OP_AC) */ - do { /* for count */ - WriteW (op[0].word, AR); /* write AR to mem */ - AR = (AR + 1) & DMASK; /* incr AR */ - op[0].word = (op[0].word + 1) & VAMASK; /* incr MA */ - op[1].word = (op[1].word - 1) & DMASK; /* decr count */ - } - while (op[1].word != 0); - break; - - case 011: /* PRFEI 105471 (OP_CVA) */ - WriteW (op[1].word, 1); /* set flag */ - reason = iogrp (op[0].word, 0); /* execute I/O instr */ - op[0].word = op[2].word; /* set rtn and fall through */ - - case 012: /* PRFEX 105472 (OP_A) */ - PCQ_ENTRY; - PC = ReadW (op[0].word) & VAMASK; /* jump indirect */ - WriteW (op[0].word, 0); /* clear exit */ - break; - - case 013: /* PRFIO 105473 (OP_CV) */ - WriteW (op[1].word, 1); /* set flag */ - reason = iogrp (op[0].word, 0); /* execute instr */ - break; - - case 014: /* SAVE 105474 (OP_N) */ - WriteW (iop_sp, AR); /* save A */ - iop_sp = (iop_sp + 1) & VAMASK; /* incr stack ptr */ - WriteW (iop_sp, BR); /* save B */ - iop_sp = (iop_sp + 1) & VAMASK; /* incr stack ptr */ - t = ((O ^ 1) << 1) | E; /* merge E and O */ - WriteW (iop_sp, t); /* save E and O */ - iop_sp = (iop_sp + 1) & VAMASK; /* incr stack ptr */ - if (UNIT_CPU_TYPE == UNIT_TYPE_2100) - mp_fence = iop_sp; /* 2100 keeps sp in MP FR */ - break; - - default: /* instruction undefined */ - return stop_inst; - } - -return reason; -} +/* hp2100_cpu2.c: HP 2100/1000 FP/DMS/EIG/IOP instructions + + Copyright (c) 2005-2016, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + CPU2 Floating-point, dynamic mapping, extended, and I/O processor + instructions + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 24-Dec-14 JDB Added casts for explicit downward conversions + 09-May-12 JDB Separated assignments from conditional expressions + 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h + 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) + 05-Aug-08 JDB Updated mp_dms_jmp calling sequence + Fixed DJP, SJP, and UJP jump target validation + 30-Jul-08 JDB RVA/B conditionally updates dms_vr before returning value + 19-Dec-06 JDB DMS self-test now executes as NOP on 1000-M + 01-Dec-06 JDB Substitutes FPP for firmware FP if HAVE_INT64 + 26-Sep-06 JDB Moved from hp2100_cpu1.c to simplify extensions + 22-Feb-05 JDB Fixed missing MPCK on JRS target + 21-Jan-05 JDB Reorganized CPU option and operand processing flags + Split code along microcode modules + 15-Jan-05 RMS Cloned from hp2100_cpu.c + + Primary references: + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - Macro/1000 Reference Manual (92059-90001, Dec-1992) + + Additional references are listed with the associated firmware + implementations, as are the HP option model numbers pertaining to the + applicable CPUs. +*/ + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + +#if !defined (HAVE_INT64) /* int64 support unavailable */ + +#include "hp2100_fp.h" + + +/* Single-Precision Floating Point Instructions + + The 2100 and 1000 CPUs share the single-precision (two word) floating-point + instruction codes. Floating-point firmware was an option on the 2100 and was + standard on the 1000-M and E. The 1000-F had a standard hardware Floating + Point Processor that executed these six instructions and added extended- and + double-precision floating- point instructions, as well as double-integer + instructions (the FPP is simulated separately). + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A 12901A std std N/A + + The instruction codes for the 2100 and 1000-M/E systems are mapped to + routines as follows: + + Instr. 2100/1000-M/E Description + ------ ------------- ----------------------------------- + 105000 FAD Single real add + 105020 FSB Single real subtract + 105040 FMP Single real multiply + 105060 FDV Single real divide + 105100 FIX Single integer to single real fix + 105120 FLT Single real to single integer float + + Bits 3-0 are not decoded by these instructions, so FAD (e.g.) would be + executed by any instruction in the range 105000-105017. + + Implementation note: rather than have two simulators that each executes the + single-precision FP instruction set, we compile conditionally, based on the + availability of 64-bit integer support in the host compiler. 64-bit integers + are required for the FPP, so if they are available, then the FPP is used to + handle the six single-precision instructions for the 2100 and M/E-Series, and + this function is omitted. If support is unavailable, this function is used + instead. + + Implementation note: the operands to FAD, etc. are floating-point values, so + OP_F would normally be used. However, the firmware FP support routines want + floating-point operands as 32-bit integer values, so OP_D is used to achieve + this. +*/ + +static const OP_PAT op_fp[8] = { + OP_D, OP_D, OP_D, OP_D, /* FAD FSB FMP FDV */ + OP_N, OP_N, OP_N, OP_N /* FIX FLT --- --- */ + }; + +t_stat cpu_fp (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +uint32 entry; + +entry = (IR >> 4) & 017; /* mask to entry point */ + +if (op_fp [entry] != OP_N) { + reason = cpu_ops (op_fp [entry], op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +switch (entry) { /* decode IR<7:4> */ + + case 000: /* FAD 105000 (OP_D) */ + O = f_as (op[0].dword, 0); /* add, upd ovflo */ + break; + + case 001: /* FSB 105020 (OP_D) */ + O = f_as (op[0].dword, 1); /* sub, upd ovflo */ + break; + + case 002: /* FMP 105040 (OP_D) */ + O = f_mul (op[0].dword); /* mul, upd ovflo */ + break; + + case 003: /* FDV 105060 (OP_D) */ + O = f_div (op[0].dword); /* div, upd ovflo */ + break; + + case 004: /* FIX 105100 (OP_N) */ + O = f_fix (); /* fix, upd ovflo */ + break; + + case 005: /* FLT 105120 (OP_N) */ + O = f_flt (); /* float, upd ovflo */ + break; + + default: /* should be impossible */ + return SCPE_IERR; + } + +return reason; +} + +#endif /* int64 support unavailable */ + + +/* Dynamic Mapping System + + The 1000 Dynamic Mapping System (DMS) consisted of the 12731A Memory + Expansion Module (MEM) card and 38 instructions to expand the basic 32K + logical address space to a 1024K physical space. The MEM provided four maps + of 32 mapping registers each: a system map, a user map, and two DCPC maps. + DMS worked in conjunction with memory protect to provide a "protected mode" + in which memory read and write violations could be trapped, and that + inhibited "privileged" instruction execution that attempted to alter the + memory mapping. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A 12976B 13307B std + + The instruction codes are mapped to routines as follows: + + Instr. 1000-M 1000-E/F Instr. 1000-M 1000-E/F + ------ ------ -------- ------ ------ -------- + 10x700 [xmm] [xmm] 10x720 XMM XMM + 10x701 [nop] [test] 10x721 XMS XMS + 10x702 MBI MBI 10x722 XM* XM* + 10x703 MBF MBF 10x723 [nop] [nop] + 10x704 MBW MBW 10x724 XL* XL* + 10x705 MWI MWI 10x725 XS* XS* + 10x706 MWF MWF 10x726 XC* XC* + 10x707 MWW MWW 10x727 LF* LF* + 10x710 SY* SY* 10x730 RS* RS* + + 10x711 US* US* 10x731 RV* RV* + 10x712 PA* PA* 10x732 DJP DJP + 10x713 PB* PB* 10x733 DJS DJS + 10x714 SSM SSM 10x734 SJP SJP + 10x715 JRS JRS 10x735 SJS SJS + 10x716 [nop] [nop] 10x736 UJP UJP + 10x717 [nop] [nop] 10x737 UJS UJS + + Instructions that use IR bit 9 to select the A or B register are designated + with a * above (e.g., 101710 is SYA, and 105710 is SYB). For those that do + not use this feature, either the 101xxx or 105xxx code will execute the + corresponding instruction, although the 105xxx form is the documented + instruction code. + + Implementation notes: + + 1. Instruction code 10x700 will execute the XMM instruction, although 10x720 + is the documented instruction value. + + 2. Instruction code 10x701 will complement the A or B register, as + indicated, on 1000-E and F-Series machines. This instruction is a NOP on + M-Series machines. + + 3. The DMS privilege violation rules are: + - load map and CTL5 set (XMM, XMS, XM*, SY*, US*, PA*, PB*) + - load state or fence and UMAP set (JRS, DJP, DJS, SJP, SJS, UJP, UJS, LF*) + + 4. The 1000 manual is incorrect in stating that M*I, M*W, XS* are + privileged. + + 5. The protected memory lower bound for the DJP, SJP, UJP, and JRS + instructions is 2. +*/ + +static const OP_PAT op_dms[32] = { + OP_N, OP_N, OP_N, OP_N, /* [xmm] [test] MBI MBF */ + OP_N, OP_N, OP_N, OP_N, /* MBW MWI MWF MWW */ + OP_N, OP_N, OP_N, OP_N, /* SYA/B USA/B PAA/B PBA/B */ + OP_A, OP_KA, OP_N, OP_N, /* SSM JRS nop nop */ + OP_N, OP_N, OP_N, OP_N, /* XMM XMS XMA/B nop */ + OP_A, OP_A, OP_A, OP_N, /* XLA/B XSA/B XCA/B LFA/B */ + OP_N, OP_N, OP_A, OP_A, /* RSA/B RVA/B DJP DJS */ + OP_A, OP_A, OP_A, OP_A /* SJP SJS UJP UJS */ + }; + +t_stat cpu_dms (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +uint32 entry, absel; +uint32 i, t, mapi, mapj; + +absel = (IR & I_AB)? 1: 0; /* get A/B select */ +entry = IR & 037; /* mask to entry point */ + +if (op_dms [entry] != OP_N) { + reason = cpu_ops (op_dms [entry], op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +switch (entry) { /* decode IR<3:0> */ + +/* DMS module 1 */ + + case 000: /* [undefined] 105700 (OP_N) */ + goto XMM; /* decodes as XMM */ + + case 001: /* [self test] 105701 (OP_N) */ + if (UNIT_CPU_MODEL != UNIT_1000_M) /* executes as NOP on 1000-M */ + ABREG[absel] = ~ABREG[absel]; /* CMA or CMB */ + break; + + case 002: /* MBI 105702 (OP_N) */ + AR = AR & ~1; /* force A, B even */ + BR = BR & ~1; + while (XR != 0) { /* loop */ + t = ReadB (AR); /* read curr */ + WriteBA (BR, t); /* write alt */ + AR = (AR + 1) & DMASK; /* incr ptrs */ + BR = (BR + 1) & DMASK; + XR = (XR - 1) & DMASK; + if (XR && intrq && !(AR & 1)) { /* more, int, even? */ + PR = err_PC; /* stop for now */ + break; + } + } + break; + + case 003: /* MBF 105703 (OP_N) */ + AR = AR & ~1; /* force A, B even */ + BR = BR & ~1; + while (XR != 0) { /* loop */ + t = ReadBA (AR); /* read alt */ + WriteB (BR, t); /* write curr */ + AR = (AR + 1) & DMASK; /* incr ptrs */ + BR = (BR + 1) & DMASK; + XR = (XR - 1) & DMASK; + if (XR && intrq && !(AR & 1)) { /* more, int, even? */ + PR = err_PC; /* stop for now */ + break; + } + } + break; + + case 004: /* MBW 105704 (OP_N) */ + AR = AR & ~1; /* force A, B even */ + BR = BR & ~1; + while (XR != 0) { /* loop */ + t = ReadBA (AR); /* read alt */ + WriteBA (BR, t); /* write alt */ + AR = (AR + 1) & DMASK; /* incr ptrs */ + BR = (BR + 1) & DMASK; + XR = (XR - 1) & DMASK; + if (XR && intrq && !(AR & 1)) { /* more, int, even? */ + PR = err_PC; /* stop for now */ + break; + } + } + break; + + case 005: /* MWI 105705 (OP_N) */ + while (XR != 0) { /* loop */ + t = ReadW (AR & VAMASK); /* read curr */ + WriteWA (BR & VAMASK, t); /* write alt */ + AR = (AR + 1) & DMASK; /* incr ptrs */ + BR = (BR + 1) & DMASK; + XR = (XR - 1) & DMASK; + if (XR && intrq) { /* more and intr? */ + PR = err_PC; /* stop for now */ + break; + } + } + break; + + case 006: /* MWF 105706 (OP_N) */ + while (XR != 0) { /* loop */ + t = ReadWA (AR & VAMASK); /* read alt */ + WriteW (BR & VAMASK, t); /* write curr */ + AR = (AR + 1) & DMASK; /* incr ptrs */ + BR = (BR + 1) & DMASK; + XR = (XR - 1) & DMASK; + if (XR && intrq) { /* more and intr? */ + PR = err_PC; /* stop for now */ + break; + } + } + break; + + case 007: /* MWW 105707 (OP_N) */ + while (XR != 0) { /* loop */ + t = ReadWA (AR & VAMASK); /* read alt */ + WriteWA (BR & VAMASK, t); /* write alt */ + AR = (AR + 1) & DMASK; /* incr ptrs */ + BR = (BR + 1) & DMASK; + XR = (XR - 1) & DMASK; + if (XR && intrq) { /* more and intr? */ + PR = err_PC; /* stop for now */ + break; + } + } + break; + + case 010: /* SYA, SYB 10x710 (OP_N) */ + case 011: /* USA, USB 10x711 (OP_N) */ + case 012: /* PAA, PAB 10x712 (OP_N) */ + case 013: /* PBA, PBB 10x713 (OP_N) */ + mapi = (IR & 03) << VA_N_PAG; /* map base */ + if (ABREG[absel] & SIGN) { /* store? */ + for (i = 0; i < MAP_LNT; i++) { + t = dms_rmap (mapi + i); /* map to memory */ + WriteW ((ABREG[absel] + i) & VAMASK, t); + } + } + else { /* load */ + dms_viol (err_PC, MVI_PRV); /* priv if PRO */ + for (i = 0; i < MAP_LNT; i++) { + t = ReadW ((ABREG[absel] + i) & VAMASK); + dms_wmap (mapi + i, t); /* mem to map */ + } + } + ABREG[absel] = (ABREG[absel] + MAP_LNT) & DMASK; + break; + + case 014: /* SSM 105714 (OP_A) */ + WriteW (op[0].word, dms_upd_sr ()); /* store stat */ + break; + + case 015: /* JRS 105715 (OP_KA) */ + if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + dms_enb = 0; /* assume off */ + dms_ump = SMAP; + if (op[0].word & 0100000) { /* set enable? */ + dms_enb = 1; + if (op[0].word & 0040000) dms_ump = UMAP; /* set/clr usr */ + } + mp_dms_jmp (op[1].word, 2); /* mpck jmp target */ + PCQ_ENTRY; /* save old P */ + PR = op[1].word; /* jump */ + ion_defer = 1; /* defer intr */ + break; + +/* DMS module 2 */ + + case 020: /* XMM 105720 (OP_N) */ + XMM: + if (XR == 0) break; /* nop? */ + while (XR != 0) { /* loop */ + if (XR & SIGN) { /* store? */ + t = dms_rmap (AR); /* map to mem */ + WriteW (BR & VAMASK, t); + XR = (XR + 1) & DMASK; + } + else { /* load */ + dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + t = ReadW (BR & VAMASK); /* mem to map */ + dms_wmap (AR, t); + XR = (XR - 1) & DMASK; + } + AR = (AR + 1) & DMASK; + BR = (BR + 1) & DMASK; + if (intrq && ((XR & 017) == 017)) { /* intr, grp of 16? */ + PR = err_PC; /* stop for now */ + break; + } + } + break; + + case 021: /* XMS 105721 (OP_N) */ + if ((XR & SIGN) || (XR == 0)) break; /* nop? */ + dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + while (XR != 0) { + dms_wmap (AR, BR); /* AR to map */ + XR = (XR - 1) & DMASK; + AR = (AR + 1) & DMASK; + BR = (BR + 1) & DMASK; + if (intrq && ((XR & 017) == 017)) { /* intr, grp of 16? */ + PR = err_PC; + break; + } + } + break; + + case 022: /* XMA, XMB 10x722 (OP_N) */ + dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + if (ABREG[absel] & 0100000) mapi = UMAP; + else mapi = SMAP; + if (ABREG[absel] & 0000001) mapj = PBMAP; + else mapj = PAMAP; + for (i = 0; i < MAP_LNT; i++) { + t = dms_rmap (mapi + i); /* read map */ + dms_wmap (mapj + i, t); /* write map */ + } + break; + + case 024: /* XLA, XLB 10x724 (OP_A) */ + ABREG[absel] = ReadWA (op[0].word); /* load alt */ + break; + + case 025: /* XSA, XSB 10x725 (OP_A) */ + WriteWA (op[0].word, ABREG[absel]); /* store alt */ + break; + + case 026: /* XCA, XCB 10x726 (OP_A) */ + if (ABREG[absel] != ReadWA (op[0].word)) /* compare alt */ + PR = (PR + 1) & VAMASK; + break; + + case 027: /* LFA, LFB 10x727 (OP_N) */ + if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + dms_sr = (dms_sr & ~(MST_FLT | MST_FENCE)) | + (ABREG[absel] & (MST_FLT | MST_FENCE)); + break; + + case 030: /* RSA, RSB 10x730 (OP_N) */ + ABREG[absel] = (uint16) dms_upd_sr (); /* save stat */ + break; + + case 031: /* RVA, RVB 10x731 (OP_N) */ + ABREG[absel] = (uint16) dms_upd_vr (err_PC); /* return updated violation register */ + break; + + case 032: /* DJP 105732 (OP_A) */ + if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + dms_enb = 0; /* disable map */ + dms_ump = SMAP; + mp_dms_jmp (op[0].word, 2); /* validate jump addr */ + PCQ_ENTRY; /* save curr P */ + PR = op[0].word; /* new P */ + ion_defer = 1; + break; + + case 033: /* DJS 105733 (OP_A) */ + if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + WriteW (op[0].word, PR); /* store ret addr */ + PCQ_ENTRY; /* save curr P */ + PR = (op[0].word + 1) & VAMASK; /* new P */ + dms_enb = 0; /* disable map */ + dms_ump = SMAP; + ion_defer = 1; /* defer intr */ + break; + + case 034: /* SJP 105734 (OP_A) */ + if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + dms_enb = 1; /* enable system */ + dms_ump = SMAP; + mp_dms_jmp (op[0].word, 2); /* validate jump addr */ + PCQ_ENTRY; /* save curr P */ + PR = op[0].word; /* jump */ + ion_defer = 1; /* defer intr */ + break; + + case 035: /* SJS 105735 (OP_A) */ + if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + t = PR; /* save retn addr */ + PCQ_ENTRY; /* save curr P */ + PR = (op[0].word + 1) & VAMASK; /* new P */ + dms_enb = 1; /* enable system */ + dms_ump = SMAP; + WriteW (op[0].word, t); /* store ret addr */ + ion_defer = 1; /* defer intr */ + break; + + case 036: /* UJP 105736 (OP_A) */ + if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + dms_enb = 1; /* enable user */ + dms_ump = UMAP; + mp_dms_jmp (op[0].word, 2); /* validate jump addr */ + PCQ_ENTRY; /* save curr P */ + PR = op[0].word; /* jump */ + ion_defer = 1; /* defer intr */ + break; + + case 037: /* UJS 105737 (OP_A) */ + if (dms_ump) dms_viol (err_PC, MVI_PRV); /* priv viol if prot */ + t = PR; /* save retn addr */ + PCQ_ENTRY; /* save curr P */ + PR = (op[0].word + 1) & VAMASK; /* new P */ + dms_enb = 1; /* enable user */ + dms_ump = UMAP; + WriteW (op[0].word, t); /* store ret addr */ + ion_defer = 1; /* defer intr */ + break; + + default: /* others NOP */ + break; + } + +return reason; +} + + +/* Extended Instruction Group + + The Extended Instruction Group (EIG) adds 32 index and 10 bit/byte/word + manipulation instructions to the 1000 base set. These instructions + use the new X and Y index registers that were added to the 1000. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A std std std + + The instruction codes are mapped to routines as follows: + + Instr. 1000-M/E/F Instr. 1000-M/E/F + ------ ---------- ------ ---------- + 10x740 S*X 10x760 ISX + 10x741 C*X 10x761 DSX + 10x742 L*X 10x762 JLY + 10x743 STX 10x763 LBT + 10x744 CX* 10x764 SBT + 10x745 LDX 10x765 MBT + 10x746 ADX 10x766 CBT + 10x747 X*X 10x767 SFB + + 10x750 S*Y 10x770 ISY + 10x751 C*Y 10x771 DSY + 10x752 L*Y 10x772 JPY + 10x753 STY 10x773 SBS + 10x754 CY* 10x774 CBS + 10x755 LDY 10x775 TBS + 10x756 ADY 10x776 CMW + 10x757 X*Y 10x777 MVW + + Instructions that use IR bit 9 to select the A or B register are designated + with a * above (e.g., 101740 is SAX, and 105740 is SBX). For those that do + not use this feature, either the 101xxx or 105xxx code will execute the + corresponding instruction, although the 105xxx form is the documented + instruction code. + + Implementation notes: + + 1. The LBT, SBT, MBT, and MVW instructions are used as part of the 2100 IOP + implementation. When so called, the MBT and MVW instructions have the + additional restriction that the count must be positive. + + 2. The protected memory lower bound for the JLY and JPY instructions is 0. +*/ + +static const OP_PAT op_eig[32] = { + OP_A, OP_N, OP_A, OP_A, /* S*X C*X L*X STX */ + OP_N, OP_K, OP_K, OP_N, /* CX* LDX ADX X*X */ + OP_A, OP_N, OP_A, OP_A, /* S*Y C*Y L*Y STY */ + OP_N, OP_K, OP_K, OP_N, /* CY* LDY ADY X*Y */ + OP_N, OP_N, OP_A, OP_N, /* ISX DSX JLY LBT */ + OP_N, OP_KV, OP_KV, OP_N, /* SBT MBT CBT SFB */ + OP_N, OP_N, OP_C, OP_KA, /* ISY DSY JPY SBS */ + OP_KA, OP_KK, OP_KV, OP_KV /* CBS TBS CMW MVW */ + }; + +t_stat cpu_eig (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +uint32 entry, absel; +uint32 t, v1, v2, wc; +int32 sop1, sop2; + +absel = (IR & I_AB)? 1: 0; /* get A/B select */ +entry = IR & 037; /* mask to entry point */ + +if (op_eig [entry] != OP_N) { + reason = cpu_ops (op_eig [entry], op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +switch (entry) { /* decode IR<4:0> */ + +/* EIG module 1 */ + + case 000: /* SAX, SBX 10x740 (OP_A) */ + op[0].word = (op[0].word + XR) & VAMASK; /* indexed addr */ + WriteW (op[0].word, ABREG[absel]); /* store */ + break; + + case 001: /* CAX, CBX 10x741 (OP_N) */ + XR = ABREG[absel]; /* copy to XR */ + break; + + case 002: /* LAX, LBX 10x742 (OP_A) */ + op[0].word = (op[0].word + XR) & VAMASK; /* indexed addr */ + ABREG[absel] = ReadW (op[0].word); /* load */ + break; + + case 003: /* STX 105743 (OP_A) */ + WriteW (op[0].word, XR); /* store XR */ + break; + + case 004: /* CXA, CXB 10x744 (OP_N) */ + ABREG[absel] = (uint16) XR; /* copy from XR */ + break; + + case 005: /* LDX 105745 (OP_K)*/ + XR = op[0].word; /* load XR */ + break; + + case 006: /* ADX 105746 (OP_K) */ + t = XR + op[0].word; /* add to XR */ + if (t > DMASK) E = 1; /* set E, O */ + if (((~XR ^ op[0].word) & (XR ^ t)) & SIGN) O = 1; + XR = t & DMASK; + break; + + case 007: /* XAX, XBX 10x747 (OP_N) */ + t = XR; /* exchange XR */ + XR = ABREG[absel]; + ABREG[absel] = (uint16) t; + break; + + case 010: /* SAY, SBY 10x750 (OP_A) */ + op[0].word = (op[0].word + YR) & VAMASK; /* indexed addr */ + WriteW (op[0].word, ABREG[absel]); /* store */ + break; + + case 011: /* CAY, CBY 10x751 (OP_N) */ + YR = ABREG[absel]; /* copy to YR */ + break; + + case 012: /* LAY, LBY 10x752 (OP_A) */ + op[0].word = (op[0].word + YR) & VAMASK; /* indexed addr */ + ABREG[absel] = ReadW (op[0].word); /* load */ + break; + + case 013: /* STY 105753 (OP_A) */ + WriteW (op[0].word, YR); /* store YR */ + break; + + case 014: /* CYA, CYB 10x754 (OP_N) */ + ABREG[absel] = (uint16) YR; /* copy from YR */ + break; + + case 015: /* LDY 105755 (OP_K) */ + YR = op[0].word; /* load YR */ + break; + + case 016: /* ADY 105756 (OP_K) */ + t = YR + op[0].word; /* add to YR */ + if (t > DMASK) E = 1; /* set E, O */ + if (((~YR ^ op[0].word) & (YR ^ t)) & SIGN) O = 1; + YR = t & DMASK; + break; + + case 017: /* XAY, XBY 10x757 (OP_N) */ + t = YR; /* exchange YR */ + YR = ABREG[absel]; + ABREG[absel] = (uint16) t; + break; + +/* EIG module 2 */ + + case 020: /* ISX 105760 (OP_N) */ + XR = (XR + 1) & DMASK; /* incr XR */ + if (XR == 0) PR = (PR + 1) & VAMASK; /* skip if zero */ + break; + + case 021: /* DSX 105761 (OP_N) */ + XR = (XR - 1) & DMASK; /* decr XR */ + if (XR == 0) PR = (PR + 1) & VAMASK; /* skip if zero */ + break; + + case 022: /* JLY 105762 (OP_A) */ + mp_dms_jmp (op[0].word, 0); /* validate jump addr */ + PCQ_ENTRY; + YR = PR; /* ret addr to YR */ + PR = op[0].word; /* jump */ + break; + + case 023: /* LBT 105763 (OP_N) */ + AR = ReadB (BR); /* load byte */ + BR = (BR + 1) & DMASK; /* incr ptr */ + break; + + case 024: /* SBT 105764 (OP_N) */ + WriteB (BR, AR); /* store byte */ + BR = (BR + 1) & DMASK; /* incr ptr */ + break; + + case 025: /* MBT 105765 (OP_KV) */ + wc = ReadW (op[1].word); /* get continuation count */ + if (wc == 0) wc = op[0].word; /* none? get initiation count */ + if ((wc & SIGN) && + (UNIT_CPU_TYPE == UNIT_TYPE_2100)) + break; /* < 0 is NOP for 2100 IOP */ + while (wc != 0) { /* while count */ + WriteW (op[1].word, wc); /* for MP abort */ + t = ReadB (AR); /* move byte */ + WriteB (BR, t); + AR = (AR + 1) & DMASK; /* incr src */ + BR = (BR + 1) & DMASK; /* incr dst */ + wc = (wc - 1) & DMASK; /* decr cnt */ + if (intrq && wc) { /* intr, more to do? */ + PR = err_PC; /* back up P */ + break; + } + } + WriteW (op[1].word, wc); /* clean up inline */ + break; + + case 026: /* CBT 105766 (OP_KV) */ + wc = ReadW (op[1].word); /* get continuation count */ + if (wc == 0) wc = op[0].word; /* none? get initiation count */ + while (wc != 0) { /* while count */ + WriteW (op[1].word, wc); /* for MP abort */ + v1 = ReadB (AR); /* get src1 */ + v2 = ReadB (BR); /* get src2 */ + if (v1 != v2) { /* compare */ + PR = (PR + 1 + (v1 > v2)) & VAMASK; + BR = (BR + wc) & DMASK; /* update BR */ + wc = 0; /* clr interim */ + break; + } + AR = (AR + 1) & DMASK; /* incr src1 */ + BR = (BR + 1) & DMASK; /* incr src2 */ + wc = (wc - 1) & DMASK; /* decr cnt */ + if (intrq && wc) { /* intr, more to do? */ + PR = err_PC; /* back up P */ + break; + } + } + WriteW (op[1].word, wc); /* clean up inline */ + break; + + case 027: /* SFB 105767 (OP_N) */ + v1 = AR & 0377; /* test byte */ + v2 = (AR >> 8) & 0377; /* term byte */ + for (;;) { /* scan */ + t = ReadB (BR); /* read byte */ + if (t == v1) break; /* test match? */ + BR = (BR + 1) & DMASK; + if (t == v2) { /* term match? */ + PR = (PR + 1) & VAMASK; + break; + } + if (intrq) { /* int pending? */ + PR = err_PC; /* back up P */ + break; + } + } + break; + + case 030: /* ISY 105770 (OP_N) */ + YR = (YR + 1) & DMASK; /* incr YR */ + if (YR == 0) PR = (PR + 1) & VAMASK; /* skip if zero */ + break; + + case 031: /* DSY 105771 (OP_N) */ + YR = (YR - 1) & DMASK; /* decr YR */ + if (YR == 0) PR = (PR + 1) & VAMASK; /* skip if zero */ + break; + + case 032: /* JPY 105772 (OP_C) */ + op[0].word = (op[0].word + YR) & VAMASK; /* index, no indir */ + mp_dms_jmp (op[0].word, 0); /* validate jump addr */ + PCQ_ENTRY; + PR = op[0].word; /* jump */ + break; + + case 033: /* SBS 105773 (OP_KA) */ + WriteW (op[1].word, /* set bits */ + ReadW (op[1].word) | op[0].word); + break; + + case 034: /* CBS 105774 (OP_KA) */ + WriteW (op[1].word, /* clear bits */ + ReadW (op[1].word) & ~op[0].word); + break; + + case 035: /* TBS 105775 (OP_KK) */ + if ((op[1].word & op[0].word) != op[0].word) /* test bits */ + PR = (PR + 1) & VAMASK; + break; + + case 036: /* CMW 105776 (OP_KV) */ + wc = ReadW (op[1].word); /* get continuation count */ + if (wc == 0) wc = op[0].word; /* none? get initiation count */ + while (wc != 0) { /* while count */ + WriteW (op[1].word, wc); /* for abort */ + v1 = ReadW (AR & VAMASK); /* first op */ + v2 = ReadW (BR & VAMASK); /* second op */ + sop1 = (int32) SEXT (v1); /* signed */ + sop2 = (int32) SEXT (v2); + if (sop1 != sop2) { /* compare */ + PR = (PR + 1 + (sop1 > sop2)) & VAMASK; + BR = (BR + wc) & DMASK; /* update BR */ + wc = 0; /* clr interim */ + break; + } + AR = (AR + 1) & DMASK; /* incr src1 */ + BR = (BR + 1) & DMASK; /* incr src2 */ + wc = (wc - 1) & DMASK; /* decr cnt */ + if (intrq && wc) { /* intr, more to do? */ + PR = err_PC; /* back up P */ + break; + } + } + WriteW (op[1].word, wc); /* clean up inline */ + break; + + case 037: /* MVW 105777 (OP_KV) */ + wc = ReadW (op[1].word); /* get continuation count */ + if (wc == 0) wc = op[0].word; /* none? get initiation count */ + if ((wc & SIGN) && + (UNIT_CPU_TYPE == UNIT_TYPE_2100)) + break; /* < 0 is NOP for 2100 IOP */ + while (wc != 0) { /* while count */ + WriteW (op[1].word, wc); /* for abort */ + t = ReadW (AR & VAMASK); /* move word */ + WriteW (BR & VAMASK, t); + AR = (AR + 1) & DMASK; /* incr src */ + BR = (BR + 1) & DMASK; /* incr dst */ + wc = (wc - 1) & DMASK; /* decr cnt */ + if (intrq && wc) { /* intr, more to do? */ + PR = err_PC; /* back up P */ + break; + } + } + WriteW (op[1].word, wc); /* clean up inline */ + break; + + } + +return reason; +} + + +/* 2000 I/O Processor + + The IOP accelerates certain operations of the HP 2000 Time-Share BASIC system + I/O processor. Most 2000 systems were delivered with 2100 CPUs, although IOP + microcode was developed for the 1000-M and 1000-E. As the I/O processors + were specific to the 2000 system, general compatibility with other CPU + microcode options was unnecessary, and indeed no other options were possible + for the 2100. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A 13206A 13207A 22702A N/A + + The routines are mapped to instruction codes as follows: + + Instr. 2100 1000-M/E Description + ------ ---------- ---------- -------------------------------------------- + SAI 105060-117 101400-037 Store A indexed by B (+/- offset in IR<4:0>) + LAI 105020-057 105400-037 Load A indexed by B (+/- offset in IR<4:0>) + CRC 105150 105460 Generate CRC + REST 105340 105461 Restore registers from stack + READF 105220 105462 Read F register (stack pointer) + INS -- 105463 Initialize F register (stack pointer) + ENQ 105240 105464 Enqueue + PENQ 105257 105465 Priority enqueue + DEQ 105260 105466 Dequeue + TRSLT 105160 105467 Translate character + ILIST 105000 105470 Indirect address list (similar to $SETP) + PRFEI 105222 105471 Power fail exit with I/O + PRFEX 105223 105472 Power fail exit + PRFIO 105221 105473 Power fail I/O + SAVE 105362 105474 Save registers to stack + + MBYTE 105120 105765 Move bytes (MBT) + MWORD 105200 105777 Move words (MVW) + SBYTE 105300 105764 Store byte (SBT) + LBYTE 105320 105763 Load byte (LBT) + + The INS instruction was not required in the 2100 implementation because the + stack pointer was actually the memory protect fence register and so could be + loaded directly with an OTA/B 05. Also, the 1000 implementation did not + offer the MBYTE, MWORD, SBYTE, and LBYTE instructions because the equivalent + instructions from the standard Extended Instruction Group were used instead. + + Note that the 2100 MBYTE and MWORD instructions operate slightly differently + from the 1000 MBT and MVW instructions. Specifically, the move count is + signed on the 2100 and unsigned on the 1000. A negative count on the 2100 + results in a NOP. + + The simulator remaps the 2100 instructions to the 1000 codes. The four EIG + equivalents are dispatched to the EIG simulator. The rest are handled here. + + Additional reference: + - HP 2000 Computer System Sources and Listings Documentation + (22687-90020, undated), section 3, pages 2-74 through 2-91. +*/ + +static const OP_PAT op_iop[16] = { + OP_V, OP_N, OP_N, OP_N, /* CRC RESTR READF INS */ + OP_N, OP_N, OP_N, OP_V, /* ENQ PENQ DEQ TRSLT */ + OP_AC, OP_CVA, OP_A, OP_CV, /* ILIST PRFEI PRFEX PRFIO */ + OP_N, OP_N, OP_N, OP_N /* SAVE --- --- --- */ + }; + +t_stat cpu_iop (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +uint32 entry; +uint32 hp, tp, i, t, wc, MA; + +if (UNIT_CPU_TYPE == UNIT_TYPE_2100) { /* 2100 IOP? */ + if ((IR >= 0105020) && (IR <= 0105057)) /* remap LAI */ + IR = 0105400 | (IR - 0105020); + else if ((IR >= 0105060) && (IR <= 0105117)) /* remap SAI */ + IR = 0101400 | (IR - 0105060); + else { + switch (IR) { /* remap others */ + case 0105000: IR = 0105470; break; /* ILIST */ + case 0105120: return cpu_eig (0105765, intrq); /* MBYTE (maps to MBT) */ + case 0105150: IR = 0105460; break; /* CRC */ + case 0105160: IR = 0105467; break; /* TRSLT */ + case 0105200: return cpu_eig (0105777, intrq); /* MWORD (maps to MVW) */ + case 0105220: IR = 0105462; break; /* READF */ + case 0105221: IR = 0105473; break; /* PRFIO */ + case 0105222: IR = 0105471; break; /* PRFEI */ + case 0105223: IR = 0105472; break; /* PRFEX */ + case 0105240: IR = 0105464; break; /* ENQ */ + case 0105257: IR = 0105465; break; /* PENQ */ + case 0105260: IR = 0105466; break; /* DEQ */ + case 0105300: return cpu_eig (0105764, intrq); /* SBYTE (maps to SBT) */ + case 0105320: return cpu_eig (0105763, intrq); /* LBYTE (maps to LBT) */ + case 0105340: IR = 0105461; break; /* REST */ + case 0105362: IR = 0105474; break; /* SAVE */ + + default: /* all others invalid */ + return stop_inst; + } + } + } + +entry = IR & 077; /* mask to entry point */ + +if (entry <= 037) { /* LAI/SAI 10x400-437 */ + MA = ((entry - 020) + BR) & VAMASK; /* +/- offset */ + if (IR & I_AB) AR = ReadW (MA); /* AB = 1 -> LAI */ + else WriteW (MA, AR); /* AB = 0 -> SAI */ + return reason; + } +else if (entry <= 057) /* IR = 10x440-457? */ + return stop_inst; /* not part of IOP */ + +entry = entry - 060; /* offset 10x460-477 */ + +if (op_iop [entry] != OP_N) { + reason = cpu_ops (op_iop [entry], op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +switch (entry) { /* decode IR<5:0> */ + + case 000: /* CRC 105460 (OP_V) */ + t = ReadW (op[0].word) ^ (AR & 0377); /* xor prev CRC and char */ + for (i = 0; i < 8; i++) { /* apply polynomial */ + t = (t >> 1) | ((t & 1) << 15); /* rotate right */ + if (t & SIGN) t = t ^ 020001; /* old t<0>? xor */ + } + WriteW (op[0].word, t); /* rewrite CRC */ + break; + + case 001: /* RESTR 105461 (OP_N) */ + iop_sp = (iop_sp - 1) & VAMASK; /* decr stack ptr */ + t = ReadW (iop_sp); /* get E and O */ + O = ((t >> 1) ^ 1) & 1; /* restore O */ + E = t & 1; /* restore E */ + iop_sp = (iop_sp - 1) & VAMASK; /* decr sp */ + BR = ReadW (iop_sp); /* restore B */ + iop_sp = (iop_sp - 1) & VAMASK; /* decr sp */ + AR = ReadW (iop_sp); /* restore A */ + if (UNIT_CPU_MODEL == UNIT_2100) + mp_fence = iop_sp; /* 2100 keeps sp in MP FR */ + break; + + case 002: /* READF 105462 (OP_N) */ + AR = (uint16) iop_sp; /* copy stk ptr */ + break; + + case 003: /* INS 105463 (OP_N) */ + iop_sp = AR; /* init stk ptr */ + break; + + case 004: /* ENQ 105464 (OP_N) */ + hp = ReadW (AR & VAMASK); /* addr of head */ + tp = ReadW ((AR + 1) & VAMASK); /* addr of tail */ + WriteW ((BR - 1) & VAMASK, 0); /* entry link */ + WriteW ((tp - 1) & VAMASK, BR); /* tail link */ + WriteW ((AR + 1) & VAMASK, BR); /* queue tail */ + if (hp != 0) PR = (PR + 1) & VAMASK; /* q not empty? skip */ + break; + + case 005: /* PENQ 105465 (OP_N) */ + hp = ReadW (AR & VAMASK); /* addr of head */ + WriteW ((BR - 1) & VAMASK, hp); /* becomes entry link */ + WriteW (AR & VAMASK, BR); /* queue head */ + if (hp == 0) /* q empty? */ + WriteW ((AR + 1) & VAMASK, BR); /* queue tail */ + else PR = (PR + 1) & VAMASK; /* skip */ + break; + + case 006: /* DEQ 105466 (OP_N) */ + BR = ReadW (AR & VAMASK); /* addr of head */ + if (BR) { /* queue not empty? */ + hp = ReadW ((BR - 1) & VAMASK); /* read hd entry link */ + WriteW (AR & VAMASK, hp); /* becomes queue head */ + if (hp == 0) /* q now empty? */ + WriteW ((AR + 1) & VAMASK, (AR + 1) & DMASK); + PR = (PR + 1) & VAMASK; /* skip */ + } + break; + + case 007: /* TRSLT 105467 (OP_V) */ + wc = ReadW (op[0].word); /* get count */ + if (wc & SIGN) break; /* cnt < 0? */ + while (wc != 0) { /* loop */ + MA = (AR + AR + ReadB (BR)) & VAMASK; + t = ReadB (MA); /* xlate */ + WriteB (BR, t); /* store char */ + BR = (BR + 1) & DMASK; /* incr ptr */ + wc = (wc - 1) & DMASK; /* decr cnt */ + if (wc && intrq) { /* more and intr? */ + WriteW (op[0].word, wc); /* save count */ + PR = err_PC; /* stop for now */ + break; + } + } + break; + + case 010: /* ILIST 105470 (OP_AC) */ + do { /* for count */ + WriteW (op[0].word, AR); /* write AR to mem */ + AR = (AR + 1) & DMASK; /* incr AR */ + op[0].word = (op[0].word + 1) & VAMASK; /* incr MA */ + op[1].word = (op[1].word - 1) & DMASK; /* decr count */ + } + while (op[1].word != 0); + break; + + case 011: /* PRFEI 105471 (OP_CVA) */ + WriteW (op[1].word, 1); /* set flag */ + reason = iogrp (op[0].word, 0); /* execute I/O instr */ + op[0].word = op[2].word; /* set rtn and fall through */ + + case 012: /* PRFEX 105472 (OP_A) */ + PCQ_ENTRY; + PR = ReadW (op[0].word) & VAMASK; /* jump indirect */ + WriteW (op[0].word, 0); /* clear exit */ + break; + + case 013: /* PRFIO 105473 (OP_CV) */ + WriteW (op[1].word, 1); /* set flag */ + reason = iogrp (op[0].word, 0); /* execute instr */ + break; + + case 014: /* SAVE 105474 (OP_N) */ + WriteW (iop_sp, AR); /* save A */ + iop_sp = (iop_sp + 1) & VAMASK; /* incr stack ptr */ + WriteW (iop_sp, BR); /* save B */ + iop_sp = (iop_sp + 1) & VAMASK; /* incr stack ptr */ + t = ((O ^ 1) << 1) | E; /* merge E and O */ + WriteW (iop_sp, t); /* save E and O */ + iop_sp = (iop_sp + 1) & VAMASK; /* incr stack ptr */ + if (UNIT_CPU_TYPE == UNIT_TYPE_2100) + mp_fence = iop_sp; /* 2100 keeps sp in MP FR */ + break; + + default: /* instruction undefined */ + return stop_inst; + } + +return reason; +} diff --git a/HP2100/hp2100_cpu3.c b/HP2100/hp2100_cpu3.c index 03ab3848..928abf63 100644 --- a/HP2100/hp2100_cpu3.c +++ b/HP2100/hp2100_cpu3.c @@ -1,827 +1,828 @@ -/* hp2100_cpu3.c: HP 2100/1000 FFP/DBI instructions - - Copyright (c) 2005-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - CPU3 Fast FORTRAN and Double Integer instructions - - 24-Dec-14 JDB Added casts for explicit downward conversions - 09-May-12 JDB Separated assignments from conditional expressions - 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h - 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) - 05-Aug-08 JDB Updated mp_dms_jmp calling sequence - 27-Feb-08 JDB Added DBI self-test instruction - 23-Oct-07 JDB Fixed unsigned-divide bug in .DDI - 17-Oct-07 JDB Fixed unsigned-multiply bug in .DMP - 16-Oct-06 JDB Calls FPP for extended-precision math - 12-Oct-06 JDB Altered DBLE, DDINT for F-Series FFP compatibility - 26-Sep-06 JDB Moved from hp2100_cpu1.c to simplify extensions - 09-Aug-06 JDB Added double-integer instruction set - 18-Feb-05 JDB Add 2100/21MX Fast FORTRAN Processor instructions - - Primary references: - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - Macro/1000 Reference Manual (92059-90001, Dec-1992) - - Additional references are listed with the associated firmware - implementations, as are the HP option model numbers pertaining to the - applicable CPUs. -*/ - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - -#if defined (HAVE_INT64) /* int64 support available */ -#include "hp2100_fp1.h" -#else /* int64 support unavailable */ -#include "hp2100_fp.h" -#endif /* end of int64 support */ - - -/* Fast FORTRAN Processor. - - The Fast FORTRAN Processor (FFP) is a set of FORTRAN language accelerators - and extended-precision (three-word) floating point routines. Although the - FFP is an option for the 2100 and later CPUs, each implements the FFP in a - slightly different form. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A 12907A 12977B 13306B std - - The instruction codes are mapped to routines as follows: - - Instr. 2100 1000-M 1000-E 1000-F Instr. 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ - 105200 -- [nop] [nop] [test] 105220 .XFER .XFER .XFER .XFER - 105201 DBLE DBLE DBLE DBLE 105221 .GOTO .GOTO .GOTO .GOTO - 105202 SNGL SNGL SNGL SNGL 105222 ..MAP ..MAP ..MAP ..MAP - 105203 .XMPY .XMPY .XMPY .DNG 105223 .ENTR .ENTR .ENTR .ENTR - 105204 .XDIV .XDIV .XDIV .DCO 105224 .ENTP .ENTP .ENTP .ENTP - 105205 .DFER .DFER .DFER .DFER 105225 -- .PWR2 .PWR2 .PWR2 - 105206 -- .XPAK .XPAK .XPAK 105226 -- .FLUN .FLUN .FLUN - 105207 -- XADD XADD .BLE 105227 $SETP $SETP $SETP $SETP - - 105210 -- XSUB XSUB .DIN 105230 -- .PACK .PACK .PACK - 105211 -- XMPY XMPY .DDE 105231 -- -- .CFER .CFER - 105212 -- XDIV XDIV .DIS 105232 -- -- -- ..FCM - 105213 .XADD .XADD .XADD .DDS 105233 -- -- -- ..TCM - 105214 .XSUB .XSUB .XSUB .NGL 105234 -- -- -- -- - 105215 -- .XCOM .XCOM .XCOM 105235 -- -- -- -- - 105216 -- ..DCM ..DCM ..DCM 105236 -- -- -- -- - 105217 -- DDINT DDINT DDINT 105237 -- -- -- -- - - The F-Series maps different instructions to several of the standard FFP - opcodes. We first look for these and dispatch them appropriately before - falling into the handler for the common instructions. - - The math functions use the F-Series FPP for implementation. The FPP requires - that the host compiler support 64-bit integers. Therefore, if 64-bit - integers are not available, the math instructions of the FFP are disabled. - We allow this partial implementation as an aid in running systems generated - for the FFP. Most system programs did not use the math instructions, but - almost all use .ENTR. Supporting the latter even on systems that do not - support the former still allows such systems to boot. - - Implementation notes: - - 1. The "$SETP" instruction is sometimes listed as ".SETP" in the - documentation. - - 2. Extended-precision arithmetic routines (e.g., .XMPY) exist on the 1000-F, - but they are assigned instruction codes in the single-precision - floating-point module range. They are replaced by several double integer - instructions, which we dispatch to the double integer handler. - - 3. The software implementation of ..MAP supports 1-, 2-, or 3-dimensional - arrays, designated by setting A = -1, 0, and +1, respectively. The - firmware implementation supports only 2- and 3-dimensional access. - - 4. The documentation for ..MAP for the 2100 FFP shows A = 0 or -1 for two or - three dimensions, respectively, but the 1000 FFP shows A = 0 or +1. The - firmware actually only checks the LSB of A. - - 5. The .DFER and .XFER implementations for the 2100 FFP return X+4 and Y+4 - in the A and B registers, whereas the 1000 FFP returns X+3 and Y+3. - - 6. The .XFER implementation for the 2100 FFP returns to P+2, whereas the - 1000 implementation returns to P+1. - - 7. The firmware implementations of DBLE, .BLE, and DDINT clear the overflow - flag. The software implementations do not change overflow. - - 8. The M/E-Series FFP arithmetic instructions (.XADD, etc.) return negative - infinity on negative overflow and positive infinity on positive overflow. - The equivalent F-Series instructions return positive infinity on both. - - 9. The protected memory lower bound for the .GOTO instruction is 2. - - Additional references: - - DOS/RTE Relocatable Library Reference Manual (24998-90001, Oct-1981) - - Implementing the HP 2100 Fast FORTRAN Processor (12907-90010, Nov-1974) -*/ - -static const OP_PAT op_ffp_f[32] = { /* patterns for F-Series only */ - OP_N, OP_AAF, OP_AX, OP_N, /* [tst] DBLE SNGL .DNG */ - OP_N, OP_AA, OP_A, OP_AAF, /* .DCO .DFER .XPAK .BLE */ - OP_N, OP_N, OP_N, OP_N, /* .DIN .DDE .DIS .DDS */ - OP_AT, OP_A, OP_A, OP_AAX, /* .NGL .XCOM ..DCM DDINT */ - OP_N, OP_AK, OP_KKKK, OP_A, /* .XFER .GOTO ..MAP .ENTR */ - OP_A, OP_RK, OP_R, OP_K, /* .ENTP .PWR2 .FLUN $SETP */ - OP_RC, OP_AA, OP_R, OP_A, /* .PACK .CFER ..FCM ..TCM */ - OP_N, OP_N, OP_N, OP_N /* --- --- --- --- */ - }; - -static const OP_PAT op_ffp_e[32] = { /* patterns for 2100/M/E-Series */ - OP_N, OP_AAF, OP_AX, OP_AXX, /* [nop] DBLE SNGL .XMPY */ - OP_AXX, OP_AA, OP_A, OP_AAXX, /* .XDIV .DFER .XPAK XADD */ - OP_AAXX, OP_AAXX, OP_AAXX, OP_AXX, /* XSUB XMPY XDIV .XADD */ - OP_AXX, OP_A, OP_A, OP_AAX, /* .XSUB .XCOM ..DCM DDINT */ - OP_N, OP_AK, OP_KKKK, OP_A, /* .XFER .GOTO ..MAP .ENTR */ - OP_A, OP_RK, OP_R, OP_K, /* .ENTP .PWR2 .FLUN $SETP */ - OP_RC, OP_AA, OP_N, OP_N, /* .PACK .CFER --- --- */ - OP_N, OP_N, OP_N, OP_N /* --- --- --- --- */ - }; - -t_stat cpu_ffp (uint32 IR, uint32 intrq) -{ -OP fpop; -OPS op, op2; -uint32 entry; -uint32 j, sa, sb, sc, da, dc, ra, MA; -int32 expon; -t_stat reason = SCPE_OK; - -#if defined (HAVE_INT64) /* int64 support available */ - -int32 i; - -#endif /* end of int64 support */ - -entry = IR & 037; /* mask to entry point */ - -if (UNIT_CPU_MODEL != UNIT_1000_F) { /* 2100/M/E-Series? */ - if (op_ffp_e [entry] != OP_N) { - reason = cpu_ops (op_ffp_e [entry], op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - } - -#if defined (HAVE_INT64) /* int64 support available */ - -else { /* F-Series */ - if (op_ffp_f [entry] != OP_N) { - reason = cpu_ops (op_ffp_f [entry], op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - - switch (entry) { /* decode IR<4:0> */ - - case 000: /* [tst] 105200 (OP_N) */ - XR = 4; /* firmware revision */ - SR = 0102077; /* test passed code */ - AR = 0; /* test clears A/B */ - BR = 0; - PC = (PC + 1) & VAMASK; /* P+2 return for firmware w/DBI */ - return reason; - - case 003: /* .DNG 105203 (OP_N) */ - return cpu_dbi (0105323, intrq); /* remap to double int handler */ - - case 004: /* .DCO 105204 (OP_N) */ - return cpu_dbi (0105324, intrq); /* remap to double int handler */ - - case 007: /* .BLE 105207 (OP_AAF) */ - O = fp_cvt (&op[2], fp_f, fp_t); /* convert value and clear overflow */ - WriteOp (op[1].word, op[2], fp_t); /* write double-precision value */ - return reason; - - case 010: /* .DIN 105210 (OP_N) */ - return cpu_dbi (0105330, intrq); /* remap to double int handler */ - - case 011: /* .DDE 105211 (OP_N) */ - return cpu_dbi (0105331, intrq); /* remap to double int handler */ - - case 012: /* .DIS 105212 (OP_N) */ - return cpu_dbi (0105332, intrq); /* remap to double int handler */ - - case 013: /* .DDS 105213 (OP_N) */ - return cpu_dbi (0105333, intrq); /* remap to double int handler */ - - case 014: /* .NGL 105214 (OP_AT) */ - O = fp_cvt (&op[1], fp_t, fp_f); /* convert value */ - AR = op[1].fpk[0]; /* move MSB to A */ - BR = op[1].fpk[1]; /* move LSB to B */ - return reason; - - case 032: /* ..FCM 105232 (OP_R) */ - O = fp_pcom (&op[0], fp_f); /* complement value */ - AR = op[0].fpk[0]; /* return result */ - BR = op[0].fpk[1]; /* to A/B registers */ - return reason; - - case 033: /* ..TCM 105233 (OP_A) */ - fpop = ReadOp (op[0].word, fp_t); /* read 4-word value */ - O = fp_pcom (&fpop, fp_t); /* complement it */ - WriteOp (op[0].word, fpop, fp_t); /* write 4-word value */ - return reason; - } /* fall thru if not special to F */ - } - -#endif /* end of int64 support */ - -switch (entry) { /* decode IR<4:0> */ - -/* FFP module 1 */ - - case 000: /* [nop] 105200 (OP_N) */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 M/E-series */ - return stop_inst; /* trap if not */ - break; - -#if defined (HAVE_INT64) /* int64 support available */ - - case 001: /* DBLE 105201 (OP_AAF) */ - O = fp_cvt (&op[2], fp_f, fp_x); /* convert value and clear overflow */ - WriteOp (op[1].word, op[2], fp_x); /* write extended-precision value */ - break; - - case 002: /* SNGL 105202 (OP_AX) */ - O = fp_cvt (&op[1], fp_x, fp_f); /* convert value */ - AR = op[1].fpk[0]; /* move MSB to A */ - BR = op[1].fpk[1]; /* move LSB to B */ - break; - - case 003: /* .XMPY 105203 (OP_AXX) */ - i = 0; /* params start at op[0] */ - goto XMPY; /* process as XMPY */ - - case 004: /* .XDIV 105204 (OP_AXX) */ - i = 0; /* params start at op[0] */ - goto XDIV; /* process as XDIV */ - -#endif /* end of int64 support */ - - case 005: /* .DFER 105205 (OP_AA) */ - BR = op[0].word; /* get destination address */ - AR = op[1].word; /* get source address */ - goto XFER; /* do transfer */ - -#if defined (HAVE_INT64) /* int64 support available */ - - case 006: /* .XPAK 105206 (OP_A) */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ - return stop_inst; /* trap if not */ - - if (intrq) { /* interrupt pending? */ - PC = err_PC; /* restart instruction */ - break; - } - - fpop = ReadOp (op[0].word, fp_x); /* read unpacked */ - O = fp_nrpack (&fpop, fpop, (int16) AR, fp_x); /* nrm/rnd/pack mantissa, exponent */ - WriteOp (op[0].word, fpop, fp_x); /* write result */ - break; - - case 007: /* XADD 105207 (OP_AAXX) */ - i = 1; /* params start at op[1] */ - XADD: /* enter here from .XADD */ - if (intrq) { /* interrupt pending? */ - PC = err_PC; /* restart instruction */ - break; - } - - O = fp_exec (001, &fpop, op[i + 1], op[i + 2]); /* three-word add */ - WriteOp (op[i].word, fpop, fp_x); /* write sum */ - break; - - case 010: /* XSUB 105210 (OP_AAXX) */ - i = 1; /* params start at op[1] */ - XSUB: /* enter here from .XSUB */ - if (intrq) { /* interrupt pending? */ - PC = err_PC; /* restart instruction */ - break; - } - - O = fp_exec (021, &fpop, op[i + 1], op[i + 2]); /* three-word subtract */ - WriteOp (op[i].word, fpop, fp_x); /* write difference */ - break; - - case 011: /* XMPY 105211 (OP_AAXX) */ - i = 1; /* params start at op[1] */ - XMPY: /* enter here from .XMPY */ - if (intrq) { /* interrupt pending? */ - PC = err_PC; /* restart instruction */ - break; - } - - O = fp_exec (041, &fpop, op[i + 1], op[i + 2]); /* three-word multiply */ - WriteOp (op[i].word, fpop, fp_x); /* write product */ - break; - - case 012: /* XDIV 105212 (OP_AAXX) */ - i = 1; /* params start at op[1] */ - XDIV: /* enter here from .XDIV */ - if (intrq) { /* interrupt pending? */ - PC = err_PC; /* restart instruction */ - break; - } - - O = fp_exec (061, &fpop, op[i + 1], op[i + 2]); /* three-word divide */ - WriteOp (op[i].word, fpop, fp_x); /* write quotient */ - break; - - case 013: /* .XADD 105213 (OP_AXX) */ - i = 0; /* params start at op[0] */ - goto XADD; /* process as XADD */ - - case 014: /* .XSUB 105214 (OP_AXX) */ - i = 0; /* params start at op[0] */ - goto XSUB; /* process as XSUB */ - - case 015: /* .XCOM 105215 (OP_A) */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ - return stop_inst; /* trap if not */ - - fpop = ReadOp (op[0].word, fp_x); /* read unpacked */ - AR = fp_ucom (&fpop, fp_x); /* complement and rtn exp adj */ - WriteOp (op[0].word, fpop, fp_x); /* write result */ - break; - - case 016: /* ..DCM 105216 (OP_A) */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ - return stop_inst; /* trap if not */ - - if (intrq) { /* interrupt pending? */ - PC = err_PC; /* restart instruction */ - break; - } - - fpop = ReadOp (op[0].word, fp_x); /* read operand */ - O = fp_pcom (&fpop, fp_x); /* complement (can't ovf neg) */ - WriteOp (op[0].word, fpop, fp_x); /* write result */ - break; - - case 017: /* DDINT 105217 (OP_AAX) */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ - return stop_inst; /* trap if not */ - - if (intrq) { /* interrupt pending? */ - PC = err_PC; /* restart instruction */ - break; - } - - O = fp_trun (&fpop, op[2], fp_x); /* truncate operand (can't ovf) */ - WriteOp (op[1].word, fpop, fp_x); /* write result */ - break; - -#endif /* end of int64 support */ - -/* FFP module 2 */ - - case 020: /* .XFER 105220 (OP_N) */ - if (UNIT_CPU_TYPE == UNIT_TYPE_2100) - PC = (PC + 1) & VAMASK; /* 2100 .XFER returns to P+2 */ - XFER: /* enter here from .DFER */ - sc = 3; /* set count for 3-wd xfer */ - goto CFER; /* do transfer */ - - case 021: /* .GOTO 105221 (OP_AK) */ - if ((int16) op[1].word < 1) /* index < 1? */ - op[1].word = 1; /* reset min */ - - sa = PC + op[1].word - 1; /* point to jump target */ - if (sa >= op[0].word) /* must be <= last target */ - sa = op[0].word - 1; - - da = ReadW (sa); /* get jump target */ - reason = resolve (da, &MA, intrq); /* resolve indirects */ - if (reason != SCPE_OK) { /* resolution failed? */ - PC = err_PC; /* irq restarts instruction */ - break; - } - - mp_dms_jmp (MA, 2); /* validate jump addr */ - PCQ_ENTRY; /* record last PC */ - PC = MA; /* jump */ - BR = op[0].word; /* (for 2100 FFP compat) */ - break; - - case 022: /* ..MAP 105222 (OP_KKKK) */ - op[1].word = op[1].word - 1; /* decrement 1st subscr */ - - if ((AR & 1) == 0) /* 2-dim access? */ - op[1].word = op[1].word + /* compute element offset */ - (op[2].word - 1) * op[3].word; - else { /* 3-dim access */ - reason = cpu_ops (OP_KK, op2, intrq); /* get 1st, 2nd ranges */ - if (reason != SCPE_OK) { /* evaluation failed? */ - PC = err_PC; /* irq restarts instruction */ - break; - } - op[1].word = op[1].word + /* offset */ - ((op[3].word - 1) * op2[1].word + - op[2].word - 1) * op2[0].word; - } - - AR = (op[0].word + op[1].word * BR) & DMASK; /* return element address */ - break; - - case 023: /* .ENTR 105223 (OP_A) */ - MA = PC - 3; /* get addr of entry point */ - ENTR: /* enter here from .ENTP */ - da = op[0].word; /* get addr of 1st formal */ - dc = MA - da; /* get count of formals */ - sa = ReadW (MA); /* get addr of return point */ - ra = ReadW (sa++); /* get rtn, ptr to 1st actual */ - WriteW (MA, ra); /* stuff rtn into caller's ent */ - sc = ra - sa; /* get count of actuals */ - if (sc > dc) /* use min (actuals, formals) */ - sc = dc; - - for (j = 0; j < sc; j++) { - MA = ReadW (sa++); /* get addr of actual */ - reason = resolve (MA, &MA, intrq); /* resolve indirect */ - if (reason != SCPE_OK) { /* resolution failed? */ - PC = err_PC; /* irq restarts instruction */ - break; - } - WriteW (da++, MA); /* put addr into formal */ - } - - AR = (uint16) ra; /* return address */ - BR = (uint16) da; /* addr of 1st unused formal */ - break; - - case 024: /* .ENTP 105224 (OP_A) */ - MA = PC - 5; /* get addr of entry point */ - goto ENTR; - - case 025: /* .PWR2 105225 (OP_RK) */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ - return stop_inst; /* trap if not */ - - fp_unpack (&fpop, &expon, op[0], fp_f); /* unpack value */ - expon = expon + (int16) (op[1].word); /* multiply by 2**n */ - fp_pack (&fpop, fpop, expon, fp_f); /* repack value */ - AR = fpop.fpk[0]; /* return result */ - BR = fpop.fpk[1]; /* to A/B registers */ - break; - - case 026: /* .FLUN 105226 (OP_R) */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ - return stop_inst; /* trap if not */ - - fp_unpack (&fpop, &expon, op[0], fp_f); /* unpack value */ - AR = (int16) expon; /* return expon to A */ - BR = fpop.fpk[1]; /* and low mant to B */ - break; - - case 027: /* $SETP 105227 (OP_K) */ - j = sa = AR; /* save initial value */ - sb = BR; /* save initial address */ - AR = 0; /* AR will return = 0 */ - BR = BR & VAMASK; /* addr must be direct */ - - do { - WriteW (BR, j); /* write value to address */ - j = (j + 1) & DMASK; /* incr value */ - BR = (BR + 1) & VAMASK; /* incr address */ - op[0].word = op[0].word - 1; /* decr count */ - if (op[0].word && intrq) { /* more and intr? */ - AR = (uint16) sa; /* restore A */ - BR = (uint16) sb; /* restore B */ - PC = err_PC; /* restart instruction */ - break; - } - } - while (op[0].word != 0); /* loop until count exhausted */ - break; - - case 030: /* .PACK 105230 (OP_RC) */ - if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ - return stop_inst; /* trap if not */ - - O = fp_nrpack (&fpop, op[0], /* nrm/rnd/pack value */ - (int16) (op[1].word), fp_f); - AR = fpop.fpk[0]; /* return result */ - BR = fpop.fpk[1]; /* to A/B registers */ - break; - - case 031: /* .CFER 105231 (OP_AA) */ - if ((UNIT_CPU_MODEL != UNIT_1000_E) && /* must be 1000 E-series */ - (UNIT_CPU_MODEL != UNIT_1000_F)) /* or 1000 F-series */ - return stop_inst; /* trap if not */ - - BR = op[0].word; /* get destination address */ - AR = op[1].word; /* get source address */ - sc = 4; /* set for 4-wd xfer */ - CFER: /* enter here from .XFER */ - for (j = 0; j < sc; j++) { /* xfer loop */ - WriteW (BR, ReadW (AR)); /* transfer word */ - AR = (AR + 1) & VAMASK; /* bump source addr */ - BR = (BR + 1) & VAMASK; /* bump destination addr */ - } - - E = 0; /* routine clears E */ - - if (UNIT_CPU_TYPE == UNIT_TYPE_2100) { /* 2100 (and .DFER/.XFER)? */ - AR = (AR + 1) & VAMASK; /* 2100 FFP returns X+4, Y+4 */ - BR = (BR + 1) & VAMASK; - } - break; - - default: /* others undefined */ - reason = stop_inst; - } - -return reason; -} - - -/* Double-Integer Instructions. - - The double-integer instructions were added to the HP instruction set at - revision 1920 of the 1000-F. They were immediately adopted in a number of HP - software products, most notably the RTE file management package (FMP) - routines. As these routines are used in nearly every RTE program, F-Series - programs were almost always a few hundred bytes smaller than their M- and - E-Series counterparts. This became significant as RTE continued to grow in - size, and some customer programs ran out of address space on E-Series - machines. - - While HP never added double-integer instructions to the standard E-Series, a - product from the HP "specials group," HP 93585A, provided microcoded - replacements for the E-Series. This could provide just enough address-space - savings to allow programs to load in E-Series systems, in addition to - accelerating these common operations. - - There was no equivalent M-Series microcode, due to the limited micromachine - address space on that system. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A N/A 93585A std - - The routines are mapped to instruction codes as follows: - - Instr. 1000-E 1000-F Description - ------ ------ ------ ----------------------------------------- - [test] 105320 -- [self test] - .DAD 105321 105014 Double integer add - .DMP 105322 105054 Double integer multiply - .DNG 105323 105203 Double integer negate - .DCO 105324 105204 Double integer compare - .DDI 105325 105074 Double integer divide - .DDIR 105326 105134 Double integer divide (reversed) - .DSB 105327 105034 Double integer subtract - .DIN 105330 105210 Double integer increment - .DDE 105331 105211 Double integer decrement - .DIS 105332 105212 Double integer increment and skip if zero - .DDS 105333 105213 Double integer decrement and skip if zero - .DSBR 105334 105114 Double integer subtraction (reversed) - - On the F-Series, the double-integer instruction codes are split among the - floating-point processor and the Fast FORTRAN Processor ranges. They are - dispatched from those respective simulators for processing here. - - Implementation notes: - - 1. Opcodes 105335-105337 are NOPs in the microcode. They generate - unimplemented instructions stops under simulation. - - 2. This is an implementation of Revision 2 of the microcode, which was - released as ROM part numbers 93585-80003, 93585-80005, and 93585-80001 - (Revision 1 substituted -80002 for -80005). - - 3. The F-Series firmware executes .DMP and .DDI/.DDIR by floating the 32-bit - double integer to a 48-bit extended-precision number, calling the FPP to - execute the extended-precision multiply/divide, and then fixing the - product to a 32-bit double integer. We simulate these directly with 64- - or 32-bit integer arithmetic. - - Additional references: - - 93585A Microcode Source (93585-18002 Rev. 2005) - - 93585A Double Integer Instructions Installation and Reference Manual - (93585-90007) -*/ - -static const OP_PAT op_dbi[16] = { - OP_N, OP_JD, OP_JD, OP_J, /* [test] .DAD .DMP .DNG */ - OP_JD, OP_JD, OP_JD, OP_JD, /* .DCO .DDI .DDIR .DSB */ - OP_J, OP_J, OP_A, OP_A, /* .DIN .DDE .DIS .DDS */ - OP_JD, OP_N, OP_N, OP_N /* .DSBR --- --- --- */ - }; - -t_stat cpu_dbi (uint32 IR, uint32 intrq) -{ -OP din; -OPS op; -uint32 entry, t; -t_stat reason = SCPE_OK; - -entry = IR & 017; /* mask to entry point */ - -if (op_dbi[entry] != OP_N) { - reason = cpu_ops (op_dbi [entry], op, intrq); /* get instruction operands */ - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -switch (entry) { /* decode IR<3:0> */ - - case 000: /* [test] 105320 (OP_N) */ - XR = 2; /* set revision */ - BR = 0377; /* side effect of microcode */ - SR = 0102077; /* set "pass" code */ - PC = (PC + 1) & VAMASK; /* return to P+1 */ - t = (AR << 16) | BR; /* set t for return */ - break; - - case 001: /* .DAD 105321 (OP_JD) */ - t = op[0].dword + op[1].dword; /* add values */ - E = E | (t < op[0].dword); /* carry if result smaller */ - O = (((~op[0].dword ^ op[1].dword) & /* overflow if sign wrong */ - (op[0].dword ^ t) & SIGN32) != 0); - break; - - case 002: /* .DMP 105322 (OP_JD) */ - { - -#if defined (HAVE_INT64) /* int64 support available */ - - t_int64 t64; - - t64 = (t_int64) INT32 (op[0].dword) * /* multiply signed values */ - (t_int64) INT32 (op[1].dword); - O = ((t64 < -(t_int64) 0x80000000) || /* overflow if out of range */ - (t64 > (t_int64) 0x7FFFFFFF)); - if (O) - t = ~SIGN32; /* if overflow, rtn max pos */ - else - t = (uint32) (t64 & DMASK32); /* else lower 32 bits of result */ - -#else /* int64 support unavailable */ - - uint32 sign, xu, yu, rh, rl; - - sign = ((int32) op[0].dword < 0) ^ /* save sign of result */ - ((int32) op[1].dword < 0); - - xu = (uint32) abs ((int32) op[0].dword); /* make operands pos */ - yu = (uint32) abs ((int32) op[1].dword); - - if ((xu & 0xFFFF0000) == 0 && /* 16 x 16 multiply? */ - (yu & 0xFFFF0000) == 0) { - t = xu * yu; /* do it */ - O = 0; /* can't overflow */ - } - - else if ((xu & 0xFFFF0000) != 0 && /* 32 x 32 multiply? */ - (yu & 0xFFFF0000) != 0) - O = 1; /* always overflows */ - - else { /* 16 x 32 or 32 x 16 */ - rl = (xu & 0xFFFF) * (yu & 0xFFFF); /* form 1st partial product */ - - if ((xu & 0xFFFF0000) == 0) - rh = xu * (yu >> 16) + (rl >> 16); /* 16 x 32 2nd partial */ - else - rh = (xu >> 16) * yu + (rl >> 16); /* 32 x 16 2nd partial */ - - O = (rh > 0x7FFF + sign); /* check for out of range */ - if (O == 0) - t = (rh << 16) | (rl & 0xFFFF); /* combine partials */ - } - - if (O) /* if overflow occurred */ - t = ~SIGN32; /* then return the largest positive number */ - else if (sign) /* otherwise if the result is negative */ - t = ~t + 1; /* then return the twos complement (set if O = 0 above) */ - -#endif /* end of int64 support */ - - } - break; - - case 003: /* .DNG 105323 (OP_J) */ - t = ~op[0].dword + 1; /* negate value */ - O = (op[0].dword == SIGN32); /* overflow if max neg */ - if (op[0].dword == 0) /* borrow if result zero */ - E = 1; - break; - - case 004: /* .DCO 105324 (OP_JD) */ - t = op[0].dword; /* copy for later store */ - if ((int32) op[0].dword < (int32) op[1].dword) - PC = (PC + 1) & VAMASK; /* < rtns to P+2 */ - else if ((int32) op[0].dword > (int32) op[1].dword) - PC = (PC + 2) & VAMASK; /* > rtns to P+3 */ - break; /* = rtns to P+1 */ - - case 005: /* .DDI 105325 (OP_JD) */ - DDI: - O = ((op[1].dword == 0) || /* overflow if div 0 */ - ((op[0].dword == SIGN32) && /* or max neg div -1 */ - ((int32) op[1].dword == -1))); - if (O) - t = ~SIGN32; /* rtn max pos for ovf */ - else - t = (uint32) (INT32 (op[0].dword) / /* else return quotient */ - INT32 (op[1].dword)); - break; - - case 006: /* .DDIR 105326 (OP_JD) */ - t = op[0].dword; /* swap operands */ - op[0].dword = op[1].dword; - op[1].dword = t; - goto DDI; /* continue at .DDI */ - - case 007: /* .DSB 105327 (OP_JD) */ - DSB: - t = op[0].dword - op[1].dword; /* subtract values */ - E = E | (op[0].dword < op[1].dword); /* borrow if minu < subtr */ - O = (((op[0].dword ^ op[1].dword) & /* overflow if sign wrong */ - (op[0].dword ^ t) & SIGN32) != 0); - break; - - case 010: /* .DIN 105330 (OP_J) */ - t = op[0].dword + 1; /* increment value */ - O = (t == SIGN32); /* overflow if sign flipped */ - if (t == 0) - E = 1; /* carry if result zero */ - break; - - case 011: /* .DDE 105331 (OP_J) */ - t = op[0].dword - 1; /* decrement value */ - O = (t == ~SIGN32); /* overflow if sign flipped */ - if ((int32) t == -1) - E = 1; /* borrow if result -1 */ - break; - - case 012: /* .DIS 105332 (OP_A) */ - din = ReadOp (op[0].word, in_d); /* get value */ - t = din.dword = din.dword + 1; /* increment value */ - WriteOp (op[0].word, din, in_d); /* store it back */ - if (t == 0) - PC = (PC + 1) & VAMASK; /* skip if result zero */ - break; - - case 013: /* .DDS 105333 (OP_A) */ - din = ReadOp (op[0].word, in_d); /* get value */ - t = din.dword = din.dword - 1; /* decrement value */ - WriteOp (op[0].word, din, in_d); /* write it back */ - if (t == 0) - PC = (PC + 1) & VAMASK; /* skip if result zero */ - break; - - case 014: /* .DSBR 105334 (OP_JD) */ - t = op[0].dword; /* swap operands */ - op[0].dword = op[1].dword; - op[1].dword = t; - goto DSB; /* continue at .DSB */ - - default: /* others undefined */ - t = (AR << 16) | BR; /* set t for NOP */ - reason = stop_inst; - } - -if (reason == SCPE_OK) { /* if return OK */ - AR = (t >> 16) & DMASK; /* break result */ - BR = t & DMASK; /* into A and B */ - } - -return reason; -} +/* hp2100_cpu3.c: HP 2100/1000 FFP/DBI instructions + + Copyright (c) 2005-2016, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + CPU3 Fast FORTRAN and Double Integer instructions + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 24-Dec-14 JDB Added casts for explicit downward conversions + 09-May-12 JDB Separated assignments from conditional expressions + 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h + 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) + 05-Aug-08 JDB Updated mp_dms_jmp calling sequence + 27-Feb-08 JDB Added DBI self-test instruction + 23-Oct-07 JDB Fixed unsigned-divide bug in .DDI + 17-Oct-07 JDB Fixed unsigned-multiply bug in .DMP + 16-Oct-06 JDB Calls FPP for extended-precision math + 12-Oct-06 JDB Altered DBLE, DDINT for F-Series FFP compatibility + 26-Sep-06 JDB Moved from hp2100_cpu1.c to simplify extensions + 09-Aug-06 JDB Added double-integer instruction set + 18-Feb-05 JDB Add 2100/21MX Fast FORTRAN Processor instructions + + Primary references: + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - Macro/1000 Reference Manual (92059-90001, Dec-1992) + + Additional references are listed with the associated firmware + implementations, as are the HP option model numbers pertaining to the + applicable CPUs. +*/ + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + +#if defined (HAVE_INT64) /* int64 support available */ +#include "hp2100_fp1.h" +#else /* int64 support unavailable */ +#include "hp2100_fp.h" +#endif /* end of int64 support */ + + +/* Fast FORTRAN Processor. + + The Fast FORTRAN Processor (FFP) is a set of FORTRAN language accelerators + and extended-precision (three-word) floating point routines. Although the + FFP is an option for the 2100 and later CPUs, each implements the FFP in a + slightly different form. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A 12907A 12977B 13306B std + + The instruction codes are mapped to routines as follows: + + Instr. 2100 1000-M 1000-E 1000-F Instr. 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ + 105200 -- [nop] [nop] [test] 105220 .XFER .XFER .XFER .XFER + 105201 DBLE DBLE DBLE DBLE 105221 .GOTO .GOTO .GOTO .GOTO + 105202 SNGL SNGL SNGL SNGL 105222 ..MAP ..MAP ..MAP ..MAP + 105203 .XMPY .XMPY .XMPY .DNG 105223 .ENTR .ENTR .ENTR .ENTR + 105204 .XDIV .XDIV .XDIV .DCO 105224 .ENTP .ENTP .ENTP .ENTP + 105205 .DFER .DFER .DFER .DFER 105225 -- .PWR2 .PWR2 .PWR2 + 105206 -- .XPAK .XPAK .XPAK 105226 -- .FLUN .FLUN .FLUN + 105207 -- XADD XADD .BLE 105227 $SETP $SETP $SETP $SETP + + 105210 -- XSUB XSUB .DIN 105230 -- .PACK .PACK .PACK + 105211 -- XMPY XMPY .DDE 105231 -- -- .CFER .CFER + 105212 -- XDIV XDIV .DIS 105232 -- -- -- ..FCM + 105213 .XADD .XADD .XADD .DDS 105233 -- -- -- ..TCM + 105214 .XSUB .XSUB .XSUB .NGL 105234 -- -- -- -- + 105215 -- .XCOM .XCOM .XCOM 105235 -- -- -- -- + 105216 -- ..DCM ..DCM ..DCM 105236 -- -- -- -- + 105217 -- DDINT DDINT DDINT 105237 -- -- -- -- + + The F-Series maps different instructions to several of the standard FFP + opcodes. We first look for these and dispatch them appropriately before + falling into the handler for the common instructions. + + The math functions use the F-Series FPP for implementation. The FPP requires + that the host compiler support 64-bit integers. Therefore, if 64-bit + integers are not available, the math instructions of the FFP are disabled. + We allow this partial implementation as an aid in running systems generated + for the FFP. Most system programs did not use the math instructions, but + almost all use .ENTR. Supporting the latter even on systems that do not + support the former still allows such systems to boot. + + Implementation notes: + + 1. The "$SETP" instruction is sometimes listed as ".SETP" in the + documentation. + + 2. Extended-precision arithmetic routines (e.g., .XMPY) exist on the 1000-F, + but they are assigned instruction codes in the single-precision + floating-point module range. They are replaced by several double integer + instructions, which we dispatch to the double integer handler. + + 3. The software implementation of ..MAP supports 1-, 2-, or 3-dimensional + arrays, designated by setting A = -1, 0, and +1, respectively. The + firmware implementation supports only 2- and 3-dimensional access. + + 4. The documentation for ..MAP for the 2100 FFP shows A = 0 or -1 for two or + three dimensions, respectively, but the 1000 FFP shows A = 0 or +1. The + firmware actually only checks the LSB of A. + + 5. The .DFER and .XFER implementations for the 2100 FFP return X+4 and Y+4 + in the A and B registers, whereas the 1000 FFP returns X+3 and Y+3. + + 6. The .XFER implementation for the 2100 FFP returns to P+2, whereas the + 1000 implementation returns to P+1. + + 7. The firmware implementations of DBLE, .BLE, and DDINT clear the overflow + flag. The software implementations do not change overflow. + + 8. The M/E-Series FFP arithmetic instructions (.XADD, etc.) return negative + infinity on negative overflow and positive infinity on positive overflow. + The equivalent F-Series instructions return positive infinity on both. + + 9. The protected memory lower bound for the .GOTO instruction is 2. + + Additional references: + - DOS/RTE Relocatable Library Reference Manual (24998-90001, Oct-1981) + - Implementing the HP 2100 Fast FORTRAN Processor (12907-90010, Nov-1974) +*/ + +static const OP_PAT op_ffp_f[32] = { /* patterns for F-Series only */ + OP_N, OP_AAF, OP_AX, OP_N, /* [tst] DBLE SNGL .DNG */ + OP_N, OP_AA, OP_A, OP_AAF, /* .DCO .DFER .XPAK .BLE */ + OP_N, OP_N, OP_N, OP_N, /* .DIN .DDE .DIS .DDS */ + OP_AT, OP_A, OP_A, OP_AAX, /* .NGL .XCOM ..DCM DDINT */ + OP_N, OP_AK, OP_KKKK, OP_A, /* .XFER .GOTO ..MAP .ENTR */ + OP_A, OP_RK, OP_R, OP_K, /* .ENTP .PWR2 .FLUN $SETP */ + OP_RC, OP_AA, OP_R, OP_A, /* .PACK .CFER ..FCM ..TCM */ + OP_N, OP_N, OP_N, OP_N /* --- --- --- --- */ + }; + +static const OP_PAT op_ffp_e[32] = { /* patterns for 2100/M/E-Series */ + OP_N, OP_AAF, OP_AX, OP_AXX, /* [nop] DBLE SNGL .XMPY */ + OP_AXX, OP_AA, OP_A, OP_AAXX, /* .XDIV .DFER .XPAK XADD */ + OP_AAXX, OP_AAXX, OP_AAXX, OP_AXX, /* XSUB XMPY XDIV .XADD */ + OP_AXX, OP_A, OP_A, OP_AAX, /* .XSUB .XCOM ..DCM DDINT */ + OP_N, OP_AK, OP_KKKK, OP_A, /* .XFER .GOTO ..MAP .ENTR */ + OP_A, OP_RK, OP_R, OP_K, /* .ENTP .PWR2 .FLUN $SETP */ + OP_RC, OP_AA, OP_N, OP_N, /* .PACK .CFER --- --- */ + OP_N, OP_N, OP_N, OP_N /* --- --- --- --- */ + }; + +t_stat cpu_ffp (uint32 IR, uint32 intrq) +{ +OP fpop; +OPS op, op2; +uint32 entry; +uint32 j, sa, sb, sc, da, dc, ra, MA; +int32 expon; +t_stat reason = SCPE_OK; + +#if defined (HAVE_INT64) /* int64 support available */ + +int32 i; + +#endif /* end of int64 support */ + +entry = IR & 037; /* mask to entry point */ + +if (UNIT_CPU_MODEL != UNIT_1000_F) { /* 2100/M/E-Series? */ + if (op_ffp_e [entry] != OP_N) { + reason = cpu_ops (op_ffp_e [entry], op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + } + +#if defined (HAVE_INT64) /* int64 support available */ + +else { /* F-Series */ + if (op_ffp_f [entry] != OP_N) { + reason = cpu_ops (op_ffp_f [entry], op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + + switch (entry) { /* decode IR<4:0> */ + + case 000: /* [tst] 105200 (OP_N) */ + XR = 4; /* firmware revision */ + SR = 0102077; /* test passed code */ + AR = 0; /* test clears A/B */ + BR = 0; + PR = (PR + 1) & VAMASK; /* P+2 return for firmware w/DBI */ + return reason; + + case 003: /* .DNG 105203 (OP_N) */ + return cpu_dbi (0105323, intrq); /* remap to double int handler */ + + case 004: /* .DCO 105204 (OP_N) */ + return cpu_dbi (0105324, intrq); /* remap to double int handler */ + + case 007: /* .BLE 105207 (OP_AAF) */ + O = fp_cvt (&op[2], fp_f, fp_t); /* convert value and clear overflow */ + WriteOp (op[1].word, op[2], fp_t); /* write double-precision value */ + return reason; + + case 010: /* .DIN 105210 (OP_N) */ + return cpu_dbi (0105330, intrq); /* remap to double int handler */ + + case 011: /* .DDE 105211 (OP_N) */ + return cpu_dbi (0105331, intrq); /* remap to double int handler */ + + case 012: /* .DIS 105212 (OP_N) */ + return cpu_dbi (0105332, intrq); /* remap to double int handler */ + + case 013: /* .DDS 105213 (OP_N) */ + return cpu_dbi (0105333, intrq); /* remap to double int handler */ + + case 014: /* .NGL 105214 (OP_AT) */ + O = fp_cvt (&op[1], fp_t, fp_f); /* convert value */ + AR = op[1].fpk[0]; /* move MSB to A */ + BR = op[1].fpk[1]; /* move LSB to B */ + return reason; + + case 032: /* ..FCM 105232 (OP_R) */ + O = fp_pcom (&op[0], fp_f); /* complement value */ + AR = op[0].fpk[0]; /* return result */ + BR = op[0].fpk[1]; /* to A/B registers */ + return reason; + + case 033: /* ..TCM 105233 (OP_A) */ + fpop = ReadOp (op[0].word, fp_t); /* read 4-word value */ + O = fp_pcom (&fpop, fp_t); /* complement it */ + WriteOp (op[0].word, fpop, fp_t); /* write 4-word value */ + return reason; + } /* fall thru if not special to F */ + } + +#endif /* end of int64 support */ + +switch (entry) { /* decode IR<4:0> */ + +/* FFP module 1 */ + + case 000: /* [nop] 105200 (OP_N) */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 M/E-series */ + return stop_inst; /* trap if not */ + break; + +#if defined (HAVE_INT64) /* int64 support available */ + + case 001: /* DBLE 105201 (OP_AAF) */ + O = fp_cvt (&op[2], fp_f, fp_x); /* convert value and clear overflow */ + WriteOp (op[1].word, op[2], fp_x); /* write extended-precision value */ + break; + + case 002: /* SNGL 105202 (OP_AX) */ + O = fp_cvt (&op[1], fp_x, fp_f); /* convert value */ + AR = op[1].fpk[0]; /* move MSB to A */ + BR = op[1].fpk[1]; /* move LSB to B */ + break; + + case 003: /* .XMPY 105203 (OP_AXX) */ + i = 0; /* params start at op[0] */ + goto XMPY; /* process as XMPY */ + + case 004: /* .XDIV 105204 (OP_AXX) */ + i = 0; /* params start at op[0] */ + goto XDIV; /* process as XDIV */ + +#endif /* end of int64 support */ + + case 005: /* .DFER 105205 (OP_AA) */ + BR = op[0].word; /* get destination address */ + AR = op[1].word; /* get source address */ + goto XFER; /* do transfer */ + +#if defined (HAVE_INT64) /* int64 support available */ + + case 006: /* .XPAK 105206 (OP_A) */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ + return stop_inst; /* trap if not */ + + if (intrq) { /* interrupt pending? */ + PR = err_PC; /* restart instruction */ + break; + } + + fpop = ReadOp (op[0].word, fp_x); /* read unpacked */ + O = fp_nrpack (&fpop, fpop, (int16) AR, fp_x); /* nrm/rnd/pack mantissa, exponent */ + WriteOp (op[0].word, fpop, fp_x); /* write result */ + break; + + case 007: /* XADD 105207 (OP_AAXX) */ + i = 1; /* params start at op[1] */ + XADD: /* enter here from .XADD */ + if (intrq) { /* interrupt pending? */ + PR = err_PC; /* restart instruction */ + break; + } + + O = fp_exec (001, &fpop, op[i + 1], op[i + 2]); /* three-word add */ + WriteOp (op[i].word, fpop, fp_x); /* write sum */ + break; + + case 010: /* XSUB 105210 (OP_AAXX) */ + i = 1; /* params start at op[1] */ + XSUB: /* enter here from .XSUB */ + if (intrq) { /* interrupt pending? */ + PR = err_PC; /* restart instruction */ + break; + } + + O = fp_exec (021, &fpop, op[i + 1], op[i + 2]); /* three-word subtract */ + WriteOp (op[i].word, fpop, fp_x); /* write difference */ + break; + + case 011: /* XMPY 105211 (OP_AAXX) */ + i = 1; /* params start at op[1] */ + XMPY: /* enter here from .XMPY */ + if (intrq) { /* interrupt pending? */ + PR = err_PC; /* restart instruction */ + break; + } + + O = fp_exec (041, &fpop, op[i + 1], op[i + 2]); /* three-word multiply */ + WriteOp (op[i].word, fpop, fp_x); /* write product */ + break; + + case 012: /* XDIV 105212 (OP_AAXX) */ + i = 1; /* params start at op[1] */ + XDIV: /* enter here from .XDIV */ + if (intrq) { /* interrupt pending? */ + PR = err_PC; /* restart instruction */ + break; + } + + O = fp_exec (061, &fpop, op[i + 1], op[i + 2]); /* three-word divide */ + WriteOp (op[i].word, fpop, fp_x); /* write quotient */ + break; + + case 013: /* .XADD 105213 (OP_AXX) */ + i = 0; /* params start at op[0] */ + goto XADD; /* process as XADD */ + + case 014: /* .XSUB 105214 (OP_AXX) */ + i = 0; /* params start at op[0] */ + goto XSUB; /* process as XSUB */ + + case 015: /* .XCOM 105215 (OP_A) */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ + return stop_inst; /* trap if not */ + + fpop = ReadOp (op[0].word, fp_x); /* read unpacked */ + AR = fp_ucom (&fpop, fp_x); /* complement and rtn exp adj */ + WriteOp (op[0].word, fpop, fp_x); /* write result */ + break; + + case 016: /* ..DCM 105216 (OP_A) */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ + return stop_inst; /* trap if not */ + + if (intrq) { /* interrupt pending? */ + PR = err_PC; /* restart instruction */ + break; + } + + fpop = ReadOp (op[0].word, fp_x); /* read operand */ + O = fp_pcom (&fpop, fp_x); /* complement (can't ovf neg) */ + WriteOp (op[0].word, fpop, fp_x); /* write result */ + break; + + case 017: /* DDINT 105217 (OP_AAX) */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ + return stop_inst; /* trap if not */ + + if (intrq) { /* interrupt pending? */ + PR = err_PC; /* restart instruction */ + break; + } + + O = fp_trun (&fpop, op[2], fp_x); /* truncate operand (can't ovf) */ + WriteOp (op[1].word, fpop, fp_x); /* write result */ + break; + +#endif /* end of int64 support */ + +/* FFP module 2 */ + + case 020: /* .XFER 105220 (OP_N) */ + if (UNIT_CPU_TYPE == UNIT_TYPE_2100) + PR = (PR + 1) & VAMASK; /* 2100 .XFER returns to P+2 */ + XFER: /* enter here from .DFER */ + sc = 3; /* set count for 3-wd xfer */ + goto CFER; /* do transfer */ + + case 021: /* .GOTO 105221 (OP_AK) */ + if ((int16) op[1].word < 1) /* index < 1? */ + op[1].word = 1; /* reset min */ + + sa = PR + op[1].word - 1; /* point to jump target */ + if (sa >= op[0].word) /* must be <= last target */ + sa = op[0].word - 1; + + da = ReadW (sa); /* get jump target */ + reason = resolve (da, &MA, intrq); /* resolve indirects */ + if (reason != SCPE_OK) { /* resolution failed? */ + PR = err_PC; /* irq restarts instruction */ + break; + } + + mp_dms_jmp (MA, 2); /* validate jump addr */ + PCQ_ENTRY; /* record last P */ + PR = MA; /* jump */ + BR = op[0].word; /* (for 2100 FFP compat) */ + break; + + case 022: /* ..MAP 105222 (OP_KKKK) */ + op[1].word = op[1].word - 1; /* decrement 1st subscr */ + + if ((AR & 1) == 0) /* 2-dim access? */ + op[1].word = op[1].word + /* compute element offset */ + (op[2].word - 1) * op[3].word; + else { /* 3-dim access */ + reason = cpu_ops (OP_KK, op2, intrq); /* get 1st, 2nd ranges */ + if (reason != SCPE_OK) { /* evaluation failed? */ + PR = err_PC; /* irq restarts instruction */ + break; + } + op[1].word = op[1].word + /* offset */ + ((op[3].word - 1) * op2[1].word + + op[2].word - 1) * op2[0].word; + } + + AR = (op[0].word + op[1].word * BR) & DMASK; /* return element address */ + break; + + case 023: /* .ENTR 105223 (OP_A) */ + MA = PR - 3; /* get addr of entry point */ + ENTR: /* enter here from .ENTP */ + da = op[0].word; /* get addr of 1st formal */ + dc = MA - da; /* get count of formals */ + sa = ReadW (MA); /* get addr of return point */ + ra = ReadW (sa++); /* get rtn, ptr to 1st actual */ + WriteW (MA, ra); /* stuff rtn into caller's ent */ + sc = ra - sa; /* get count of actuals */ + if (sc > dc) /* use min (actuals, formals) */ + sc = dc; + + for (j = 0; j < sc; j++) { + MA = ReadW (sa++); /* get addr of actual */ + reason = resolve (MA, &MA, intrq); /* resolve indirect */ + if (reason != SCPE_OK) { /* resolution failed? */ + PR = err_PC; /* irq restarts instruction */ + break; + } + WriteW (da++, MA); /* put addr into formal */ + } + + AR = (uint16) ra; /* return address */ + BR = (uint16) da; /* addr of 1st unused formal */ + break; + + case 024: /* .ENTP 105224 (OP_A) */ + MA = PR - 5; /* get addr of entry point */ + goto ENTR; + + case 025: /* .PWR2 105225 (OP_RK) */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ + return stop_inst; /* trap if not */ + + fp_unpack (&fpop, &expon, op[0], fp_f); /* unpack value */ + expon = expon + (int16) (op[1].word); /* multiply by 2**n */ + fp_pack (&fpop, fpop, expon, fp_f); /* repack value */ + AR = fpop.fpk[0]; /* return result */ + BR = fpop.fpk[1]; /* to A/B registers */ + break; + + case 026: /* .FLUN 105226 (OP_R) */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ + return stop_inst; /* trap if not */ + + fp_unpack (&fpop, &expon, op[0], fp_f); /* unpack value */ + AR = (int16) expon; /* return expon to A */ + BR = fpop.fpk[1]; /* and low mant to B */ + break; + + case 027: /* $SETP 105227 (OP_K) */ + j = sa = AR; /* save initial value */ + sb = BR; /* save initial address */ + AR = 0; /* AR will return = 0 */ + BR = BR & VAMASK; /* addr must be direct */ + + do { + WriteW (BR, j); /* write value to address */ + j = (j + 1) & DMASK; /* incr value */ + BR = (BR + 1) & VAMASK; /* incr address */ + op[0].word = op[0].word - 1; /* decr count */ + if (op[0].word && intrq) { /* more and intr? */ + AR = (uint16) sa; /* restore A */ + BR = (uint16) sb; /* restore B */ + PR = err_PC; /* restart instruction */ + break; + } + } + while (op[0].word != 0); /* loop until count exhausted */ + break; + + case 030: /* .PACK 105230 (OP_RC) */ + if (UNIT_CPU_TYPE != UNIT_TYPE_1000) /* must be 1000 */ + return stop_inst; /* trap if not */ + + O = fp_nrpack (&fpop, op[0], /* nrm/rnd/pack value */ + (int16) (op[1].word), fp_f); + AR = fpop.fpk[0]; /* return result */ + BR = fpop.fpk[1]; /* to A/B registers */ + break; + + case 031: /* .CFER 105231 (OP_AA) */ + if ((UNIT_CPU_MODEL != UNIT_1000_E) && /* must be 1000 E-series */ + (UNIT_CPU_MODEL != UNIT_1000_F)) /* or 1000 F-series */ + return stop_inst; /* trap if not */ + + BR = op[0].word; /* get destination address */ + AR = op[1].word; /* get source address */ + sc = 4; /* set for 4-wd xfer */ + CFER: /* enter here from .XFER */ + for (j = 0; j < sc; j++) { /* xfer loop */ + WriteW (BR, ReadW (AR)); /* transfer word */ + AR = (AR + 1) & VAMASK; /* bump source addr */ + BR = (BR + 1) & VAMASK; /* bump destination addr */ + } + + E = 0; /* routine clears E */ + + if (UNIT_CPU_TYPE == UNIT_TYPE_2100) { /* 2100 (and .DFER/.XFER)? */ + AR = (AR + 1) & VAMASK; /* 2100 FFP returns X+4, Y+4 */ + BR = (BR + 1) & VAMASK; + } + break; + + default: /* others undefined */ + reason = stop_inst; + } + +return reason; +} + + +/* Double-Integer Instructions. + + The double-integer instructions were added to the HP instruction set at + revision 1920 of the 1000-F. They were immediately adopted in a number of HP + software products, most notably the RTE file management package (FMP) + routines. As these routines are used in nearly every RTE program, F-Series + programs were almost always a few hundred bytes smaller than their M- and + E-Series counterparts. This became significant as RTE continued to grow in + size, and some customer programs ran out of address space on E-Series + machines. + + While HP never added double-integer instructions to the standard E-Series, a + product from the HP "specials group," HP 93585A, provided microcoded + replacements for the E-Series. This could provide just enough address-space + savings to allow programs to load in E-Series systems, in addition to + accelerating these common operations. + + There was no equivalent M-Series microcode, due to the limited micromachine + address space on that system. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A N/A 93585A std + + The routines are mapped to instruction codes as follows: + + Instr. 1000-E 1000-F Description + ------ ------ ------ ----------------------------------------- + [test] 105320 -- [self test] + .DAD 105321 105014 Double integer add + .DMP 105322 105054 Double integer multiply + .DNG 105323 105203 Double integer negate + .DCO 105324 105204 Double integer compare + .DDI 105325 105074 Double integer divide + .DDIR 105326 105134 Double integer divide (reversed) + .DSB 105327 105034 Double integer subtract + .DIN 105330 105210 Double integer increment + .DDE 105331 105211 Double integer decrement + .DIS 105332 105212 Double integer increment and skip if zero + .DDS 105333 105213 Double integer decrement and skip if zero + .DSBR 105334 105114 Double integer subtraction (reversed) + + On the F-Series, the double-integer instruction codes are split among the + floating-point processor and the Fast FORTRAN Processor ranges. They are + dispatched from those respective simulators for processing here. + + Implementation notes: + + 1. Opcodes 105335-105337 are NOPs in the microcode. They generate + unimplemented instructions stops under simulation. + + 2. This is an implementation of Revision 2 of the microcode, which was + released as ROM part numbers 93585-80003, 93585-80005, and 93585-80001 + (Revision 1 substituted -80002 for -80005). + + 3. The F-Series firmware executes .DMP and .DDI/.DDIR by floating the 32-bit + double integer to a 48-bit extended-precision number, calling the FPP to + execute the extended-precision multiply/divide, and then fixing the + product to a 32-bit double integer. We simulate these directly with 64- + or 32-bit integer arithmetic. + + Additional references: + - 93585A Microcode Source (93585-18002 Rev. 2005) + - 93585A Double Integer Instructions Installation and Reference Manual + (93585-90007) +*/ + +static const OP_PAT op_dbi[16] = { + OP_N, OP_JD, OP_JD, OP_J, /* [test] .DAD .DMP .DNG */ + OP_JD, OP_JD, OP_JD, OP_JD, /* .DCO .DDI .DDIR .DSB */ + OP_J, OP_J, OP_A, OP_A, /* .DIN .DDE .DIS .DDS */ + OP_JD, OP_N, OP_N, OP_N /* .DSBR --- --- --- */ + }; + +t_stat cpu_dbi (uint32 IR, uint32 intrq) +{ +OP din; +OPS op; +uint32 entry, t; +t_stat reason = SCPE_OK; + +entry = IR & 017; /* mask to entry point */ + +if (op_dbi[entry] != OP_N) { + reason = cpu_ops (op_dbi [entry], op, intrq); /* get instruction operands */ + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +switch (entry) { /* decode IR<3:0> */ + + case 000: /* [test] 105320 (OP_N) */ + XR = 2; /* set revision */ + BR = 0377; /* side effect of microcode */ + SR = 0102077; /* set "pass" code */ + PR = (PR + 1) & VAMASK; /* return to P+1 */ + t = (AR << 16) | BR; /* set t for return */ + break; + + case 001: /* .DAD 105321 (OP_JD) */ + t = op[0].dword + op[1].dword; /* add values */ + E = E | (t < op[0].dword); /* carry if result smaller */ + O = (((~op[0].dword ^ op[1].dword) & /* overflow if sign wrong */ + (op[0].dword ^ t) & SIGN32) != 0); + break; + + case 002: /* .DMP 105322 (OP_JD) */ + { + +#if defined (HAVE_INT64) /* int64 support available */ + + t_int64 t64; + + t64 = (t_int64) INT32 (op[0].dword) * /* multiply signed values */ + (t_int64) INT32 (op[1].dword); + O = ((t64 < -(t_int64) 0x80000000) || /* overflow if out of range */ + (t64 > (t_int64) 0x7FFFFFFF)); + if (O) + t = ~SIGN32; /* if overflow, rtn max pos */ + else + t = (uint32) (t64 & DMASK32); /* else lower 32 bits of result */ + +#else /* int64 support unavailable */ + + uint32 sign, xu, yu, rh, rl; + + sign = ((int32) op[0].dword < 0) ^ /* save sign of result */ + ((int32) op[1].dword < 0); + + xu = (uint32) abs ((int32) op[0].dword); /* make operands pos */ + yu = (uint32) abs ((int32) op[1].dword); + + if ((xu & 0xFFFF0000) == 0 && /* 16 x 16 multiply? */ + (yu & 0xFFFF0000) == 0) { + t = xu * yu; /* do it */ + O = 0; /* can't overflow */ + } + + else if ((xu & 0xFFFF0000) != 0 && /* 32 x 32 multiply? */ + (yu & 0xFFFF0000) != 0) + O = 1; /* always overflows */ + + else { /* 16 x 32 or 32 x 16 */ + rl = (xu & 0xFFFF) * (yu & 0xFFFF); /* form 1st partial product */ + + if ((xu & 0xFFFF0000) == 0) + rh = xu * (yu >> 16) + (rl >> 16); /* 16 x 32 2nd partial */ + else + rh = (xu >> 16) * yu + (rl >> 16); /* 32 x 16 2nd partial */ + + O = (rh > 0x7FFF + sign); /* check for out of range */ + if (O == 0) + t = (rh << 16) | (rl & 0xFFFF); /* combine partials */ + } + + if (O) /* if overflow occurred */ + t = ~SIGN32; /* then return the largest positive number */ + else if (sign) /* otherwise if the result is negative */ + t = ~t + 1; /* then return the twos complement (set if O = 0 above) */ + +#endif /* end of int64 support */ + + } + break; + + case 003: /* .DNG 105323 (OP_J) */ + t = ~op[0].dword + 1; /* negate value */ + O = (op[0].dword == SIGN32); /* overflow if max neg */ + if (op[0].dword == 0) /* borrow if result zero */ + E = 1; + break; + + case 004: /* .DCO 105324 (OP_JD) */ + t = op[0].dword; /* copy for later store */ + if ((int32) op[0].dword < (int32) op[1].dword) + PR = (PR + 1) & VAMASK; /* < rtns to P+2 */ + else if ((int32) op[0].dword > (int32) op[1].dword) + PR = (PR + 2) & VAMASK; /* > rtns to P+3 */ + break; /* = rtns to P+1 */ + + case 005: /* .DDI 105325 (OP_JD) */ + DDI: + O = ((op[1].dword == 0) || /* overflow if div 0 */ + ((op[0].dword == SIGN32) && /* or max neg div -1 */ + ((int32) op[1].dword == -1))); + if (O) + t = ~SIGN32; /* rtn max pos for ovf */ + else + t = (uint32) (INT32 (op[0].dword) / /* else return quotient */ + INT32 (op[1].dword)); + break; + + case 006: /* .DDIR 105326 (OP_JD) */ + t = op[0].dword; /* swap operands */ + op[0].dword = op[1].dword; + op[1].dword = t; + goto DDI; /* continue at .DDI */ + + case 007: /* .DSB 105327 (OP_JD) */ + DSB: + t = op[0].dword - op[1].dword; /* subtract values */ + E = E | (op[0].dword < op[1].dword); /* borrow if minu < subtr */ + O = (((op[0].dword ^ op[1].dword) & /* overflow if sign wrong */ + (op[0].dword ^ t) & SIGN32) != 0); + break; + + case 010: /* .DIN 105330 (OP_J) */ + t = op[0].dword + 1; /* increment value */ + O = (t == SIGN32); /* overflow if sign flipped */ + if (t == 0) + E = 1; /* carry if result zero */ + break; + + case 011: /* .DDE 105331 (OP_J) */ + t = op[0].dword - 1; /* decrement value */ + O = (t == ~SIGN32); /* overflow if sign flipped */ + if ((int32) t == -1) + E = 1; /* borrow if result -1 */ + break; + + case 012: /* .DIS 105332 (OP_A) */ + din = ReadOp (op[0].word, in_d); /* get value */ + t = din.dword = din.dword + 1; /* increment value */ + WriteOp (op[0].word, din, in_d); /* store it back */ + if (t == 0) + PR = (PR + 1) & VAMASK; /* skip if result zero */ + break; + + case 013: /* .DDS 105333 (OP_A) */ + din = ReadOp (op[0].word, in_d); /* get value */ + t = din.dword = din.dword - 1; /* decrement value */ + WriteOp (op[0].word, din, in_d); /* write it back */ + if (t == 0) + PR = (PR + 1) & VAMASK; /* skip if result zero */ + break; + + case 014: /* .DSBR 105334 (OP_JD) */ + t = op[0].dword; /* swap operands */ + op[0].dword = op[1].dword; + op[1].dword = t; + goto DSB; /* continue at .DSB */ + + default: /* others undefined */ + t = (AR << 16) | BR; /* set t for NOP */ + reason = stop_inst; + } + +if (reason == SCPE_OK) { /* if return OK */ + AR = (t >> 16) & DMASK; /* break result */ + BR = t & DMASK; /* into A and B */ + } + +return reason; +} diff --git a/HP2100/hp2100_cpu4.c b/HP2100/hp2100_cpu4.c index 0d4502b5..b7a2482a 100644 --- a/HP2100/hp2100_cpu4.c +++ b/HP2100/hp2100_cpu4.c @@ -1,1128 +1,1129 @@ -/* hp2100_cpu4.c: HP 1000 FPP/SIS - - Copyright (c) 2006-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - CPU4 Floating Point Processor and Scientific Instruction Set - - 24-Dec-14 JDB Added casts for explicit downward conversions - 09-May-12 JDB Separated assignments from conditional expressions - 06-Feb-12 JDB Added OPSIZE casts to fp_accum calls in .FPWR/.TPWR - 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h - 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) - 18-Mar-08 JDB Fixed B register return bug in /CMRT - 01-Dec-06 JDB Substitutes FPP for firmware FP if HAVE_INT64 - - Primary references: - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - Macro/1000 Reference Manual (92059-90001, Dec-1992) - - Additional references are listed with the associated firmware - implementations, as are the HP option model numbers pertaining to the - applicable CPUs. -*/ - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - -#if defined (HAVE_INT64) /* int64 support available */ - -#include "hp2100_fp1.h" - - -/* Floating-Point Processor. - - The 1000 F-Series replaces the six 2100/1000-M/E single-precision firmware - floating-point instructions with a hardware floating-point processor (FPP). - The FPP executes single-, extended-, and double-precision floating-point - instructions, as well as double-integer instructions. All of the - floating-point instructions, as well as the single- and double-integer fix - and float instructions, are handled here. Pure double-integer instructions - are dispatched to the double-integer handler for simulation. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A N/A N/A std - - For the F-Series, the instruction codes are mapped to routines as follows: - - Instr. 1000-F Description - ------ ------ ------------------------------------- - 105000 FAD Single real add - 105001 .XADD Extended real add - 105002 .TADD Double real add - 105003 [EAD] [5-word add] - 105004 [tst] [Floating Point Processor self test] - 105005 [xpd] [Expand exponent] - 105006 [rst] [Floating Point Processor reset] - 105007 [stk] [Process stack of operands] - 105010 [chk] [FPP addressing check] - 105014 .DAD Double integer add - 105020 FSB Single real subtract - 105021 .XSUB Extended real subtract - 105022 .TSUB Double real subtract - 105023 [ESB] [5-word subtract] - 105034 .DSB Double integer subtract - 105040 FMP Single real multiply - 105041 .XMPY Extended real multiply - 105042 .TMPY Double real multiply - 105043 [EMP] [5-word multiply] - 105054 .DMP Double integer multiply - 105060 FDV Single real divide - 105061 .XDIV Extended real divide - 105062 .TDIV Double real divide - 105063 [EDV] [5-word divide] - 105074 .DDI Double integer divide - 105100 FIX Single real to integer fix - 105101 .XFXS Extended real to integer fix (.DINT) - 105102 .TXFS Double real to integer fix (.TINT) - 105103 [EFS] [5-word FIXS] - 105104 .FIXD Real to double integer fix - 105105 .XFXD Extended real to double integer fix - 105106 .TFXD Double real to double integer fix - 105107 [EFD] [5-word FIXD] - 105114 .DSBR Double integer subtraction (reversed) - 105120 FLT Integer to single real float - 105121 .XFTS Integer to extended real float (.IDBL) - 105122 .TFTS Integer to double real float (.ITBL) - 105123 [ELS] [5-word FLTS] - 105124 .FLTD Double integer to real float - 105125 .XFTD Double integer to extended real float - 105126 .TFTD Double integer to double real float - 105127 [ELD] [5-word FLTD] - 105134 .DDIR Double integer divide (reversed) - - Implementation note: rather than have two simulators that each executes the - single-precision FP instruction set, we compile conditionally, based on the - availability of 64-bit integer support in the host compiler. 64-bit integers - are required for the FPP, so if they are available, then we handle the - single-precision instructions for the 2100 and M/E-Series here, and the - firmware simulation is omitted. If support is unavailable, then the firmware - function is used instead. - - Notes: - - 1. Single-precision arithmetic instructions (.FAD, etc.) and extended- and - double-precision F-Series FPP arithmetic instructions (.XADD, .TADD, - etc.) return positive infinity on both positive and negative overflow. - The equivalent extended-precision M/E-Series FFP instructions return - negative infinity on negative overflow and positive infinity on positive - overflow. - - 2. The items in brackets above are undocumented instructions that are used - by the 12740 FPP-SIS-FFP diagnostic only. - - 3. The five-word arithmetic instructions (e.g., 105003) use an expanded - operand format that dedicates a separate word to the exponent. See the - implementation notes in the hardware floating-point processor simulation - for details. - - 4. The "self test" instruction (105004) returned to P+1 for early F-Series - units without double-integer support. Units incorporating such support - returned to P+2. - - 5. The "expand exponent" instruction (105005) is used as a "prefix" - instruction to enable a 10-bit exponent range. It is placed immediately - before a 5-word arithmetic instruction sequence, e.g., immediately - preceding an EAD instruction sequence. The arithmetic instruction - executes normally, except that under/overflow is not indicated unless - the exponent exceeds the 10-bit range, instead of the normal 8-bit - range. If overflow is indicated, the exponent is still set to +128. - - Note that as 2-, 3-, and 4-word packed numbers only have room for 8-bit - exponents, the Expand Exponent instruction serves no useful purpose in - conjunction with instructions associated with these precisions. If - used, the resulting values may be in error, as overflow from the 8-bit - exponents will not be indicated. - - 6. The "FPP reset" instruction (105006) is provided to reset a hung box, - e.g., in cases where an improper number of parameters is supplied. The - hardware resets its internal state machine in response to this - instruction. Under simulation, the instruction has no effect, as the - simulated FPP cannot hang. - - 7. The "process stack" instruction (105007) executes a series of FPP - instruction sets in sequence. Each set consists of a single FPP - instruction and associated operands that specifies the operation, - followed by a "result" instruction and operand. The result instruction - is optional and is only used to specify the result precision; the - instruction itself is not executed. If the result instruction is NOP, - then the result precision is that of the executed FPP instruction. If - the result operand is null, then the result is kept in the internal FPP - accumulator for later use. - - The calling sequence is as follows: - - STK Process stack instruction - DEF ERRTN Address of error return - DEF SET1 Address of first instruction set - DEF SET2 Address of second instruction set - . - . - . - ERRTN EQU * Return here if execution in error - OKRTN EQU * Return here if execution OK - - Instruction sets are specified as follows (e.g.): - - SET1 .TADD Operation instruction (NOP to terminate series) - DEC 4 Number of words in first operand (or 0 if accum) - DEF OP1 Address of first operand - DEC 4 Number of words in second operand (or 0 if accum) - DEF OP2 Address of second operand - .XADD Result precision conversion instruction (or NOP) - DEC 3 Number of words to store (or 0 if no store) - DEF RSLT Address of buffer to hold value - - The primary use of the "process stack" instruction is to enable chained - operations employing the FPP accumulator for intermediate results and to - enable expanded exponent usage across multiple instructions. - - 8. The "addressing check" instruction sets bit 0 of the L register to 1, - copies the X register value to the FPP, and then reads the FPP and - stores the result in the Y register. Setting the L register bit 0 to 1 - normally deselects the FPP, so that the value in Y is 177777. However, - the FPP box has a strap that inverts the selection logic, even though - the box will not work with the base-set firmware if this is done. The - "addressing check" instruction is provided to test whether the strap is - in the alternate location. Under simulation, the return value is always - 177777, indicating that the strap is correctly set. - - Additional references: - - DOS/RTE Relocatable Library Reference Manual (24998-90001, Oct-1981) - - FPP-SIS-FFP Diagnostic Source (12740-18001, Rev. 1926) -*/ - -static const OP_PAT op_fpp[96] = { - OP_RF, OP_AXX, OP_ATT, OP_AEE, /* FAD .XADD .TADD .EADD */ - OP_N, OP_C, OP_N, OP_A, /* [tst] [xpd] [rst] [stk] */ - OP_N, OP_N, OP_N, OP_N, /* [chk] --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* .DAD --- --- --- */ - OP_RF, OP_AXX, OP_ATT, OP_AEE, /* FSB .XSUB .TSUB .ESUB */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* .DSB --- --- --- */ - OP_RF, OP_AXX, OP_ATT, OP_AEE, /* FMP .XMPY .TMPY .EMPY */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* .DMP --- --- --- */ - OP_RF, OP_AXX, OP_ATT, OP_AEE, /* FDV .XDIV .TDIV .EDIV */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* .DDI --- --- --- */ - OP_R, OP_X, OP_T, OP_E, /* FIX .XFXS .TFXS .EFXS */ - OP_R, OP_X, OP_T, OP_E, /* .FIXD .XFXD .TFXD .EFXD */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* .DSBR --- --- --- */ - OP_I, OP_IA, OP_IA, OP_IA, /* FLT .XFTS .TFTS .EFTS */ - OP_J, OP_JA, OP_JA, OP_JA, /* .FLTD .XFTD .TFTD .EFTD */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N /* .DDIR --- --- --- */ - }; - -t_stat cpu_fpp (uint32 IR, uint32 intrq) -{ -OP fpop; -OPS op; -OPSIZE op1_prec, op2_prec, rslt_prec, cvt_prec; -uint16 opcode, rtn_addr, stk_ptr; -uint32 entry; -t_stat reason = SCPE_OK; - -if (UNIT_CPU_MODEL == UNIT_1000_F) /* F-Series? */ - opcode = (uint16) (IR & 0377); /* yes, use full opcode */ -else - opcode = (uint16) (IR & 0160); /* no, use 6 SP FP opcodes */ - -entry = opcode & 0177; /* map to <6:0> */ - -if (op_fpp [entry] != OP_N) { - reason = cpu_ops (op_fpp [entry], op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -switch (entry) { /* decode IR<6:0> */ - case 0000: /* FAD 105000 (OP_RF) */ - case 0020: /* FSB 105020 (OP_RF) */ - case 0040: /* FMP 105040 (OP_RF) */ - case 0060: /* FDV 105060 (OP_RF) */ - O = fp_exec (opcode, &fpop, op[0], op[1]); /* execute operation */ - AR = fpop.fpk[0]; /* return result to A/B */ - BR = fpop.fpk[1]; - break; - - case 0001: /* .XADD 105001 (OP_AXX) */ - case 0002: /* .TADD 105002 (OP_ATT) */ - case 0003: /* .EADD 105003 (OP_AEE) */ - - case 0021: /* .XSUB 105021 (OP_AXX) */ - case 0022: /* .TSUB 105022 (OP_ATT) */ - case 0023: /* .ESUB 105023 (OP_AEE) */ - - case 0041: /* .XMPY 105041 (OP_AXX) */ - case 0042: /* .TMPY 105042 (OP_ATT) */ - case 0043: /* .EMPY 105043 (OP_AEE) */ - - case 0061: /* .XDIV 105061 (OP_AXX) */ - case 0062: /* .TDIV 105062 (OP_ATT) */ - case 0063: /* .EDIV 105063 (OP_AEE) */ - O = fp_exec (opcode, &fpop, op[1], op[2]); /* execute operation */ - fp_prec (opcode, NULL, NULL, &rslt_prec); /* determine result precision */ - WriteOp (op[0].word, fpop, rslt_prec); /* write result */ - break; - - case 0004: /* [tst] 105004 (OP_N) */ - XR = 3; /* firmware revision */ - SR = 0102077; /* test passed code */ - PC = (PC + 1) & VAMASK; /* P+2 return for firmware w/DBI */ - break; - - case 0005: /* [xpd] 105005 (OP_C) */ - return cpu_fpp (op[0].word | 0200, intrq); /* set bit 7, execute instr */ - - case 0006: /* [rst] 105006 (OP_N) */ - break; /* do nothing for FPP reset */ - - case 0007: /* [stk] 105007 (OP_A) */ - O = 0; /* clear overflow */ - stk_ptr = (uint16) PC; /* save ptr to next buf */ - rtn_addr = op[0].word; /* save return address */ - - while (TRUE) { - PC = ReadW (stk_ptr) & VAMASK; /* point at next instruction set */ - stk_ptr = (stk_ptr + 1) & VAMASK; - - reason = cpu_ops (OP_CCACACCA, op, intrq); /* get instruction set */ - - if (reason) { - PC = err_PC; /* irq restarts */ - break; - } - - if (op[0].word == 0) { /* opcode = NOP? */ - PC = (rtn_addr + 1) & VAMASK; /* bump to good return */ - break; /* done */ - } - - fp_prec ((uint16) (op[0].word & 0377), /* determine operand precisions */ - &op1_prec, &op2_prec, &rslt_prec); - - if (TO_COUNT(op1_prec) != op[1].word) { /* first operand precisions agree? */ - PC = rtn_addr; /* no, so take error return */ - break; - } - - else if (op1_prec != fp_a) /* operand in accumulator? */ - op[1] = ReadOp (op[2].word, op1_prec); /* no, so get operand 1 */ - - if (TO_COUNT(op2_prec) != op[3].word) { /* second operand precisions agree? */ - PC = rtn_addr; /* no, so take error return */ - break; - } - - else if (op2_prec != fp_a) /* operand in accumulator? */ - op[2] = ReadOp (op[4].word, op2_prec); /* no, so get operand 2 */ - - O = O | /* execute instruction */ - fp_exec ((uint16) (op[0].word & 0377), /* and accumulate overflow */ - &fpop, op[1], op[2]); - - if (op[5].word) { /* precision conversion? */ - fp_prec ((uint16) (op[5].word & 0377), /* determine conversion precision */ - NULL, NULL, &cvt_prec); - - fpop = fp_accum (NULL, cvt_prec); /* convert result */ - } - else /* no conversion specified */ - cvt_prec = rslt_prec; /* so use original precision */ - - if (op[6].word) /* store result? */ - WriteOp (op[7].word, fpop, cvt_prec); /* yes, so write it */ - } - - break; - - case 0010: /* [chk] 105010 (OP_N) */ - YR = 0177777; /* -1 if selection strap OK */ - break; - - case 0014: /* .DAD 105014 (OP_N) */ - return cpu_dbi (0105321, intrq); /* remap to double int handler */ - - case 0034: /* .DSB 105034 (OP_N) */ - return cpu_dbi (0105327, intrq); /* remap to double int handler */ - - case 0054: /* .DMP 105054 (OP_N) */ - return cpu_dbi (0105322, intrq); /* remap to double int handler */ - - case 0074: /* .DDI 105074 (OP_N) */ - return cpu_dbi (0105325, intrq); /* remap to double int handler */ - - case 0100: /* FIX 105100 (OP_R) */ - case 0101: /* .XFXS 105101 (OP_X) */ - case 0102: /* .TFXS 105102 (OP_T) */ - case 0103: /* .EFXS 105103 (OP_E) */ - O = fp_exec (opcode, &fpop, op[0], NOP); /* fix to integer */ - AR = fpop.fpk[0]; /* save result */ - break; - - case 0104: /* .FIXD 105104 (OP_R) */ - case 0105: /* .XFXD 105105 (OP_X) */ - case 0106: /* .TFXD 105106 (OP_T) */ - case 0107: /* .EFXD 105107 (OP_E) */ - O = fp_exec (opcode, &fpop, op[0], NOP); /* fix to integer */ - AR = (fpop.dword >> 16) & DMASK; /* save result */ - BR = fpop.dword & DMASK; /* in A and B */ - break; - - case 0114: /* .DSBR 105114 (OP_N) */ - return cpu_dbi (0105334, intrq); /* remap to double int handler */ - - case 0120: /* FLT 105120 (OP_I) */ - case 0124: /* .FLTD 105124 (OP_J) */ - O = fp_exec (opcode, &fpop, op[0], NOP); /* float to single */ - AR = fpop.fpk[0]; /* save result */ - BR = fpop.fpk[1]; /* into A/B */ - break; - - case 0121: /* .XFTS 105121 (OP_IA) */ - case 0122: /* .TFTS 105122 (OP_IA) */ - case 0123: /* .EFTS 105123 (OP_IA) */ - case 0125: /* .XFTD 105125 (OP_JA) */ - case 0126: /* .TFTD 105126 (OP_JA) */ - case 0127: /* .EFTD 105127 (OP_JA) */ - O = fp_exec (opcode, &fpop, op[0], NOP); /* float integer */ - fp_prec (opcode, NULL, NULL, &rslt_prec); /* determine result precision */ - WriteOp (op[1].word, fpop, rslt_prec); /* write result */ - break; - - case 0134: /* .DDIR 105134 (OP_N) */ - return cpu_dbi (0105326, intrq); /* remap to double int handler */ - - default: /* others undefined */ - reason = stop_inst; - } - -return reason; -} - - -/* Scientific Instruction Set. - - The SIS adds single-precision trigonometric and logarithmic, and - double-precision polynomial evaluation instructions to the 1000-F instruction - set. The SIS is standard on the 1000-F. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A N/A N/A std - - The routines are mapped to instruction codes as follows: - - Instr. 1000-F Description - ------ ------ ---------------------------------------------- - TAN 105320 Tangent - SQRT 105321 Square root - ALOG 105322 Natural logarithm - ATAN 105323 Arc tangent - COS 105324 Cosine - SIN 105325 Sine - EXP 105326 E to the power X - ALOGT 105327 Common logarithm - TANH 105330 Hyperbolic tangent - DPOLY 105331 Double-precision polynomial evaluation - /CMRT 105332 Double-precision common range reduction - /ATLG 105333 Compute (1-x)/(1+x) for .ATAN and .LOG - .FPWR 105334 Single-precision exponentiation - .TPWR 105335 Double-precision exponentiation - [tst] 105337 [self test] - - The SIS simulation follows the F-Series SIS microcode, which, in turn, - follows the algebraic approximations given in the Relocatable Library manual - descriptions of the equivalent software routines. - - Notes: - - 1. The word following the DPOLY instruction contains up to three flag bits - to indicate one of several polynomial forms to evaluate. The comments - in the DPOLY software library routine source interchange the actions of - the bit 14 and bit 0 flags. The DPOLY description in the Technical - Reference Handbook is correct. - - 2. Several instructions (e.g., DPOLY) are documented as leaving undefined - values in the A, B, X, Y, E, or O registers. Simulation does not - attempt to reproduce the same values as would be obtained with the - hardware. - - 3. The SIS uses the hardware FPP of the F-Series. FPP malfunctions are - detected by the SIS firmware and are indicated by a memory-protect - violation and setting the overflow flag. Under simulation, - malfunctions cannot occur. - - 4. We use OP_IIT for the .FPWR operand pattern. The "II" is redundant, but - it aligns the operands with the OP_IAT of .TPWR, so the code may be - shared. - - Additional references: - - DOS/RTE Relocatable Library Reference Manual (24998-90001, Oct-1981) - - HP 1000 E-Series and F-Series Computer Microprogramming Reference Manual - (02109-90004, Apr-1980). -*/ - - -/* Common single-precision range reduction for SIN, COS, TAN, and EXP. - - This routine is called by the SIN, COS, TAN, and EXP handlers to reduce the - range of the argument. Reduction is performed in extended-precision. We - calculate: - - multiple = (nearest even integer to argument * multiplier) - argument = argument * multiplier - multiple -*/ - -static uint32 reduce (OP *argument, int32 *multiple, OP multiplier) -{ -OP product, count; -uint32 overflow; - -fp_cvt (argument, fp_f, fp_x); /* convert to extended precision */ -fp_exec (0041, &product, *argument, multiplier); /* product = argument * multiplier */ -overflow = fp_exec (0111, &count, NOP, NOP); /* count = FIX (acc) */ - -if ((int16) count.word >= 0) /* nearest even integer */ - count.word = count.word + 1; -count.word = count.word & ~1; -*multiple = (int16) count.word; - -if (overflow == 0) { /* in range? */ - fp_exec (0121, ACCUM, count, NOP); /* acc = FLT (count) */ - overflow = fp_exec (0025, ACCUM, product, NOP); /* acc = product - acc */ - *argument = fp_accum (NULL, fp_f); /* trim to single-precision */ - } -return overflow; -} - - -/* SIS dispatcher. */ - -static const OP_PAT op_sis[16] = { - OP_R, OP_R, OP_R, OP_R, /* TAN SQRT ALOG ATAN */ - OP_R, OP_R, OP_R, OP_R, /* COS SIN EXP ALOGT */ - OP_R, OP_CATAKK, OP_AAT, OP_A, /* TANH DPOLY /CMRT /ATLG */ - OP_IIF, OP_IAT, OP_N, OP_N /* .FPWR .TPWR --- [tst] */ - }; - -t_stat cpu_sis (uint32 IR, uint32 intrq) -{ -OPS op; -OP arg, coeff, pwr, product, count, result; -int16 f, p; -int32 multiple, power, exponent, rsltexp; -uint32 entry, i; -t_bool flag, sign; -t_stat reason = SCPE_OK; - -static const OP tan_c4 = { { 0137763, 0051006 } }; /* DEC -4.0030956 */ -static const OP tan_c3 = { { 0130007, 0051026 } }; /* DEC -1279.5424 */ -static const OP tan_c2 = { { 0040564, 0012761 } }; /* DEC 0.0019974806 */ -static const OP tan_c1 = { { 0045472, 0001375 } }; /* DEC 0.14692695 */ - -static const OP alog_c3 = { { 0065010, 0063002 } }; /* DEC 1.6567626301 */ -static const OP alog_c2 = { { 0125606, 0044404 } }; /* DEC -2.6398577035 */ -static const OP alog_c1 = { { 0051260, 0037402 } }; /* DEC 1.2920070987 */ - -static const OP atan_c4 = { { 0040257, 0154404 } }; /* DEC 2.0214656 */ -static const OP atan_c3 = { { 0132062, 0133406 } }; /* DEC -4.7376165 */ -static const OP atan_c2 = { { 0047407, 0173775 } }; /* DEC 0.154357652 */ -static const OP atan_c1 = { { 0053447, 0014002 } }; /* DEC 1.3617611 */ - -static const OP sin_c4 = { { 0132233, 0040745 } }; /* DEC -0.000035950439 */ -static const OP sin_c3 = { { 0050627, 0122361 } }; /* DEC 0.002490001 */ -static const OP sin_c2 = { { 0126521, 0011373 } }; /* DEC -0.0807454325 */ -static const OP sin_c1 = { { 0062207, 0166400 } }; /* DEC 0.78539816 */ - -static const OP cos_c4 = { { 0126072, 0002753 } }; /* DEC -0.00031957 */ -static const OP cos_c3 = { { 0040355, 0007767 } }; /* DEC 0.015851077 */ -static const OP cos_c2 = { { 0130413, 0011377 } }; /* DEC -0.30842483 */ -static const OP cos_c1 = { { 0040000, 0000002 } }; /* DEC 1.0 */ - -static const OP sqrt_a2 = { { 0045612, 0067400 } }; /* DEC 0.5901621 */ -static const OP sqrt_b2 = { { 0065324, 0126377 } }; /* DEC 0.4173076 */ -static const OP sqrt_a1 = { { 0065324, 0126400 } }; /* DEC 0.8346152 */ -static const OP sqrt_b1 = { { 0045612, 0067400 } }; /* DEC 0.5901621 */ - -static const OP exp_c2 = { { 0073000, 0070771 } }; /* DEC 0.05761803 */ -static const OP exp_c1 = { { 0056125, 0041406 } }; /* DEC 5.7708162 */ - -static const OP tanh_c3 = { { 0050045, 0022004 } }; /* DEC 2.5045337 */ -static const OP tanh_c2 = { { 0041347, 0101404 } }; /* DEC 2.0907609 */ -static const OP tanh_c1 = { { 0052226, 0047375 } }; /* DEC 0.16520923 */ - -static const OP minus_1 = { { 0100000, 0000000 } }; /* DEC -1.0 */ -static const OP plus_1 = { { 0040000, 0000002 } }; /* DEC +1.0 */ -static const OP plus_half = { { 0040000, 0000000 } }; /* DEC +0.5 */ -static const OP ln_2 = { { 0054271, 0006000 } }; /* DEC 0.6931471806 (ln 2.0) */ -static const OP log_e = { { 0067455, 0166377 } }; /* DEC 0.43429228 (log e) */ -static const OP pi_over_4 = { { 0062207, 0166400 } }; /* Pi / 4.0 */ -static const OP pi_over_2 = { { 0062207, 0166402 } }; /* Pi / 2.0 */ - -static const OP four_over_pi = { { 0050574, 0140667, 0023402 } }; /* 4.0 / Pi */ -static const OP two_over_ln2 = { { 0056125, 0016624, 0127404 } }; /* 2.0 / ln(2.0) */ - -static const OP t_one = { { 0040000, 0000000, 0000000, 0000002 } }; /* DEY 1.0 */ - - -entry = IR & 017; /* mask to entry point */ - -if (op_sis [entry] != OP_N) { - reason = cpu_ops (op_sis [entry], op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -switch (entry) { /* decode IR<3:0> */ - - case 000: /* TAN 105320 (OP_R) */ - O = reduce (&op[0], &multiple, four_over_pi); /* reduce range */ - - if (O) { /* out of range? */ - op[0].fpk[0] = '0' << 8 | '9'; /* return '09' */ - op[0].fpk[1] = 'O' << 8 | 'R'; /* return 'OR' */ - break; /* error return is P+1 */ - } - - fp_exec (0040, &op[1], op[0], op[0]); /* op1 = arg ^ 2 */ - fp_exec (0010, ACCUM, NOP, tan_c4); /* acc = acc + C4 */ - fp_exec (0064, ACCUM, tan_c3, NOP); /* acc = C3 / acc */ - fp_exec (0010, ACCUM, NOP, op[1]); /* acc = acc + op1 */ - fp_exec (0050, ACCUM, NOP, tan_c2); /* acc = acc * C2 */ - fp_exec (0010, ACCUM, NOP, tan_c1); /* acc = acc + C1 */ - fp_exec (0050, &op[0], NOP, op[0]); /* res = acc * arg */ - - if (multiple & 0002) /* multiple * 2 odd? */ - fp_exec (0064, &op[0], minus_1, NOP); /* res = -1.0 / acc */ - - PC = (PC + 1) & VAMASK; /* normal return is P+2 */ - break; - - - case 001: /* SQRT 105321 (OP_R) */ - O = 0; /* clear overflow */ - - if (op[0].fpk[0] == 0) { /* arg = 0? */ - PC = (PC + 1) & VAMASK; /* normal return is P+2 */ - break; - } - - else if ((int16) op[0].fpk[0] < 0) { /* sqrt of neg? */ - op[0].fpk[0] = '0' << 8 | '3'; /* return '03' */ - op[0].fpk[1] = 'U' << 8 | 'N'; /* return 'UN' */ - O = 1; /* set overflow */ - break; /* error return is P+1 */ - } - - fp_unpack (&op[1], &exponent, op[0], fp_f); /* unpack argument */ - - if (exponent & 1) { /* exponent odd? */ - fp_exec (0040, ACCUM, op[1], sqrt_a1); /* acc = op1 * A1 */ - fp_exec (0010, &op[2], NOP, sqrt_b1); /* op2 = acc + B1 */ - op[1].fpk[1] = op[1].fpk[1] + 2; /* op1 = op1 * 2.0 */ - } - else { /* exponent even */ - fp_exec (0040, ACCUM, op[1], sqrt_a2); /* acc = op1 * A2 */ - fp_exec (0010, &op[2], NOP, sqrt_b2); /* op2 = acc + B2 */ - } - - fp_exec (0064, ACCUM, op[1], NOP); /* acc = op1 / acc */ - fp_exec (0010, &op[2], NOP, op[2]); /* op2 = acc + op2 */ - - op[1].fpk[1] = op[1].fpk[1] + 4; /* op1 = op1 * 4.0 */ - - fp_exec (0064, ACCUM, op[1], NOP); /* acc = op1 / acc */ - fp_exec (0010, &op[0], NOP, op[2]); /* res = acc + op2 */ - - power = (exponent >> 1) - 2; - - if (op[0].fpk[0]) { /* calc x * 2**n */ - fp_unpack (&op[1], &exponent, op[0], fp_f); /* unpack argument */ - exponent = exponent + power; /* multiply by 2**n */ - - if ((exponent > 0177) || /* exponent overflow? */ - (exponent < -0200)) { /* or underflow? */ - O = 1; /* rtn unscaled val, set ovf */ - break; /* error return is P+1 */ - } - - else - fp_pack (&op[0], op[1], exponent, fp_f);/* repack result */ - } - - PC = (PC + 1) & VAMASK; /* normal return is P+2 */ - break; - - - case 002: /* ALOG 105322 (OP_R) */ - case 007: /* ALOGT 105327 (OP_R) */ - O = 0; /* clear overflow */ - - if ((int16) op[0].fpk[0] <= 0) { /* log of neg or zero? */ - op[0].fpk[0] = '0' << 8 | '2'; /* return '02' */ - op[0].fpk[1] = 'U' << 8 | 'N'; /* return 'UN' */ - O = 1; /* set overflow */ - break; /* error return is P+1 */ - } - - fp_unpack (&op[1], &exponent, op[0], fp_f); /* unpack argument */ - - if (op[0].fpk[0] < 0055000) { /* out of range? */ - exponent = exponent - 1; /* drop exponent */ - op[1].fpk[1] = op[1].fpk[1] | 2; /* set "exponent" to 1 */ - } - - op[2].fpk[0] = (uint16) exponent; - fp_exec (0120, &op[3], op[2], NOP); /* op3 = FLT(exponent) */ - - fp_exec (0020, &op[4], op[1], plus_1); /* op4 = op1 - 1.0 */ - fp_exec (0000, ACCUM, op[1], plus_1); /* acc = op1 + 1.0 */ - fp_exec (0064, &op[5], op[4], NOP); /* op5 = op4 / acc */ - - fp_exec (0054, ACCUM, NOP, NOP); /* acc = acc * acc */ - fp_exec (0030, ACCUM, NOP, alog_c3); /* acc = acc - c3 */ - fp_exec (0064, ACCUM, alog_c2, NOP); /* acc = c2 / acc */ - fp_exec (0010, ACCUM, NOP, alog_c1); /* acc = acc + c1 */ - fp_exec (0050, ACCUM, NOP, op[5]); /* acc = acc * op5 */ - fp_exec (0010, ACCUM, NOP, op[3]); /* acc = acc + op3 */ - fp_exec (0050, &op[0], NOP, ln_2); /* res = acc * ln2 */ - - if (entry == 007) /* ALOGT? */ - fp_exec (0050, &op[0], NOP, log_e); /* res = acc * log(e) */ - - PC = (PC + 1) & VAMASK; /* normal return is P+2 */ - break; - - - case 003: /* ATAN 105323 (OP_R) */ - O = 0; /* clear overflow */ - - if (op[0].fpk[0] == 0) /* argument zero? */ - break; /* result zero */ - - flag = (op[0].fpk[1] & 1); /* get exponent sign */ - sign = ((int16) op[0].fpk[0] < 0); /* get argument sign */ - - if (flag == 0) { /* exp pos? (abs >= 0.5)? */ - if (sign) /* argument negative? */ - fp_pcom (&op[0], fp_f); /* make positive */ - - if (op[0].fpk[1] & 0374) { /* arg >= 2? */ - fp_exec(0060, &op[0], plus_1, op[0]); /* arg = 1.0 / arg */ - op[2] = pi_over_2; /* constant = pi / 2.0 */ - } - else { - fp_exec (0020, &op[1], plus_1, op[0]); /* op1 = 1.0 - arg */ - fp_exec (0000, ACCUM, plus_1, op[0]); /* acc = 1.0 + arg */ - fp_exec (0064, &op[0], op[1], NOP); /* arg = op1 / acc */ - op[2] = pi_over_4; /* constant = pi / 4.0 */ - } - } - - fp_exec (0040, &op[1], op[0], op[0]); /* op1 = arg * arg */ - fp_exec (0010, ACCUM, NOP, atan_c4); /* acc = acc + C4 */ - fp_exec (0064, ACCUM, atan_c3, NOP); /* acc = C3 / acc */ - fp_exec (0010, ACCUM, NOP, op[1]); /* acc = acc + op1 */ - fp_exec (0050, ACCUM, NOP, atan_c2); /* acc = acc * C2 */ - fp_exec (0010, ACCUM, NOP, atan_c1); /* acc = acc + C1 */ - fp_exec (0064, &op[0], op[0], NOP); /* res = arg / acc */ - - if (flag == 0) { /* exp pos? (abs >= 0.5)? */ - fp_exec (0030, &op[0], NOP, op[2]); /* res = acc - pi / n */ - - if (sign == 0) /* argument positive? */ - fp_pcom (&op[0], fp_f); /* make negative */ - } - - break; - - - case 004: /* COS 105324 (OP_R) */ - case 005: /* SIN 105325 (OP_R) */ - O = reduce (&op[0], &multiple, four_over_pi); /* reduce range */ - - if (O) { /* out of range? */ - op[0].fpk[0] = '0' << 8 | '5'; /* return '05' */ - op[0].fpk[1] = 'O' << 8 | 'R'; /* return 'OR' */ - break; /* error return is P+1 */ - } - - multiple = multiple / 2 + (entry == 004); /* add one for cosine */ - flag = (multiple & 1); /* decide on series */ - - fp_exec (0040, &op[1], op[0], op[0]); /* op1 = arg ^ 2 */ - - if (flag) { - fp_exec (0050, ACCUM, NOP, cos_c4); /* acc = acc * c4 */ - fp_exec (0010, ACCUM, NOP, cos_c3); /* acc = acc + c3 */ - fp_exec (0050, ACCUM, NOP, op[1]); /* acc = acc * op1 */ - fp_exec (0010, ACCUM, NOP, cos_c2); /* acc = acc + c2 */ - fp_exec (0050, ACCUM, NOP, op[1]); /* acc = acc * op1 */ - fp_exec (0010, &op[0], NOP, cos_c1); /* res = acc + c1 */ - } - - else { - fp_exec (0050, ACCUM, NOP, sin_c4); /* acc = acc * c4 */ - fp_exec (0010, ACCUM, NOP, sin_c3); /* acc = acc + c3 */ - fp_exec (0050, ACCUM, NOP, op[1]); /* acc = acc * op1 */ - fp_exec (0010, ACCUM, NOP, sin_c2); /* acc = acc + c2 */ - fp_exec (0050, ACCUM, NOP, op[1]); /* acc = acc * op1 */ - fp_exec (0010, ACCUM, NOP, sin_c1); /* acc = acc + c1 */ - fp_exec (0050, &op[0], NOP, op[0]); /* res = acc * arg */ - } - - if (multiple & 0002) /* multiple * 2 odd? */ - fp_pcom (&op[0], fp_f); /* make negative */ - - PC = (PC + 1) & VAMASK; /* normal return is P+2 */ - break; - - - case 006: /* EXP 105326 (OP_R) */ - sign = ((int16) op[0].fpk[0] < 0); /* get argument sign */ - - O = reduce (&op[0], &multiple, two_over_ln2); /* reduce range */ - multiple = multiple / 2; /* get true multiple */ - - if ((sign == 0) && (O | (multiple > 128))) { /* pos and ovf or out of range? */ - op[0].fpk[0] = '0' << 8 | '7'; /* return '07' */ - op[0].fpk[1] = 'O' << 8 | 'F'; /* return 'OF' */ - O = 1; /* set overflow */ - break; /* error return is P+1 */ - } - - else if (sign && (multiple < -128)) { /* neg and out of range? */ - op[0].fpk[0] = 0; /* result is zero */ - op[0].fpk[1] = 0; - O = 0; /* clear for underflow */ - PC = (PC + 1) & VAMASK; /* normal return is P+2 */ - break; - } - - fp_exec (0040, ACCUM, op[0], op[0]); /* acc = arg ^ 2 */ - fp_exec (0050, ACCUM, NOP, exp_c2); /* acc = acc * c2 */ - fp_exec (0030, ACCUM, NOP, op[0]); /* acc = acc - op0 */ - fp_exec (0010, ACCUM, NOP, exp_c1); /* acc = acc + c1 */ - fp_exec (0064, ACCUM, op[0], NOP); /* acc = op0 / acc */ - fp_exec (0010, &op[0], NOP, plus_half); /* res = acc + 0.5 */ - - power = multiple + 1; - - if (op[0].fpk[0]) { /* calc x * 2**n */ - fp_unpack (&op[1], &exponent, op[0], fp_f); /* unpack argument */ - exponent = exponent + power; /* multiply by 2**n */ - - if ((exponent > 0177) || /* exponent overflow? */ - (exponent < -0200)) { /* or underflow? */ - if (sign == 0) { /* arg positive? */ - op[0].fpk[0] = '0' << 8 | '7'; /* return '07' */ - op[0].fpk[1] = 'O' << 8 | 'F'; /* return 'OF' */ - O = 1; /* set overflow */ - } - else { - op[0].fpk[0] = 0; /* result is zero */ - op[0].fpk[1] = 0; - O = 0; /* clear for underflow */ - } - break; /* error return is P+1 */ - } - - else { - fp_pack (&op[0], op[1], exponent, fp_f);/* repack value */ - O = 0; - } - } - - PC = (PC + 1) & VAMASK; /* normal return is P+2 */ - break; - - - case 010: /* TANH 105330 (OP_R) */ - O = 0; - sign = ((int16) op[0].fpk[0] < 0); /* get argument sign */ - - if (op[0].fpk[1] & 1) { /* abs (arg) < 0.5? */ - fp_exec (0040, ACCUM, op[0], op[0]); /* acc = arg ^ 2 */ - fp_exec (0010, ACCUM, NOP, tanh_c3); /* acc = acc + c3 */ - fp_exec (0064, ACCUM, tanh_c2, NOP); /* acc = c2 / acc */ - fp_exec (0010, ACCUM, NOP, tanh_c1); /* acc = acc + c1 */ - fp_exec (0050, &op[0], NOP, op[0]); /* res = acc * arg */ - } - - else if (op[0].fpk[1] & 0370) /* abs (arg) >= 8.0? */ - if (sign) /* arg negative? */ - op[0] = minus_1; /* result = -1.0 */ - else /* arg positive */ - op[0] = plus_1; /* result = +1.0 */ - - else { /* 0.5 <= abs (arg) < 8.0 */ - BR = BR + 2; /* arg = arg * 2.0 */ - cpu_sis (0105326, intrq); /* calc exp (arg) */ - PC = (PC - 1) & VAMASK; /* correct P (always good rtn) */ - - op[0].fpk[0] = AR; /* save value */ - op[0].fpk[1] = BR; - - fp_exec (0020, &op[1], op[0], plus_1); /* op1 = op0 - 1.0 */ - fp_exec (0000, ACCUM, op[0], plus_1); /* acc = op0 + 1.0 */ - fp_exec (0064, &op[0], op[1], NOP); /* res = op1 / acc */ - } - - break; - - - case 011: /* DPOLY 105331 (OP_CATAKK) */ - O = 0; /* clear overflow */ - AR = op[0].word; /* get flag word */ - - if ((int16) AR >= 0) { /* flags present? */ - AR = 1; /* no, so set default */ - arg = op[2]; /* arg = X */ - } - - else /* bit 15 set */ - fp_exec (0042, &arg, op[2], op[2]); /* arg = X ^ 2 */ - - coeff = ReadOp (op[3].word, fp_t); /* get first coefficient */ - op[3].word = (op[3].word + 4) & VAMASK; /* point at next */ - fp_accum (&coeff, fp_t); /* acc = coeff */ - - for (i = 0; i < op[4].word; i++) { /* compute numerator */ - fp_exec (0052, ACCUM, NOP, arg); /* acc = P[m] * arg */ - coeff = ReadOp (op[3].word, fp_t); /* get next coefficient */ - op[3].word = (op[3].word + 4) & VAMASK; /* point at next */ - fp_exec (0012, ACCUM, NOP, coeff); /* acc = acc + P[m-1] */ - } - - if (AR & 1) /* bit 0 set? */ - op[6] = fp_accum (NULL, fp_t); /* save numerator */ - else - fp_exec (0046, &op[6], op[2], NOP); /* acc = X * acc */ - - - if (op[5].word) { /* n > 0 ? */ - fp_accum (&t_one, fp_t); /* acc = 1.0 */ - - for (i = 0; i < op[5].word; i++) { /* compute denominator */ - fp_exec (0052, ACCUM, NOP, arg); /* acc = P[m] * arg */ - coeff = ReadOp (op[3].word, fp_t); /* get next coefficient */ - op[3].word = (op[3].word + 4) & VAMASK; /* point at next */ - fp_exec (0012, ACCUM, NOP, coeff); /* acc = acc + P[m-1] */ - } - - if (AR & 0040000) /* bit 14 set? */ - fp_exec (0032, ACCUM, NOP, op[6]); /* acc = den - num */ - - fp_exec (0066, &op[6], op[6], NOP); /* op6 = num / den */ - } - - WriteOp (op[1].word, op[6], fp_t); /* write result */ - - if (O) /* overflow? */ - op[0].fpk[0] = 0; /* microcode rtns with A = 0 */ - break; - - - case 012: /* /CMRT 105332 (OP_AAT) */ - O = 0; - f = (int16) AR; /* save flags */ - - coeff = ReadOp (op[1].word, fp_t); /* get coefficient (C) */ - - fp_unpack (NULL, &exponent, op[2], fp_t); /* unpack exponent */ - - if ((f == -1) || (exponent < 4)) { /* TANH or abs (arg) < 16.0? */ - - /* result = x * c - n */ - - fp_exec (0042, &product, op[2], coeff); /* product = arg * C */ - O = fp_exec (0112, &count, NOP, NOP); /* count = FIX (acc) */ - - if ((int16) count.word >= 0) /* nearest even integer */ - count.word = count.word + 1; - BR = count.word = count.word & ~1; /* save LSBs of N */ - - O = O | fp_exec (0122, ACCUM, count, NOP); /* acc = FLT (count) */ - - if (O) { /* out of range? */ - op[0].fpk[0] = 0; /* microcode rtns with A = 0 */ - break; /* error return is P+1 */ - } - - fp_exec (0026, &result, product, NOP); /* acc = product - acc */ - fp_unpack (NULL, &rsltexp, result, fp_t); /* unpack exponent */ - - /* determine if cancellation matters */ - - if ((f < 0) || (f == 2) || (f == 6) || /* EXP, TANH, or COS? */ - (exponent - rsltexp < 5)) { /* bits lost < 5? */ - WriteOp (op[0].word, result, fp_t); /* write result */ - PC = (PC + 1) & VAMASK; /* P+2 return for good result */ - op[0].fpk[1] = BR; /* return LSBs of N in B */ - break; /* all done! */ - } - } - - /* result = (xu * cu - n) + (x - xu) * c + xu * cl */ - - if (exponent >= (8 + 16 * (f >= 0))) { /* exp >= 8 (EXP,TANH)? */ - op[0].fpk[0] = 0; /* or 24 (SIN/COS/TAN)? */ - break; /* range error return is P+1 */ - } - - op[3].fpk[0] = coeff.fpk[0]; /* form upper bits of C (CU) */ - op[3].fpk[1] = coeff.fpk[1] & 0177770; - op[3].fpk[2] = 0; - op[3].fpk[3] = coeff.fpk[3] & 0000377; - - op[4].fpk[0] = op[2].fpk[0]; /* form upper bits of X (XU) */ - op[4].fpk[1] = op[2].fpk[1] & 0177770; - op[4].fpk[2] = 0; - op[4].fpk[3] = op[2].fpk[3] & 0000377; - - fp_exec (0042, &op[5], op[3], op[4]); /* op5 = cu * xu */ - - fp_exec (0116, &op[6], NOP, NOP); /* op6 = fix (acc) (2wd) */ - - if ((int32) op[6].dword >= 0) /* nearest even integer */ - op[6].dword = op[6].dword + 1; - op[6].dword = op[6].dword & ~1; - BR = op[6].dword & DMASK; /* save LSBs of N */ - - O = fp_exec (0126, ACCUM, op[6], NOP); /* acc = flt (op6) */ - - if (O) { /* overflow? */ - op[0].fpk[0] = 0; /* microcode rtns with A = 0 */ - break; /* range error return is P+1 */ - } - - fp_exec (0026, &op[7], op[5], NOP); /* op7 = cu * xu - n */ - - fp_exec (0022, ACCUM, op[2], op[4]); /* acc = x - xu */ - fp_exec (0052, ACCUM, NOP, coeff); /* acc = (x - xu) * c */ - fp_exec (0012, &op[5], NOP, op[7]); /* op5 = acc + (cu * xu - n) */ - - op[1].word = (op[1].word + 4) & VAMASK; /* point at second coefficient */ - coeff = ReadOp (op[1].word, fp_t); /* get coefficient (CL) */ - - fp_exec (0042, ACCUM, op[4], coeff); /* acc = xu * cl */ - fp_exec (0012, &result, NOP, op[5]); /* result = acc + (x - xu) * c + (cu * xu - n) */ - - WriteOp (op[0].word, result, fp_t); /* write result */ - PC = (PC + 1) & VAMASK; /* P+2 return for good result */ - op[0].fpk[1] = BR; /* return LSBs of N in B */ - break; - - - case 013: /* /ATLG 105333 (OP_A) */ - arg = ReadOp (op[0].word, fp_t); /* get argument */ - - fp_exec (0022, &op[1], t_one, arg); /* op1 = 1.0 - arg */ - fp_exec (0002, ACCUM, t_one, arg); /* acc = 1.0 + arg */ - fp_exec (0066, &op[1], op[1], NOP); /* res = op1 / acc */ - - WriteOp (op[0].word, op[1], fp_t); /* write result */ - break; - - - case 014: /* .FPWR 105334 (OP_IIF) */ - p = 0; /* set to single-precision */ - goto NPWR; - - case 015: /* .TPWR 105335 (OP_IAT) */ - p = 2; /* set to double-precision */ - - NPWR: - if (op[2].fpk[0]) { /* non-zero base? */ - fp_exec (0120, &pwr, op[0], NOP); /* float power */ - - sign = ((int16) pwr.fpk[0] < 0); /* save sign of power */ - i = (pwr.fpk[0] << 2) & DMASK; /* clear it */ - - fp_unpack (NULL, &exponent, pwr, fp_f); /* unpack exponent */ - - if (sign == 0) - exponent = exponent - 1; - - O = 0; /* clear overflow */ - fp_accum (&op[2], (OPSIZE) (fp_f + p)); /* acc = arg */ - - while (exponent-- > 0) { - O = O | fp_exec ((uint16) (0054 | p), /* square acc */ - ACCUM, NOP, NOP); - - if (i & SIGN) - O = O | fp_exec ((uint16) (0050 | p), /* acc = acc * arg */ - ACCUM, NOP, op[2]); - i = i << 1; - } - - op[2] = fp_accum (NULL, (OPSIZE) (fp_f + p)); /* get accum */ - - if (op[2].fpk[0] == 0) /* result zero? */ - O = 1; /* underflow */ - } - - if (entry == 014) /* .FPWR ? */ - op[0] = op[2]; /* copy result */ - else /* .TPWR */ - WriteOp (op[1].word, op[2], fp_t); /* write result */ - - break; - - - case 017: /* [tst] 105337 (OP_N) */ - XR = 4; /* firmware revision */ - SR = 0102077; /* test passed code */ - PC = (PC + 1) & VAMASK; /* P+2 return for firmware w/DPOLY */ - return reason; - - - default: /* others undefined */ - return stop_inst; - } - -AR = op[0].fpk[0]; /* save result */ -BR = op[0].fpk[1]; /* into A/B */ -return reason; -} - -#endif /* end of int64 support */ +/* hp2100_cpu4.c: HP 1000 FPP/SIS + + Copyright (c) 2006-2016, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + CPU4 Floating Point Processor and Scientific Instruction Set + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 24-Dec-14 JDB Added casts for explicit downward conversions + 09-May-12 JDB Separated assignments from conditional expressions + 06-Feb-12 JDB Added OPSIZE casts to fp_accum calls in .FPWR/.TPWR + 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h + 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) + 18-Mar-08 JDB Fixed B register return bug in /CMRT + 01-Dec-06 JDB Substitutes FPP for firmware FP if HAVE_INT64 + + Primary references: + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - Macro/1000 Reference Manual (92059-90001, Dec-1992) + + Additional references are listed with the associated firmware + implementations, as are the HP option model numbers pertaining to the + applicable CPUs. +*/ + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + +#if defined (HAVE_INT64) /* int64 support available */ + +#include "hp2100_fp1.h" + + +/* Floating-Point Processor. + + The 1000 F-Series replaces the six 2100/1000-M/E single-precision firmware + floating-point instructions with a hardware floating-point processor (FPP). + The FPP executes single-, extended-, and double-precision floating-point + instructions, as well as double-integer instructions. All of the + floating-point instructions, as well as the single- and double-integer fix + and float instructions, are handled here. Pure double-integer instructions + are dispatched to the double-integer handler for simulation. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A N/A N/A std + + For the F-Series, the instruction codes are mapped to routines as follows: + + Instr. 1000-F Description + ------ ------ ------------------------------------- + 105000 FAD Single real add + 105001 .XADD Extended real add + 105002 .TADD Double real add + 105003 [EAD] [5-word add] + 105004 [tst] [Floating Point Processor self test] + 105005 [xpd] [Expand exponent] + 105006 [rst] [Floating Point Processor reset] + 105007 [stk] [Process stack of operands] + 105010 [chk] [FPP addressing check] + 105014 .DAD Double integer add + 105020 FSB Single real subtract + 105021 .XSUB Extended real subtract + 105022 .TSUB Double real subtract + 105023 [ESB] [5-word subtract] + 105034 .DSB Double integer subtract + 105040 FMP Single real multiply + 105041 .XMPY Extended real multiply + 105042 .TMPY Double real multiply + 105043 [EMP] [5-word multiply] + 105054 .DMP Double integer multiply + 105060 FDV Single real divide + 105061 .XDIV Extended real divide + 105062 .TDIV Double real divide + 105063 [EDV] [5-word divide] + 105074 .DDI Double integer divide + 105100 FIX Single real to integer fix + 105101 .XFXS Extended real to integer fix (.DINT) + 105102 .TXFS Double real to integer fix (.TINT) + 105103 [EFS] [5-word FIXS] + 105104 .FIXD Real to double integer fix + 105105 .XFXD Extended real to double integer fix + 105106 .TFXD Double real to double integer fix + 105107 [EFD] [5-word FIXD] + 105114 .DSBR Double integer subtraction (reversed) + 105120 FLT Integer to single real float + 105121 .XFTS Integer to extended real float (.IDBL) + 105122 .TFTS Integer to double real float (.ITBL) + 105123 [ELS] [5-word FLTS] + 105124 .FLTD Double integer to real float + 105125 .XFTD Double integer to extended real float + 105126 .TFTD Double integer to double real float + 105127 [ELD] [5-word FLTD] + 105134 .DDIR Double integer divide (reversed) + + Implementation note: rather than have two simulators that each executes the + single-precision FP instruction set, we compile conditionally, based on the + availability of 64-bit integer support in the host compiler. 64-bit integers + are required for the FPP, so if they are available, then we handle the + single-precision instructions for the 2100 and M/E-Series here, and the + firmware simulation is omitted. If support is unavailable, then the firmware + function is used instead. + + Notes: + + 1. Single-precision arithmetic instructions (.FAD, etc.) and extended- and + double-precision F-Series FPP arithmetic instructions (.XADD, .TADD, + etc.) return positive infinity on both positive and negative overflow. + The equivalent extended-precision M/E-Series FFP instructions return + negative infinity on negative overflow and positive infinity on positive + overflow. + + 2. The items in brackets above are undocumented instructions that are used + by the 12740 FPP-SIS-FFP diagnostic only. + + 3. The five-word arithmetic instructions (e.g., 105003) use an expanded + operand format that dedicates a separate word to the exponent. See the + implementation notes in the hardware floating-point processor simulation + for details. + + 4. The "self test" instruction (105004) returned to P+1 for early F-Series + units without double-integer support. Units incorporating such support + returned to P+2. + + 5. The "expand exponent" instruction (105005) is used as a "prefix" + instruction to enable a 10-bit exponent range. It is placed immediately + before a 5-word arithmetic instruction sequence, e.g., immediately + preceding an EAD instruction sequence. The arithmetic instruction + executes normally, except that under/overflow is not indicated unless + the exponent exceeds the 10-bit range, instead of the normal 8-bit + range. If overflow is indicated, the exponent is still set to +128. + + Note that as 2-, 3-, and 4-word packed numbers only have room for 8-bit + exponents, the Expand Exponent instruction serves no useful purpose in + conjunction with instructions associated with these precisions. If + used, the resulting values may be in error, as overflow from the 8-bit + exponents will not be indicated. + + 6. The "FPP reset" instruction (105006) is provided to reset a hung box, + e.g., in cases where an improper number of parameters is supplied. The + hardware resets its internal state machine in response to this + instruction. Under simulation, the instruction has no effect, as the + simulated FPP cannot hang. + + 7. The "process stack" instruction (105007) executes a series of FPP + instruction sets in sequence. Each set consists of a single FPP + instruction and associated operands that specifies the operation, + followed by a "result" instruction and operand. The result instruction + is optional and is only used to specify the result precision; the + instruction itself is not executed. If the result instruction is NOP, + then the result precision is that of the executed FPP instruction. If + the result operand is null, then the result is kept in the internal FPP + accumulator for later use. + + The calling sequence is as follows: + + STK Process stack instruction + DEF ERRTN Address of error return + DEF SET1 Address of first instruction set + DEF SET2 Address of second instruction set + . + . + . + ERRTN EQU * Return here if execution in error + OKRTN EQU * Return here if execution OK + + Instruction sets are specified as follows (e.g.): + + SET1 .TADD Operation instruction (NOP to terminate series) + DEC 4 Number of words in first operand (or 0 if accum) + DEF OP1 Address of first operand + DEC 4 Number of words in second operand (or 0 if accum) + DEF OP2 Address of second operand + .XADD Result precision conversion instruction (or NOP) + DEC 3 Number of words to store (or 0 if no store) + DEF RSLT Address of buffer to hold value + + The primary use of the "process stack" instruction is to enable chained + operations employing the FPP accumulator for intermediate results and to + enable expanded exponent usage across multiple instructions. + + 8. The "addressing check" instruction sets bit 0 of the L register to 1, + copies the X register value to the FPP, and then reads the FPP and + stores the result in the Y register. Setting the L register bit 0 to 1 + normally deselects the FPP, so that the value in Y is 177777. However, + the FPP box has a strap that inverts the selection logic, even though + the box will not work with the base-set firmware if this is done. The + "addressing check" instruction is provided to test whether the strap is + in the alternate location. Under simulation, the return value is always + 177777, indicating that the strap is correctly set. + + Additional references: + - DOS/RTE Relocatable Library Reference Manual (24998-90001, Oct-1981) + - FPP-SIS-FFP Diagnostic Source (12740-18001, Rev. 1926) +*/ + +static const OP_PAT op_fpp[96] = { + OP_RF, OP_AXX, OP_ATT, OP_AEE, /* FAD .XADD .TADD .EADD */ + OP_N, OP_C, OP_N, OP_A, /* [tst] [xpd] [rst] [stk] */ + OP_N, OP_N, OP_N, OP_N, /* [chk] --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* .DAD --- --- --- */ + OP_RF, OP_AXX, OP_ATT, OP_AEE, /* FSB .XSUB .TSUB .ESUB */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* .DSB --- --- --- */ + OP_RF, OP_AXX, OP_ATT, OP_AEE, /* FMP .XMPY .TMPY .EMPY */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* .DMP --- --- --- */ + OP_RF, OP_AXX, OP_ATT, OP_AEE, /* FDV .XDIV .TDIV .EDIV */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* .DDI --- --- --- */ + OP_R, OP_X, OP_T, OP_E, /* FIX .XFXS .TFXS .EFXS */ + OP_R, OP_X, OP_T, OP_E, /* .FIXD .XFXD .TFXD .EFXD */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* .DSBR --- --- --- */ + OP_I, OP_IA, OP_IA, OP_IA, /* FLT .XFTS .TFTS .EFTS */ + OP_J, OP_JA, OP_JA, OP_JA, /* .FLTD .XFTD .TFTD .EFTD */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N /* .DDIR --- --- --- */ + }; + +t_stat cpu_fpp (uint32 IR, uint32 intrq) +{ +OP fpop; +OPS op; +OPSIZE op1_prec, op2_prec, rslt_prec, cvt_prec; +uint16 opcode, rtn_addr, stk_ptr; +uint32 entry; +t_stat reason = SCPE_OK; + +if (UNIT_CPU_MODEL == UNIT_1000_F) /* F-Series? */ + opcode = (uint16) (IR & 0377); /* yes, use full opcode */ +else + opcode = (uint16) (IR & 0160); /* no, use 6 SP FP opcodes */ + +entry = opcode & 0177; /* map to <6:0> */ + +if (op_fpp [entry] != OP_N) { + reason = cpu_ops (op_fpp [entry], op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +switch (entry) { /* decode IR<6:0> */ + case 0000: /* FAD 105000 (OP_RF) */ + case 0020: /* FSB 105020 (OP_RF) */ + case 0040: /* FMP 105040 (OP_RF) */ + case 0060: /* FDV 105060 (OP_RF) */ + O = fp_exec (opcode, &fpop, op[0], op[1]); /* execute operation */ + AR = fpop.fpk[0]; /* return result to A/B */ + BR = fpop.fpk[1]; + break; + + case 0001: /* .XADD 105001 (OP_AXX) */ + case 0002: /* .TADD 105002 (OP_ATT) */ + case 0003: /* .EADD 105003 (OP_AEE) */ + + case 0021: /* .XSUB 105021 (OP_AXX) */ + case 0022: /* .TSUB 105022 (OP_ATT) */ + case 0023: /* .ESUB 105023 (OP_AEE) */ + + case 0041: /* .XMPY 105041 (OP_AXX) */ + case 0042: /* .TMPY 105042 (OP_ATT) */ + case 0043: /* .EMPY 105043 (OP_AEE) */ + + case 0061: /* .XDIV 105061 (OP_AXX) */ + case 0062: /* .TDIV 105062 (OP_ATT) */ + case 0063: /* .EDIV 105063 (OP_AEE) */ + O = fp_exec (opcode, &fpop, op[1], op[2]); /* execute operation */ + fp_prec (opcode, NULL, NULL, &rslt_prec); /* determine result precision */ + WriteOp (op[0].word, fpop, rslt_prec); /* write result */ + break; + + case 0004: /* [tst] 105004 (OP_N) */ + XR = 3; /* firmware revision */ + SR = 0102077; /* test passed code */ + PR = (PR + 1) & VAMASK; /* P+2 return for firmware w/DBI */ + break; + + case 0005: /* [xpd] 105005 (OP_C) */ + return cpu_fpp (op[0].word | 0200, intrq); /* set bit 7, execute instr */ + + case 0006: /* [rst] 105006 (OP_N) */ + break; /* do nothing for FPP reset */ + + case 0007: /* [stk] 105007 (OP_A) */ + O = 0; /* clear overflow */ + stk_ptr = (uint16) PR; /* save ptr to next buf */ + rtn_addr = op[0].word; /* save return address */ + + while (TRUE) { + PR = ReadW (stk_ptr) & VAMASK; /* point at next instruction set */ + stk_ptr = (stk_ptr + 1) & VAMASK; + + reason = cpu_ops (OP_CCACACCA, op, intrq); /* get instruction set */ + + if (reason) { + PR = err_PC; /* irq restarts */ + break; + } + + if (op[0].word == 0) { /* opcode = NOP? */ + PR = (rtn_addr + 1) & VAMASK; /* bump to good return */ + break; /* done */ + } + + fp_prec ((uint16) (op[0].word & 0377), /* determine operand precisions */ + &op1_prec, &op2_prec, &rslt_prec); + + if (TO_COUNT(op1_prec) != op[1].word) { /* first operand precisions agree? */ + PR = rtn_addr; /* no, so take error return */ + break; + } + + else if (op1_prec != fp_a) /* operand in accumulator? */ + op[1] = ReadOp (op[2].word, op1_prec); /* no, so get operand 1 */ + + if (TO_COUNT(op2_prec) != op[3].word) { /* second operand precisions agree? */ + PR = rtn_addr; /* no, so take error return */ + break; + } + + else if (op2_prec != fp_a) /* operand in accumulator? */ + op[2] = ReadOp (op[4].word, op2_prec); /* no, so get operand 2 */ + + O = O | /* execute instruction */ + fp_exec ((uint16) (op[0].word & 0377), /* and accumulate overflow */ + &fpop, op[1], op[2]); + + if (op[5].word) { /* precision conversion? */ + fp_prec ((uint16) (op[5].word & 0377), /* determine conversion precision */ + NULL, NULL, &cvt_prec); + + fpop = fp_accum (NULL, cvt_prec); /* convert result */ + } + else /* no conversion specified */ + cvt_prec = rslt_prec; /* so use original precision */ + + if (op[6].word) /* store result? */ + WriteOp (op[7].word, fpop, cvt_prec); /* yes, so write it */ + } + + break; + + case 0010: /* [chk] 105010 (OP_N) */ + YR = 0177777; /* -1 if selection strap OK */ + break; + + case 0014: /* .DAD 105014 (OP_N) */ + return cpu_dbi (0105321, intrq); /* remap to double int handler */ + + case 0034: /* .DSB 105034 (OP_N) */ + return cpu_dbi (0105327, intrq); /* remap to double int handler */ + + case 0054: /* .DMP 105054 (OP_N) */ + return cpu_dbi (0105322, intrq); /* remap to double int handler */ + + case 0074: /* .DDI 105074 (OP_N) */ + return cpu_dbi (0105325, intrq); /* remap to double int handler */ + + case 0100: /* FIX 105100 (OP_R) */ + case 0101: /* .XFXS 105101 (OP_X) */ + case 0102: /* .TFXS 105102 (OP_T) */ + case 0103: /* .EFXS 105103 (OP_E) */ + O = fp_exec (opcode, &fpop, op[0], NOP); /* fix to integer */ + AR = fpop.fpk[0]; /* save result */ + break; + + case 0104: /* .FIXD 105104 (OP_R) */ + case 0105: /* .XFXD 105105 (OP_X) */ + case 0106: /* .TFXD 105106 (OP_T) */ + case 0107: /* .EFXD 105107 (OP_E) */ + O = fp_exec (opcode, &fpop, op[0], NOP); /* fix to integer */ + AR = (fpop.dword >> 16) & DMASK; /* save result */ + BR = fpop.dword & DMASK; /* in A and B */ + break; + + case 0114: /* .DSBR 105114 (OP_N) */ + return cpu_dbi (0105334, intrq); /* remap to double int handler */ + + case 0120: /* FLT 105120 (OP_I) */ + case 0124: /* .FLTD 105124 (OP_J) */ + O = fp_exec (opcode, &fpop, op[0], NOP); /* float to single */ + AR = fpop.fpk[0]; /* save result */ + BR = fpop.fpk[1]; /* into A/B */ + break; + + case 0121: /* .XFTS 105121 (OP_IA) */ + case 0122: /* .TFTS 105122 (OP_IA) */ + case 0123: /* .EFTS 105123 (OP_IA) */ + case 0125: /* .XFTD 105125 (OP_JA) */ + case 0126: /* .TFTD 105126 (OP_JA) */ + case 0127: /* .EFTD 105127 (OP_JA) */ + O = fp_exec (opcode, &fpop, op[0], NOP); /* float integer */ + fp_prec (opcode, NULL, NULL, &rslt_prec); /* determine result precision */ + WriteOp (op[1].word, fpop, rslt_prec); /* write result */ + break; + + case 0134: /* .DDIR 105134 (OP_N) */ + return cpu_dbi (0105326, intrq); /* remap to double int handler */ + + default: /* others undefined */ + reason = stop_inst; + } + +return reason; +} + + +/* Scientific Instruction Set. + + The SIS adds single-precision trigonometric and logarithmic, and + double-precision polynomial evaluation instructions to the 1000-F instruction + set. The SIS is standard on the 1000-F. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A N/A N/A std + + The routines are mapped to instruction codes as follows: + + Instr. 1000-F Description + ------ ------ ---------------------------------------------- + TAN 105320 Tangent + SQRT 105321 Square root + ALOG 105322 Natural logarithm + ATAN 105323 Arc tangent + COS 105324 Cosine + SIN 105325 Sine + EXP 105326 E to the power X + ALOGT 105327 Common logarithm + TANH 105330 Hyperbolic tangent + DPOLY 105331 Double-precision polynomial evaluation + /CMRT 105332 Double-precision common range reduction + /ATLG 105333 Compute (1-x)/(1+x) for .ATAN and .LOG + .FPWR 105334 Single-precision exponentiation + .TPWR 105335 Double-precision exponentiation + [tst] 105337 [self test] + + The SIS simulation follows the F-Series SIS microcode, which, in turn, + follows the algebraic approximations given in the Relocatable Library manual + descriptions of the equivalent software routines. + + Notes: + + 1. The word following the DPOLY instruction contains up to three flag bits + to indicate one of several polynomial forms to evaluate. The comments + in the DPOLY software library routine source interchange the actions of + the bit 14 and bit 0 flags. The DPOLY description in the Technical + Reference Handbook is correct. + + 2. Several instructions (e.g., DPOLY) are documented as leaving undefined + values in the A, B, X, Y, E, or O registers. Simulation does not + attempt to reproduce the same values as would be obtained with the + hardware. + + 3. The SIS uses the hardware FPP of the F-Series. FPP malfunctions are + detected by the SIS firmware and are indicated by a memory-protect + violation and setting the overflow flag. Under simulation, + malfunctions cannot occur. + + 4. We use OP_IIT for the .FPWR operand pattern. The "II" is redundant, but + it aligns the operands with the OP_IAT of .TPWR, so the code may be + shared. + + Additional references: + - DOS/RTE Relocatable Library Reference Manual (24998-90001, Oct-1981) + - HP 1000 E-Series and F-Series Computer Microprogramming Reference Manual + (02109-90004, Apr-1980). +*/ + + +/* Common single-precision range reduction for SIN, COS, TAN, and EXP. + + This routine is called by the SIN, COS, TAN, and EXP handlers to reduce the + range of the argument. Reduction is performed in extended-precision. We + calculate: + + multiple = (nearest even integer to argument * multiplier) + argument = argument * multiplier - multiple +*/ + +static uint32 reduce (OP *argument, int32 *multiple, OP multiplier) +{ +OP product, count; +uint32 overflow; + +fp_cvt (argument, fp_f, fp_x); /* convert to extended precision */ +fp_exec (0041, &product, *argument, multiplier); /* product = argument * multiplier */ +overflow = fp_exec (0111, &count, NOP, NOP); /* count = FIX (acc) */ + +if ((int16) count.word >= 0) /* nearest even integer */ + count.word = count.word + 1; +count.word = count.word & ~1; +*multiple = (int16) count.word; + +if (overflow == 0) { /* in range? */ + fp_exec (0121, ACCUM, count, NOP); /* acc = FLT (count) */ + overflow = fp_exec (0025, ACCUM, product, NOP); /* acc = product - acc */ + *argument = fp_accum (NULL, fp_f); /* trim to single-precision */ + } +return overflow; +} + + +/* SIS dispatcher. */ + +static const OP_PAT op_sis[16] = { + OP_R, OP_R, OP_R, OP_R, /* TAN SQRT ALOG ATAN */ + OP_R, OP_R, OP_R, OP_R, /* COS SIN EXP ALOGT */ + OP_R, OP_CATAKK, OP_AAT, OP_A, /* TANH DPOLY /CMRT /ATLG */ + OP_IIF, OP_IAT, OP_N, OP_N /* .FPWR .TPWR --- [tst] */ + }; + +t_stat cpu_sis (uint32 IR, uint32 intrq) +{ +OPS op; +OP arg, coeff, pwr, product, count, result; +int16 f, p; +int32 multiple, power, exponent, rsltexp; +uint32 entry, i; +t_bool flag, sign; +t_stat reason = SCPE_OK; + +static const OP tan_c4 = { { 0137763, 0051006 } }; /* DEC -4.0030956 */ +static const OP tan_c3 = { { 0130007, 0051026 } }; /* DEC -1279.5424 */ +static const OP tan_c2 = { { 0040564, 0012761 } }; /* DEC 0.0019974806 */ +static const OP tan_c1 = { { 0045472, 0001375 } }; /* DEC 0.14692695 */ + +static const OP alog_c3 = { { 0065010, 0063002 } }; /* DEC 1.6567626301 */ +static const OP alog_c2 = { { 0125606, 0044404 } }; /* DEC -2.6398577035 */ +static const OP alog_c1 = { { 0051260, 0037402 } }; /* DEC 1.2920070987 */ + +static const OP atan_c4 = { { 0040257, 0154404 } }; /* DEC 2.0214656 */ +static const OP atan_c3 = { { 0132062, 0133406 } }; /* DEC -4.7376165 */ +static const OP atan_c2 = { { 0047407, 0173775 } }; /* DEC 0.154357652 */ +static const OP atan_c1 = { { 0053447, 0014002 } }; /* DEC 1.3617611 */ + +static const OP sin_c4 = { { 0132233, 0040745 } }; /* DEC -0.000035950439 */ +static const OP sin_c3 = { { 0050627, 0122361 } }; /* DEC 0.002490001 */ +static const OP sin_c2 = { { 0126521, 0011373 } }; /* DEC -0.0807454325 */ +static const OP sin_c1 = { { 0062207, 0166400 } }; /* DEC 0.78539816 */ + +static const OP cos_c4 = { { 0126072, 0002753 } }; /* DEC -0.00031957 */ +static const OP cos_c3 = { { 0040355, 0007767 } }; /* DEC 0.015851077 */ +static const OP cos_c2 = { { 0130413, 0011377 } }; /* DEC -0.30842483 */ +static const OP cos_c1 = { { 0040000, 0000002 } }; /* DEC 1.0 */ + +static const OP sqrt_a2 = { { 0045612, 0067400 } }; /* DEC 0.5901621 */ +static const OP sqrt_b2 = { { 0065324, 0126377 } }; /* DEC 0.4173076 */ +static const OP sqrt_a1 = { { 0065324, 0126400 } }; /* DEC 0.8346152 */ +static const OP sqrt_b1 = { { 0045612, 0067400 } }; /* DEC 0.5901621 */ + +static const OP exp_c2 = { { 0073000, 0070771 } }; /* DEC 0.05761803 */ +static const OP exp_c1 = { { 0056125, 0041406 } }; /* DEC 5.7708162 */ + +static const OP tanh_c3 = { { 0050045, 0022004 } }; /* DEC 2.5045337 */ +static const OP tanh_c2 = { { 0041347, 0101404 } }; /* DEC 2.0907609 */ +static const OP tanh_c1 = { { 0052226, 0047375 } }; /* DEC 0.16520923 */ + +static const OP minus_1 = { { 0100000, 0000000 } }; /* DEC -1.0 */ +static const OP plus_1 = { { 0040000, 0000002 } }; /* DEC +1.0 */ +static const OP plus_half = { { 0040000, 0000000 } }; /* DEC +0.5 */ +static const OP ln_2 = { { 0054271, 0006000 } }; /* DEC 0.6931471806 (ln 2.0) */ +static const OP log_e = { { 0067455, 0166377 } }; /* DEC 0.43429228 (log e) */ +static const OP pi_over_4 = { { 0062207, 0166400 } }; /* Pi / 4.0 */ +static const OP pi_over_2 = { { 0062207, 0166402 } }; /* Pi / 2.0 */ + +static const OP four_over_pi = { { 0050574, 0140667, 0023402 } }; /* 4.0 / Pi */ +static const OP two_over_ln2 = { { 0056125, 0016624, 0127404 } }; /* 2.0 / ln(2.0) */ + +static const OP t_one = { { 0040000, 0000000, 0000000, 0000002 } }; /* DEY 1.0 */ + + +entry = IR & 017; /* mask to entry point */ + +if (op_sis [entry] != OP_N) { + reason = cpu_ops (op_sis [entry], op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +switch (entry) { /* decode IR<3:0> */ + + case 000: /* TAN 105320 (OP_R) */ + O = reduce (&op[0], &multiple, four_over_pi); /* reduce range */ + + if (O) { /* out of range? */ + op[0].fpk[0] = '0' << 8 | '9'; /* return '09' */ + op[0].fpk[1] = 'O' << 8 | 'R'; /* return 'OR' */ + break; /* error return is P+1 */ + } + + fp_exec (0040, &op[1], op[0], op[0]); /* op1 = arg ^ 2 */ + fp_exec (0010, ACCUM, NOP, tan_c4); /* acc = acc + C4 */ + fp_exec (0064, ACCUM, tan_c3, NOP); /* acc = C3 / acc */ + fp_exec (0010, ACCUM, NOP, op[1]); /* acc = acc + op1 */ + fp_exec (0050, ACCUM, NOP, tan_c2); /* acc = acc * C2 */ + fp_exec (0010, ACCUM, NOP, tan_c1); /* acc = acc + C1 */ + fp_exec (0050, &op[0], NOP, op[0]); /* res = acc * arg */ + + if (multiple & 0002) /* multiple * 2 odd? */ + fp_exec (0064, &op[0], minus_1, NOP); /* res = -1.0 / acc */ + + PR = (PR + 1) & VAMASK; /* normal return is P+2 */ + break; + + + case 001: /* SQRT 105321 (OP_R) */ + O = 0; /* clear overflow */ + + if (op[0].fpk[0] == 0) { /* arg = 0? */ + PR = (PR + 1) & VAMASK; /* normal return is P+2 */ + break; + } + + else if ((int16) op[0].fpk[0] < 0) { /* sqrt of neg? */ + op[0].fpk[0] = '0' << 8 | '3'; /* return '03' */ + op[0].fpk[1] = 'U' << 8 | 'N'; /* return 'UN' */ + O = 1; /* set overflow */ + break; /* error return is P+1 */ + } + + fp_unpack (&op[1], &exponent, op[0], fp_f); /* unpack argument */ + + if (exponent & 1) { /* exponent odd? */ + fp_exec (0040, ACCUM, op[1], sqrt_a1); /* acc = op1 * A1 */ + fp_exec (0010, &op[2], NOP, sqrt_b1); /* op2 = acc + B1 */ + op[1].fpk[1] = op[1].fpk[1] + 2; /* op1 = op1 * 2.0 */ + } + else { /* exponent even */ + fp_exec (0040, ACCUM, op[1], sqrt_a2); /* acc = op1 * A2 */ + fp_exec (0010, &op[2], NOP, sqrt_b2); /* op2 = acc + B2 */ + } + + fp_exec (0064, ACCUM, op[1], NOP); /* acc = op1 / acc */ + fp_exec (0010, &op[2], NOP, op[2]); /* op2 = acc + op2 */ + + op[1].fpk[1] = op[1].fpk[1] + 4; /* op1 = op1 * 4.0 */ + + fp_exec (0064, ACCUM, op[1], NOP); /* acc = op1 / acc */ + fp_exec (0010, &op[0], NOP, op[2]); /* res = acc + op2 */ + + power = (exponent >> 1) - 2; + + if (op[0].fpk[0]) { /* calc x * 2**n */ + fp_unpack (&op[1], &exponent, op[0], fp_f); /* unpack argument */ + exponent = exponent + power; /* multiply by 2**n */ + + if ((exponent > 0177) || /* exponent overflow? */ + (exponent < -0200)) { /* or underflow? */ + O = 1; /* rtn unscaled val, set ovf */ + break; /* error return is P+1 */ + } + + else + fp_pack (&op[0], op[1], exponent, fp_f);/* repack result */ + } + + PR = (PR + 1) & VAMASK; /* normal return is P+2 */ + break; + + + case 002: /* ALOG 105322 (OP_R) */ + case 007: /* ALOGT 105327 (OP_R) */ + O = 0; /* clear overflow */ + + if ((int16) op[0].fpk[0] <= 0) { /* log of neg or zero? */ + op[0].fpk[0] = '0' << 8 | '2'; /* return '02' */ + op[0].fpk[1] = 'U' << 8 | 'N'; /* return 'UN' */ + O = 1; /* set overflow */ + break; /* error return is P+1 */ + } + + fp_unpack (&op[1], &exponent, op[0], fp_f); /* unpack argument */ + + if (op[0].fpk[0] < 0055000) { /* out of range? */ + exponent = exponent - 1; /* drop exponent */ + op[1].fpk[1] = op[1].fpk[1] | 2; /* set "exponent" to 1 */ + } + + op[2].fpk[0] = (uint16) exponent; + fp_exec (0120, &op[3], op[2], NOP); /* op3 = FLT(exponent) */ + + fp_exec (0020, &op[4], op[1], plus_1); /* op4 = op1 - 1.0 */ + fp_exec (0000, ACCUM, op[1], plus_1); /* acc = op1 + 1.0 */ + fp_exec (0064, &op[5], op[4], NOP); /* op5 = op4 / acc */ + + fp_exec (0054, ACCUM, NOP, NOP); /* acc = acc * acc */ + fp_exec (0030, ACCUM, NOP, alog_c3); /* acc = acc - c3 */ + fp_exec (0064, ACCUM, alog_c2, NOP); /* acc = c2 / acc */ + fp_exec (0010, ACCUM, NOP, alog_c1); /* acc = acc + c1 */ + fp_exec (0050, ACCUM, NOP, op[5]); /* acc = acc * op5 */ + fp_exec (0010, ACCUM, NOP, op[3]); /* acc = acc + op3 */ + fp_exec (0050, &op[0], NOP, ln_2); /* res = acc * ln2 */ + + if (entry == 007) /* ALOGT? */ + fp_exec (0050, &op[0], NOP, log_e); /* res = acc * log(e) */ + + PR = (PR + 1) & VAMASK; /* normal return is P+2 */ + break; + + + case 003: /* ATAN 105323 (OP_R) */ + O = 0; /* clear overflow */ + + if (op[0].fpk[0] == 0) /* argument zero? */ + break; /* result zero */ + + flag = (op[0].fpk[1] & 1); /* get exponent sign */ + sign = ((int16) op[0].fpk[0] < 0); /* get argument sign */ + + if (flag == 0) { /* exp pos? (abs >= 0.5)? */ + if (sign) /* argument negative? */ + fp_pcom (&op[0], fp_f); /* make positive */ + + if (op[0].fpk[1] & 0374) { /* arg >= 2? */ + fp_exec(0060, &op[0], plus_1, op[0]); /* arg = 1.0 / arg */ + op[2] = pi_over_2; /* constant = pi / 2.0 */ + } + else { + fp_exec (0020, &op[1], plus_1, op[0]); /* op1 = 1.0 - arg */ + fp_exec (0000, ACCUM, plus_1, op[0]); /* acc = 1.0 + arg */ + fp_exec (0064, &op[0], op[1], NOP); /* arg = op1 / acc */ + op[2] = pi_over_4; /* constant = pi / 4.0 */ + } + } + + fp_exec (0040, &op[1], op[0], op[0]); /* op1 = arg * arg */ + fp_exec (0010, ACCUM, NOP, atan_c4); /* acc = acc + C4 */ + fp_exec (0064, ACCUM, atan_c3, NOP); /* acc = C3 / acc */ + fp_exec (0010, ACCUM, NOP, op[1]); /* acc = acc + op1 */ + fp_exec (0050, ACCUM, NOP, atan_c2); /* acc = acc * C2 */ + fp_exec (0010, ACCUM, NOP, atan_c1); /* acc = acc + C1 */ + fp_exec (0064, &op[0], op[0], NOP); /* res = arg / acc */ + + if (flag == 0) { /* exp pos? (abs >= 0.5)? */ + fp_exec (0030, &op[0], NOP, op[2]); /* res = acc - pi / n */ + + if (sign == 0) /* argument positive? */ + fp_pcom (&op[0], fp_f); /* make negative */ + } + + break; + + + case 004: /* COS 105324 (OP_R) */ + case 005: /* SIN 105325 (OP_R) */ + O = reduce (&op[0], &multiple, four_over_pi); /* reduce range */ + + if (O) { /* out of range? */ + op[0].fpk[0] = '0' << 8 | '5'; /* return '05' */ + op[0].fpk[1] = 'O' << 8 | 'R'; /* return 'OR' */ + break; /* error return is P+1 */ + } + + multiple = multiple / 2 + (entry == 004); /* add one for cosine */ + flag = (multiple & 1); /* decide on series */ + + fp_exec (0040, &op[1], op[0], op[0]); /* op1 = arg ^ 2 */ + + if (flag) { + fp_exec (0050, ACCUM, NOP, cos_c4); /* acc = acc * c4 */ + fp_exec (0010, ACCUM, NOP, cos_c3); /* acc = acc + c3 */ + fp_exec (0050, ACCUM, NOP, op[1]); /* acc = acc * op1 */ + fp_exec (0010, ACCUM, NOP, cos_c2); /* acc = acc + c2 */ + fp_exec (0050, ACCUM, NOP, op[1]); /* acc = acc * op1 */ + fp_exec (0010, &op[0], NOP, cos_c1); /* res = acc + c1 */ + } + + else { + fp_exec (0050, ACCUM, NOP, sin_c4); /* acc = acc * c4 */ + fp_exec (0010, ACCUM, NOP, sin_c3); /* acc = acc + c3 */ + fp_exec (0050, ACCUM, NOP, op[1]); /* acc = acc * op1 */ + fp_exec (0010, ACCUM, NOP, sin_c2); /* acc = acc + c2 */ + fp_exec (0050, ACCUM, NOP, op[1]); /* acc = acc * op1 */ + fp_exec (0010, ACCUM, NOP, sin_c1); /* acc = acc + c1 */ + fp_exec (0050, &op[0], NOP, op[0]); /* res = acc * arg */ + } + + if (multiple & 0002) /* multiple * 2 odd? */ + fp_pcom (&op[0], fp_f); /* make negative */ + + PR = (PR + 1) & VAMASK; /* normal return is P+2 */ + break; + + + case 006: /* EXP 105326 (OP_R) */ + sign = ((int16) op[0].fpk[0] < 0); /* get argument sign */ + + O = reduce (&op[0], &multiple, two_over_ln2); /* reduce range */ + multiple = multiple / 2; /* get true multiple */ + + if ((sign == 0) && (O | (multiple > 128))) { /* pos and ovf or out of range? */ + op[0].fpk[0] = '0' << 8 | '7'; /* return '07' */ + op[0].fpk[1] = 'O' << 8 | 'F'; /* return 'OF' */ + O = 1; /* set overflow */ + break; /* error return is P+1 */ + } + + else if (sign && (multiple < -128)) { /* neg and out of range? */ + op[0].fpk[0] = 0; /* result is zero */ + op[0].fpk[1] = 0; + O = 0; /* clear for underflow */ + PR = (PR + 1) & VAMASK; /* normal return is P+2 */ + break; + } + + fp_exec (0040, ACCUM, op[0], op[0]); /* acc = arg ^ 2 */ + fp_exec (0050, ACCUM, NOP, exp_c2); /* acc = acc * c2 */ + fp_exec (0030, ACCUM, NOP, op[0]); /* acc = acc - op0 */ + fp_exec (0010, ACCUM, NOP, exp_c1); /* acc = acc + c1 */ + fp_exec (0064, ACCUM, op[0], NOP); /* acc = op0 / acc */ + fp_exec (0010, &op[0], NOP, plus_half); /* res = acc + 0.5 */ + + power = multiple + 1; + + if (op[0].fpk[0]) { /* calc x * 2**n */ + fp_unpack (&op[1], &exponent, op[0], fp_f); /* unpack argument */ + exponent = exponent + power; /* multiply by 2**n */ + + if ((exponent > 0177) || /* exponent overflow? */ + (exponent < -0200)) { /* or underflow? */ + if (sign == 0) { /* arg positive? */ + op[0].fpk[0] = '0' << 8 | '7'; /* return '07' */ + op[0].fpk[1] = 'O' << 8 | 'F'; /* return 'OF' */ + O = 1; /* set overflow */ + } + else { + op[0].fpk[0] = 0; /* result is zero */ + op[0].fpk[1] = 0; + O = 0; /* clear for underflow */ + } + break; /* error return is P+1 */ + } + + else { + fp_pack (&op[0], op[1], exponent, fp_f);/* repack value */ + O = 0; + } + } + + PR = (PR + 1) & VAMASK; /* normal return is P+2 */ + break; + + + case 010: /* TANH 105330 (OP_R) */ + O = 0; + sign = ((int16) op[0].fpk[0] < 0); /* get argument sign */ + + if (op[0].fpk[1] & 1) { /* abs (arg) < 0.5? */ + fp_exec (0040, ACCUM, op[0], op[0]); /* acc = arg ^ 2 */ + fp_exec (0010, ACCUM, NOP, tanh_c3); /* acc = acc + c3 */ + fp_exec (0064, ACCUM, tanh_c2, NOP); /* acc = c2 / acc */ + fp_exec (0010, ACCUM, NOP, tanh_c1); /* acc = acc + c1 */ + fp_exec (0050, &op[0], NOP, op[0]); /* res = acc * arg */ + } + + else if (op[0].fpk[1] & 0370) /* abs (arg) >= 8.0? */ + if (sign) /* arg negative? */ + op[0] = minus_1; /* result = -1.0 */ + else /* arg positive */ + op[0] = plus_1; /* result = +1.0 */ + + else { /* 0.5 <= abs (arg) < 8.0 */ + BR = BR + 2; /* arg = arg * 2.0 */ + cpu_sis (0105326, intrq); /* calc exp (arg) */ + PR = (PR - 1) & VAMASK; /* correct P (always good rtn) */ + + op[0].fpk[0] = AR; /* save value */ + op[0].fpk[1] = BR; + + fp_exec (0020, &op[1], op[0], plus_1); /* op1 = op0 - 1.0 */ + fp_exec (0000, ACCUM, op[0], plus_1); /* acc = op0 + 1.0 */ + fp_exec (0064, &op[0], op[1], NOP); /* res = op1 / acc */ + } + + break; + + + case 011: /* DPOLY 105331 (OP_CATAKK) */ + O = 0; /* clear overflow */ + AR = op[0].word; /* get flag word */ + + if ((int16) AR >= 0) { /* flags present? */ + AR = 1; /* no, so set default */ + arg = op[2]; /* arg = X */ + } + + else /* bit 15 set */ + fp_exec (0042, &arg, op[2], op[2]); /* arg = X ^ 2 */ + + coeff = ReadOp (op[3].word, fp_t); /* get first coefficient */ + op[3].word = (op[3].word + 4) & VAMASK; /* point at next */ + fp_accum (&coeff, fp_t); /* acc = coeff */ + + for (i = 0; i < op[4].word; i++) { /* compute numerator */ + fp_exec (0052, ACCUM, NOP, arg); /* acc = P[m] * arg */ + coeff = ReadOp (op[3].word, fp_t); /* get next coefficient */ + op[3].word = (op[3].word + 4) & VAMASK; /* point at next */ + fp_exec (0012, ACCUM, NOP, coeff); /* acc = acc + P[m-1] */ + } + + if (AR & 1) /* bit 0 set? */ + op[6] = fp_accum (NULL, fp_t); /* save numerator */ + else + fp_exec (0046, &op[6], op[2], NOP); /* acc = X * acc */ + + + if (op[5].word) { /* n > 0 ? */ + fp_accum (&t_one, fp_t); /* acc = 1.0 */ + + for (i = 0; i < op[5].word; i++) { /* compute denominator */ + fp_exec (0052, ACCUM, NOP, arg); /* acc = P[m] * arg */ + coeff = ReadOp (op[3].word, fp_t); /* get next coefficient */ + op[3].word = (op[3].word + 4) & VAMASK; /* point at next */ + fp_exec (0012, ACCUM, NOP, coeff); /* acc = acc + P[m-1] */ + } + + if (AR & 0040000) /* bit 14 set? */ + fp_exec (0032, ACCUM, NOP, op[6]); /* acc = den - num */ + + fp_exec (0066, &op[6], op[6], NOP); /* op6 = num / den */ + } + + WriteOp (op[1].word, op[6], fp_t); /* write result */ + + if (O) /* overflow? */ + op[0].fpk[0] = 0; /* microcode rtns with A = 0 */ + break; + + + case 012: /* /CMRT 105332 (OP_AAT) */ + O = 0; + f = (int16) AR; /* save flags */ + + coeff = ReadOp (op[1].word, fp_t); /* get coefficient (C) */ + + fp_unpack (NULL, &exponent, op[2], fp_t); /* unpack exponent */ + + if ((f == -1) || (exponent < 4)) { /* TANH or abs (arg) < 16.0? */ + + /* result = x * c - n */ + + fp_exec (0042, &product, op[2], coeff); /* product = arg * C */ + O = fp_exec (0112, &count, NOP, NOP); /* count = FIX (acc) */ + + if ((int16) count.word >= 0) /* nearest even integer */ + count.word = count.word + 1; + BR = count.word = count.word & ~1; /* save LSBs of N */ + + O = O | fp_exec (0122, ACCUM, count, NOP); /* acc = FLT (count) */ + + if (O) { /* out of range? */ + op[0].fpk[0] = 0; /* microcode rtns with A = 0 */ + break; /* error return is P+1 */ + } + + fp_exec (0026, &result, product, NOP); /* acc = product - acc */ + fp_unpack (NULL, &rsltexp, result, fp_t); /* unpack exponent */ + + /* determine if cancellation matters */ + + if ((f < 0) || (f == 2) || (f == 6) || /* EXP, TANH, or COS? */ + (exponent - rsltexp < 5)) { /* bits lost < 5? */ + WriteOp (op[0].word, result, fp_t); /* write result */ + PR = (PR + 1) & VAMASK; /* P+2 return for good result */ + op[0].fpk[1] = BR; /* return LSBs of N in B */ + break; /* all done! */ + } + } + + /* result = (xu * cu - n) + (x - xu) * c + xu * cl */ + + if (exponent >= (8 + 16 * (f >= 0))) { /* exp >= 8 (EXP,TANH)? */ + op[0].fpk[0] = 0; /* or 24 (SIN/COS/TAN)? */ + break; /* range error return is P+1 */ + } + + op[3].fpk[0] = coeff.fpk[0]; /* form upper bits of C (CU) */ + op[3].fpk[1] = coeff.fpk[1] & 0177770; + op[3].fpk[2] = 0; + op[3].fpk[3] = coeff.fpk[3] & 0000377; + + op[4].fpk[0] = op[2].fpk[0]; /* form upper bits of X (XU) */ + op[4].fpk[1] = op[2].fpk[1] & 0177770; + op[4].fpk[2] = 0; + op[4].fpk[3] = op[2].fpk[3] & 0000377; + + fp_exec (0042, &op[5], op[3], op[4]); /* op5 = cu * xu */ + + fp_exec (0116, &op[6], NOP, NOP); /* op6 = fix (acc) (2wd) */ + + if ((int32) op[6].dword >= 0) /* nearest even integer */ + op[6].dword = op[6].dword + 1; + op[6].dword = op[6].dword & ~1; + BR = op[6].dword & DMASK; /* save LSBs of N */ + + O = fp_exec (0126, ACCUM, op[6], NOP); /* acc = flt (op6) */ + + if (O) { /* overflow? */ + op[0].fpk[0] = 0; /* microcode rtns with A = 0 */ + break; /* range error return is P+1 */ + } + + fp_exec (0026, &op[7], op[5], NOP); /* op7 = cu * xu - n */ + + fp_exec (0022, ACCUM, op[2], op[4]); /* acc = x - xu */ + fp_exec (0052, ACCUM, NOP, coeff); /* acc = (x - xu) * c */ + fp_exec (0012, &op[5], NOP, op[7]); /* op5 = acc + (cu * xu - n) */ + + op[1].word = (op[1].word + 4) & VAMASK; /* point at second coefficient */ + coeff = ReadOp (op[1].word, fp_t); /* get coefficient (CL) */ + + fp_exec (0042, ACCUM, op[4], coeff); /* acc = xu * cl */ + fp_exec (0012, &result, NOP, op[5]); /* result = acc + (x - xu) * c + (cu * xu - n) */ + + WriteOp (op[0].word, result, fp_t); /* write result */ + PR = (PR + 1) & VAMASK; /* P+2 return for good result */ + op[0].fpk[1] = BR; /* return LSBs of N in B */ + break; + + + case 013: /* /ATLG 105333 (OP_A) */ + arg = ReadOp (op[0].word, fp_t); /* get argument */ + + fp_exec (0022, &op[1], t_one, arg); /* op1 = 1.0 - arg */ + fp_exec (0002, ACCUM, t_one, arg); /* acc = 1.0 + arg */ + fp_exec (0066, &op[1], op[1], NOP); /* res = op1 / acc */ + + WriteOp (op[0].word, op[1], fp_t); /* write result */ + break; + + + case 014: /* .FPWR 105334 (OP_IIF) */ + p = 0; /* set to single-precision */ + goto NPWR; + + case 015: /* .TPWR 105335 (OP_IAT) */ + p = 2; /* set to double-precision */ + + NPWR: + if (op[2].fpk[0]) { /* non-zero base? */ + fp_exec (0120, &pwr, op[0], NOP); /* float power */ + + sign = ((int16) pwr.fpk[0] < 0); /* save sign of power */ + i = (pwr.fpk[0] << 2) & DMASK; /* clear it */ + + fp_unpack (NULL, &exponent, pwr, fp_f); /* unpack exponent */ + + if (sign == 0) + exponent = exponent - 1; + + O = 0; /* clear overflow */ + fp_accum (&op[2], (OPSIZE) (fp_f + p)); /* acc = arg */ + + while (exponent-- > 0) { + O = O | fp_exec ((uint16) (0054 | p), /* square acc */ + ACCUM, NOP, NOP); + + if (i & SIGN) + O = O | fp_exec ((uint16) (0050 | p), /* acc = acc * arg */ + ACCUM, NOP, op[2]); + i = i << 1; + } + + op[2] = fp_accum (NULL, (OPSIZE) (fp_f + p)); /* get accum */ + + if (op[2].fpk[0] == 0) /* result zero? */ + O = 1; /* underflow */ + } + + if (entry == 014) /* .FPWR ? */ + op[0] = op[2]; /* copy result */ + else /* .TPWR */ + WriteOp (op[1].word, op[2], fp_t); /* write result */ + + break; + + + case 017: /* [tst] 105337 (OP_N) */ + XR = 4; /* firmware revision */ + SR = 0102077; /* test passed code */ + PR = (PR + 1) & VAMASK; /* P+2 return for firmware w/DPOLY */ + return reason; + + + default: /* others undefined */ + return stop_inst; + } + +AR = op[0].fpk[0]; /* save result */ +BR = op[0].fpk[1]; /* into A/B */ +return reason; +} + +#endif /* end of int64 support */ diff --git a/HP2100/hp2100_cpu5.c b/HP2100/hp2100_cpu5.c index c8c64e20..f88f89eb 100644 --- a/HP2100/hp2100_cpu5.c +++ b/HP2100/hp2100_cpu5.c @@ -1,1442 +1,1443 @@ -/* hp2100_cpu5.c: HP 1000 RTE-6/VM VMA and RTE-IV EMA instructions - - Copyright (c) 2007-2008, Holger Veit - Copyright (c) 2006-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the authors shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the authors. - - CPU5 RTE-6/VM and RTE-IV firmware option instructions - - 24-Dec-14 JDB Added casts for explicit downward conversions - 17-Dec-12 JDB Fixed cpu_vma_mapte to return FALSE if not a VMA program - 09-May-12 JDB Separated assignments from conditional expressions - 23-Mar-12 JDB Added sign extension for dim count in "cpu_ema_resolve" - 28-Dec-11 JDB Eliminated unused variable in "cpu_ema_vset" - 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h - 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) - 30-Jul-08 JDB Redefined ABORT to pass address, moved def to hp2100_cpu.h - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 01-May-08 HV Fixed mapping bug in "cpu_ema_emap" - 21-Apr-08 JDB Added EMA support from Holger - 25-Nov-07 JDB Added TF fix from Holger - 07-Nov-07 HV VMACK diagnostic tests 1...32 passed - 19-Oct-07 JDB Corrected $LOC operand profile to OP_CCCACC - 03-Oct-07 HV Moved RTE-6/VM instrs from hp2100_cpu0.c - 26-Sep-06 JDB Created - - Primary references: - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - Macro/1000 Reference Manual (92059-90001, Dec-1992) - - Additional references are listed with the associated firmware - implementations, as are the HP option model numbers pertaining to the - applicable CPUs. -*/ - -#include -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - - -/* RTE-6/VM Virtual Memory Area Instructions - - RTE-6/VM (product number 92084A) introduced Virtual Memory Area (VMA) - instructions -- a superset of the RTE-IV EMA instructions. Different - microcode was supplied with the operating system that replaced the microcode - used with RTE-IV. Microcode was limited to the E/F-Series, and the M-Series - used software equivalents. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A N/A 92084A 92084A - - The routines are mapped to instruction codes as follows: - - Instr. 1000-E/F Description - ------ -------- ---------------------------------------------- - .PMAP 105240 Map VMA page into map register - $LOC 105241 Load on call - [test] 105242 [self test] - .SWP 105243 [Swap A and B registers] - .STAS 105244 [STA B; LDA SP] - .LDAS 105245 [LDA SP] - .MYAD 105246 [NOP in microcode] - .UMPY 105247 [Unsigned multiply and add] - - .IMAP 105250 Integer element resolve address and map - .IMAR 105251 Integer element resolve address - .JMAP 105252 Double integer element resolve address and map - .JMAR 105253 Double integer element resolve address - .LPXR 105254 Map pointer in P+1 plus offset in P+2 - .LPX 105255 Map pointer in A/B plus offset in P+1 - .LBPR 105256 Map pointer in P+1 - .LBP 105257 Map pointer in A/B registers - - Implementation notes: - - 1. The opcodes 105243-247 are undocumented and do not appear to be used in - any HP software. - - 2. The opcode list in the CE Handbook incorrectly shows 105246 as ".MYAD - - multiply 2 signed integers." The microcode listing shows that this - instruction was deleted, and the opcode is now a NOP. - - 3. RTE-IV EMA and RTE-6 VMA instructions shared the same address space, so a - given machine could run one or the other, but not both. - - Additional references: - - RTE-6/VM VMA/EMA Microcode Source (92084-18828, revision 3). - - RTE-6/VM Technical Specifications (92084-90015, Apr-1983). - - M/E/F-Series Computer Systems CE Handbook (5950-3767, Jul-1984). -*/ - -static const OP_PAT op_vma[16] = { - OP_N, OP_CCCACC, OP_N, OP_N, /* .PMAP $LOC [test] .SWAP */ - OP_N, OP_N, OP_N, OP_K, /* .STAS .LDAS .MYAD .UMPY */ - OP_A, OP_A, OP_A, OP_A, /* .IMAP .IMAR .JMAP .JMAR */ - OP_AA, OP_A, OP_A, OP_N /* .LPXR .LPX .LBPR .LBP */ - }; - -/* some addresses in page0 of RTE-6/VM */ -static const uint32 idx = 0001645; -static const uint32 xmata = 0001646; -static const uint32 xi = 0001647; -static const uint32 xeqt = 0001717; -static const uint32 vswp = 0001776; -static const uint32 umaps = 0003740; -static const uint32 page30 = 0074000; -static const uint32 page31 = 0076000; -static const uint32 ptemiss = 0176000; - -/* frequent constants in paging */ -#define SUITMASK 0176000 -#define NILPAGE 0176000 -#define PAGEIDX 0001777 -#define MSEGMASK 0076000 -#define RWPROT 0141777 - - -/* microcode version of resolve(): allows a much higher # of indirection levels. Used for - instance for LBP microcode diagnostics which will check > 100 levels. - */ -#define VMA_INDMAX 200 - -static t_stat vma_resolve (uint32 MA, uint32 *addr, t_bool debug) -{ -uint32 i; -uint32 faultma = MA; - -for (i = 0; (i < VMA_INDMAX) && (MA & I_IA); i++) { /* resolve multilevel */ - MA = ReadW (MA & VAMASK); /* follow address chain */ - } - -if (MA & I_IA) { - if (debug) - fprintf(sim_deb,">>CPU VMA: vma_resolve indirect loop addr=%06o\n",faultma); - return STOP_IND; /* indirect loop */ - } - -*addr = MA; -return SCPE_OK; -} - -/* $LOC - ASSEMBLER CALLING SEQUENCE: - - $MTHK NOP RETURN ADDRESS OF CALL (REDONE AFTER THIS ROUTINE) - JSB $LOC - .DTAB OCT LGPG# LOGICAL PAGE # AT WHICH THE NODE TO - * BE MAPPED IN BELONGS (0-31) - OCT RELPG RELATIVE PAGE OFFSET FROM BEGINING - * OF PARTITION OF WHERE THAT NODE RESIDES. - * (0 - 1023) - OCT RELBP RELATIVE PAGE OFFSET FROM BEGINING OF - * PARTITION OF WHERE BASE PAGE RESIDES - * (0 - 1023) - CNODE DEF .CNOD THIS IS THE ADDRESS OF CURRENT PATH # WORD - .ORD OCT XXXXX THIS NODE'S LEAF # (IE PATH #) - .NOD# OCT XXXXX THIS NODE'S ORDINAL # -*/ - -static t_stat cpu_vma_loc(OPS op,uint32 intrq,t_bool debug) -{ -uint32 eqt,mls,pnod,lstpg,fstpg,rotsz,lgpg,relpg,relbp,matloc,ptnpg,physpg,cnt,pgs,umapr; - -eqt = ReadIO(xeqt,UMAP); /* get ID segment */ -mls = ReadIO(eqt+33,SMAP); /* get word33 of alternate map */ -if ((mls & 0x8000) == 0) { /* this is not an MLS prog! */ - PC = err_PC; - if (debug) - fprintf(sim_deb,">>CPU VMA: cpu_vma_loc at P=%06o: not an MLS program\n", PC); - if (mp_control) MP_ABORT (eqt+33); /* allow an MP abort */ - return STOP_HALT; /* FATAL error! */ - } - -pnod = mls & 01777; /* get #pages of mem res nodes */ -if (pnod == 0) { /* no pages? FATAL! */ - PC = err_PC; - if (debug) - fprintf(sim_deb,">>CPU VMA: cpu_vma_loc at P=%06o: no mem resident pages\n", PC); - if (mp_control) MP_ABORT (eqt+33); /* allow an MP abort */ - return STOP_HALT; - } - -lstpg = (ReadIO(eqt+29,SMAP) >> 10) - 1; /* last page# of code */ -fstpg = ReadIO(eqt+23,SMAP) >> 10; /* index to 1st addr + mem nodes */ -rotsz = fstpg - (ReadIO(eqt+22,SMAP) >> 10); /* #pages in root */ -lgpg = op[0].word; - -/* lets do some consistency checks, CPU halt if they fail */ -if (lstpg < lgpg || lgpg < fstpg) { /* assert LSTPG >= LGPG# >= FSTPG */ - PC = err_PC; - if (debug) - fprintf(sim_deb, - ">>CPU VMA: $LOC at P=%06o: failed check LSTPG >= LGPG# >= FSTPG\n",PC); - if (mp_control) MP_ABORT (eqt+22); /* allow an MP abort */ - return STOP_HALT; - } - -relpg = op[1].word; -if (pnod < relpg || relpg < (rotsz+1)) { /* assert #PNOD >= RELPG >= ROTSZ+1 */ - PC = err_PC; - if (debug) - fprintf(sim_deb, - ">>CPU VMA: $LOC at %06o: failed check #PNOD >= RELPG >= ROTSZ+1\n",PC); - if (mp_control) MP_ABORT (eqt+22); /* allow an MP abort */ - return STOP_HALT; - } - -relbp = op[2].word; -if (relbp != 0) /* assert RELBP == 0 OR */ - if (pnod < relbp || relbp < (rotsz+1)) { /* #PNOD >= RELBP >= ROTSZ+1 */ - PC = err_PC; - if (debug) - fprintf(sim_deb, - ">>CPU VMA: $LOC at P=%06o: failed check: #PNOD >= RELBP >= ROTSZ+1\n",PC); - if (mp_control) MP_ABORT (eqt+22); /* allow an MP abort */ - return STOP_HALT; - } - -cnt = lstpg - lgpg + 1; /* #pages to map */ -pgs = pnod - relpg + 1; /* #pages from start node to end of code */ -if (pgs < cnt) cnt = pgs; /* ensure minimum, so not to map into EMA */ - -matloc = ReadIO(xmata,UMAP); /* get MAT $LOC address */ -ptnpg = ReadIO(matloc+3,SMAP) & 01777; /* index to start phys pg */ -physpg = ptnpg + relpg; /* phys pg # of node */ -umapr = 32 + lgpg; /* map register to start */ - -/* do an XMS with AR=umapr,BR=physpg,XR=cnt */ -if (debug) - fprintf(sim_deb, - ">>CPU VMA: $LOC map %d pgs from phys=%06o to mapr=%d\n", - cnt,physpg,umapr); -while (cnt != 0) { - dms_wmap (umapr, physpg); /* map pages of new overlay segment */ - cnt = (cnt - 1) & DMASK; - umapr = (umapr + 1) & DMASK; - physpg = (physpg + 1) & DMASK; - } - -dms_wmap(32,relbp+ptnpg); /* map base page again */ -WriteW(op[3].word,op[4].word); /* path# we are going to */ - -PC = (PC - 8) & DMASK; /* adjust PC to return address */ - /* word before the $LOC microinstr. */ -PC = (ReadW(PC) - 1) & DMASK; /* but the call has to be rerun, */ - /* so must skip back to the original call */ - /* which will now lead to the real routine */ -if (debug) - fprintf(sim_deb,">>CPU VMA: $LOC done: path#=%06o, P=%06o\n",op[4].word,PC); -return SCPE_OK; -} - -/* map pte into last page - return FALSE if page fault, nil flag in PTE or suit mismatch - return TRUE if suit match, physpg = physical page - or page=0 -> last+1 page -*/ -static t_bool cpu_vma_ptevl(uint32 pagid,uint32* physpg) -{ -uint32 suit; -uint32 pteidx = pagid & 0001777; /* build index */ -uint32 reqst = pagid & SUITMASK; /* required suit */ -uint32 pteval = ReadW(page31 | pteidx); /* get PTE entry */ -*physpg = pteval & 0001777; /* store physical page number */ -suit = pteval & SUITMASK; /* suit number seen */ -if (pteval == NILPAGE) return FALSE; /* NIL value in PTE */ -return suit == reqst || !*physpg; /* good page or last+1 */ -} - -/* handle page fault */ -static t_stat cpu_vma_fault(uint32 x,uint32 y,int32 mapr, - uint32 ptepg,uint32 ptr,uint32 faultpc, t_bool debug) -{ -uint32 pre = ReadIO(xi,UMAP); /* get program preamble */ -uint32 ema = ReadIO(pre+2,UMAP); /* get address of $EMA$/$VMA$ */ -WriteIO(ema,faultpc,UMAP); /* write addr of fault instr */ -XR = x; /* X = faulting page */ -YR = y; /* Y = faulting address for page */ - -if (mapr>0) - dms_wmap(mapr+UMAP,ptepg); /* map PTE into specified user dmsmap */ - -/* do a safety check: first instr of $EMA$/$VMA$ must be a DST instr */ -if (ReadIO(ema+1,UMAP) != 0104400) { - if (debug) - fprintf(sim_deb, ">>CPU VMA: pg fault: no EMA/VMA user code present\n"); - if (mp_control) MP_ABORT (ema+1); /* allow an MP abort */ - return STOP_HALT; /* FATAL: no EMA/VMA! */ - } - -PC = (ema+1) & VAMASK; /* restart $EMA$ user code, */ - /* will return to fault instruction */ - -AR = (ptr >> 16) & DMASK; /* restore A, B */ -BR = ptr & DMASK; -E = 0; /* enforce E = 0 */ -if (debug) - fprintf(sim_deb, - ">>CPU VMA: Call pg fault OS exit, AR=%06o BR=%06o P=%06o\n", - AR, BR, PC); -return SCPE_OK; -} - -/* map in PTE into last page, return false, if page fault */ -static t_bool cpu_vma_mapte(uint32* ptepg) -{ -uint32 idext,idext2; -uint32 dispatch = ReadIO(vswp,UMAP) & 01777; /* get fresh dispatch flag */ -t_bool swapflag = TRUE; - -if (dispatch == 0) { /* not yet set */ - idext = ReadIO(idx,UMAP); /* go into ID segment extent */ - if (idext == 0) { /* is ema/vma program? */ - swapflag = FALSE; /* no, so mark PTE as invalid */ - *ptepg = (uint32) -1; /* and return an invalid page number */ - } - - else { /* is an EMA/VMA program */ - dispatch = ReadWA(idext+1) & 01777; /* get 1st ema page: new vswp */ - WriteIO(vswp,dispatch,UMAP); /* move into $VSWP */ - idext2 = ReadWA(idext+2); /* get swap bit */ - swapflag = (idext2 & 020000) != 0; /* bit 13 = swap bit */ - } - } - -if (dispatch) { /* some page is defined */ - dms_wmap(31 + UMAP,dispatch); /* map $VSWP to register 31 */ - *ptepg = dispatch; /* return PTEPG# for later */ - } - -return swapflag; /* true for valid PTE */ -} - -/* .LBP - ASSEMBLER CALLING SEQUENCE: - - DLD PONTR TRANSLATE 32 BIT POINTER TO 15 - JSB .LBP BIT POINTER. - - - 32 bit pointer: - ----------AR------------ -----BR----- - 15 14....10 9....4 3...0 15.10 9....0 - L<----------------------------------- L=1 local reference bit - XXXXXXXX<------------------------- 5 bit unused - PPPPPP PPPPP PPPPP<------ 16 bit PAGEID - SSSSSS<------------------ SUIT# within PAGEID - PPPPP PPPPP<------ 10 bit PAGEID index into PTE - OOOOOO 10 bit OFFSET -*/ - -static t_stat cpu_vma_lbp(uint32 ptr,uint32 aoffset,uint32 faultpc,uint32 intrq,t_bool debug) -{ -uint32 pagid,offset,ptrl,pgidx,ptepg; -uint16 p30,p31,suit; -t_stat reason = SCPE_OK; -uint32 faultab = ptr; /* remember A,B for page fault */ -ptr += aoffset; /* add the offset e.g. for .LPX */ - -if (debug) - fprintf(sim_deb,">>CPU VMA: cpu_vma_lbp: ptr=%o/%o\n", - (ptr>>16) & DMASK,ptr & DMASK); - -O = 0; /* clear overflow */ -if (ptr & 0x80000000) { /* is it a local reference? */ - ptrl = ptr & VAMASK; - if (ptr&I_IA) { - reason = vma_resolve (ReadW (ptrl), &ptrl, debug); - if (reason) - return reason; /* yes, resolve indirect ref */ - } - BR = ptrl & VAMASK; /* address is local */ - AR = (ptr >> 16) & DMASK; - if (debug) - fprintf(sim_deb,">>CPU VMA: cpu_vma_lbp: local ref AR=%06o BR=%06o\n",AR,BR); - return SCPE_OK; - } - -pagid = (ptr >> 10) & DMASK; /* extract page id (16 bit idx, incl suit*/ -offset = ptr & 01777; /* and offset */ -suit = pagid & SUITMASK; /* suit of page */ -pgidx = pagid & PAGEIDX; /* index into PTE */ - -if (!cpu_vma_mapte(&ptepg)) /* map in PTE */ - return cpu_vma_fault(65535,ptemiss,-1,ptepg,faultab,faultpc, debug); /* oops, must init PTE */ - -/* ok, we have the PTE mapped to page31 */ -/* the microcode tries to reads two consecutive data pages into page30 and page31 */ - -/* read the 1st page value from PTE */ -p30 = ReadW(page31 | pgidx) ^ suit; -if (!p30) /* matched suit for 1st page */ - return cpu_vma_fault(pagid,page30,30,ptepg,faultab,faultpc,debug); - -/* suit switch situation: 1st page is in last idx of PTE, then following page - * must be in idx 0 of PTE */ -if (pgidx==01777) { /* suit switch situation */ - pgidx = 0; /* select correct idx 0 */ - suit = (uint16) (pagid + 1); /* suit needs increment */ - if (suit==0) { /* is it page 65536? */ - offset += 02000; /* adjust to 2nd page */ - suit = NILPAGE; - pgidx = 01777; - } -} else - pgidx++; /* select next page */ - -p31 = ReadW(page31 | pgidx) ^ suit; -if (!p31) { /* matched suit for 2nd page */ - dms_wmap(31+UMAP,p30); - if (p30 & SUITMASK) - return cpu_vma_fault(pagid,page30,30,ptepg,faultab,faultpc,debug); - if (!(p31 ^ NILPAGE)) /* suit is 63: fault */ - return cpu_vma_fault(pagid+1,page31,31,ptepg,faultab,faultpc,debug); - - offset += 02000; /* adjust offset to last user map because */ - /* the address requested page 76xxx */ - } -else { - dms_wmap(30+UMAP,p30); - if (p30 & SUITMASK) - return cpu_vma_fault(pagid,page30,30,ptepg,faultab,faultpc,debug); - dms_wmap(31+UMAP,p31); - if (p31 & SUITMASK) - return cpu_vma_fault(pagid+1,page31,31,ptepg,faultab,faultpc,debug); - } - -AR = (uint16) pagid; /* return pagid in A */ -BR = (uint16) (page30 + offset); /* mapped address in B */ -if (debug) - fprintf(sim_deb,">>CPU VMA: cpu_vma_lbp: map done AR=%06o BR=%o6o\n",AR,BR); -return SCPE_OK; -} - -/* .PMAP - ASSEMBLER CALLING SEQUENCE: - - LDA UMAPR (MSEG - 31) - LDB PAGID (0-65535) - JSB .PMAP GO MAP IT IN - A-REG = REASON, NOTE 1 - > SEE NOTE 2> - - NOTE 1 : IF BIT 15 OF A-REG SET, THEN ALL NORMAL BRANCHES TO THE - $EMA$/$VMA$ CODE WILL BE CHANGED TO P+1 EXIT. THE A-REG - WILL BE THE REASON THE MAPPING WAS NOT SUCCESSFUL IF BIT 15 - OF THE A-REG WAS NOT SET. - THIS WAS DONE SO THAT A ROUTINE ($VMA$) CAN DO A MAPPING - WITHOUT THE POSSIBILITY OF BEING RE-CURRED. IT IS USED - BY $VMA$ AND PSTVM IN THE PRIVLEDGED MODE. - NOTE 2: E-REG WILL = 1 IF THE LAST+1 PAGE IS REQUESTED AND - MAPPED READ/WRITE PROTECTED ON A GOOD P+2 RETURN. -*/ -static t_stat cpu_vma_pmap(uint32 umapr,uint32 pagid, t_bool debug) -{ -uint32 physpg, ptr, pgpte; -uint32 mapnm = umapr & 0x7fff; /* strip off bit 15 */ - -if (debug) - fprintf(sim_deb, ">>CPU VMA: .PMAP AR=%06o(umapr) BR=%06o(pagid)\n",umapr,pagid); - -if (mapnm > 31) { /* check for invalid map register */ - AR = 80; /* error: corrupt EMA/VMA system */ - if (debug) - fprintf(sim_deb, ">>CPU VMA: .PMAP invalid mapr: AR=80, exit P+1\n"); - return SCPE_OK; /* return exit PC+1 */ - } - -ptr = (umapr << 16) | (pagid & DMASK); /* build the ptr argument for vma_fault */ -if (!cpu_vma_mapte(&pgpte)) { /* map the PTE */ - if (umapr & 0x8000) { - XR = 65535; - YR = ptemiss; - if (debug) - fprintf(sim_deb, - ">>CPU VMA: .PMAP pg fault&bit15: XR=%06o YR=%06o, exit P+1\n", - XR, YR); - return SCPE_OK; /* use PC+1 error exit */ - } - return cpu_vma_fault(65535,ptemiss,-1,pgpte,ptr,PC-1,debug); /* oops: fix PTE */ - } - -/* PTE is successfully mapped to page31 and dmsmap[63] */ -if (!cpu_vma_ptevl(pagid,&physpg)) { - if (umapr & 0x8000) { - XR = pagid; - YR = page31; - if (debug) - fprintf(sim_deb, - ">>CPU VMA: .PMAP pg map&bit15: XR=%06o YR=%06o, exit P+1\n", - XR, YR); - return SCPE_OK; /* use PC+1 error exit*/ - } - return cpu_vma_fault(pagid,page31,31,pgpte,ptr,PC-1,debug); /* page not present */ - } - -E = 1; -if (physpg == 0) /* last+1 page ? */ - physpg = RWPROT; /* yes, use page 1023 RW/Protected */ -else E = 0; /* normal page to map */ - -dms_wmap(mapnm+UMAP,physpg); /* map page to user page reg */ -if (mapnm != 31) /* unless already unmapped, */ - dms_wmap(31+UMAP,RWPROT); /* unmap PTE */ - -AR = (umapr + 1) & DMASK; /* increment mapr for next call */ -BR = (pagid + 1) & DMASK; /* increment pagid for next call */ -O = 0; /* clear overflow */ -PC = (PC + 1) & VAMASK; /* normal PC+2 return */ -if (debug) - fprintf(sim_deb,">>CPU VMA: .PMAP map done: AR=%06o BR=%o6o exit P+2\n",AR,BR); -return SCPE_OK; -} - -/* array calc helper for .imar, .jmar, .imap, .jmap - ij=in_s: 16 bit descriptors - ij=in_d: 32 bit descriptors - - This helper expects mainly the following arguments: - dtbl: pointer to an array descriptor table - atbl: pointer to the table of actual subscripts - - where subscript table is the following: - atbl-> DEF last_subscript,I (point to single or double integer) - ... - DEF first subscript,I (point to single or double integer) - - where Descriptor_table is the following table: - dtbl-> DEC #dimensions - DEC/DIN next-to-last dimension (single or double integer) - ... - DEC/DIN first dimension (single or double integer) - DEC elementsize in words - DEC high,low offset from start of EMA to element(0,0...0) - - Note that subscripts are counting from 0 -*/ -static t_stat cpu_vma_ijmar(OPSIZE ij,uint32 dtbl,uint32 atbl,uint32* dimret, - uint32 intrq,t_bool debug) -{ -t_stat reason = SCPE_OK; -uint32 ndim,MA,i,ws; -int32 accu,ax,dx; -OP din; -int opsz = ij==in_d ? 2 : 1; - -ndim = ReadW(dtbl++); /* get #dimensions itself */ -if (debug) { - fprintf(sim_deb, ">>CPU VMA array calc #dim=%d, size=%d\n",ndim,opsz); - fprintf(sim_deb, ">>CPU VMA: array actual subscripts ("); - for (i=0; i0) fputc(',',sim_deb); - fprintf(sim_deb,"%d",ij==in_d?INT32(din.dword) : INT16(din.word)); - } - - fprintf(sim_deb,")\n>>CPU VMA: array descriptor table ("); - if (ndim) { - for (i=0; i0) fputc(',',sim_deb); - fprintf(sim_deb,"%d",ij==in_d?INT32(din.dword) : INT16(din.word)); - } - i = dtbl+1+(ndim-1)*opsz; - ws = ReadW(i-1); - } - else { - i = dtbl; - ws = 1; - } - fprintf(sim_deb,")\n>>CPU VMA: array elemsz=%d base=%o/%o\n", - ws,ReadW(i),ReadW(i+1)); - } - -if (dimret) *dimret = ndim; /* return dimensions */ -if (ndim == 0) { /* no dimensions: */ - AR = ReadW(dtbl++); /* return the array base itself */ - BR = ReadW(dtbl); - if (debug) - fprintf(sim_deb,">>CPU VMA: #dim=0, AR=%06o, BR=%06o\n",AR,BR); - return SCPE_OK; - } - -/* calculate - * (...(An*Dn-1)+An-1)*Dn-2)+An-2....)+A2)*D1)+A1)*#words + Array base - * Depending on ij, Ax and Dx can be 16 or 32 bit - */ -accu = 0; -while (ndim-- > 0) { - MA = ReadW(atbl++); /* get addr of subscript */ - reason = resolve (MA, &MA, intrq); /* and resolve it */ - if (reason) - return reason; - din = ReadOp(MA,ij); /* get actual subscript value */ - ax = ij==in_d ? INT32(din.dword) : INT16(din.word); - accu += ax; /* add to accu */ - - if (ndim==0) ij = in_s; /* #words is single */ - din = ReadOp(dtbl,ij); /* get dimension from descriptor table */ - if (ij==in_d) { - dx = INT32(din.dword); /* either get double or single dimension */ - dtbl += 2; - } else { - dx = INT16(din.word); - dtbl++; - } - accu *= dx; /* multiply */ - } - -din = ReadOp(dtbl,in_d); /* add base address */ -accu += din.dword; - -AR = (accu >> 16) & DMASK; /* transfer to AB */ -BR = accu & DMASK; -if (debug) - fprintf(sim_deb,">>CPU VMA: resulting virt addr=%o (AR=%06o, BR=%06o)\n",accu,AR,BR); -return reason; -} - -/* - * This is the main handler for the RTE6/VMA microcodes */ -t_stat cpu_rte_vma (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -OP_PAT pattern; -uint16 t16; -uint32 entry,t32,ndim; -uint32 dtbl,atbl; /* descriptor table ptr, actual args ptr */ -OP dop0,dop1; -uint32 pcsave = (PC+1) & VAMASK; /* save PC to check for redo in imap/jmap */ -t_bool debug = DEBUG_PRI (cpu_dev, DEB_VMA); - -entry = IR & 017; /* mask to entry point */ -pattern = op_vma[entry]; /* get operand pattern */ - -if (pattern != OP_N) { - reason = cpu_ops (pattern, op, intrq); /* get instruction operands */ - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -if (debug) { /* debugging? */ - fprintf (sim_deb, ">>CPU VMA: IR = %06o (", IR); /* print preamble and IR */ - fprint_sym (sim_deb, err_PC, (t_value *) &IR, /* print instruction mnemonic */ - NULL, SWMASK('M')); - fprintf (sim_deb, "), P = %06o, XEQT = %06o", /* print location and program ID */ - err_PC, ReadW (xeqt)); - - fprint_ops (pattern, op); /* print operands */ - fputc ('\n', sim_deb); /* terminate line */ - } - -switch (entry) { /* decode IR<3:0> */ - - case 000: /* .PMAP 105240 (OP_N) */ - reason = cpu_vma_pmap(AR,BR,debug); /* map pages */ - break; - - case 001: /* $LOC 105241 (OP_CCCACC) */ - reason = cpu_vma_loc(op,intrq,debug); /* handle the coroutine switch */ - break; - - case 002: /* [test] 105242 (OP_N) */ - XR = 3; /* refer to src code 92084-18828 rev 3 */ - SR = 0102077; /* HLT 77 instruction */ - YR = 1; /* ROMs correctly installed */ - PC = (PC+1) & VAMASK; /* skip instr if VMA/EMA ROM installed */ - break; - - case 003: /* [swap] 105243 (OP_N) */ - t16 = AR; /* swap A and B registers */ - AR = BR; - BR = t16; - break; - - case 004: /* [---] 105244 (OP_N) */ - reason = stop_inst; /* fragment of dead code */ - break; /* in microrom */ - - case 005: /* [---] 105245 (OP_N) */ - reason = stop_inst; /* fragment of dead code */ - break; /* in microrom */ - - case 006: /* [nop] 105246 (OP_N) */ - break; /* do nothing */ - - case 007: /* [umpy] 105247 (OP_K) */ - t32 = AR * op[0].word; /* get multiplier */ - t32 += BR; /* add B */ - AR = (t32 >> 16) & DMASK; /* move result back to AB */ - BR = t32 & DMASK; - O = 0; /* instr clears OV */ - break; - - case 010: /* .IMAP 105250 (OP_A) */ - dtbl = op[0].word; - atbl = PC; - reason = cpu_vma_ijmar(in_s,dtbl,atbl,&ndim,intrq,debug); /* calc the virt address to AB */ - if (reason) - return reason; - t32 = (AR << 16) | (BR & DMASK); - reason = cpu_vma_lbp(t32,0,PC-2,intrq,debug); - if (reason) - return reason; - if (PC==pcsave) - PC = (PC+ndim) & VAMASK; /* adjust PC: skip ndim subscript words */ - break; - - case 011: /* .IMAR 105251 (OP_A) */ - dtbl = ReadW(op[0].word); - atbl = (op[0].word+1) & VAMASK; - reason = cpu_vma_ijmar(in_s,dtbl,atbl,0,intrq,debug); /* calc the virt address to AB */ - break; - - case 012: /* .JMAP 105252 (OP_A) */ - dtbl = op[0].word; - atbl = PC; - reason = cpu_vma_ijmar(in_d,dtbl,atbl,&ndim,intrq,debug); /* calc the virtual address to AB */ - if (reason) - return reason; - t32 = (AR << 16) | (BR & DMASK); - reason = cpu_vma_lbp(t32,0,PC-2,intrq,debug); - if (reason) - return reason; - if (PC==pcsave) - PC = (PC + ndim) & VAMASK; /* adjust PC: skip ndim subscript dword ptr */ - break; - - case 013: /* .JMAR 105253 (OP_A) */ - dtbl = ReadW(op[0].word); - atbl = (op[0].word+1) & VAMASK; - reason = cpu_vma_ijmar(in_d,dtbl,atbl,0,intrq,debug); /* calc the virt address to AB */ - break; - - case 014: /* .LPXR 105254 (OP_AA) */ - dop0 = ReadOp(op[0].word,in_d); /* get pointer from arg */ - dop1 = ReadOp(op[1].word,in_d); - t32 = dop0.dword + dop1.dword; /* add offset to it */ - reason = cpu_vma_lbp(t32,0,PC-3,intrq,debug); - break; - - case 015: /* .LPX 105255 (OP_A) */ - t32 = (AR << 16) | (BR & DMASK); /* pointer in AB */ - dop0 = ReadOp(op[0].word,in_d); - reason = cpu_vma_lbp(t32,dop0.dword,PC-2,intrq,debug); - break; - - case 016: /* .LBPR 105256 (OP_A) */ - dop0 = ReadOp(op[0].word,in_d); /* get the pointer */ - reason = cpu_vma_lbp(dop0.dword,0,PC-2,intrq,debug); - break; - - case 017: /* .LBP 105257 (OP_N) */ - t32 = (AR << 16) | (BR & DMASK); - reason = cpu_vma_lbp(t32,0,PC-1,intrq,debug); - break; - } - -return reason; -} - - -/* RTE-IV Extended Memory Area Instructions - - The RTE-IV operating system (HP product number 92067A) introduced the - Extended Memory Area (EMA) instructions. EMA provided a mappable data area - up to one megaword in size. These three instructions accelerated data - accesses to variables stored in EMA partitions. Support was limited to - E/F-Series machines; M-Series machines used software equivalents. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A N/A 92067A 92067A - - The routines are mapped to instruction codes as follows: - - Instr. 1000-E/F Description - ------ -------- ---------------------------------------------- - .EMIO 105240 EMA I/O - MMAP 105241 Map physical to logical memory - [test] 105242 [self test] - .EMAP 105257 Resolve array element address - - Notes: - - 1. RTE-IV EMA and RTE-6 VMA instructions share the same address space, so a - given machine can run one or the other, but not both. - - 2. The EMA diagnostic (92067-16013) reports bogus MMAP failures if it is - not loaded at the start of its partition (e.g., because of a LOADR "LO" - command). The "ICMPS" map comparison check in the diagnostic assumes - that the starting page of the program's partition contains the first - instruction of the program and prints "MMAP ERROR" if it does not. - - Additional references: - - RTE-IVB Programmer's Reference Manual (92068-90004, Dec-1983). - - RTE-IVB Technical Specifications (92068-90013, Jan-1980). -*/ - -static const OP_PAT op_ema[16] = { - OP_AKA, OP_AKK, OP_N, OP_N, /* .EMIO MMAP [test] --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ - OP_N, OP_N, OP_N, OP_AAA /* --- --- --- .EMAP */ - }; - -/* calculate the 32 bit EMA subscript for an array */ -static t_bool cpu_ema_resolve(uint32 dtbl,uint32 atbl,uint32* sum) -{ -int32 sub, act, low, sz, ndim; -uint32 MA, base; - -ndim = ReadW(dtbl++); /* # dimensions */ -ndim = SEXT(ndim); /* sign extend */ -if (ndim < 0) return FALSE; /* invalid? */ - -*sum = 0; /* accu for index calc */ -while (ndim > 0) { - MA = ReadW(atbl++); /* get address of A(N) */ - resolve (MA, &MA, 0); - act = ReadW(MA); /* A(N) */ - low = ReadW(dtbl++); /* -L(N) */ - sub = SEXT(act) + SEXT(low); /* subscript */ - if (sub & 0xffff8000) return FALSE; /* overflow? */ - *sum += sub; /* accumulate */ - sz = ReadW(dtbl++); - sz = SEXT(sz); - if (sz < 0) return FALSE; - *sum *= sz; - if (*sum > (512*1024)) return FALSE; /* overflow? */ - ndim--; -} -base = (ReadW(dtbl+1)<<16) | (ReadW(dtbl) & 0xffff); /* base of array in EMA */ -if (base & 0x8000000) return FALSE; -*sum += base; /* calculate address into EMA */ -if (*sum & 0xf8000000) return FALSE; /* overflow? */ -return TRUE; -} - -/* implementation of VIS RTE-IVB EMA support - * .ERES microcode routine, resolves only EMA addresses - * Call: - * .OCT 101474B - * DEF RTN error return (rtn), good return is rtn+1 - * DEF DUMMY dummy argument for compatibility with .EMAP - * DEF TABLE[,I] array declaration (dtbl) - * DEF A(N)[,I] actual subscripts (atbl) - * DEF A(N-1)[,I] - * ... - * DEF A(2)[,I] - * DEF A(1)[,I] - * RTN EQU * error return A="20", B="EM" - * RTN+1 EQU *+1 good return B=logical address - * - * TABLE DEC # # dimensions - * DEC -L(N) - * DEC D(N-1) - * DEC -L(N-1) lower bound (n-1)st dim - * DEC D(N-2) (n-2)st dim - * ... - * DEC D(1) 1st dim - * DEC -L(1) lower bound 1st dim - * DEC # # words/element - * OFFSET 1 EMA Low - * OFFSET 2 EMA High - */ -t_stat cpu_ema_eres(uint32 *rtn,uint32 dtbl,uint32 atbl,t_bool debug) -{ -uint32 sum; -if (cpu_ema_resolve(dtbl,atbl,&sum)) { /* calculate subscript */ - AR = sum & 0xffff; - BR = sum >> 16; - if (!(BR & SIGN)) { /* no overflow? */ - (*rtn)++; /* return via good exit */ - return SCPE_OK; - } -} -AR = 0x3230; /* error condition: */ -BR = 0x454d; /* AR = '20', BR = 'EM' */ -return SCPE_OK; /* return via unmodified rtn */ -} - -/* implementation of VIS RTE-IVB EMA support - * .ESEG microcode routine - * Call: - * LDA FIRST first map to set - * LDB N # of maps to set - * .OCT 101475B/105475B - * DEF RTN ptr to return - * DEF TABLE map table - * RTN EQU * error return A="21", B="EM" - * RTN+1 EQU *+1 good return B=logical address - * - * load maps FIRST to FIRST+N from TABLE, with FIRST = FIRST + LOG_START MSEG - * update map table in base page. Set LOG_START MSEG=0 if opcode==105475 - */ -t_stat cpu_ema_eseg(uint32* rtn, uint32 IR, uint32 tbl, t_bool debug) -{ -uint32 xidex,eqt,idext0,idext1; -uint32 msegsz,phys,msegn,last,emasz,pg0,pg1,pg,i,lp; - -if ((BR & SIGN) || BR==0) goto em21; /* #maps not positive? */ -xidex = ReadIO(idx,UMAP); /* read ID extension */ -if (xidex==0) goto em21; -idext0 = ReadWA(xidex+0); /* get 1st word idext */ -msegsz = idext0 & 037; /* S7 MSEG size */ -WriteIO(xidex+0, idext0 | 0100000, SMAP); /* enforce nonstd MSEG */ -idext1 = ReadWA(xidex+1); /* get 2nd word idext */ -phys = idext1 & 01777; /* S5 phys start of EMA */ -msegn = (idext1 >> 11) & 037; /* S9 get logical start MSEG# */ -if (IR & 04000) { /* opcode == 105475? (.VPRG) */ - msegn = 0; /* log start = 0 */ - msegsz = 32; /* size = full range */ -} -last = AR-1 + BR; /* last page */ -if (last > msegsz) goto em21; /* too many? error */ -eqt = ReadIO(xeqt,UMAP); -emasz = (ReadWA(eqt+28) & 01777) - 1; /* S6 EMA size in pages */ - -/* locations 1740...1777 of user base page contain the map entries we need. - * They are normally hidden by BP fence, therefore they have to be accessed by - * another fence-less map register. uCode uses #1 temporarily */ -pg0 = dms_rmap(UMAP+0); /* read map #0 */ -pg1 = dms_rmap(UMAP+1); /* save map #1 */ -dms_wmap(UMAP+1,pg0); /* copy #0 into reg #1 */ -lp = AR + msegn; /* first */ -for (i=0; i emasz) pg |= 0140000; /* write protect if outside */ - pg += phys; /* adjust into EMA page range */ - WriteIO(umaps+lp+i, pg, UMAP); /* copy pg to user map */ -/* printf("MAP val %oB to reg %d (addr=%oB)\n",pg,lp+i,umaps+lp+i); */ - dms_wmap(UMAP+lp+i, pg); /* set DMS reg */ -} -dms_wmap(UMAP+1,pg1); /* restore map #1 */ -O = 0; /* clear overflow */ -(*rtn)++; /* return via good exit */ -return SCPE_OK; - -em21: -AR = 0x3231; /* error condition: */ -BR = 0x454d; /* AR = '21', BR = 'EM' */ -return SCPE_OK; /* return via unmodified rtn */ -} - -/* implementation of VIS RTE-IVB EMA support - * .VSET microcode routine - * Call: - * .OCT 101476B - * DEF RTN return address - * DEF VIN input vector - * DEF VOUT output vector - * DEF MAPS - * OCT #SCALARS - * OCT #VECTORS - * OCT K 1024/(#words/element) - * RTN EQU * error return (B,A) = "VI22" - * RTN+1 EQU *+1 hard return, A = K/IMAX - * RTN+2 EQU *+2 easy return, A = 0, B = 2* #VCTRS - */ -t_stat cpu_ema_vset(uint32* rtn, OPS op, t_bool debug) -{ -uint32 vin = op[0].word; /* S1 */ -uint32 vout = op[1].word; /* S2 */ -uint32 maps = op[2].word; /* S3 */ -uint32 scalars = op[3].word; /* S4 */ -uint32 vectors = op[4].word; /* S5 */ -uint32 k = op[5].word; /* S6 */ -uint32 imax = 0; /* imax S11*/ -uint32 xidex, idext1, mseg, addr, i, MA; -t_bool negflag = FALSE; - -for (i=0; i> 1) & MSEGMASK; /* S9 get logical start MSEG */ - -for (i=0; i> 10) & 0xffff; /* get page */ - WriteW(maps++, addr); /* save page# */ - WriteW(maps++, addr+1); /* save next page# as well */ - MA = ReadW(vin++); /* get index into Y */ - resolve(MA, &MA, 0); - YR = ReadW(MA); /* get index value */ - WriteW(vout++, MA); /* copy address of index */ - if (YR & SIGN) { /* index is negative */ - negflag = TRUE; /* mark a negative index (HARD) */ - YR = (~YR + 1) & DMASK; /* make index positive */ - } - if (imax < YR) imax = YR; /* set maximum index */ - mseg += 04000; /* incr mseg address by 2 more pages */ -} -MA = ReadW(vin); /* get N index into Y */ -resolve(MA, &MA, 0); -YR = ReadW(MA); -WriteW(vout++, MA); vin++; /* copy address of N */ - -if (imax==0) goto easy; /* easy case */ -AR = (uint16) (k / imax); AR++; /* calculate K/IMAX */ -if (negflag) goto hard; /* had a negative index? */ -if (YR > AR) goto hard; - -easy: -(*rtn)++; /* return via exit 2 */ -AR = 0; - -hard: -(*rtn)++; /* return via exit 1 */ -BR = 2 * op[4].word; /* B = 2* vectors */ -return SCPE_OK; - -vi22: /* error condition */ - AR=0x3232; /* AR = '22' */ - BR=0x5649; /* BR = 'VI' */ - return SCPE_OK; /* return via unmodified e->rtn */ -} - -typedef struct ema4 { - uint32 mseg; /* logical start of MSEG */ - uint32 msegsz; /* size of std mseg in pgs */ - uint32 pgoff; /* pg # in EMA containing element */ - uint32 offs; /* offset into page of element */ - uint32 msoff; /* total offset to element in MSEG */ - uint32 emasz; /* size of ema in pgs */ - uint32 msegno; /* # of std mseg */ - uint32 ipgs; /* # of pgs to start of MSEG */ - uint32 npgs; /* # of pgs needed */ - uint32 spmseg; /* first phys pg of MSEG */ -} EMA4; - -static t_bool cpu_ema_emas(uint32 dtbl,uint32 atbl,EMA4* e) -{ -uint32 xidex, eqt; -uint32 sum, msegsz,pgoff,offs,emasz,msegno,msoff,ipgs; - -if (!cpu_ema_resolve(dtbl,atbl,&sum)) return FALSE; /* calculate 32 bit index */ - -xidex = ReadIO(idx,UMAP); /* read ID extension */ -msegsz = ReadWA(xidex+0) & 037; /* S5 # pgs for std MSEG */ -pgoff = sum >> 10; /* S2 page containing element */ -offs = sum & 01777; /* S6 offset in page to element */ -if (pgoff > 1023) return FALSE; /* overflow? */ -eqt = ReadIO(xeqt,UMAP); -emasz = ReadWA(eqt+28) & 01777; /* S EMA size in pages */ -if (pgoff > emasz) return FALSE; /* outside EMA? */ -msegno = pgoff / msegsz; /* S4 # of MSEG */ -msoff = pgoff % msegsz; /* offset within MSEG in pgs */ -ipgs = pgoff - msoff; /* S7 # pgs to start of MSEG */ -msoff = msoff << 10; /* offset within MSEG in words */ -msoff += offs; /* S1 offset to element in words */ - -e->msegsz = msegsz; /* return calculated data */ -e->pgoff = pgoff; -e->offs = offs; -e->emasz = emasz; -e->msegno = msegno; -e->ipgs = ipgs; -e->msoff = msoff; -return TRUE; -} - -static t_bool cpu_ema_mmap01(EMA4* e) -{ -uint32 xidex,idext0, pg, pg0, pg1, i; - -uint32 base = e->mseg >> 10; /* get the # of first MSEG DMS reg */ -xidex = ReadIO(idx,UMAP); /* get ID extension */ -idext0 = ReadWA(xidex+1); - -if (e->npgs==0) return FALSE; /* no pages to map? */ -if ((e->npgs+1+e->ipgs) <= e->emasz) e->npgs++; /* actually map npgs+1 pgs */ - -/* locations 1740...1777 of user base page contain the map entries we need. - * They are normally hidden by BP fence, therefore they have to be accessed by - * another fence-less map register. uCode uses #1, macro code uses $DVCT (==2) - */ -pg0 = dms_rmap(UMAP+0); /* read base page map# */ -pg1 = dms_rmap(UMAP+1); /* save map# 1 */ -dms_wmap(UMAP+1,pg0); /* map #0 into reg #1 */ -for (i=0; (base+i)<32; i++) { - pg = inpgs ? e->spmseg : 0140000; /* write protect if outside */ - WriteIO(umaps+base+i, pg, UMAP); /* copy pg to user map */ -/* printf("MAP val %d to reg %d (addr=%o)\n",pg,base+i,umaps+base+i); */ - dms_wmap(UMAP+base+i, pg); /* set DMS reg */ - e->spmseg++; -} -dms_wmap(UMAP+1,pg1); /* restore map #1 */ - -xidex = ReadIO(idx,UMAP); /* get ID extension */ -idext0 = ReadWA(xidex+0); -if (e->msegno == 0xffff) /* non std mseg */ - idext0 |= 0x8000; /* set nonstd marker */ -else - idext0 = (idext0 & 037) | (e->msegno<<5); /* set new current mseg# */ -WriteIO(xidex, idext0, SMAP); /* save back value */ -AR = 0; /* was successful */ -return TRUE; -} - -static t_bool cpu_ema_mmap02(EMA4* e) -{ -uint32 xidex, eqt, idext1; -uint32 mseg,phys,spmseg,emasz,msegsz,msegno; - -xidex = ReadIO(idx,UMAP); /* get ID extension */ -msegsz = ReadWA(xidex+0) & 037; /* P size of std MSEG */ -idext1 = ReadWA(xidex+1); -mseg = (idext1 >> 1) & MSEGMASK; /* S9 get logical start MSEG */ -phys = idext1 & 01777; /* S phys start of EMA */ -spmseg = phys + e->ipgs; /* S7 phys pg# of MSEG */ -msegno = e->ipgs / msegsz; -if ((e->ipgs % msegsz) != 0) /* non std MSEG? */ - msegno = 0xffff; /* S4 yes, set marker */ -if (e->npgs > msegsz) return FALSE; /* map more pages than MSEG sz? */ -eqt = ReadIO(xeqt,UMAP); -emasz = ReadWA(eqt+28) & 01777; /* B EMA size in pages */ -if ((e->ipgs+e->npgs) > emasz) return FALSE; /* outside EMA? */ -if ((e->ipgs+msegsz) > emasz) /* if MSEG overlaps end of EMA */ - e->npgs = emasz - e->ipgs; /* only map until end of EMA */ - -e->emasz = emasz; /* copy arguments */ -e->msegsz = msegsz; -e->msegno = msegno; -e->spmseg = spmseg; -e->mseg = mseg; -return cpu_ema_mmap01(e); -} - -static t_stat cpu_ema_mmap(uint32 ipage,uint32 npgs, t_bool debug) -{ -uint32 xidex; -EMA4 ema4, *e = &ema4; - -e->ipgs = ipage; /* S6 set the arguments */ -e->npgs = npgs; /* S5 */ - -AR = 0; -xidex = ReadIO(idx,UMAP); -if ((ipage & SIGN) || /* negative page displacement? */ - (npgs & SIGN) || /* negative # of pages? */ - xidex == 0 || /* no EMA? */ - !cpu_ema_mmap02(e)) /* mapping failed? */ - AR = 0177777; /* return with error */ -return SCPE_OK; /* leave */ -} - -static t_bool cpu_ema_emat(EMA4* e) -{ -uint32 xidex,idext0; -uint32 curmseg,phys,msnum,lastpgs; - -xidex = ReadIO(idx,UMAP); /* read ID extension */ -idext0 = ReadWA(xidex+0); /* get current segment */ -curmseg = idext0 >> 5; -if ((idext0 & 0100000) || /* was nonstd MSEG? */ - curmseg != e->msegno) { /* or different MSEG last time? */ - phys = ReadWA(xidex+1) & 01777; /* physical start pg of EMA */ - e->spmseg = phys + e->ipgs; /* physical start pg of MSEG */ - msnum = e->emasz / e->msegsz; /* find last MSEG# */ - lastpgs = e->emasz % e->msegsz; /* #pgs in last MSEG */ - if (lastpgs==0) msnum--; /* adjust # of last MSEG */ - e->npgs = msnum==e->msegno ? lastpgs : e->msegsz; /* for last MSEG, only map available pgs */ - if (!cpu_ema_mmap01(e)) return FALSE; /* map npgs pages at ipgs */ -} -BR = (uint16) (e->mseg + e->msoff); /* return address of element */ -return TRUE; /* and everything done */ -} - -/* .EMIO microcode routine, resolves element addr for EMA array - * and maps the appropriate map segment - * - * Call: - * OCT 105250B - * DEF RTN error return (rtn), good return is rtn+1 - * DEF BUFLEN length of buffer in words (bufl) - * DEF TABLE[,I] array declaration (dtbl) - * DEF A(N)[,I] actual subscripts (atbl) - * DEF A(N-1)[,I] - * ... - * DEF A(2)[,I] - * DEF A(1)[,I] - * RTN EQU * error return A="15", B="EM" - * RTN+1 EQU *+1 good return B=logical address - * - * TABLE DEC # # dimensions - * DEC -L(N) - * DEC D(N-1) - * DEC -L(N-1) lower bound (n-1)st dim - * DEC D(N-2) (n-2)st dim - * ... - * DEC D(1) 1st dim - * DEC -L(1) lower bound 1st dim - * DEC # # words/element - * OFFSET 1 EMA Low - * OFFSET 2 EMA High - */ -static t_stat cpu_ema_emio(uint32* rtn,uint32 bufl,uint32 dtbl,uint32 atbl,t_bool debug) -{ -uint32 xidex, idext1; -uint32 mseg, bufpgs, npgs; -EMA4 ema4, *e = &ema4; - -xidex = ReadIO(idx,UMAP); /* read ID extension */ -if (bufl & SIGN || /* buffer length negative? */ - xidex==0) goto em16; /* no EMA declared? */ - -idext1 = ReadWA(xidex+1); /* |logstrt mseg|d|physstrt ema| */ -mseg = (idext1 >> 1) & MSEGMASK; /* get logical start MSEG */ -if (!cpu_ema_emas(dtbl,atbl,e)) goto em16; /* resolve address */ -bufpgs = (bufl + e->offs) >> 10; /* # of pgs reqd for buffer */ -if ((bufl + e->offs) & 01777) bufpgs++; /* S11 add 1 if not at pg boundary */ -if ((bufpgs + e->pgoff) > e->emasz) goto em16; /* exceeds EMA limit? */ -npgs = (e->msoff + bufl) >> 10; /* # of pgs reqd for MSEG */ -if ((e->msoff + bufl) & 01777) npgs++; /* add 1 if not at pg boundary */ -if (npgs < e->msegsz) { - e->mseg = mseg; /* logical stat of MSEG */ - if (!cpu_ema_emat(e)) goto em16; /* do a std mapping */ -} else { - BR = (uint16) (mseg + e->offs); /* logical start of buffer */ - e->npgs = bufpgs; /* S5 # pgs required */ - e->ipgs = e->pgoff; /* S6 page offset to reqd pg */ - if (!cpu_ema_mmap02(e)) goto em16; /* do nonstd mapping */ -} -(*rtn)++; /* return via good exit */ -return SCPE_OK; - -em16: /* error condition */ -AR=0x3136; /* AR = '16' */ -BR=0x454d; /* BR = 'EM' */ -return SCPE_OK; /* return via unmodified rtn */ -} - -/* .EMAP microcode routine, resolves both EMA/non-EMA calls - * Call: - * OCT 105257B - * DEF RTN error return (rtn), good return is rtn+1 - * DEF ARRAY[,I] array base (abase) - * DEF TABLE[,I] array declaration (dtbl) - * DEF A(N)[,I] actual subscripts (atbl) - * DEF A(N-1)[,I] - * ... - * DEF A(2)[,I] - * DEF A(1)[,I] - * RTN EQU * error return A="15", B="EM" - * RTN+1 EQU *+1 good return B=logical address - * - * TABLE DEC # # dimensions - * DEC -L(N) - * DEC D(N-1) - * DEC -L(N-1) lower bound (n-1)st dim - * DEC D(N-2) (n-2)st dim - * ... - * DEC D(1) 1st dim - * DEC -L(1) lower bound 1st dim - * DEC # # words/element - * OFFSET 1 EMA Low - * OFFSET 2 EMA High - */ -static t_stat cpu_ema_emap(uint32* rtn,uint32 abase,uint32 dtbl,uint32 atbl,t_bool debug) -{ -uint32 xidex, eqt, idext0, idext1; -int32 sub, act, low, ndim, sz; -uint32 offs, pgoff, emasz, phys, msgn, mseg, sum, MA, pg0, pg1; - -xidex = ReadIO(idx,UMAP); /* read ID Extension */ -if (xidex) { /* is EMA declared? */ - idext1 = ReadWA(xidex+1); /* get word 1 of idext */ - mseg = (idext1 >> 1) & MSEGMASK; /* get logical start MSEG */ - if (abase >= mseg) { /* EMA reference? */ - if (!cpu_ema_resolve(dtbl,atbl,&sum)) /* calculate subscript */ - goto em15; - offs = sum & 01777; /* address offset within page */ - pgoff = sum >> 10; /* ema offset in pages */ - if (pgoff > 1023) goto em15; /* overflow? */ - eqt = ReadIO(xeqt,UMAP); - emasz = ReadWA(eqt+28) & 01777; /* EMA size in pages */ - phys = idext1 & 01777; /* physical start pg of EMA */ - if (pgoff > emasz) goto em15; /* outside EMA range? */ - - msgn = mseg >> 10; /* get # of 1st MSEG reg */ - phys += pgoff; - - pg0 = dms_rmap(UMAP+0); /* read base page map# */ - pg1 = dms_rmap(UMAP+1); /* save map# 1 */ - dms_wmap(UMAP+1,pg0); /* map #0 into reg #1 */ - - WriteIO(umaps+msgn, phys, UMAP); /* store 1st mapped pg in user map */ - dms_wmap(UMAP+msgn, phys); /* and set the map register */ - phys = (pgoff+1)==emasz ? 0140000 : phys+1; /* protect 2nd map if end of EMA */ - WriteIO(umaps+msgn+1, phys, UMAP); /* store 2nd mapped pg in user map */ - dms_wmap(UMAP+msgn+1, phys); /* and set the map register */ - - dms_wmap(UMAP+1,pg1); /* restore map #1 */ - - idext0 = ReadWA(xidex+0) | 0100000; /* set NS flag in id extension */ - WriteIO(xidex+0, idext0, SMAP); /* save back value */ - AR = 0; /* was successful */ - BR = (uint16) (mseg + offs); /* calculate log address */ - (*rtn)++; /* return via good exit */ - return SCPE_OK; - } -} /* not EMA reference */ -ndim = ReadW(dtbl++); -if (ndim<0) goto em15; /* negative ´dimensions */ -sum = 0; /* accu for index calc */ -while (ndim > 0) { - MA = ReadW(atbl++); /* get address of A(N) */ - resolve (MA, &MA, 0); - act = ReadW(MA); /* A(N) */ - low = ReadW(dtbl++); /* -L(N) */ - sub = SEXT(act) + SEXT(low); /* subscript */ - if (sub & 0xffff8000) goto em15; /* overflow? */ - sum += sub; /* accumulate */ - sz = ReadW(dtbl++); - sz = SEXT(sz); - if (sz < 0) goto em15; - sum *= sz; /* and multiply with sz of dimension */ - if (sum & 0xffff8000) goto em15; /* overflow? */ - ndim--; -} -BR = (uint16) (abase + sum); /* add displacement */ -(*rtn)++; /* return via good exit */ -return SCPE_OK; - -em15: /* error condition */ - AR=0x3135; /* AR = '15' */ - BR=0x454d; /* BR = 'EM' */ - return SCPE_OK; /* return via unmodified e->rtn */ -} - -t_stat cpu_rte_ema (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -OP_PAT pattern; -uint32 entry, rtn; -t_bool debug = DEBUG_PRI (cpu_dev, DEB_EMA); - -entry = IR & 017; /* mask to entry point */ -pattern = op_ema[entry]; /* get operand pattern */ - -if (pattern != OP_N) { - reason = cpu_ops (pattern, op, intrq); /* get instruction operands */ - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -if (debug) { /* debugging? */ - fprintf (sim_deb, ">>CPU EMA: PC = %06o, IR = %06o (", err_PC,IR); /* print preamble and IR */ - fprint_sym (sim_deb, err_PC, (t_value *) &IR, /* print instruction mnemonic */ - NULL, SWMASK('M')); - fputc (')', sim_deb); - - fprint_ops (pattern, op); /* print operands */ - fputc ('\n', sim_deb); /* terminate line */ - } - -switch (entry) { /* decode IR<3:0> */ - case 000: /* .EMIO 105240 (OP_A) */ - rtn = op[0].word; - reason = cpu_ema_emio(&rtn, op[1].word, - op[2].word, PC, debug); /* handle the EMIO instruction */ - PC = rtn; - if (debug) - fprintf (sim_deb, ">>CPU EMA: return .EMIO: AR = %06o, BR = %06o, rtn=%s\n", - AR, BR, PC==op[0].word?"error":"good"); - break; - - case 001: /* .MMAP 105241 (OP_AKK) */ - reason = cpu_ema_mmap(op[1].word, - op[2].word, debug); /* handle the MMAP instruction */ - if (debug) - fprintf (sim_deb, ">>CPU EMA: return .MMAP: AR = %06o\n",AR); - break; - - case 002: /* [test] 105242 (OP_N) */ - /* effectively, this code just returns without error: - * real microcode will set S register to 102077B when in single step mode */ - if (sim_step==1) { - if (debug) - fprintf(sim_deb, ">>CPU EMA: EMA option 92067 correctly installed: S=102077\n"); - SR = 0102077; - } - break; - - case 017: /* .EMAP 105247 (OP_A) */ - rtn = op[0].word; /* error return */ - reason = cpu_ema_emap(&rtn, op[1].word, - op[2].word, PC, debug); /* handle the EMAP instruction */ - PC = rtn; - if (debug) { - fprintf (sim_deb, ">>CPU EMA: return .EMAP: AR = %06o, BR = %06o, rtn=%s\n", - AR, BR, PC==op[0].word?"error":"good"); - } - break; - - default: /* others undefined */ - reason = stop_inst; - } - -return reason; -} +/* hp2100_cpu5.c: HP 1000 RTE-6/VM VMA and RTE-IV EMA instructions + + Copyright (c) 2007-2008, Holger Veit + Copyright (c) 2006-2016, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. + + CPU5 RTE-6/VM and RTE-IV firmware option instructions + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 24-Dec-14 JDB Added casts for explicit downward conversions + 17-Dec-12 JDB Fixed cpu_vma_mapte to return FALSE if not a VMA program + 09-May-12 JDB Separated assignments from conditional expressions + 23-Mar-12 JDB Added sign extension for dim count in "cpu_ema_resolve" + 28-Dec-11 JDB Eliminated unused variable in "cpu_ema_vset" + 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h + 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) + 30-Jul-08 JDB Redefined ABORT to pass address, moved def to hp2100_cpu.h + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 01-May-08 HV Fixed mapping bug in "cpu_ema_emap" + 21-Apr-08 JDB Added EMA support from Holger + 25-Nov-07 JDB Added TF fix from Holger + 07-Nov-07 HV VMACK diagnostic tests 1...32 passed + 19-Oct-07 JDB Corrected $LOC operand profile to OP_CCCACC + 03-Oct-07 HV Moved RTE-6/VM instrs from hp2100_cpu0.c + 26-Sep-06 JDB Created + + Primary references: + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - Macro/1000 Reference Manual (92059-90001, Dec-1992) + + Additional references are listed with the associated firmware + implementations, as are the HP option model numbers pertaining to the + applicable CPUs. +*/ + +#include +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + + +/* RTE-6/VM Virtual Memory Area Instructions + + RTE-6/VM (product number 92084A) introduced Virtual Memory Area (VMA) + instructions -- a superset of the RTE-IV EMA instructions. Different + microcode was supplied with the operating system that replaced the microcode + used with RTE-IV. Microcode was limited to the E/F-Series, and the M-Series + used software equivalents. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A N/A 92084A 92084A + + The routines are mapped to instruction codes as follows: + + Instr. 1000-E/F Description + ------ -------- ---------------------------------------------- + .PMAP 105240 Map VMA page into map register + $LOC 105241 Load on call + [test] 105242 [self test] + .SWP 105243 [Swap A and B registers] + .STAS 105244 [STA B; LDA SP] + .LDAS 105245 [LDA SP] + .MYAD 105246 [NOP in microcode] + .UMPY 105247 [Unsigned multiply and add] + + .IMAP 105250 Integer element resolve address and map + .IMAR 105251 Integer element resolve address + .JMAP 105252 Double integer element resolve address and map + .JMAR 105253 Double integer element resolve address + .LPXR 105254 Map pointer in P+1 plus offset in P+2 + .LPX 105255 Map pointer in A/B plus offset in P+1 + .LBPR 105256 Map pointer in P+1 + .LBP 105257 Map pointer in A/B registers + + Implementation notes: + + 1. The opcodes 105243-247 are undocumented and do not appear to be used in + any HP software. + + 2. The opcode list in the CE Handbook incorrectly shows 105246 as ".MYAD - + multiply 2 signed integers." The microcode listing shows that this + instruction was deleted, and the opcode is now a NOP. + + 3. RTE-IV EMA and RTE-6 VMA instructions shared the same address space, so a + given machine could run one or the other, but not both. + + Additional references: + - RTE-6/VM VMA/EMA Microcode Source (92084-18828, revision 3). + - RTE-6/VM Technical Specifications (92084-90015, Apr-1983). + - M/E/F-Series Computer Systems CE Handbook (5950-3767, Jul-1984). +*/ + +static const OP_PAT op_vma[16] = { + OP_N, OP_CCCACC, OP_N, OP_N, /* .PMAP $LOC [test] .SWAP */ + OP_N, OP_N, OP_N, OP_K, /* .STAS .LDAS .MYAD .UMPY */ + OP_A, OP_A, OP_A, OP_A, /* .IMAP .IMAR .JMAP .JMAR */ + OP_AA, OP_A, OP_A, OP_N /* .LPXR .LPX .LBPR .LBP */ + }; + +/* some addresses in page0 of RTE-6/VM */ +static const uint32 idx = 0001645; +static const uint32 xmata = 0001646; +static const uint32 xi = 0001647; +static const uint32 xeqt = 0001717; +static const uint32 vswp = 0001776; +static const uint32 umaps = 0003740; +static const uint32 page30 = 0074000; +static const uint32 page31 = 0076000; +static const uint32 ptemiss = 0176000; + +/* frequent constants in paging */ +#define SUITMASK 0176000 +#define NILPAGE 0176000 +#define PAGEIDX 0001777 +#define MSEGMASK 0076000 +#define RWPROT 0141777 + + +/* microcode version of resolve(): allows a much higher # of indirection levels. Used for + instance for LBP microcode diagnostics which will check > 100 levels. + */ +#define VMA_INDMAX 200 + +static t_stat vma_resolve (uint32 MA, uint32 *addr, t_bool debug) +{ +uint32 i; +uint32 faultma = MA; + +for (i = 0; (i < VMA_INDMAX) && (MA & I_IA); i++) { /* resolve multilevel */ + MA = ReadW (MA & VAMASK); /* follow address chain */ + } + +if (MA & I_IA) { + if (debug) + fprintf(sim_deb,">>CPU VMA: vma_resolve indirect loop addr=%06o\n",faultma); + return STOP_IND; /* indirect loop */ + } + +*addr = MA; +return SCPE_OK; +} + +/* $LOC + ASSEMBLER CALLING SEQUENCE: + + $MTHK NOP RETURN ADDRESS OF CALL (REDONE AFTER THIS ROUTINE) + JSB $LOC + .DTAB OCT LGPG# LOGICAL PAGE # AT WHICH THE NODE TO + * BE MAPPED IN BELONGS (0-31) + OCT RELPG RELATIVE PAGE OFFSET FROM BEGINING + * OF PARTITION OF WHERE THAT NODE RESIDES. + * (0 - 1023) + OCT RELBP RELATIVE PAGE OFFSET FROM BEGINING OF + * PARTITION OF WHERE BASE PAGE RESIDES + * (0 - 1023) + CNODE DEF .CNOD THIS IS THE ADDRESS OF CURRENT PATH # WORD + .ORD OCT XXXXX THIS NODE'S LEAF # (IE PATH #) + .NOD# OCT XXXXX THIS NODE'S ORDINAL # +*/ + +static t_stat cpu_vma_loc(OPS op,uint32 intrq,t_bool debug) +{ +uint32 eqt,mls,pnod,lstpg,fstpg,rotsz,lgpg,relpg,relbp,matloc,ptnpg,physpg,cnt,pgs,umapr; + +eqt = ReadIO(xeqt,UMAP); /* get ID segment */ +mls = ReadIO(eqt+33,SMAP); /* get word33 of alternate map */ +if ((mls & 0x8000) == 0) { /* this is not an MLS prog! */ + PR = err_PC; + if (debug) + fprintf(sim_deb,">>CPU VMA: cpu_vma_loc at P=%06o: not an MLS program\n", PR); + if (mp_control) MP_ABORT (eqt+33); /* allow an MP abort */ + return STOP_HALT; /* FATAL error! */ + } + +pnod = mls & 01777; /* get #pages of mem res nodes */ +if (pnod == 0) { /* no pages? FATAL! */ + PR = err_PC; + if (debug) + fprintf(sim_deb,">>CPU VMA: cpu_vma_loc at P=%06o: no mem resident pages\n", PR); + if (mp_control) MP_ABORT (eqt+33); /* allow an MP abort */ + return STOP_HALT; + } + +lstpg = (ReadIO(eqt+29,SMAP) >> 10) - 1; /* last page# of code */ +fstpg = ReadIO(eqt+23,SMAP) >> 10; /* index to 1st addr + mem nodes */ +rotsz = fstpg - (ReadIO(eqt+22,SMAP) >> 10); /* #pages in root */ +lgpg = op[0].word; + +/* lets do some consistency checks, CPU halt if they fail */ +if (lstpg < lgpg || lgpg < fstpg) { /* assert LSTPG >= LGPG# >= FSTPG */ + PR = err_PC; + if (debug) + fprintf(sim_deb, + ">>CPU VMA: $LOC at P=%06o: failed check LSTPG >= LGPG# >= FSTPG\n",PR); + if (mp_control) MP_ABORT (eqt+22); /* allow an MP abort */ + return STOP_HALT; + } + +relpg = op[1].word; +if (pnod < relpg || relpg < (rotsz+1)) { /* assert #PNOD >= RELPG >= ROTSZ+1 */ + PR = err_PC; + if (debug) + fprintf(sim_deb, + ">>CPU VMA: $LOC at %06o: failed check #PNOD >= RELPG >= ROTSZ+1\n",PR); + if (mp_control) MP_ABORT (eqt+22); /* allow an MP abort */ + return STOP_HALT; + } + +relbp = op[2].word; +if (relbp != 0) /* assert RELBP == 0 OR */ + if (pnod < relbp || relbp < (rotsz+1)) { /* #PNOD >= RELBP >= ROTSZ+1 */ + PR = err_PC; + if (debug) + fprintf(sim_deb, + ">>CPU VMA: $LOC at P=%06o: failed check: #PNOD >= RELBP >= ROTSZ+1\n",PR); + if (mp_control) MP_ABORT (eqt+22); /* allow an MP abort */ + return STOP_HALT; + } + +cnt = lstpg - lgpg + 1; /* #pages to map */ +pgs = pnod - relpg + 1; /* #pages from start node to end of code */ +if (pgs < cnt) cnt = pgs; /* ensure minimum, so not to map into EMA */ + +matloc = ReadIO(xmata,UMAP); /* get MAT $LOC address */ +ptnpg = ReadIO(matloc+3,SMAP) & 01777; /* index to start phys pg */ +physpg = ptnpg + relpg; /* phys pg # of node */ +umapr = 32 + lgpg; /* map register to start */ + +/* do an XMS with AR=umapr,BR=physpg,XR=cnt */ +if (debug) + fprintf(sim_deb, + ">>CPU VMA: $LOC map %d pgs from phys=%06o to mapr=%d\n", + cnt,physpg,umapr); +while (cnt != 0) { + dms_wmap (umapr, physpg); /* map pages of new overlay segment */ + cnt = (cnt - 1) & DMASK; + umapr = (umapr + 1) & DMASK; + physpg = (physpg + 1) & DMASK; + } + +dms_wmap(32,relbp+ptnpg); /* map base page again */ +WriteW(op[3].word,op[4].word); /* path# we are going to */ + +PR = (PR - 8) & DMASK; /* adjust P to return address */ + /* word before the $LOC microinstr. */ +PR = (ReadW(PR) - 1) & DMASK; /* but the call has to be rerun, */ + /* so must skip back to the original call */ + /* which will now lead to the real routine */ +if (debug) + fprintf(sim_deb,">>CPU VMA: $LOC done: path#=%06o, P=%06o\n",op[4].word,PR); +return SCPE_OK; +} + +/* map pte into last page + return FALSE if page fault, nil flag in PTE or suit mismatch + return TRUE if suit match, physpg = physical page + or page=0 -> last+1 page +*/ +static t_bool cpu_vma_ptevl(uint32 pagid,uint32* physpg) +{ +uint32 suit; +uint32 pteidx = pagid & 0001777; /* build index */ +uint32 reqst = pagid & SUITMASK; /* required suit */ +uint32 pteval = ReadW(page31 | pteidx); /* get PTE entry */ +*physpg = pteval & 0001777; /* store physical page number */ +suit = pteval & SUITMASK; /* suit number seen */ +if (pteval == NILPAGE) return FALSE; /* NIL value in PTE */ +return suit == reqst || !*physpg; /* good page or last+1 */ +} + +/* handle page fault */ +static t_stat cpu_vma_fault(uint32 x,uint32 y,int32 mapr, + uint32 ptepg,uint32 ptr,uint32 faultpc, t_bool debug) +{ +uint32 pre = ReadIO(xi,UMAP); /* get program preamble */ +uint32 ema = ReadIO(pre+2,UMAP); /* get address of $EMA$/$VMA$ */ +WriteIO(ema,faultpc,UMAP); /* write addr of fault instr */ +XR = x; /* X = faulting page */ +YR = y; /* Y = faulting address for page */ + +if (mapr>0) + dms_wmap(mapr+UMAP,ptepg); /* map PTE into specified user dmsmap */ + +/* do a safety check: first instr of $EMA$/$VMA$ must be a DST instr */ +if (ReadIO(ema+1,UMAP) != 0104400) { + if (debug) + fprintf(sim_deb, ">>CPU VMA: pg fault: no EMA/VMA user code present\n"); + if (mp_control) MP_ABORT (ema+1); /* allow an MP abort */ + return STOP_HALT; /* FATAL: no EMA/VMA! */ + } + +PR = (ema+1) & VAMASK; /* restart $EMA$ user code, */ + /* will return to fault instruction */ + +AR = (ptr >> 16) & DMASK; /* restore A, B */ +BR = ptr & DMASK; +E = 0; /* enforce E = 0 */ +if (debug) + fprintf(sim_deb, + ">>CPU VMA: Call pg fault OS exit, AR=%06o BR=%06o P=%06o\n", + AR, BR, PR); +return SCPE_OK; +} + +/* map in PTE into last page, return false, if page fault */ +static t_bool cpu_vma_mapte(uint32* ptepg) +{ +uint32 idext,idext2; +uint32 dispatch = ReadIO(vswp,UMAP) & 01777; /* get fresh dispatch flag */ +t_bool swapflag = TRUE; + +if (dispatch == 0) { /* not yet set */ + idext = ReadIO(idx,UMAP); /* go into ID segment extent */ + if (idext == 0) { /* is ema/vma program? */ + swapflag = FALSE; /* no, so mark PTE as invalid */ + *ptepg = (uint32) -1; /* and return an invalid page number */ + } + + else { /* is an EMA/VMA program */ + dispatch = ReadWA(idext+1) & 01777; /* get 1st ema page: new vswp */ + WriteIO(vswp,dispatch,UMAP); /* move into $VSWP */ + idext2 = ReadWA(idext+2); /* get swap bit */ + swapflag = (idext2 & 020000) != 0; /* bit 13 = swap bit */ + } + } + +if (dispatch) { /* some page is defined */ + dms_wmap(31 + UMAP,dispatch); /* map $VSWP to register 31 */ + *ptepg = dispatch; /* return PTEPG# for later */ + } + +return swapflag; /* true for valid PTE */ +} + +/* .LBP + ASSEMBLER CALLING SEQUENCE: + + DLD PONTR TRANSLATE 32 BIT POINTER TO 15 + JSB .LBP BIT POINTER. + + + 32 bit pointer: + ----------AR------------ -----BR----- + 15 14....10 9....4 3...0 15.10 9....0 + L<----------------------------------- L=1 local reference bit + XXXXXXXX<------------------------- 5 bit unused + PPPPPP PPPPP PPPPP<------ 16 bit PAGEID + SSSSSS<------------------ SUIT# within PAGEID + PPPPP PPPPP<------ 10 bit PAGEID index into PTE + OOOOOO 10 bit OFFSET +*/ + +static t_stat cpu_vma_lbp(uint32 ptr,uint32 aoffset,uint32 faultpc,uint32 intrq,t_bool debug) +{ +uint32 pagid,offset,ptrl,pgidx,ptepg; +uint16 p30,p31,suit; +t_stat reason = SCPE_OK; +uint32 faultab = ptr; /* remember A,B for page fault */ +ptr += aoffset; /* add the offset e.g. for .LPX */ + +if (debug) + fprintf(sim_deb,">>CPU VMA: cpu_vma_lbp: ptr=%o/%o\n", + (ptr>>16) & DMASK,ptr & DMASK); + +O = 0; /* clear overflow */ +if (ptr & 0x80000000) { /* is it a local reference? */ + ptrl = ptr & VAMASK; + if (ptr&I_IA) { + reason = vma_resolve (ReadW (ptrl), &ptrl, debug); + if (reason) + return reason; /* yes, resolve indirect ref */ + } + BR = ptrl & VAMASK; /* address is local */ + AR = (ptr >> 16) & DMASK; + if (debug) + fprintf(sim_deb,">>CPU VMA: cpu_vma_lbp: local ref AR=%06o BR=%06o\n",AR,BR); + return SCPE_OK; + } + +pagid = (ptr >> 10) & DMASK; /* extract page id (16 bit idx, incl suit*/ +offset = ptr & 01777; /* and offset */ +suit = pagid & SUITMASK; /* suit of page */ +pgidx = pagid & PAGEIDX; /* index into PTE */ + +if (!cpu_vma_mapte(&ptepg)) /* map in PTE */ + return cpu_vma_fault(65535,ptemiss,-1,ptepg,faultab,faultpc, debug); /* oops, must init PTE */ + +/* ok, we have the PTE mapped to page31 */ +/* the microcode tries to reads two consecutive data pages into page30 and page31 */ + +/* read the 1st page value from PTE */ +p30 = ReadW(page31 | pgidx) ^ suit; +if (!p30) /* matched suit for 1st page */ + return cpu_vma_fault(pagid,page30,30,ptepg,faultab,faultpc,debug); + +/* suit switch situation: 1st page is in last idx of PTE, then following page + * must be in idx 0 of PTE */ +if (pgidx==01777) { /* suit switch situation */ + pgidx = 0; /* select correct idx 0 */ + suit = (uint16) (pagid + 1); /* suit needs increment */ + if (suit==0) { /* is it page 65536? */ + offset += 02000; /* adjust to 2nd page */ + suit = NILPAGE; + pgidx = 01777; + } +} else + pgidx++; /* select next page */ + +p31 = ReadW(page31 | pgidx) ^ suit; +if (!p31) { /* matched suit for 2nd page */ + dms_wmap(31+UMAP,p30); + if (p30 & SUITMASK) + return cpu_vma_fault(pagid,page30,30,ptepg,faultab,faultpc,debug); + if (!(p31 ^ NILPAGE)) /* suit is 63: fault */ + return cpu_vma_fault(pagid+1,page31,31,ptepg,faultab,faultpc,debug); + + offset += 02000; /* adjust offset to last user map because */ + /* the address requested page 76xxx */ + } +else { + dms_wmap(30+UMAP,p30); + if (p30 & SUITMASK) + return cpu_vma_fault(pagid,page30,30,ptepg,faultab,faultpc,debug); + dms_wmap(31+UMAP,p31); + if (p31 & SUITMASK) + return cpu_vma_fault(pagid+1,page31,31,ptepg,faultab,faultpc,debug); + } + +AR = (uint16) pagid; /* return pagid in A */ +BR = (uint16) (page30 + offset); /* mapped address in B */ +if (debug) + fprintf(sim_deb,">>CPU VMA: cpu_vma_lbp: map done AR=%06o BR=%o6o\n",AR,BR); +return SCPE_OK; +} + +/* .PMAP + ASSEMBLER CALLING SEQUENCE: + + LDA UMAPR (MSEG - 31) + LDB PAGID (0-65535) + JSB .PMAP GO MAP IT IN + A-REG = REASON, NOTE 1 + > SEE NOTE 2> + + NOTE 1 : IF BIT 15 OF A-REG SET, THEN ALL NORMAL BRANCHES TO THE + $EMA$/$VMA$ CODE WILL BE CHANGED TO P+1 EXIT. THE A-REG + WILL BE THE REASON THE MAPPING WAS NOT SUCCESSFUL IF BIT 15 + OF THE A-REG WAS NOT SET. + THIS WAS DONE SO THAT A ROUTINE ($VMA$) CAN DO A MAPPING + WITHOUT THE POSSIBILITY OF BEING RE-CURRED. IT IS USED + BY $VMA$ AND PSTVM IN THE PRIVLEDGED MODE. + NOTE 2: E-REG WILL = 1 IF THE LAST+1 PAGE IS REQUESTED AND + MAPPED READ/WRITE PROTECTED ON A GOOD P+2 RETURN. +*/ +static t_stat cpu_vma_pmap(uint32 umapr,uint32 pagid, t_bool debug) +{ +uint32 physpg, ptr, pgpte; +uint32 mapnm = umapr & 0x7fff; /* strip off bit 15 */ + +if (debug) + fprintf(sim_deb, ">>CPU VMA: .PMAP AR=%06o(umapr) BR=%06o(pagid)\n",umapr,pagid); + +if (mapnm > 31) { /* check for invalid map register */ + AR = 80; /* error: corrupt EMA/VMA system */ + if (debug) + fprintf(sim_deb, ">>CPU VMA: .PMAP invalid mapr: AR=80, exit P+1\n"); + return SCPE_OK; /* return exit P+1 */ + } + +ptr = (umapr << 16) | (pagid & DMASK); /* build the ptr argument for vma_fault */ +if (!cpu_vma_mapte(&pgpte)) { /* map the PTE */ + if (umapr & 0x8000) { + XR = 65535; + YR = ptemiss; + if (debug) + fprintf(sim_deb, + ">>CPU VMA: .PMAP pg fault&bit15: XR=%06o YR=%06o, exit P+1\n", + XR, YR); + return SCPE_OK; /* use P+1 error exit */ + } + return cpu_vma_fault(65535,ptemiss,-1,pgpte,ptr,PR-1,debug); /* oops: fix PTE */ + } + +/* PTE is successfully mapped to page31 and dmsmap[63] */ +if (!cpu_vma_ptevl(pagid,&physpg)) { + if (umapr & 0x8000) { + XR = pagid; + YR = page31; + if (debug) + fprintf(sim_deb, + ">>CPU VMA: .PMAP pg map&bit15: XR=%06o YR=%06o, exit P+1\n", + XR, YR); + return SCPE_OK; /* use P+1 error exit*/ + } + return cpu_vma_fault(pagid,page31,31,pgpte,ptr,PR-1,debug); /* page not present */ + } + +E = 1; +if (physpg == 0) /* last+1 page ? */ + physpg = RWPROT; /* yes, use page 1023 RW/Protected */ +else E = 0; /* normal page to map */ + +dms_wmap(mapnm+UMAP,physpg); /* map page to user page reg */ +if (mapnm != 31) /* unless already unmapped, */ + dms_wmap(31+UMAP,RWPROT); /* unmap PTE */ + +AR = (umapr + 1) & DMASK; /* increment mapr for next call */ +BR = (pagid + 1) & DMASK; /* increment pagid for next call */ +O = 0; /* clear overflow */ +PR = (PR + 1) & VAMASK; /* normal P+2 return */ +if (debug) + fprintf(sim_deb,">>CPU VMA: .PMAP map done: AR=%06o BR=%o6o exit P+2\n",AR,BR); +return SCPE_OK; +} + +/* array calc helper for .imar, .jmar, .imap, .jmap + ij=in_s: 16 bit descriptors + ij=in_d: 32 bit descriptors + + This helper expects mainly the following arguments: + dtbl: pointer to an array descriptor table + atbl: pointer to the table of actual subscripts + + where subscript table is the following: + atbl-> DEF last_subscript,I (point to single or double integer) + ... + DEF first subscript,I (point to single or double integer) + + where Descriptor_table is the following table: + dtbl-> DEC #dimensions + DEC/DIN next-to-last dimension (single or double integer) + ... + DEC/DIN first dimension (single or double integer) + DEC elementsize in words + DEC high,low offset from start of EMA to element(0,0...0) + + Note that subscripts are counting from 0 +*/ +static t_stat cpu_vma_ijmar(OPSIZE ij,uint32 dtbl,uint32 atbl,uint32* dimret, + uint32 intrq,t_bool debug) +{ +t_stat reason = SCPE_OK; +uint32 ndim,MA,i,ws; +int32 accu,ax,dx; +OP din; +int opsz = ij==in_d ? 2 : 1; + +ndim = ReadW(dtbl++); /* get #dimensions itself */ +if (debug) { + fprintf(sim_deb, ">>CPU VMA array calc #dim=%d, size=%d\n",ndim,opsz); + fprintf(sim_deb, ">>CPU VMA: array actual subscripts ("); + for (i=0; i0) fputc(',',sim_deb); + fprintf(sim_deb,"%d",ij==in_d?INT32(din.dword) : INT16(din.word)); + } + + fprintf(sim_deb,")\n>>CPU VMA: array descriptor table ("); + if (ndim) { + for (i=0; i0) fputc(',',sim_deb); + fprintf(sim_deb,"%d",ij==in_d?INT32(din.dword) : INT16(din.word)); + } + i = dtbl+1+(ndim-1)*opsz; + ws = ReadW(i-1); + } + else { + i = dtbl; + ws = 1; + } + fprintf(sim_deb,")\n>>CPU VMA: array elemsz=%d base=%o/%o\n", + ws,ReadW(i),ReadW(i+1)); + } + +if (dimret) *dimret = ndim; /* return dimensions */ +if (ndim == 0) { /* no dimensions: */ + AR = ReadW(dtbl++); /* return the array base itself */ + BR = ReadW(dtbl); + if (debug) + fprintf(sim_deb,">>CPU VMA: #dim=0, AR=%06o, BR=%06o\n",AR,BR); + return SCPE_OK; + } + +/* calculate + * (...(An*Dn-1)+An-1)*Dn-2)+An-2....)+A2)*D1)+A1)*#words + Array base + * Depending on ij, Ax and Dx can be 16 or 32 bit + */ +accu = 0; +while (ndim-- > 0) { + MA = ReadW(atbl++); /* get addr of subscript */ + reason = resolve (MA, &MA, intrq); /* and resolve it */ + if (reason) + return reason; + din = ReadOp(MA,ij); /* get actual subscript value */ + ax = ij==in_d ? INT32(din.dword) : INT16(din.word); + accu += ax; /* add to accu */ + + if (ndim==0) ij = in_s; /* #words is single */ + din = ReadOp(dtbl,ij); /* get dimension from descriptor table */ + if (ij==in_d) { + dx = INT32(din.dword); /* either get double or single dimension */ + dtbl += 2; + } else { + dx = INT16(din.word); + dtbl++; + } + accu *= dx; /* multiply */ + } + +din = ReadOp(dtbl,in_d); /* add base address */ +accu += din.dword; + +AR = (accu >> 16) & DMASK; /* transfer to AB */ +BR = accu & DMASK; +if (debug) + fprintf(sim_deb,">>CPU VMA: resulting virt addr=%o (AR=%06o, BR=%06o)\n",accu,AR,BR); +return reason; +} + +/* + * This is the main handler for the RTE6/VMA microcodes */ +t_stat cpu_rte_vma (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +OP_PAT pattern; +uint16 t16; +uint32 entry,t32,ndim; +uint32 dtbl,atbl; /* descriptor table ptr, actual args ptr */ +OP dop0,dop1; +uint32 pcsave = (PR+1) & VAMASK; /* save P to check for redo in imap/jmap */ +t_bool debug = DEBUG_PRI (cpu_dev, DEB_VMA); + +entry = IR & 017; /* mask to entry point */ +pattern = op_vma[entry]; /* get operand pattern */ + +if (pattern != OP_N) { + reason = cpu_ops (pattern, op, intrq); /* get instruction operands */ + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +if (debug) { /* debugging? */ + fprintf (sim_deb, ">>CPU VMA: IR = %06o (", IR); /* print preamble and IR */ + fprint_sym (sim_deb, err_PC, (t_value *) &IR, /* print instruction mnemonic */ + NULL, SWMASK('M')); + fprintf (sim_deb, "), P = %06o, XEQT = %06o", /* print location and program ID */ + err_PC, ReadW (xeqt)); + + fprint_ops (pattern, op); /* print operands */ + fputc ('\n', sim_deb); /* terminate line */ + } + +switch (entry) { /* decode IR<3:0> */ + + case 000: /* .PMAP 105240 (OP_N) */ + reason = cpu_vma_pmap(AR,BR,debug); /* map pages */ + break; + + case 001: /* $LOC 105241 (OP_CCCACC) */ + reason = cpu_vma_loc(op,intrq,debug); /* handle the coroutine switch */ + break; + + case 002: /* [test] 105242 (OP_N) */ + XR = 3; /* refer to src code 92084-18828 rev 3 */ + SR = 0102077; /* HLT 77 instruction */ + YR = 1; /* ROMs correctly installed */ + PR = (PR+1) & VAMASK; /* skip instr if VMA/EMA ROM installed */ + break; + + case 003: /* [swap] 105243 (OP_N) */ + t16 = AR; /* swap A and B registers */ + AR = BR; + BR = t16; + break; + + case 004: /* [---] 105244 (OP_N) */ + reason = stop_inst; /* fragment of dead code */ + break; /* in microrom */ + + case 005: /* [---] 105245 (OP_N) */ + reason = stop_inst; /* fragment of dead code */ + break; /* in microrom */ + + case 006: /* [nop] 105246 (OP_N) */ + break; /* do nothing */ + + case 007: /* [umpy] 105247 (OP_K) */ + t32 = AR * op[0].word; /* get multiplier */ + t32 += BR; /* add B */ + AR = (t32 >> 16) & DMASK; /* move result back to AB */ + BR = t32 & DMASK; + O = 0; /* instr clears OV */ + break; + + case 010: /* .IMAP 105250 (OP_A) */ + dtbl = op[0].word; + atbl = PR; + reason = cpu_vma_ijmar(in_s,dtbl,atbl,&ndim,intrq,debug); /* calc the virt address to AB */ + if (reason) + return reason; + t32 = (AR << 16) | (BR & DMASK); + reason = cpu_vma_lbp(t32,0,PR-2,intrq,debug); + if (reason) + return reason; + if (PR==pcsave) + PR = (PR+ndim) & VAMASK; /* adjust P: skip ndim subscript words */ + break; + + case 011: /* .IMAR 105251 (OP_A) */ + dtbl = ReadW(op[0].word); + atbl = (op[0].word+1) & VAMASK; + reason = cpu_vma_ijmar(in_s,dtbl,atbl,0,intrq,debug); /* calc the virt address to AB */ + break; + + case 012: /* .JMAP 105252 (OP_A) */ + dtbl = op[0].word; + atbl = PR; + reason = cpu_vma_ijmar(in_d,dtbl,atbl,&ndim,intrq,debug); /* calc the virtual address to AB */ + if (reason) + return reason; + t32 = (AR << 16) | (BR & DMASK); + reason = cpu_vma_lbp(t32,0,PR-2,intrq,debug); + if (reason) + return reason; + if (PR==pcsave) + PR = (PR + ndim) & VAMASK; /* adjust P: skip ndim subscript dword ptr */ + break; + + case 013: /* .JMAR 105253 (OP_A) */ + dtbl = ReadW(op[0].word); + atbl = (op[0].word+1) & VAMASK; + reason = cpu_vma_ijmar(in_d,dtbl,atbl,0,intrq,debug); /* calc the virt address to AB */ + break; + + case 014: /* .LPXR 105254 (OP_AA) */ + dop0 = ReadOp(op[0].word,in_d); /* get pointer from arg */ + dop1 = ReadOp(op[1].word,in_d); + t32 = dop0.dword + dop1.dword; /* add offset to it */ + reason = cpu_vma_lbp(t32,0,PR-3,intrq,debug); + break; + + case 015: /* .LPX 105255 (OP_A) */ + t32 = (AR << 16) | (BR & DMASK); /* pointer in AB */ + dop0 = ReadOp(op[0].word,in_d); + reason = cpu_vma_lbp(t32,dop0.dword,PR-2,intrq,debug); + break; + + case 016: /* .LBPR 105256 (OP_A) */ + dop0 = ReadOp(op[0].word,in_d); /* get the pointer */ + reason = cpu_vma_lbp(dop0.dword,0,PR-2,intrq,debug); + break; + + case 017: /* .LBP 105257 (OP_N) */ + t32 = (AR << 16) | (BR & DMASK); + reason = cpu_vma_lbp(t32,0,PR-1,intrq,debug); + break; + } + +return reason; +} + + +/* RTE-IV Extended Memory Area Instructions + + The RTE-IV operating system (HP product number 92067A) introduced the + Extended Memory Area (EMA) instructions. EMA provided a mappable data area + up to one megaword in size. These three instructions accelerated data + accesses to variables stored in EMA partitions. Support was limited to + E/F-Series machines; M-Series machines used software equivalents. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A N/A 92067A 92067A + + The routines are mapped to instruction codes as follows: + + Instr. 1000-E/F Description + ------ -------- ---------------------------------------------- + .EMIO 105240 EMA I/O + MMAP 105241 Map physical to logical memory + [test] 105242 [self test] + .EMAP 105257 Resolve array element address + + Notes: + + 1. RTE-IV EMA and RTE-6 VMA instructions share the same address space, so a + given machine can run one or the other, but not both. + + 2. The EMA diagnostic (92067-16013) reports bogus MMAP failures if it is + not loaded at the start of its partition (e.g., because of a LOADR "LO" + command). The "ICMPS" map comparison check in the diagnostic assumes + that the starting page of the program's partition contains the first + instruction of the program and prints "MMAP ERROR" if it does not. + + Additional references: + - RTE-IVB Programmer's Reference Manual (92068-90004, Dec-1983). + - RTE-IVB Technical Specifications (92068-90013, Jan-1980). +*/ + +static const OP_PAT op_ema[16] = { + OP_AKA, OP_AKK, OP_N, OP_N, /* .EMIO MMAP [test] --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_N, /* --- --- --- --- */ + OP_N, OP_N, OP_N, OP_AAA /* --- --- --- .EMAP */ + }; + +/* calculate the 32 bit EMA subscript for an array */ +static t_bool cpu_ema_resolve(uint32 dtbl,uint32 atbl,uint32* sum) +{ +int32 sub, act, low, sz, ndim; +uint32 MA, base; + +ndim = ReadW(dtbl++); /* # dimensions */ +ndim = SEXT(ndim); /* sign extend */ +if (ndim < 0) return FALSE; /* invalid? */ + +*sum = 0; /* accu for index calc */ +while (ndim > 0) { + MA = ReadW(atbl++); /* get address of A(N) */ + resolve (MA, &MA, 0); + act = ReadW(MA); /* A(N) */ + low = ReadW(dtbl++); /* -L(N) */ + sub = SEXT(act) + SEXT(low); /* subscript */ + if (sub & 0xffff8000) return FALSE; /* overflow? */ + *sum += sub; /* accumulate */ + sz = ReadW(dtbl++); + sz = SEXT(sz); + if (sz < 0) return FALSE; + *sum *= sz; + if (*sum > (512*1024)) return FALSE; /* overflow? */ + ndim--; +} +base = (ReadW(dtbl+1)<<16) | (ReadW(dtbl) & 0xffff); /* base of array in EMA */ +if (base & 0x8000000) return FALSE; +*sum += base; /* calculate address into EMA */ +if (*sum & 0xf8000000) return FALSE; /* overflow? */ +return TRUE; +} + +/* implementation of VIS RTE-IVB EMA support + * .ERES microcode routine, resolves only EMA addresses + * Call: + * .OCT 101474B + * DEF RTN error return (rtn), good return is rtn+1 + * DEF DUMMY dummy argument for compatibility with .EMAP + * DEF TABLE[,I] array declaration (dtbl) + * DEF A(N)[,I] actual subscripts (atbl) + * DEF A(N-1)[,I] + * ... + * DEF A(2)[,I] + * DEF A(1)[,I] + * RTN EQU * error return A="20", B="EM" + * RTN+1 EQU *+1 good return B=logical address + * + * TABLE DEC # # dimensions + * DEC -L(N) + * DEC D(N-1) + * DEC -L(N-1) lower bound (n-1)st dim + * DEC D(N-2) (n-2)st dim + * ... + * DEC D(1) 1st dim + * DEC -L(1) lower bound 1st dim + * DEC # # words/element + * OFFSET 1 EMA Low + * OFFSET 2 EMA High + */ +t_stat cpu_ema_eres(uint32 *rtn,uint32 dtbl,uint32 atbl,t_bool debug) +{ +uint32 sum; +if (cpu_ema_resolve(dtbl,atbl,&sum)) { /* calculate subscript */ + AR = sum & 0xffff; + BR = sum >> 16; + if (!(BR & SIGN)) { /* no overflow? */ + (*rtn)++; /* return via good exit */ + return SCPE_OK; + } +} +AR = 0x3230; /* error condition: */ +BR = 0x454d; /* AR = '20', BR = 'EM' */ +return SCPE_OK; /* return via unmodified rtn */ +} + +/* implementation of VIS RTE-IVB EMA support + * .ESEG microcode routine + * Call: + * LDA FIRST first map to set + * LDB N # of maps to set + * .OCT 101475B/105475B + * DEF RTN ptr to return + * DEF TABLE map table + * RTN EQU * error return A="21", B="EM" + * RTN+1 EQU *+1 good return B=logical address + * + * load maps FIRST to FIRST+N from TABLE, with FIRST = FIRST + LOG_START MSEG + * update map table in base page. Set LOG_START MSEG=0 if opcode==105475 + */ +t_stat cpu_ema_eseg(uint32* rtn, uint32 IR, uint32 tbl, t_bool debug) +{ +uint32 xidex,eqt,idext0,idext1; +uint32 msegsz,phys,msegn,last,emasz,pg0,pg1,pg,i,lp; + +if ((BR & SIGN) || BR==0) goto em21; /* #maps not positive? */ +xidex = ReadIO(idx,UMAP); /* read ID extension */ +if (xidex==0) goto em21; +idext0 = ReadWA(xidex+0); /* get 1st word idext */ +msegsz = idext0 & 037; /* S7 MSEG size */ +WriteIO(xidex+0, idext0 | 0100000, SMAP); /* enforce nonstd MSEG */ +idext1 = ReadWA(xidex+1); /* get 2nd word idext */ +phys = idext1 & 01777; /* S5 phys start of EMA */ +msegn = (idext1 >> 11) & 037; /* S9 get logical start MSEG# */ +if (IR & 04000) { /* opcode == 105475? (.VPRG) */ + msegn = 0; /* log start = 0 */ + msegsz = 32; /* size = full range */ +} +last = AR-1 + BR; /* last page */ +if (last > msegsz) goto em21; /* too many? error */ +eqt = ReadIO(xeqt,UMAP); +emasz = (ReadWA(eqt+28) & 01777) - 1; /* S6 EMA size in pages */ + +/* locations 1740...1777 of user base page contain the map entries we need. + * They are normally hidden by BP fence, therefore they have to be accessed by + * another fence-less map register. uCode uses #1 temporarily */ +pg0 = dms_rmap(UMAP+0); /* read map #0 */ +pg1 = dms_rmap(UMAP+1); /* save map #1 */ +dms_wmap(UMAP+1,pg0); /* copy #0 into reg #1 */ +lp = AR + msegn; /* first */ +for (i=0; i emasz) pg |= 0140000; /* write protect if outside */ + pg += phys; /* adjust into EMA page range */ + WriteIO(umaps+lp+i, pg, UMAP); /* copy pg to user map */ +/* printf("MAP val %oB to reg %d (addr=%oB)\n",pg,lp+i,umaps+lp+i); */ + dms_wmap(UMAP+lp+i, pg); /* set DMS reg */ +} +dms_wmap(UMAP+1,pg1); /* restore map #1 */ +O = 0; /* clear overflow */ +(*rtn)++; /* return via good exit */ +return SCPE_OK; + +em21: +AR = 0x3231; /* error condition: */ +BR = 0x454d; /* AR = '21', BR = 'EM' */ +return SCPE_OK; /* return via unmodified rtn */ +} + +/* implementation of VIS RTE-IVB EMA support + * .VSET microcode routine + * Call: + * .OCT 101476B + * DEF RTN return address + * DEF VIN input vector + * DEF VOUT output vector + * DEF MAPS + * OCT #SCALARS + * OCT #VECTORS + * OCT K 1024/(#words/element) + * RTN EQU * error return (B,A) = "VI22" + * RTN+1 EQU *+1 hard return, A = K/IMAX + * RTN+2 EQU *+2 easy return, A = 0, B = 2* #VCTRS + */ +t_stat cpu_ema_vset(uint32* rtn, OPS op, t_bool debug) +{ +uint32 vin = op[0].word; /* S1 */ +uint32 vout = op[1].word; /* S2 */ +uint32 maps = op[2].word; /* S3 */ +uint32 scalars = op[3].word; /* S4 */ +uint32 vectors = op[4].word; /* S5 */ +uint32 k = op[5].word; /* S6 */ +uint32 imax = 0; /* imax S11*/ +uint32 xidex, idext1, mseg, addr, i, MA; +t_bool negflag = FALSE; + +for (i=0; i> 1) & MSEGMASK; /* S9 get logical start MSEG */ + +for (i=0; i> 10) & 0xffff; /* get page */ + WriteW(maps++, addr); /* save page# */ + WriteW(maps++, addr+1); /* save next page# as well */ + MA = ReadW(vin++); /* get index into Y */ + resolve(MA, &MA, 0); + YR = ReadW(MA); /* get index value */ + WriteW(vout++, MA); /* copy address of index */ + if (YR & SIGN) { /* index is negative */ + negflag = TRUE; /* mark a negative index (HARD) */ + YR = (~YR + 1) & DMASK; /* make index positive */ + } + if (imax < YR) imax = YR; /* set maximum index */ + mseg += 04000; /* incr mseg address by 2 more pages */ +} +MA = ReadW(vin); /* get N index into Y */ +resolve(MA, &MA, 0); +YR = ReadW(MA); +WriteW(vout++, MA); vin++; /* copy address of N */ + +if (imax==0) goto easy; /* easy case */ +AR = (uint16) (k / imax); AR++; /* calculate K/IMAX */ +if (negflag) goto hard; /* had a negative index? */ +if (YR > AR) goto hard; + +easy: +(*rtn)++; /* return via exit 2 */ +AR = 0; + +hard: +(*rtn)++; /* return via exit 1 */ +BR = 2 * op[4].word; /* B = 2* vectors */ +return SCPE_OK; + +vi22: /* error condition */ + AR=0x3232; /* AR = '22' */ + BR=0x5649; /* BR = 'VI' */ + return SCPE_OK; /* return via unmodified e->rtn */ +} + +typedef struct ema4 { + uint32 mseg; /* logical start of MSEG */ + uint32 msegsz; /* size of std mseg in pgs */ + uint32 pgoff; /* pg # in EMA containing element */ + uint32 offs; /* offset into page of element */ + uint32 msoff; /* total offset to element in MSEG */ + uint32 emasz; /* size of ema in pgs */ + uint32 msegno; /* # of std mseg */ + uint32 ipgs; /* # of pgs to start of MSEG */ + uint32 npgs; /* # of pgs needed */ + uint32 spmseg; /* first phys pg of MSEG */ +} EMA4; + +static t_bool cpu_ema_emas(uint32 dtbl,uint32 atbl,EMA4* e) +{ +uint32 xidex, eqt; +uint32 sum, msegsz,pgoff,offs,emasz,msegno,msoff,ipgs; + +if (!cpu_ema_resolve(dtbl,atbl,&sum)) return FALSE; /* calculate 32 bit index */ + +xidex = ReadIO(idx,UMAP); /* read ID extension */ +msegsz = ReadWA(xidex+0) & 037; /* S5 # pgs for std MSEG */ +pgoff = sum >> 10; /* S2 page containing element */ +offs = sum & 01777; /* S6 offset in page to element */ +if (pgoff > 1023) return FALSE; /* overflow? */ +eqt = ReadIO(xeqt,UMAP); +emasz = ReadWA(eqt+28) & 01777; /* S EMA size in pages */ +if (pgoff > emasz) return FALSE; /* outside EMA? */ +msegno = pgoff / msegsz; /* S4 # of MSEG */ +msoff = pgoff % msegsz; /* offset within MSEG in pgs */ +ipgs = pgoff - msoff; /* S7 # pgs to start of MSEG */ +msoff = msoff << 10; /* offset within MSEG in words */ +msoff += offs; /* S1 offset to element in words */ + +e->msegsz = msegsz; /* return calculated data */ +e->pgoff = pgoff; +e->offs = offs; +e->emasz = emasz; +e->msegno = msegno; +e->ipgs = ipgs; +e->msoff = msoff; +return TRUE; +} + +static t_bool cpu_ema_mmap01(EMA4* e) +{ +uint32 xidex,idext0, pg, pg0, pg1, i; + +uint32 base = e->mseg >> 10; /* get the # of first MSEG DMS reg */ +xidex = ReadIO(idx,UMAP); /* get ID extension */ +idext0 = ReadWA(xidex+1); + +if (e->npgs==0) return FALSE; /* no pages to map? */ +if ((e->npgs+1+e->ipgs) <= e->emasz) e->npgs++; /* actually map npgs+1 pgs */ + +/* locations 1740...1777 of user base page contain the map entries we need. + * They are normally hidden by BP fence, therefore they have to be accessed by + * another fence-less map register. uCode uses #1, macro code uses $DVCT (==2) + */ +pg0 = dms_rmap(UMAP+0); /* read base page map# */ +pg1 = dms_rmap(UMAP+1); /* save map# 1 */ +dms_wmap(UMAP+1,pg0); /* map #0 into reg #1 */ +for (i=0; (base+i)<32; i++) { + pg = inpgs ? e->spmseg : 0140000; /* write protect if outside */ + WriteIO(umaps+base+i, pg, UMAP); /* copy pg to user map */ +/* printf("MAP val %d to reg %d (addr=%o)\n",pg,base+i,umaps+base+i); */ + dms_wmap(UMAP+base+i, pg); /* set DMS reg */ + e->spmseg++; +} +dms_wmap(UMAP+1,pg1); /* restore map #1 */ + +xidex = ReadIO(idx,UMAP); /* get ID extension */ +idext0 = ReadWA(xidex+0); +if (e->msegno == 0xffff) /* non std mseg */ + idext0 |= 0x8000; /* set nonstd marker */ +else + idext0 = (idext0 & 037) | (e->msegno<<5); /* set new current mseg# */ +WriteIO(xidex, idext0, SMAP); /* save back value */ +AR = 0; /* was successful */ +return TRUE; +} + +static t_bool cpu_ema_mmap02(EMA4* e) +{ +uint32 xidex, eqt, idext1; +uint32 mseg,phys,spmseg,emasz,msegsz,msegno; + +xidex = ReadIO(idx,UMAP); /* get ID extension */ +msegsz = ReadWA(xidex+0) & 037; /* P size of std MSEG */ +idext1 = ReadWA(xidex+1); +mseg = (idext1 >> 1) & MSEGMASK; /* S9 get logical start MSEG */ +phys = idext1 & 01777; /* S phys start of EMA */ +spmseg = phys + e->ipgs; /* S7 phys pg# of MSEG */ +msegno = e->ipgs / msegsz; +if ((e->ipgs % msegsz) != 0) /* non std MSEG? */ + msegno = 0xffff; /* S4 yes, set marker */ +if (e->npgs > msegsz) return FALSE; /* map more pages than MSEG sz? */ +eqt = ReadIO(xeqt,UMAP); +emasz = ReadWA(eqt+28) & 01777; /* B EMA size in pages */ +if ((e->ipgs+e->npgs) > emasz) return FALSE; /* outside EMA? */ +if ((e->ipgs+msegsz) > emasz) /* if MSEG overlaps end of EMA */ + e->npgs = emasz - e->ipgs; /* only map until end of EMA */ + +e->emasz = emasz; /* copy arguments */ +e->msegsz = msegsz; +e->msegno = msegno; +e->spmseg = spmseg; +e->mseg = mseg; +return cpu_ema_mmap01(e); +} + +static t_stat cpu_ema_mmap(uint32 ipage,uint32 npgs, t_bool debug) +{ +uint32 xidex; +EMA4 ema4, *e = &ema4; + +e->ipgs = ipage; /* S6 set the arguments */ +e->npgs = npgs; /* S5 */ + +AR = 0; +xidex = ReadIO(idx,UMAP); +if ((ipage & SIGN) || /* negative page displacement? */ + (npgs & SIGN) || /* negative # of pages? */ + xidex == 0 || /* no EMA? */ + !cpu_ema_mmap02(e)) /* mapping failed? */ + AR = 0177777; /* return with error */ +return SCPE_OK; /* leave */ +} + +static t_bool cpu_ema_emat(EMA4* e) +{ +uint32 xidex,idext0; +uint32 curmseg,phys,msnum,lastpgs; + +xidex = ReadIO(idx,UMAP); /* read ID extension */ +idext0 = ReadWA(xidex+0); /* get current segment */ +curmseg = idext0 >> 5; +if ((idext0 & 0100000) || /* was nonstd MSEG? */ + curmseg != e->msegno) { /* or different MSEG last time? */ + phys = ReadWA(xidex+1) & 01777; /* physical start pg of EMA */ + e->spmseg = phys + e->ipgs; /* physical start pg of MSEG */ + msnum = e->emasz / e->msegsz; /* find last MSEG# */ + lastpgs = e->emasz % e->msegsz; /* #pgs in last MSEG */ + if (lastpgs==0) msnum--; /* adjust # of last MSEG */ + e->npgs = msnum==e->msegno ? lastpgs : e->msegsz; /* for last MSEG, only map available pgs */ + if (!cpu_ema_mmap01(e)) return FALSE; /* map npgs pages at ipgs */ +} +BR = (uint16) (e->mseg + e->msoff); /* return address of element */ +return TRUE; /* and everything done */ +} + +/* .EMIO microcode routine, resolves element addr for EMA array + * and maps the appropriate map segment + * + * Call: + * OCT 105250B + * DEF RTN error return (rtn), good return is rtn+1 + * DEF BUFLEN length of buffer in words (bufl) + * DEF TABLE[,I] array declaration (dtbl) + * DEF A(N)[,I] actual subscripts (atbl) + * DEF A(N-1)[,I] + * ... + * DEF A(2)[,I] + * DEF A(1)[,I] + * RTN EQU * error return A="15", B="EM" + * RTN+1 EQU *+1 good return B=logical address + * + * TABLE DEC # # dimensions + * DEC -L(N) + * DEC D(N-1) + * DEC -L(N-1) lower bound (n-1)st dim + * DEC D(N-2) (n-2)st dim + * ... + * DEC D(1) 1st dim + * DEC -L(1) lower bound 1st dim + * DEC # # words/element + * OFFSET 1 EMA Low + * OFFSET 2 EMA High + */ +static t_stat cpu_ema_emio(uint32* rtn,uint32 bufl,uint32 dtbl,uint32 atbl,t_bool debug) +{ +uint32 xidex, idext1; +uint32 mseg, bufpgs, npgs; +EMA4 ema4, *e = &ema4; + +xidex = ReadIO(idx,UMAP); /* read ID extension */ +if (bufl & SIGN || /* buffer length negative? */ + xidex==0) goto em16; /* no EMA declared? */ + +idext1 = ReadWA(xidex+1); /* |logstrt mseg|d|physstrt ema| */ +mseg = (idext1 >> 1) & MSEGMASK; /* get logical start MSEG */ +if (!cpu_ema_emas(dtbl,atbl,e)) goto em16; /* resolve address */ +bufpgs = (bufl + e->offs) >> 10; /* # of pgs reqd for buffer */ +if ((bufl + e->offs) & 01777) bufpgs++; /* S11 add 1 if not at pg boundary */ +if ((bufpgs + e->pgoff) > e->emasz) goto em16; /* exceeds EMA limit? */ +npgs = (e->msoff + bufl) >> 10; /* # of pgs reqd for MSEG */ +if ((e->msoff + bufl) & 01777) npgs++; /* add 1 if not at pg boundary */ +if (npgs < e->msegsz) { + e->mseg = mseg; /* logical stat of MSEG */ + if (!cpu_ema_emat(e)) goto em16; /* do a std mapping */ +} else { + BR = (uint16) (mseg + e->offs); /* logical start of buffer */ + e->npgs = bufpgs; /* S5 # pgs required */ + e->ipgs = e->pgoff; /* S6 page offset to reqd pg */ + if (!cpu_ema_mmap02(e)) goto em16; /* do nonstd mapping */ +} +(*rtn)++; /* return via good exit */ +return SCPE_OK; + +em16: /* error condition */ +AR=0x3136; /* AR = '16' */ +BR=0x454d; /* BR = 'EM' */ +return SCPE_OK; /* return via unmodified rtn */ +} + +/* .EMAP microcode routine, resolves both EMA/non-EMA calls + * Call: + * OCT 105257B + * DEF RTN error return (rtn), good return is rtn+1 + * DEF ARRAY[,I] array base (abase) + * DEF TABLE[,I] array declaration (dtbl) + * DEF A(N)[,I] actual subscripts (atbl) + * DEF A(N-1)[,I] + * ... + * DEF A(2)[,I] + * DEF A(1)[,I] + * RTN EQU * error return A="15", B="EM" + * RTN+1 EQU *+1 good return B=logical address + * + * TABLE DEC # # dimensions + * DEC -L(N) + * DEC D(N-1) + * DEC -L(N-1) lower bound (n-1)st dim + * DEC D(N-2) (n-2)st dim + * ... + * DEC D(1) 1st dim + * DEC -L(1) lower bound 1st dim + * DEC # # words/element + * OFFSET 1 EMA Low + * OFFSET 2 EMA High + */ +static t_stat cpu_ema_emap(uint32* rtn,uint32 abase,uint32 dtbl,uint32 atbl,t_bool debug) +{ +uint32 xidex, eqt, idext0, idext1; +int32 sub, act, low, ndim, sz; +uint32 offs, pgoff, emasz, phys, msgn, mseg, sum, MA, pg0, pg1; + +xidex = ReadIO(idx,UMAP); /* read ID Extension */ +if (xidex) { /* is EMA declared? */ + idext1 = ReadWA(xidex+1); /* get word 1 of idext */ + mseg = (idext1 >> 1) & MSEGMASK; /* get logical start MSEG */ + if (abase >= mseg) { /* EMA reference? */ + if (!cpu_ema_resolve(dtbl,atbl,&sum)) /* calculate subscript */ + goto em15; + offs = sum & 01777; /* address offset within page */ + pgoff = sum >> 10; /* ema offset in pages */ + if (pgoff > 1023) goto em15; /* overflow? */ + eqt = ReadIO(xeqt,UMAP); + emasz = ReadWA(eqt+28) & 01777; /* EMA size in pages */ + phys = idext1 & 01777; /* physical start pg of EMA */ + if (pgoff > emasz) goto em15; /* outside EMA range? */ + + msgn = mseg >> 10; /* get # of 1st MSEG reg */ + phys += pgoff; + + pg0 = dms_rmap(UMAP+0); /* read base page map# */ + pg1 = dms_rmap(UMAP+1); /* save map# 1 */ + dms_wmap(UMAP+1,pg0); /* map #0 into reg #1 */ + + WriteIO(umaps+msgn, phys, UMAP); /* store 1st mapped pg in user map */ + dms_wmap(UMAP+msgn, phys); /* and set the map register */ + phys = (pgoff+1)==emasz ? 0140000 : phys+1; /* protect 2nd map if end of EMA */ + WriteIO(umaps+msgn+1, phys, UMAP); /* store 2nd mapped pg in user map */ + dms_wmap(UMAP+msgn+1, phys); /* and set the map register */ + + dms_wmap(UMAP+1,pg1); /* restore map #1 */ + + idext0 = ReadWA(xidex+0) | 0100000; /* set NS flag in id extension */ + WriteIO(xidex+0, idext0, SMAP); /* save back value */ + AR = 0; /* was successful */ + BR = (uint16) (mseg + offs); /* calculate log address */ + (*rtn)++; /* return via good exit */ + return SCPE_OK; + } +} /* not EMA reference */ +ndim = ReadW(dtbl++); +if (ndim<0) goto em15; /* negative ´dimensions */ +sum = 0; /* accu for index calc */ +while (ndim > 0) { + MA = ReadW(atbl++); /* get address of A(N) */ + resolve (MA, &MA, 0); + act = ReadW(MA); /* A(N) */ + low = ReadW(dtbl++); /* -L(N) */ + sub = SEXT(act) + SEXT(low); /* subscript */ + if (sub & 0xffff8000) goto em15; /* overflow? */ + sum += sub; /* accumulate */ + sz = ReadW(dtbl++); + sz = SEXT(sz); + if (sz < 0) goto em15; + sum *= sz; /* and multiply with sz of dimension */ + if (sum & 0xffff8000) goto em15; /* overflow? */ + ndim--; +} +BR = (uint16) (abase + sum); /* add displacement */ +(*rtn)++; /* return via good exit */ +return SCPE_OK; + +em15: /* error condition */ + AR=0x3135; /* AR = '15' */ + BR=0x454d; /* BR = 'EM' */ + return SCPE_OK; /* return via unmodified e->rtn */ +} + +t_stat cpu_rte_ema (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +OP_PAT pattern; +uint32 entry, rtn; +t_bool debug = DEBUG_PRI (cpu_dev, DEB_EMA); + +entry = IR & 017; /* mask to entry point */ +pattern = op_ema[entry]; /* get operand pattern */ + +if (pattern != OP_N) { + reason = cpu_ops (pattern, op, intrq); /* get instruction operands */ + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +if (debug) { /* debugging? */ + fprintf (sim_deb, ">>CPU EMA: P = %06o, IR = %06o (", err_PC,IR); /* print preamble and IR */ + fprint_sym (sim_deb, err_PC, (t_value *) &IR, /* print instruction mnemonic */ + NULL, SWMASK('M')); + fputc (')', sim_deb); + + fprint_ops (pattern, op); /* print operands */ + fputc ('\n', sim_deb); /* terminate line */ + } + +switch (entry) { /* decode IR<3:0> */ + case 000: /* .EMIO 105240 (OP_A) */ + rtn = op[0].word; + reason = cpu_ema_emio(&rtn, op[1].word, + op[2].word, PR, debug); /* handle the EMIO instruction */ + PR = rtn; + if (debug) + fprintf (sim_deb, ">>CPU EMA: return .EMIO: AR = %06o, BR = %06o, rtn=%s\n", + AR, BR, PR==op[0].word?"error":"good"); + break; + + case 001: /* .MMAP 105241 (OP_AKK) */ + reason = cpu_ema_mmap(op[1].word, + op[2].word, debug); /* handle the MMAP instruction */ + if (debug) + fprintf (sim_deb, ">>CPU EMA: return .MMAP: AR = %06o\n",AR); + break; + + case 002: /* [test] 105242 (OP_N) */ + /* effectively, this code just returns without error: + * real microcode will set S register to 102077B when in single step mode */ + if (sim_step==1) { + if (debug) + fprintf(sim_deb, ">>CPU EMA: EMA option 92067 correctly installed: S=102077\n"); + SR = 0102077; + } + break; + + case 017: /* .EMAP 105247 (OP_A) */ + rtn = op[0].word; /* error return */ + reason = cpu_ema_emap(&rtn, op[1].word, + op[2].word, PR, debug); /* handle the EMAP instruction */ + PR = rtn; + if (debug) { + fprintf (sim_deb, ">>CPU EMA: return .EMAP: AR = %06o, BR = %06o, rtn=%s\n", + AR, BR, PR==op[0].word?"error":"good"); + } + break; + + default: /* others undefined */ + reason = stop_inst; + } + +return reason; +} diff --git a/HP2100/hp2100_cpu6.c b/HP2100/hp2100_cpu6.c index efa3565f..6654bd8f 100644 --- a/HP2100/hp2100_cpu6.c +++ b/HP2100/hp2100_cpu6.c @@ -1,828 +1,830 @@ -/* hp2100_cpu6.c: HP 1000 RTE-6/VM OS instructions - - Copyright (c) 2006-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - CPU6 RTE-6/VM OS instructions - - 24-Dec-14 JDB Added casts for explicit downward conversions - 18-Mar-13 JDB Use MP abort handler declaration in hp2100_cpu.h - 09-May-12 JDB Separated assignments from conditional expressions - 29-Oct-10 JDB DMA channels renamed from 0,1 to 1,2 to match documentation - 18-Sep-08 JDB Corrected .SIP debug formatting - 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h - 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 27-Nov-07 JDB Implemented OS instructions - 26-Sep-06 JDB Created - - Primary references: - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - Macro/1000 Reference Manual (92059-90001, Dec-1992) - - Additional references are listed with the associated firmware - implementations, as are the HP option model numbers pertaining to the - applicable CPUs. -*/ - - -#include -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - - -/* Offsets to data and addresses within RTE. */ - -static const uint32 xi = 0001647; /* XI address */ -static const uint32 intba = 0001654; /* INTBA address */ -static const uint32 intlg = 0001655; /* INTLG address */ -static const uint32 eqt1 = 0001660; /* EQT1 address */ -static const uint32 eqt11 = 0001672; /* EQT11 address */ -static const uint32 pvcn = 0001712; /* PVCN address */ -static const uint32 xsusp = 0001730; /* XSUSP address */ -static const uint32 dummy = 0001737; /* DUMMY address */ -static const uint32 mptfl = 0001770; /* MPTFL address */ -static const uint32 eqt12 = 0001771; /* EQT12 address */ -static const uint32 eqt15 = 0001774; /* EQT15 address */ -static const uint32 vctr = 0002000; /* VCTR address */ - -static const uint32 CLC_0 = 0004700; /* CLC 0 instruction */ -static const uint32 STC_0 = 0000700; /* STC 0 instruction */ -static const uint32 CLF_0 = 0001100; /* CLF 0 instruction */ -static const uint32 STF_0 = 0000100; /* STF 0 instruction */ -static const uint32 SFS_0_C = 0003300; /* SFS 0,C instruction */ - -enum vctr_offsets { dms_offset = 0, /* DMS status */ - int_offset, /* interrupt system status */ - sc_offset, /* select code */ - clck_offset, /* TBG IRQ handler */ - cic4_offset, /* illegal IRQ handler */ - cic2_offset, /* device IRQ handler */ - sked_offset, /* prog sched IRQ handler */ - rqst_offset, /* EXEC request handler */ - cic_offset, /* IRQ location */ - perr_offset, /* parity error IRQ handler */ - mper_offset, /* memory protect IRQ handler */ - lxnd_offset }; /* $LIBR return */ - - -/* RTE-6/VM Operating System Instructions - - The OS instructions were added to acccelerate certain time-consuming - operations of the RTE-6/VM operating system, HP product number 92084A. - Microcode was available for the E- and F-Series; the M-Series used software - equivalents. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A N/A 92084A 92084A - - The routines are mapped to instruction codes as follows: - - Instr. 1000-E/F Description - ------ -------- ---------------------------------------------- - $LIBR 105340 Enter privileged/reentrant library routine - $LIBX 105341 Exit privileged/reentrant library routine - .TICK 105342 TBG tick interrupt handler - .TNAM 105343 Find ID segment that matches name - .STIO 105344 Configure I/O instructions - .FNW 105345 Find word with user increment - .IRT 105346 Interrupt return processing - .LLS 105347 Linked list search - - .SIP 105350 Skip if interrupt pending - .YLD 105351 .SIP completion return point - .CPM 105352 Compare words LT/EQ/GT - .ETEQ 105353 Set up EQT pointers in base page - .ENTN 105354 Transfer parameter addresses (utility) - $OTST * 105355 OS firmware self test - .ENTC 105356 Transfer parameter addresses (priv/reent) - .DSPI 105357 Set display indicator - - Opcodes 105354-105357 are "dual use" instructions that take different - actions, depending on whether they are executed from a trap cell during an - interrupt. When executed from a trap cell, they have these actions: - - Instr. 1000-E/F Description - ------ -------- ---------------------------------------------- - $DCPC * 105354 DCPC channel interrupt processing - $MPV * 105355 MP/DMS/PE interrupt processing - $DEV * 105356 Standard device interrupt processing - $TBG * 105357 TBG interrupt processing - - * These mnemonics are recognized by symbolic examine/deposit but are not - official HP mnemonics. - - Implementation notes: - - 1. The microcode differentiates between interrupt processing and normal - execution of the "dual use" instructions by testing the CPU flag. - Interrupt vectoring sets the flag; a normal instruction fetch clears it. - Under simulation, interrupt vectoring is indicated by the value of the - "iotrap" parameter (0 = normal instruction, 1 = trap cell instruction). - - 2. The operand patterns for .ENTN and .ENTC normally would be coded as - "OP_A", as each takes a single address as a parameter. However, because - they might also be executed from a trap cell, we cannot assume that P+1 - is an address, or we might cause a DM abort when trying to resolve - indirects. Therefore, "OP_A" handling is done within each routine, once - the type of use is determined. - - 3. The microcode for .ENTC, .ENTN, .FNW, .LLS, .TICK, and .TNAM explicitly - checks for interrupts during instruction execution. In addition, the - .STIO, .CPM, and .LLS instructions implicitly check for interrupts during - parameter indirect resolution. Because the simulator calculates - interrupt requests only between instructions, this behavior is not - simulated. - - 4. The microcode executes certain I/O instructions (e.g., CLF 0) by building - the instruction in the IR and executing an IOG micro-order. We simulate - this behavior by calling the "iogrp" handler with the appropriate - instruction, rather than manipulating the I/O system directly, so that we - will remain unaffected by any future changes to the underlying I/O - simulation structure. - - 5. The $OTST and .DSPI microcode uses features (reading the RPL switches and - boot loader ROM data, loading the display register) that are not - simulated. The remaining functions of the $OTST instruction are - provided. The .DSPI instruction is a NOP or unimplemented instruction - stop. - - 6. Because of the volume of calls to the OS firmware, debug printouts - attempt to write only one line per instruction invocation. This means - that calling and returned register values are printed separately, with a - newline added at the end of execution. However, many instructions can MP - or DM abort, either intentionally or due to improper use. That would - leave debug lines without the required trailing newlines. - - There are two ways to address this: either we could replace the CPU's - setjmp buffer with one that points to a routine that adds the missing - newline, or we can add a semaphore that is tested on entry to see if it - is already set, implying a longjmp occurred, and then add the newline if - so. The latter would add the newline when the MP trap cell instruction - was entered or when the next user-level instruction was executed. - However, the merged-line problem would still exist if some other module - generated intervening debug printouts. So we do the former. This does - mean that this routine must be changed if the MP abort mechanism is - changed. - - 7. The $LIBX instruction is executed to complete either a privileged or - reentrant execution. In the former case, the privileged nest counter - ($PVCN) is decremented. In the latter, $PVCN decrement is attempted but - the write will trap with an MP violation, as reentrant routines execute - with the interrupt system on. RTE will then complete the release of - memory allocated for the original $LIBR call. - - 8. The documentation for the .SIP and .YLD instructions is misleading in - several places. Comments in the RTE $SIP source file say that .SIP - doesn't return if a "known" interrupt is pending. Actually, .SIP always - returns, either to P+1 for no pending interrupt, or to P+2 if one is - pending. There is no check for "known" interrupt handlers. The - microcode source comments say that the interrupting select code is - returned in the B register. Actually, the B register is unchanged. The - RTE Tech Specs say that .SIP "services any pending system interrupts." - Actually, .SIP only checks for interrupts; no servicing is performed. - - For .YLD, the microcode comments say that two parameters are passed: the - new P value, and the interrupting select code. Actually, only the new P - value is passed. - - The .SIP and .YLD simulations follow the actual microcode rather than the - documentation. - - Additional references: - - RTE-6/VM OS Microcode Source (92084-18831, revision 8). - - RTE-6/VM Technical Specifications (92084-90015, Apr-1983). -*/ - - -/* Save the CPU registers. - - The CPU registers are saved in the current ID segment in preparation for - interrupt handling. Although the RTE base page has separate pointers for the - P, A, B, and E/O registers, they are always contiguous, and the microcode - simply increments the P-register pointer (XSUSP) to store the remaining - values. - - This routine is called from the trap cell interrupt handlers and from the - $LIBX processor. In the latter case, the privileged system interrupt - handling is not required, so it is bypassed. In either case, the current map - will be the system map when we are called. -*/ - -static t_stat cpu_save_regs (uint32 iotrap) -{ -uint16 save_area, priv_fence; -t_stat reason = SCPE_OK; - -save_area = ReadW (xsusp); /* addr of PABEO save area */ - -WriteW (save_area + 0, PC); /* save P */ -WriteW (save_area + 1, AR); /* save A */ -WriteW (save_area + 2, BR); /* save B */ -WriteW (save_area + 3, (E << 15) & SIGN | O & 1); /* save E and O */ - -save_area = ReadW (xi); /* addr of XY save area */ -WriteWA (save_area + 0, XR); /* save X (in user map) */ -WriteWA (save_area + 1, YR); /* save Y (in user map) */ - -if (iotrap) { /* do priv setup only if IRQ */ - priv_fence = ReadW (dummy); /* get priv fence select code */ - - if (priv_fence) { /* privileged system? */ - reason = iogrp (STC_0 + priv_fence, iotrap); /* STC SC on priv fence */ - reason = iogrp (CLC_0 + DMA1, iotrap); /* CLC 6 to inh IRQ on DCPC 1 */ - reason = iogrp (CLC_0 + DMA2, iotrap); /* CLC 7 to inh IRQ on DCPC 2 */ - reason = iogrp (STF_0, iotrap); /* turn interrupt system back on */ - } - } - -return reason; -} - - -/* Save the machine state at interrupt. - - This routine is called from each of the trap cell instructions. Its purpose - is to save the complete state of the machine in preparation for interrupt - handling. - - For the MP/DMS/PE interrupt, the interrupting device must not be cleared and - the CPU registers must not be saved until it is established that the - interrupt is not caused by a parity error. Parity errors cannot be - inhibited, so the interrupt may have occurred while in RTE. Saving the - registers would overwrite the user's registers that were saved at RTE entry. - - Note that the trap cell instructions are dual-use and invoke this routine - only when they are executed during interrupts. Therefore, the current map - will always be the system map when we are called. -*/ - -static t_stat cpu_save_state (uint32 iotrap) -{ -uint16 vectors; -uint32 saved_PC, int_sys_off; -t_stat reason; - -saved_PC = PC; /* save current PC */ -reason = iogrp (SFS_0_C, iotrap); /* turn interrupt system off */ -int_sys_off = (PC == saved_PC); /* set flag if already off */ -PC = saved_PC; /* restore PC in case it bumped */ - -vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ - -WriteW (vectors + dms_offset, dms_upd_sr ()); /* save DMS status (SSM) */ -WriteW (vectors + int_offset, int_sys_off); /* save int status */ -WriteW (vectors + sc_offset, intaddr); /* save select code */ - -WriteW (mptfl, 1); /* show MP is off */ - -if (intaddr != 5) { /* only if not MP interrupt */ - reason = iogrp (CLF_0 + intaddr, iotrap); /* issue CLF to device */ - cpu_save_regs (iotrap); /* save CPU registers */ - } - -return reason; -} - - -/* Get the interrupt table entry corresponding to a select code. - - Return the word in the RTE interrupt table that corresponds to the - interrupting select code. Return 0 if the select code is beyond the end of - the table. -*/ - -static uint16 cpu_get_intbl (uint32 select_code) -{ -uint16 interrupt_table; /* interrupt table (starts with SC 06) */ -uint16 table_length; /* length of interrupt table */ - -interrupt_table = ReadW (intba); /* get int table address */ -table_length = ReadW (intlg); /* get int table length */ - -if (select_code - 6 > table_length) /* SC beyond end of table? */ - return 0; /* return 0 for illegal interrupt */ -else - return ReadW (interrupt_table + select_code - 6); /* else return table entry */ -} - - -/* RTE-6/VM OS instruction dispatcher. - - Debugging printouts are provided with the OS and OSTBG debug flags. The OS - flag enables tracing for all instructions except for the three-instruction - sequence executed for the time-base generator interrupt ($TBG, .TICK, and - .IRT). The OSTBG flag enables tracing for just the TBG sequence. The flags - are separate, as the TBG generates 100 interrupts per second. Use caution - when specifying the OSTBG flag, as the debug output file will grow rapidly. - Note that the OS flag enables the .IRT instruction trace for all cases except - a TBG interrupt. - - The default (user microcode) dispatcher will allow the firmware self-test - instruction (105355) to execute as NOP. This is because RTE-6/VM will always - test for the presence of OS and VMA firmware on E/F-Series machines. If the - firmware is not present, then these instructions will return to P+1, and RTE - will then HLT 21. This means that RTE-6/VM will not run on an E/F-Series - machine without the OS and VMA firmware. - - Howwever, RTE allows the firmware instructions to be disabled for debugging - purposes. If the firmware is present and returns to P+2 but sets the X - register to 0, then RTE will use software equivalents. We enable this - condition when the OS firmware is enabled (SET CPU VMA), the OS debug flag is - set (SET CPU DEBUG=OS), but debug output has been disabled (SET CONSOLE - NODEBUG). That is: - - OS Debug - Firmware Debug Output Tracing Self-Test Instruction - ======== ===== ====== ======= ===================== - disabled x x off NOP - enabled clear x off X = revision code - enabled set off off X = 0 - enabled set on on X = revision code -*/ - -static const OP_PAT op_os[16] = { - OP_A, OP_A, OP_N, OP_N, /* $LIBR $LIBX .TICK .TNAM */ - OP_A, OP_K, OP_A, OP_KK, /* .STIO .FNW .IRT .LLS */ - OP_N, OP_C, OP_KK, OP_N, /* .SIP .YLD .CPM .ETEQ */ - OP_N, OP_N, OP_N, OP_N /* .ENTN $OTST .ENTC .DSPI */ - }; - -t_stat cpu_rte_os (uint32 IR, uint32 intrq, uint32 iotrap) -{ -t_stat reason = SCPE_OK; -OPS op; -OP_PAT pattern; -uint32 entry, count, cp, sa, da, i, ma, eqta; -uint16 vectors, save_area, priv_fence, eoreg, eqt, key; -char test[6], target[6]; -jmp_buf mp_handler; -int abortval; -t_bool debug_print; -static t_bool tbg_tick = FALSE; /* set if processing TBG interrupt */ - -entry = IR & 017; /* mask to entry point */ -pattern = op_os[entry]; /* get operand pattern */ - -if (pattern != OP_N) { - reason = cpu_ops (pattern, op, intrq); /* get instruction operands */ - - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -tbg_tick = tbg_tick || (IR == 0105357) && iotrap; /* set TBG interrupting flag */ - -debug_print = (DEBUG_PRI (cpu_dev, DEB_OS) && !tbg_tick) || - (DEBUG_PRI (cpu_dev, DEB_OSTBG) && tbg_tick); - -if (debug_print) { - fprintf (sim_deb, ">>CPU OS: IR = %06o (", IR); /* print preamble and IR */ - fprint_sym (sim_deb, (iotrap ? intaddr : err_PC), /* print instruction mnemonic */ - (t_value *) &IR, NULL, SWMASK('M')); - fputc (')', sim_deb); - - fprint_ops (pattern, op); /* print operands */ - - memcpy (mp_handler, save_env, sizeof (jmp_buf)); /* save original MP handler */ - abortval = setjmp (save_env); /* set new MP abort handler */ - - if (abortval != 0) { /* MP abort? */ - fputs ("...MP abort\n", sim_deb); /* report it and terminate line */ - memcpy (save_env, mp_handler, sizeof (jmp_buf)); /* restore original MP handler */ - longjmp (save_env, abortval); /* transfer to MP handler */ - } - } - -switch (entry) { /* decode IR<3:0> */ - - case 000: /* $LIBR 105340 (OP_A) */ - if ((op[0].word != 0) || /* reentrant call? */ - (mp_control && (ReadW (dummy) != 0))) { /* or priv call + MP on + priv sys? */ - if (dms_ump) { /* called from user map? */ - dms_viol (err_PC, MVI_PRV); /* privilege violation */ - } - dms_ump = SMAP; /* set system map */ - - vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ - PC = ReadW (vectors + mper_offset); /* vector to $MPER for processing */ - } - - else { /* privileged call */ - if (mp_control) { /* memory protect on? */ - mp_control = CLEAR; /* turn it off */ - reason = iogrp (CLF_0, iotrap); /* turn interrupt system off */ - WriteW (mptfl, 1); /* show MP is off */ - save_area = ReadW (xsusp); /* get addr of P save area */ - - if (dms_ump) /* user map current? */ - WriteWA (save_area, (PC - 2) & VAMASK); /* set point of suspension */ - else /* system map current */ - WriteW (save_area, (PC - 2) & VAMASK); /* set point of suspension */ - } - - WriteW (pvcn, (ReadW (pvcn) + 1) & DMASK); /* increment priv nest counter */ - } - break; - - case 001: /* $LIBX 105341 (OP_A) */ - PC = ReadW (op[0].word); /* set P to return point */ - count = (ReadW (pvcn) - 1) & DMASK; /* decrement priv nest counter */ - WriteW (pvcn, count); /* write it back */ - - if (count == 0) { /* end of priv mode? */ - dms_ump = SMAP; /* set system map */ - reason = cpu_save_regs (iotrap); /* save registers */ - vectors = ReadW (vctr); /* get address of vectors */ - PC = ReadW (vectors + lxnd_offset); /* vector to $LXND for processing */ - } - break; - - case 002: /* .TICK 105342 (OP_N) */ - if (debug_print) /* debugging? */ - fprint_regs (",", REG_A | REG_B, 0); /* print entry registers */ - - do { - eqt = (ReadW (AR) + 1) & DMASK; /* bump timeout from EQT15 */ - - if (eqt != 1) { /* was timeout active? */ - WriteW (AR, eqt); /* yes, write it back */ - - if (eqt == 0) /* did timeout expire? */ - break; /* P+0 return for timeout */ - } - - AR = (AR + 15) & DMASK; /* point at next EQT15 */ - BR = (BR - 1) & DMASK; /* decrement count of EQTs */ - } while ((BR > 0) && (eqt != 0)); /* loop until timeout or done */ - - if (BR == 0) /* which termination condition? */ - PC = (PC + 1) & VAMASK; /* P+1 return for no timeout */ - - if (debug_print) /* debugging? */ - fprint_regs ("; result:", /* print return registers */ - REG_A | REG_B | REG_P_REL, - err_PC + 1); - break; - - case 003: /* .TNAM 105343 (OP_N) */ - if (debug_print) /* debugging? */ - fprint_regs (",", REG_A | REG_B, 0); /* print entry registers */ - - E = 1; /* preset flag for not found */ - cp = (BR << 1) & DMASK; /* form char addr (B is direct) */ - - for (i = 0; i < 5; i++) { /* copy target name */ - target[i] = (char) ReadB (cp); /* name is only 5 chars */ - cp = (cp + 1) & DMASK; - } - - if ((target[0] == '\0') && (target[1] == '\0')) /* if name is null, */ - break; /* return immed to P+0 */ - - key = ReadW (AR); /* get first keyword addr */ - - while (key != 0) { /* end of keywords? */ - cp = ((key + 12) << 1) & DMASK; /* form char addr of name */ - - for (i = 0; i < 6; i++) { /* copy test name */ - test[i] = (char) ReadB (cp); /* name is only 5 chars */ - cp = (cp + 1) & DMASK; /* but copy 6 to get flags */ - } - - if (strncmp (target, test, 5) == 0) { /* names match? */ - AR = (key + 15) & DMASK; /* A = addr of IDSEG [15] */ - BR = key; /* B = addr of IDSEG [0] */ - E = (uint32) ((test[5] >> 4) & 1); /* E = short ID segment bit */ - PC = (PC + 1) & VAMASK; /* P+1 for found return */ - break; - } - - AR = (AR + 1) & DMASK; /* bump to next keyword */ - key = ReadW (AR); /* get next keyword */ - }; - - if (debug_print) /* debugging? */ - fprint_regs ("; result:", /* print return registers */ - REG_A | REG_B | REG_E | REG_P_REL, - err_PC + 1); - break; - - case 004: /* .STIO 105344 (OP_A) */ - count = op[0].word - PC; /* get count of operands */ - - if (debug_print) /* debugging? */ - fprintf (sim_deb, /* print registers on entry */ - ", A = %06o, count = %d", AR, count); - - for (i = 0; i < count; i++) { - ma = ReadW (PC); /* get operand address */ - - reason = resolve (ma, &ma, intrq); /* resolve indirect */ - - if (reason != SCPE_OK) { /* resolution failed? */ - PC = err_PC; /* IRQ restarts instruction */ - break; - } - - WriteW (ma, ReadW (ma) & ~I_DEVMASK | AR); /* set SC into instruction */ - PC = (PC + 1) & VAMASK; /* bump to next */ - } - break; - - case 005: /* .FNW 105345 (OP_K) */ - if (debug_print) /* debugging? */ - fprint_regs (",", REG_A | REG_B | REG_X, 0); /* print entry registers */ - - while (XR != 0) { /* all comparisons done? */ - key = ReadW (BR); /* read a buffer word */ - - if (key == AR) { /* does it match? */ - PC = (PC + 1) & VAMASK; /* P+1 found return */ - break; - } - - BR = (BR + op[0].word) & DMASK; /* increment buffer ptr */ - XR = (XR - 1) & DMASK; /* decrement remaining count */ - } - /* P+0 not found return */ - if (debug_print) /* debugging? */ - fprint_regs ("; result:", /* print return registers */ - REG_A | REG_B | REG_X | REG_P_REL, - err_PC + 2); - break; - - case 006: /* .IRT 105346 (OP_A) */ - save_area = ReadW (xsusp); /* addr of PABEO save area */ - - WriteW (op[0].word, ReadW (save_area + 0)); /* restore P to DEF RTN */ - - AR = ReadW (save_area + 1); /* restore A */ - BR = ReadW (save_area + 2); /* restore B */ - - eoreg = ReadW (save_area + 3); /* get combined E and O */ - E = (eoreg >> 15) & 1; /* restore E */ - O = eoreg & 1; /* restore O */ - - save_area = ReadW (xi); /* addr of XY save area */ - XR = ReadWA (save_area + 0); /* restore X (from user map) */ - YR = ReadWA (save_area + 1); /* restore Y (from user map) */ - - reason = iogrp (CLF_0, iotrap); /* turn interrupt system off */ - WriteW (mptfl, 0); /* show MP is on */ - - priv_fence = ReadW (dummy); /* get priv fence select code */ - - if (priv_fence) { /* privileged system? */ - reason = iogrp (CLC_0 + priv_fence, iotrap); /* CLC SC on priv fence */ - reason = iogrp (STF_0 + priv_fence, iotrap); /* STF SC on priv fence */ - - if (cpu_get_intbl (DMA1) & SIGN) /* DCPC 1 active? */ - reason = iogrp (STC_0 + DMA1, iotrap); /* STC 6 to enable IRQ on DCPC 1 */ - - if (cpu_get_intbl (DMA2) & SIGN) /* DCPC 2 active? */ - reason = iogrp (STC_0 + DMA2, iotrap); /* STC 7 to enable IRQ on DCPC 2 */ - } - - tbg_tick = 0; /* .IRT terminates TBG servicing */ - break; - - case 007: /* .LLS 105347 (OP_KK) */ - if (debug_print) /* debugging? */ - fprint_regs (",", REG_A | REG_B | REG_E, 0); /* print entry registers */ - - AR = AR & ~SIGN; /* clear sign bit of A */ - - while ((AR != 0) && ((AR & SIGN) == 0)) { /* end of list or bad list? */ - key = ReadW ((AR + op[1].word) & VAMASK); /* get key value */ - - if ((E == 0) && (key == op[0].word) || /* for E = 0, key = arg? */ - (E != 0) && (key > op[0].word)) /* for E = 1, key > arg? */ - break; /* search is done */ - - BR = AR; /* B = last link */ - AR = ReadW (AR); /* A = next link */ - } - - if (AR == 0) /* exhausted list? */ - PC = (PC + 1) & VAMASK; /* P+1 arg not found */ - else if ((AR & SIGN) == 0) /* good link? */ - PC = (PC + 2) & VAMASK; /* P+2 arg found */ - /* P+0 bad link */ - if (debug_print) /* debugging? */ - fprint_regs ("; result:", /* print return registers */ - REG_A | REG_B | REG_P_REL, - err_PC + 3); - break; - - case 010: /* .SIP 105350 (OP_N) */ - reason = iogrp (STF_0, iotrap); /* turn interrupt system on */ - intrq = calc_int (); /* check for interrupt requests */ - reason = iogrp (CLF_0, iotrap); /* turn interrupt system off */ - - if (intrq) /* was interrupt pending? */ - PC = (PC + 1) & VAMASK; /* P+1 return for pending IRQ */ - /* P+0 return for no pending IRQ */ - if (debug_print) /* debugging? */ - fprintf (sim_deb, /* print return registers */ - ", CIR = %02o, return = P+%d", - intrq, PC - (err_PC + 1)); - break; - - case 011: /* .YLD 105351 (OP_C) */ - PC = op[0].word; /* pick up point of resumption */ - reason = iogrp (STF_0, iotrap); /* turn interrupt system on */ - ion_defer = 0; /* kill defer so irq occurs immed */ - break; - - case 012: /* .CPM 105352 (OP_KK) */ - if (INT16 (op[0].word) > INT16 (op[1].word)) - PC = (PC + 2) & VAMASK; /* P+2 arg1 > arg2 */ - else if (INT16 (op[0].word) < INT16 (op[1].word)) - PC = (PC + 1) & VAMASK; /* P+1 arg1 < arg2 */ - /* P+0 arg1 = arg2 */ - if (debug_print) /* debugging? */ - fprint_regs (",", REG_P_REL, err_PC + 3); /* print return registers */ - break; - - case 013: /* .ETEQ 105353 (OP_N) */ - eqt = ReadW (eqt1); /* get addr of EQT1 */ - - if (AR != eqt) { /* already set up? */ - for (eqta = eqt1; eqta <= eqt11; eqta++) /* init EQT1-EQT11 */ - WriteW (eqta, AR++ & DMASK); - for (eqta = eqt12; eqta <= eqt15; eqta++) /* init EQT12-EQT15 */ - WriteW (eqta, AR++ & DMASK); /* (not contig with EQT1-11) */ - } - - AR = AR & DMASK; /* ensure wraparound */ - - if (debug_print) /* debugging? */ - fprintf (sim_deb, /* print return registers */ - ", A = %06o, EQT1 = %06o", AR, eqt); - break; - - case 014: /* .ENTN/$DCPC 105354 (OP_N) */ - if (iotrap) { /* in trap cell? */ - reason = cpu_save_state (iotrap); /* DMA interrupt */ - AR = cpu_get_intbl (intaddr) & ~SIGN; /* get intbl value and strip sign */ - goto DEVINT; /* vector by intbl value */ - } - - else { /* .ENTN instruction */ - ma = (PC - 2) & VAMASK; /* get addr of entry point */ - - ENTX: /* enter here from .ENTC */ - reason = cpu_ops (OP_A, op, intrq); /* get instruction operand */ - da = op[0].word; /* get addr of 1st formal */ - count = ma - da; /* get count of formals */ - sa = ReadW (ma); /* get addr of 1st actual */ - WriteW (ma, (sa + count) & VAMASK); /* adjust return point to skip actuals */ - - if (debug_print) /* debugging? */ - fprintf (sim_deb, /* print entry registers */ - ", op [0] = %06o, pcount = %d", - da, count); - - for (i = 0; i < count; i++) { /* parameter loop */ - ma = ReadW (sa); /* get addr of actual */ - sa = (sa + 1) & VAMASK; /* increment address */ - - reason = resolve (ma, &ma, intrq); /* resolve indirect */ - - if (reason != SCPE_OK) { /* resolution failed? */ - PC = err_PC; /* irq restarts instruction */ - break; - } - - WriteW (da, ma); /* put addr into formal */ - da = (da + 1) & VAMASK; /* increment address */ - } - - if (entry == 016) /* call was .ENTC? */ - AR = (uint16) sa; /* set A to return address */ - } - break; - - case 015: /* $OTST/$MPV 105355 (OP_N) */ - if (iotrap) { /* in trap cell? */ - reason = cpu_save_state (iotrap); /* MP/DMS/PE interrupt */ - vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ - - if (mp_viol & SIGN) { /* parity error? */ - WriteW (vectors + cic_offset, PC); /* save point of suspension in $CIC */ - PC = ReadW (vectors + perr_offset); /* vector to $PERR for processing */ - } - - else { /* MP/DMS violation */ - cpu_save_regs (iotrap); /* save CPU registers */ - PC = ReadW (vectors + rqst_offset); /* vector to $RQST for processing */ - } - - if (debug_print) { /* debugging? */ - fprint_regs (",", REG_CIR, 0); /* print interrupt source */ - /* and cause */ - if (mp_viol & SIGN) - fputs (", parity error", sim_deb); - else if (mp_mevff) - fputs (", DM violation", sim_deb); - else - fputs (", MP violation", sim_deb); - } - } - - else { /* self-test instruction */ - YR = 0000000; /* RPL switch (not implemented) */ - AR = 0000000; /* LDR [B] (not implemented) */ - SR = 0102077; /* test passed code */ - PC = (PC + 1) & VAMASK; /* P+1 return for firmware OK */ - - if ((cpu_dev.dctrl & DEB_OS) && /* OS debug flag set, */ - (sim_deb == NULL)) /* but debugging disabled? */ - XR = 0; /* rev = 0 means RTE won't use ucode */ - else - XR = 010; /* firmware revision 10B = 8 */ - - if (debug_print) /* debugging? */ - fprint_regs (",", REG_X | REG_P_REL, /* print return registers */ - err_PC + 1); - } - break; - - case 016: /* .ENTC/$DEV 105356 (OP_N) */ - if (iotrap) { /* in trap cell? */ - reason = cpu_save_state (iotrap); /* device interrupt */ - AR = cpu_get_intbl (intaddr); /* get interrupt table value */ - - DEVINT: - vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ - - if (INT16 (AR) < 0) /* negative (program ID)? */ - PC = ReadW (vectors + sked_offset); /* vector to $SKED for processing */ - else if (AR > 0) /* positive (EQT address)? */ - PC = ReadW (vectors + cic2_offset); /* vector to $CIC2 for processing */ - else /* zero (illegal interrupt) */ - PC = ReadW (vectors + cic4_offset); /* vector to $CIC4 for processing */ - - if (debug_print) /* debugging? */ - fprintf (sim_deb, /* print return registers */ - ", CIR = %02o, INTBL = %06o", - intaddr, AR); - } - - else { /* .ENTC instruction */ - ma = (PC - 4) & VAMASK; /* get addr of entry point */ - goto ENTX; /* continue with common processing */ - } - break; - - case 017: /* .DSPI/$TBG 105357 (OP_N) */ - if (iotrap) { /* in trap cell? */ - reason = cpu_save_state (iotrap); /* TBG interrupt */ - vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ - PC = ReadW (vectors + clck_offset); /* vector to $CLCK for processing */ - - if (debug_print) /* debugging? */ - fprint_regs (",", REG_CIR, 0); /* print interrupt source */ - } - - else /* .DSPI instruction */ - reason = stop_inst; /* not implemented yet */ - - break; - } - -if (debug_print) { /* debugging? */ - fputc ('\n', sim_deb); /* terminate line */ - memcpy (save_env, mp_handler, sizeof (jmp_buf)); /* restore original MP handler */ - } - -return reason; -} +/* hp2100_cpu6.c: HP 1000 RTE-6/VM OS instructions + + Copyright (c) 2006-2016, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + CPU6 RTE-6/VM OS instructions + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 17-May-16 JDB Set local variable instead of call parameter for .SIP test + 24-Dec-14 JDB Added casts for explicit downward conversions + 18-Mar-13 JDB Use MP abort handler declaration in hp2100_cpu.h + 09-May-12 JDB Separated assignments from conditional expressions + 29-Oct-10 JDB DMA channels renamed from 0,1 to 1,2 to match documentation + 18-Sep-08 JDB Corrected .SIP debug formatting + 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h + 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 27-Nov-07 JDB Implemented OS instructions + 26-Sep-06 JDB Created + + Primary references: + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - Macro/1000 Reference Manual (92059-90001, Dec-1992) + + Additional references are listed with the associated firmware + implementations, as are the HP option model numbers pertaining to the + applicable CPUs. +*/ + + +#include +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + + +/* Offsets to data and addresses within RTE. */ + +static const uint32 xi = 0001647; /* XI address */ +static const uint32 intba = 0001654; /* INTBA address */ +static const uint32 intlg = 0001655; /* INTLG address */ +static const uint32 eqt1 = 0001660; /* EQT1 address */ +static const uint32 eqt11 = 0001672; /* EQT11 address */ +static const uint32 pvcn = 0001712; /* PVCN address */ +static const uint32 xsusp = 0001730; /* XSUSP address */ +static const uint32 dummy = 0001737; /* DUMMY address */ +static const uint32 mptfl = 0001770; /* MPTFL address */ +static const uint32 eqt12 = 0001771; /* EQT12 address */ +static const uint32 eqt15 = 0001774; /* EQT15 address */ +static const uint32 vctr = 0002000; /* VCTR address */ + +static const uint32 CLC_0 = 0004700; /* CLC 0 instruction */ +static const uint32 STC_0 = 0000700; /* STC 0 instruction */ +static const uint32 CLF_0 = 0001100; /* CLF 0 instruction */ +static const uint32 STF_0 = 0000100; /* STF 0 instruction */ +static const uint32 SFS_0_C = 0003300; /* SFS 0,C instruction */ + +enum vctr_offsets { dms_offset = 0, /* DMS status */ + int_offset, /* interrupt system status */ + sc_offset, /* select code */ + clck_offset, /* TBG IRQ handler */ + cic4_offset, /* illegal IRQ handler */ + cic2_offset, /* device IRQ handler */ + sked_offset, /* prog sched IRQ handler */ + rqst_offset, /* EXEC request handler */ + cic_offset, /* IRQ location */ + perr_offset, /* parity error IRQ handler */ + mper_offset, /* memory protect IRQ handler */ + lxnd_offset }; /* $LIBR return */ + + +/* RTE-6/VM Operating System Instructions + + The OS instructions were added to acccelerate certain time-consuming + operations of the RTE-6/VM operating system, HP product number 92084A. + Microcode was available for the E- and F-Series; the M-Series used software + equivalents. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A N/A 92084A 92084A + + The routines are mapped to instruction codes as follows: + + Instr. 1000-E/F Description + ------ -------- ---------------------------------------------- + $LIBR 105340 Enter privileged/reentrant library routine + $LIBX 105341 Exit privileged/reentrant library routine + .TICK 105342 TBG tick interrupt handler + .TNAM 105343 Find ID segment that matches name + .STIO 105344 Configure I/O instructions + .FNW 105345 Find word with user increment + .IRT 105346 Interrupt return processing + .LLS 105347 Linked list search + + .SIP 105350 Skip if interrupt pending + .YLD 105351 .SIP completion return point + .CPM 105352 Compare words LT/EQ/GT + .ETEQ 105353 Set up EQT pointers in base page + .ENTN 105354 Transfer parameter addresses (utility) + $OTST * 105355 OS firmware self test + .ENTC 105356 Transfer parameter addresses (priv/reent) + .DSPI 105357 Set display indicator + + Opcodes 105354-105357 are "dual use" instructions that take different + actions, depending on whether they are executed from a trap cell during an + interrupt. When executed from a trap cell, they have these actions: + + Instr. 1000-E/F Description + ------ -------- ---------------------------------------------- + $DCPC * 105354 DCPC channel interrupt processing + $MPV * 105355 MP/DMS/PE interrupt processing + $DEV * 105356 Standard device interrupt processing + $TBG * 105357 TBG interrupt processing + + * These mnemonics are recognized by symbolic examine/deposit but are not + official HP mnemonics. + + Implementation notes: + + 1. The microcode differentiates between interrupt processing and normal + execution of the "dual use" instructions by testing the CPU flag. + Interrupt vectoring sets the flag; a normal instruction fetch clears it. + Under simulation, interrupt vectoring is indicated by the value of the + "iotrap" parameter (0 = normal instruction, 1 = trap cell instruction). + + 2. The operand patterns for .ENTN and .ENTC normally would be coded as + "OP_A", as each takes a single address as a parameter. However, because + they might also be executed from a trap cell, we cannot assume that P+1 + is an address, or we might cause a DM abort when trying to resolve + indirects. Therefore, "OP_A" handling is done within each routine, once + the type of use is determined. + + 3. The microcode for .ENTC, .ENTN, .FNW, .LLS, .TICK, and .TNAM explicitly + checks for interrupts during instruction execution. In addition, the + .STIO, .CPM, and .LLS instructions implicitly check for interrupts during + parameter indirect resolution. Because the simulator calculates + interrupt requests only between instructions, this behavior is not + simulated. + + 4. The microcode executes certain I/O instructions (e.g., CLF 0) by building + the instruction in the IR and executing an IOG micro-order. We simulate + this behavior by calling the "iogrp" handler with the appropriate + instruction, rather than manipulating the I/O system directly, so that we + will remain unaffected by any future changes to the underlying I/O + simulation structure. + + 5. The $OTST and .DSPI microcode uses features (reading the RPL switches and + boot loader ROM data, loading the display register) that are not + simulated. The remaining functions of the $OTST instruction are + provided. The .DSPI instruction is a NOP or unimplemented instruction + stop. + + 6. Because of the volume of calls to the OS firmware, debug printouts + attempt to write only one line per instruction invocation. This means + that calling and returned register values are printed separately, with a + newline added at the end of execution. However, many instructions can MP + or DM abort, either intentionally or due to improper use. That would + leave debug lines without the required trailing newlines. + + There are two ways to address this: either we could replace the CPU's + setjmp buffer with one that points to a routine that adds the missing + newline, or we can add a semaphore that is tested on entry to see if it + is already set, implying a longjmp occurred, and then add the newline if + so. The latter would add the newline when the MP trap cell instruction + was entered or when the next user-level instruction was executed. + However, the merged-line problem would still exist if some other module + generated intervening debug printouts. So we do the former. This does + mean that this routine must be changed if the MP abort mechanism is + changed. + + 7. The $LIBX instruction is executed to complete either a privileged or + reentrant execution. In the former case, the privileged nest counter + ($PVCN) is decremented. In the latter, $PVCN decrement is attempted but + the write will trap with an MP violation, as reentrant routines execute + with the interrupt system on. RTE will then complete the release of + memory allocated for the original $LIBR call. + + 8. The documentation for the .SIP and .YLD instructions is misleading in + several places. Comments in the RTE $SIP source file say that .SIP + doesn't return if a "known" interrupt is pending. Actually, .SIP always + returns, either to P+1 for no pending interrupt, or to P+2 if one is + pending. There is no check for "known" interrupt handlers. The + microcode source comments say that the interrupting select code is + returned in the B register. Actually, the B register is unchanged. The + RTE Tech Specs say that .SIP "services any pending system interrupts." + Actually, .SIP only checks for interrupts; no servicing is performed. + + For .YLD, the microcode comments say that two parameters are passed: the + new P value, and the interrupting select code. Actually, only the new P + value is passed. + + The .SIP and .YLD simulations follow the actual microcode rather than the + documentation. + + Additional references: + - RTE-6/VM OS Microcode Source (92084-18831, revision 8). + - RTE-6/VM Technical Specifications (92084-90015, Apr-1983). +*/ + + +/* Save the CPU registers. + + The CPU registers are saved in the current ID segment in preparation for + interrupt handling. Although the RTE base page has separate pointers for the + P, A, B, and E/O registers, they are always contiguous, and the microcode + simply increments the P-register pointer (XSUSP) to store the remaining + values. + + This routine is called from the trap cell interrupt handlers and from the + $LIBX processor. In the latter case, the privileged system interrupt + handling is not required, so it is bypassed. In either case, the current map + will be the system map when we are called. +*/ + +static t_stat cpu_save_regs (uint32 iotrap) +{ +uint16 save_area, priv_fence; +t_stat reason = SCPE_OK; + +save_area = ReadW (xsusp); /* addr of PABEO save area */ + +WriteW (save_area + 0, PR); /* save P */ +WriteW (save_area + 1, AR); /* save A */ +WriteW (save_area + 2, BR); /* save B */ +WriteW (save_area + 3, (E << 15) & SIGN | O & 1); /* save E and O */ + +save_area = ReadW (xi); /* addr of XY save area */ +WriteWA (save_area + 0, XR); /* save X (in user map) */ +WriteWA (save_area + 1, YR); /* save Y (in user map) */ + +if (iotrap) { /* do priv setup only if IRQ */ + priv_fence = ReadW (dummy); /* get priv fence select code */ + + if (priv_fence) { /* privileged system? */ + reason = iogrp (STC_0 + priv_fence, iotrap); /* STC SC on priv fence */ + reason = iogrp (CLC_0 + DMA1, iotrap); /* CLC 6 to inh IRQ on DCPC 1 */ + reason = iogrp (CLC_0 + DMA2, iotrap); /* CLC 7 to inh IRQ on DCPC 2 */ + reason = iogrp (STF_0, iotrap); /* turn interrupt system back on */ + } + } + +return reason; +} + + +/* Save the machine state at interrupt. + + This routine is called from each of the trap cell instructions. Its purpose + is to save the complete state of the machine in preparation for interrupt + handling. + + For the MP/DMS/PE interrupt, the interrupting device must not be cleared and + the CPU registers must not be saved until it is established that the + interrupt is not caused by a parity error. Parity errors cannot be + inhibited, so the interrupt may have occurred while in RTE. Saving the + registers would overwrite the user's registers that were saved at RTE entry. + + Note that the trap cell instructions are dual-use and invoke this routine + only when they are executed during interrupts. Therefore, the current map + will always be the system map when we are called. +*/ + +static t_stat cpu_save_state (uint32 iotrap) +{ +uint16 vectors; +uint32 saved_PR, int_sys_off; +t_stat reason; + +saved_PR = PR; /* save current P register */ +reason = iogrp (SFS_0_C, iotrap); /* turn interrupt system off */ +int_sys_off = (PR == saved_PR); /* set flag if already off */ +PR = saved_PR; /* restore P in case it bumped */ + +vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ + +WriteW (vectors + dms_offset, dms_upd_sr ()); /* save DMS status (SSM) */ +WriteW (vectors + int_offset, int_sys_off); /* save int status */ +WriteW (vectors + sc_offset, intaddr); /* save select code */ + +WriteW (mptfl, 1); /* show MP is off */ + +if (intaddr != 5) { /* only if not MP interrupt */ + reason = iogrp (CLF_0 + intaddr, iotrap); /* issue CLF to device */ + cpu_save_regs (iotrap); /* save CPU registers */ + } + +return reason; +} + + +/* Get the interrupt table entry corresponding to a select code. + + Return the word in the RTE interrupt table that corresponds to the + interrupting select code. Return 0 if the select code is beyond the end of + the table. +*/ + +static uint16 cpu_get_intbl (uint32 select_code) +{ +uint16 interrupt_table; /* interrupt table (starts with SC 06) */ +uint16 table_length; /* length of interrupt table */ + +interrupt_table = ReadW (intba); /* get int table address */ +table_length = ReadW (intlg); /* get int table length */ + +if (select_code - 6 > table_length) /* SC beyond end of table? */ + return 0; /* return 0 for illegal interrupt */ +else + return ReadW (interrupt_table + select_code - 6); /* else return table entry */ +} + + +/* RTE-6/VM OS instruction dispatcher. + + Debugging printouts are provided with the OS and OSTBG debug flags. The OS + flag enables tracing for all instructions except for the three-instruction + sequence executed for the time-base generator interrupt ($TBG, .TICK, and + .IRT). The OSTBG flag enables tracing for just the TBG sequence. The flags + are separate, as the TBG generates 100 interrupts per second. Use caution + when specifying the OSTBG flag, as the debug output file will grow rapidly. + Note that the OS flag enables the .IRT instruction trace for all cases except + a TBG interrupt. + + The default (user microcode) dispatcher will allow the firmware self-test + instruction (105355) to execute as NOP. This is because RTE-6/VM will always + test for the presence of OS and VMA firmware on E/F-Series machines. If the + firmware is not present, then these instructions will return to P+1, and RTE + will then HLT 21. This means that RTE-6/VM will not run on an E/F-Series + machine without the OS and VMA firmware. + + Howwever, RTE allows the firmware instructions to be disabled for debugging + purposes. If the firmware is present and returns to P+2 but sets the X + register to 0, then RTE will use software equivalents. We enable this + condition when the OS firmware is enabled (SET CPU VMA), the OS debug flag is + set (SET CPU DEBUG=OS), but debug output has been disabled (SET CONSOLE + NODEBUG). That is: + + OS Debug + Firmware Debug Output Tracing Self-Test Instruction + ======== ===== ====== ======= ===================== + disabled x x off NOP + enabled clear x off X = revision code + enabled set off off X = 0 + enabled set on on X = revision code +*/ + +static const OP_PAT op_os[16] = { + OP_A, OP_A, OP_N, OP_N, /* $LIBR $LIBX .TICK .TNAM */ + OP_A, OP_K, OP_A, OP_KK, /* .STIO .FNW .IRT .LLS */ + OP_N, OP_C, OP_KK, OP_N, /* .SIP .YLD .CPM .ETEQ */ + OP_N, OP_N, OP_N, OP_N /* .ENTN $OTST .ENTC .DSPI */ + }; + +t_stat cpu_rte_os (uint32 IR, uint32 intrq, uint32 iotrap) +{ +t_stat reason = SCPE_OK; +OPS op; +OP_PAT pattern; +uint32 entry, count, cp, sa, da, i, ma, eqta, irq; +uint16 vectors, save_area, priv_fence, eoreg, eqt, key; +char test[6], target[6]; +jmp_buf mp_handler; +int abortval; +t_bool debug_print; +static t_bool tbg_tick = FALSE; /* set if processing TBG interrupt */ + +entry = IR & 017; /* mask to entry point */ +pattern = op_os[entry]; /* get operand pattern */ + +if (pattern != OP_N) { + reason = cpu_ops (pattern, op, intrq); /* get instruction operands */ + + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +tbg_tick = tbg_tick || (IR == 0105357) && iotrap; /* set TBG interrupting flag */ + +debug_print = (DEBUG_PRI (cpu_dev, DEB_OS) && !tbg_tick) || + (DEBUG_PRI (cpu_dev, DEB_OSTBG) && tbg_tick); + +if (debug_print) { + fprintf (sim_deb, ">>CPU OS: IR = %06o (", IR); /* print preamble and IR */ + fprint_sym (sim_deb, (iotrap ? intaddr : err_PC), /* print instruction mnemonic */ + (t_value *) &IR, NULL, SWMASK('M')); + fputc (')', sim_deb); + + fprint_ops (pattern, op); /* print operands */ + + memcpy (mp_handler, save_env, sizeof (jmp_buf)); /* save original MP handler */ + abortval = setjmp (save_env); /* set new MP abort handler */ + + if (abortval != 0) { /* MP abort? */ + fputs ("...MP abort\n", sim_deb); /* report it and terminate line */ + memcpy (save_env, mp_handler, sizeof (jmp_buf)); /* restore original MP handler */ + longjmp (save_env, abortval); /* transfer to MP handler */ + } + } + +switch (entry) { /* decode IR<3:0> */ + + case 000: /* $LIBR 105340 (OP_A) */ + if ((op[0].word != 0) || /* reentrant call? */ + (mp_control && (ReadW (dummy) != 0))) { /* or priv call + MP on + priv sys? */ + if (dms_ump) { /* called from user map? */ + dms_viol (err_PC, MVI_PRV); /* privilege violation */ + } + dms_ump = SMAP; /* set system map */ + + vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ + PR = ReadW (vectors + mper_offset); /* vector to $MPER for processing */ + } + + else { /* privileged call */ + if (mp_control) { /* memory protect on? */ + mp_control = CLEAR; /* turn it off */ + reason = iogrp (CLF_0, iotrap); /* turn interrupt system off */ + WriteW (mptfl, 1); /* show MP is off */ + save_area = ReadW (xsusp); /* get addr of P save area */ + + if (dms_ump) /* user map current? */ + WriteWA (save_area, (PR - 2) & VAMASK); /* set point of suspension */ + else /* system map current */ + WriteW (save_area, (PR - 2) & VAMASK); /* set point of suspension */ + } + + WriteW (pvcn, (ReadW (pvcn) + 1) & DMASK); /* increment priv nest counter */ + } + break; + + case 001: /* $LIBX 105341 (OP_A) */ + PR = ReadW (op[0].word); /* set P to return point */ + count = (ReadW (pvcn) - 1) & DMASK; /* decrement priv nest counter */ + WriteW (pvcn, count); /* write it back */ + + if (count == 0) { /* end of priv mode? */ + dms_ump = SMAP; /* set system map */ + reason = cpu_save_regs (iotrap); /* save registers */ + vectors = ReadW (vctr); /* get address of vectors */ + PR = ReadW (vectors + lxnd_offset); /* vector to $LXND for processing */ + } + break; + + case 002: /* .TICK 105342 (OP_N) */ + if (debug_print) /* debugging? */ + fprint_regs (",", REG_A | REG_B, 0); /* print entry registers */ + + do { + eqt = (ReadW (AR) + 1) & DMASK; /* bump timeout from EQT15 */ + + if (eqt != 1) { /* was timeout active? */ + WriteW (AR, eqt); /* yes, write it back */ + + if (eqt == 0) /* did timeout expire? */ + break; /* P+0 return for timeout */ + } + + AR = (AR + 15) & DMASK; /* point at next EQT15 */ + BR = (BR - 1) & DMASK; /* decrement count of EQTs */ + } while ((BR > 0) && (eqt != 0)); /* loop until timeout or done */ + + if (BR == 0) /* which termination condition? */ + PR = (PR + 1) & VAMASK; /* P+1 return for no timeout */ + + if (debug_print) /* debugging? */ + fprint_regs ("; result:", /* print return registers */ + REG_A | REG_B | REG_P_REL, + err_PC + 1); + break; + + case 003: /* .TNAM 105343 (OP_N) */ + if (debug_print) /* debugging? */ + fprint_regs (",", REG_A | REG_B, 0); /* print entry registers */ + + E = 1; /* preset flag for not found */ + cp = (BR << 1) & DMASK; /* form char addr (B is direct) */ + + for (i = 0; i < 5; i++) { /* copy target name */ + target[i] = (char) ReadB (cp); /* name is only 5 chars */ + cp = (cp + 1) & DMASK; + } + + if ((target[0] == '\0') && (target[1] == '\0')) /* if name is null, */ + break; /* return immed to P+0 */ + + key = ReadW (AR); /* get first keyword addr */ + + while (key != 0) { /* end of keywords? */ + cp = ((key + 12) << 1) & DMASK; /* form char addr of name */ + + for (i = 0; i < 6; i++) { /* copy test name */ + test[i] = (char) ReadB (cp); /* name is only 5 chars */ + cp = (cp + 1) & DMASK; /* but copy 6 to get flags */ + } + + if (strncmp (target, test, 5) == 0) { /* names match? */ + AR = (key + 15) & DMASK; /* A = addr of IDSEG [15] */ + BR = key; /* B = addr of IDSEG [0] */ + E = (uint32) ((test[5] >> 4) & 1); /* E = short ID segment bit */ + PR = (PR + 1) & VAMASK; /* P+1 for found return */ + break; + } + + AR = (AR + 1) & DMASK; /* bump to next keyword */ + key = ReadW (AR); /* get next keyword */ + }; + + if (debug_print) /* debugging? */ + fprint_regs ("; result:", /* print return registers */ + REG_A | REG_B | REG_E | REG_P_REL, + err_PC + 1); + break; + + case 004: /* .STIO 105344 (OP_A) */ + count = op[0].word - PR; /* get count of operands */ + + if (debug_print) /* debugging? */ + fprintf (sim_deb, /* print registers on entry */ + ", A = %06o, count = %d", AR, count); + + for (i = 0; i < count; i++) { + ma = ReadW (PR); /* get operand address */ + + reason = resolve (ma, &ma, intrq); /* resolve indirect */ + + if (reason != SCPE_OK) { /* resolution failed? */ + PR = err_PC; /* IRQ restarts instruction */ + break; + } + + WriteW (ma, ReadW (ma) & ~I_DEVMASK | AR); /* set SC into instruction */ + PR = (PR + 1) & VAMASK; /* bump to next */ + } + break; + + case 005: /* .FNW 105345 (OP_K) */ + if (debug_print) /* debugging? */ + fprint_regs (",", REG_A | REG_B | REG_X, 0); /* print entry registers */ + + while (XR != 0) { /* all comparisons done? */ + key = ReadW (BR); /* read a buffer word */ + + if (key == AR) { /* does it match? */ + PR = (PR + 1) & VAMASK; /* P+1 found return */ + break; + } + + BR = (BR + op[0].word) & DMASK; /* increment buffer ptr */ + XR = (XR - 1) & DMASK; /* decrement remaining count */ + } + /* P+0 not found return */ + if (debug_print) /* debugging? */ + fprint_regs ("; result:", /* print return registers */ + REG_A | REG_B | REG_X | REG_P_REL, + err_PC + 2); + break; + + case 006: /* .IRT 105346 (OP_A) */ + save_area = ReadW (xsusp); /* addr of PABEO save area */ + + WriteW (op[0].word, ReadW (save_area + 0)); /* restore P to DEF RTN */ + + AR = ReadW (save_area + 1); /* restore A */ + BR = ReadW (save_area + 2); /* restore B */ + + eoreg = ReadW (save_area + 3); /* get combined E and O */ + E = (eoreg >> 15) & 1; /* restore E */ + O = eoreg & 1; /* restore O */ + + save_area = ReadW (xi); /* addr of XY save area */ + XR = ReadWA (save_area + 0); /* restore X (from user map) */ + YR = ReadWA (save_area + 1); /* restore Y (from user map) */ + + reason = iogrp (CLF_0, iotrap); /* turn interrupt system off */ + WriteW (mptfl, 0); /* show MP is on */ + + priv_fence = ReadW (dummy); /* get priv fence select code */ + + if (priv_fence) { /* privileged system? */ + reason = iogrp (CLC_0 + priv_fence, iotrap); /* CLC SC on priv fence */ + reason = iogrp (STF_0 + priv_fence, iotrap); /* STF SC on priv fence */ + + if (cpu_get_intbl (DMA1) & SIGN) /* DCPC 1 active? */ + reason = iogrp (STC_0 + DMA1, iotrap); /* STC 6 to enable IRQ on DCPC 1 */ + + if (cpu_get_intbl (DMA2) & SIGN) /* DCPC 2 active? */ + reason = iogrp (STC_0 + DMA2, iotrap); /* STC 7 to enable IRQ on DCPC 2 */ + } + + tbg_tick = 0; /* .IRT terminates TBG servicing */ + break; + + case 007: /* .LLS 105347 (OP_KK) */ + if (debug_print) /* debugging? */ + fprint_regs (",", REG_A | REG_B | REG_E, 0); /* print entry registers */ + + AR = AR & ~SIGN; /* clear sign bit of A */ + + while ((AR != 0) && ((AR & SIGN) == 0)) { /* end of list or bad list? */ + key = ReadW ((AR + op[1].word) & VAMASK); /* get key value */ + + if ((E == 0) && (key == op[0].word) || /* for E = 0, key = arg? */ + (E != 0) && (key > op[0].word)) /* for E = 1, key > arg? */ + break; /* search is done */ + + BR = AR; /* B = last link */ + AR = ReadW (AR); /* A = next link */ + } + + if (AR == 0) /* exhausted list? */ + PR = (PR + 1) & VAMASK; /* P+1 arg not found */ + else if ((AR & SIGN) == 0) /* good link? */ + PR = (PR + 2) & VAMASK; /* P+2 arg found */ + /* P+0 bad link */ + if (debug_print) /* debugging? */ + fprint_regs ("; result:", /* print return registers */ + REG_A | REG_B | REG_P_REL, + err_PC + 3); + break; + + case 010: /* .SIP 105350 (OP_N) */ + reason = iogrp (STF_0, iotrap); /* turn interrupt system on */ + irq = calc_int (); /* check for interrupt requests */ + reason = iogrp (CLF_0, iotrap); /* turn interrupt system off */ + + if (irq) /* was interrupt pending? */ + PR = (PR + 1) & VAMASK; /* P+1 return for pending IRQ */ + /* P+0 return for no pending IRQ */ + if (debug_print) /* debugging? */ + fprintf (sim_deb, /* print return registers */ + ", CIR = %02o, return = P+%d", + irq, PR - (err_PC + 1)); + break; + + case 011: /* .YLD 105351 (OP_C) */ + PR = op[0].word; /* pick up point of resumption */ + reason = iogrp (STF_0, iotrap); /* turn interrupt system on */ + ion_defer = 0; /* kill defer so irq occurs immed */ + break; + + case 012: /* .CPM 105352 (OP_KK) */ + if (INT16 (op[0].word) > INT16 (op[1].word)) + PR = (PR + 2) & VAMASK; /* P+2 arg1 > arg2 */ + else if (INT16 (op[0].word) < INT16 (op[1].word)) + PR = (PR + 1) & VAMASK; /* P+1 arg1 < arg2 */ + /* P+0 arg1 = arg2 */ + if (debug_print) /* debugging? */ + fprint_regs (",", REG_P_REL, err_PC + 3); /* print return registers */ + break; + + case 013: /* .ETEQ 105353 (OP_N) */ + eqt = ReadW (eqt1); /* get addr of EQT1 */ + + if (AR != eqt) { /* already set up? */ + for (eqta = eqt1; eqta <= eqt11; eqta++) /* init EQT1-EQT11 */ + WriteW (eqta, AR++ & DMASK); + for (eqta = eqt12; eqta <= eqt15; eqta++) /* init EQT12-EQT15 */ + WriteW (eqta, AR++ & DMASK); /* (not contig with EQT1-11) */ + } + + AR = AR & DMASK; /* ensure wraparound */ + + if (debug_print) /* debugging? */ + fprintf (sim_deb, /* print return registers */ + ", A = %06o, EQT1 = %06o", AR, eqt); + break; + + case 014: /* .ENTN/$DCPC 105354 (OP_N) */ + if (iotrap) { /* in trap cell? */ + reason = cpu_save_state (iotrap); /* DMA interrupt */ + AR = cpu_get_intbl (intaddr) & ~SIGN; /* get intbl value and strip sign */ + goto DEVINT; /* vector by intbl value */ + } + + else { /* .ENTN instruction */ + ma = (PR - 2) & VAMASK; /* get addr of entry point */ + + ENTX: /* enter here from .ENTC */ + reason = cpu_ops (OP_A, op, intrq); /* get instruction operand */ + da = op[0].word; /* get addr of 1st formal */ + count = ma - da; /* get count of formals */ + sa = ReadW (ma); /* get addr of 1st actual */ + WriteW (ma, (sa + count) & VAMASK); /* adjust return point to skip actuals */ + + if (debug_print) /* debugging? */ + fprintf (sim_deb, /* print entry registers */ + ", op [0] = %06o, pcount = %d", + da, count); + + for (i = 0; i < count; i++) { /* parameter loop */ + ma = ReadW (sa); /* get addr of actual */ + sa = (sa + 1) & VAMASK; /* increment address */ + + reason = resolve (ma, &ma, intrq); /* resolve indirect */ + + if (reason != SCPE_OK) { /* resolution failed? */ + PR = err_PC; /* irq restarts instruction */ + break; + } + + WriteW (da, ma); /* put addr into formal */ + da = (da + 1) & VAMASK; /* increment address */ + } + + if (entry == 016) /* call was .ENTC? */ + AR = (uint16) sa; /* set A to return address */ + } + break; + + case 015: /* $OTST/$MPV 105355 (OP_N) */ + if (iotrap) { /* in trap cell? */ + reason = cpu_save_state (iotrap); /* MP/DMS/PE interrupt */ + vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ + + if (mp_viol & SIGN) { /* parity error? */ + WriteW (vectors + cic_offset, PR); /* save point of suspension in $CIC */ + PR = ReadW (vectors + perr_offset); /* vector to $PERR for processing */ + } + + else { /* MP/DMS violation */ + cpu_save_regs (iotrap); /* save CPU registers */ + PR = ReadW (vectors + rqst_offset); /* vector to $RQST for processing */ + } + + if (debug_print) { /* debugging? */ + fprint_regs (",", REG_CIR, 0); /* print interrupt source */ + /* and cause */ + if (mp_viol & SIGN) + fputs (", parity error", sim_deb); + else if (mp_mevff) + fputs (", DM violation", sim_deb); + else + fputs (", MP violation", sim_deb); + } + } + + else { /* self-test instruction */ + YR = 0000000; /* RPL switch (not implemented) */ + AR = 0000000; /* LDR [B] (not implemented) */ + SR = 0102077; /* test passed code */ + PR = (PR + 1) & VAMASK; /* P+1 return for firmware OK */ + + if ((cpu_dev.dctrl & DEB_OS) && /* OS debug flag set, */ + (sim_deb == NULL)) /* but debugging disabled? */ + XR = 0; /* rev = 0 means RTE won't use ucode */ + else + XR = 010; /* firmware revision 10B = 8 */ + + if (debug_print) /* debugging? */ + fprint_regs (",", REG_X | REG_P_REL, /* print return registers */ + err_PC + 1); + } + break; + + case 016: /* .ENTC/$DEV 105356 (OP_N) */ + if (iotrap) { /* in trap cell? */ + reason = cpu_save_state (iotrap); /* device interrupt */ + AR = cpu_get_intbl (intaddr); /* get interrupt table value */ + + DEVINT: + vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ + + if (INT16 (AR) < 0) /* negative (program ID)? */ + PR = ReadW (vectors + sked_offset); /* vector to $SKED for processing */ + else if (AR > 0) /* positive (EQT address)? */ + PR = ReadW (vectors + cic2_offset); /* vector to $CIC2 for processing */ + else /* zero (illegal interrupt) */ + PR = ReadW (vectors + cic4_offset); /* vector to $CIC4 for processing */ + + if (debug_print) /* debugging? */ + fprintf (sim_deb, /* print return registers */ + ", CIR = %02o, INTBL = %06o", + intaddr, AR); + } + + else { /* .ENTC instruction */ + ma = (PR - 4) & VAMASK; /* get addr of entry point */ + goto ENTX; /* continue with common processing */ + } + break; + + case 017: /* .DSPI/$TBG 105357 (OP_N) */ + if (iotrap) { /* in trap cell? */ + reason = cpu_save_state (iotrap); /* TBG interrupt */ + vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ + PR = ReadW (vectors + clck_offset); /* vector to $CLCK for processing */ + + if (debug_print) /* debugging? */ + fprint_regs (",", REG_CIR, 0); /* print interrupt source */ + } + + else /* .DSPI instruction */ + reason = stop_inst; /* not implemented yet */ + + break; + } + +if (debug_print) { /* debugging? */ + fputc ('\n', sim_deb); /* terminate line */ + memcpy (save_env, mp_handler, sizeof (jmp_buf)); /* restore original MP handler */ + } + +return reason; +} diff --git a/HP2100/hp2100_cpu7.c b/HP2100/hp2100_cpu7.c index fccccb2d..4a8cd721 100644 --- a/HP2100/hp2100_cpu7.c +++ b/HP2100/hp2100_cpu7.c @@ -1,942 +1,943 @@ -/* hp2100_cpu7.c: HP 1000 VIS and SIGNAL/1000 microcode - - Copyright (c) 2008, Holger Veit - Copyright (c) 2006-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the authors shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the authors. - - CPU7 Vector Instruction Set and SIGNAL firmware - - 24-Dec-14 JDB Added casts for explicit downward conversions - 18-Mar-13 JDB Moved EMA helper declarations to hp2100_cpu1.h - 09-May-12 JDB Separated assignments from conditional expressions - 06-Feb-12 JDB Corrected "opsize" parameter type in vis_abs - 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h - 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) - 30-Apr-08 JDB Updated SIGNAL code from Holger - 24-Apr-08 HV Implemented SIGNAL - 20-Apr-08 JDB Updated comments - 26-Feb-08 HV Implemented VIS - - Primary references: - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - Macro/1000 Reference Manual (92059-90001, Dec-1992) - - Additional references are listed with the associated firmware - implementations, as are the HP option model numbers pertaining to the - applicable CPUs. -*/ - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" - -#if defined (HAVE_INT64) /* int64 support available */ - -#include "hp2100_fp1.h" - - -static const OP zero = { { 0, 0, 0, 0, 0 } }; /* DEC 0.0D0 */ - - -/* Vector Instruction Set - - The VIS provides instructions that operate on one-dimensional arrays of - floating-point values. Both single- and double-precision operations are - supported. VIS uses the F-Series floating-point processor to handle the - floating-point math. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A N/A N/A 12824A - - The routines are mapped to instruction codes as follows: - - Single-Precision Double-Precision - Instr. Opcode Subcod Instr. Opcode Subcod Description - ------ ------ ------ ------ ------ ------ ----------------------------- - VADD 101460 000000 DVADD 105460 004002 Vector add - VSUB 101460 000020 DVSUB 105460 004022 Vector subtract - VMPY 101460 000040 DVMPY 105460 004042 Vector multiply - VDIV 101460 000060 DVDIV 105460 004062 Vector divide - VSAD 101460 000400 DVSAD 105460 004402 Scalar-vector add - VSSB 101460 000420 DVSSB 105460 004422 Scalar-vector subtract - VSMY 101460 000440 DVSMY 105460 004442 Scalar-vector multiply - VSDV 101460 000460 DVSDV 105460 004462 Scalar-vector divide - VPIV 101461 0xxxxx DVPIV 105461 0xxxxx Vector pivot - VABS 101462 0xxxxx DVABS 105462 0xxxxx Vector absolute value - VSUM 101463 0xxxxx DVSUM 105463 0xxxxx Vector sum - VNRM 101464 0xxxxx DVNRM 105464 0xxxxx Vector norm - VDOT 101465 0xxxxx DVDOT 105465 0xxxxx Vector dot product - VMAX 101466 0xxxxx DVMAX 105466 0xxxxx Vector maximum value - VMAB 101467 0xxxxx DVMAB 105467 0xxxxx Vector maximum absolute value - VMIN 101470 0xxxxx DVMIN 105470 0xxxxx Vector minimum value - VMIB 101471 0xxxxx DVMIB 105471 0xxxxx Vector minimum absolute value - VMOV 101472 0xxxxx DVMOV 105472 0xxxxx Vector move - VSWP 101473 0xxxxx DVSWP 105473 0xxxxx Vector swap - .ERES 101474 -- -- -- -- Resolve array element address - .ESEG 101475 -- -- -- -- Load MSEG maps - .VSET 101476 -- -- -- -- Vector setup - [test] -- -- -- 105477 -- [self test] - - Instructions use IR bit 11 to select single- or double-precision format. The - double-precision instruction names begin with "D" (e.g., DVADD vs. VADD). - Most VIS instructions are two words in length, with a sub-opcode immediately - following the primary opcode. - - Notes: - - 1. The .VECT (101460) and .DVCT (105460) opcodes preface a single- or - double-precision arithmetic operation that is determined by the - sub-opcode value. The remainder of the dual-precision sub-opcode values - are "don't care," except for requiring a zero in bit 15. - - 2. The VIS uses the hardware FPP of the F-Series. FPP malfunctions are - detected by the VIS firmware and are indicated by a memory-protect - violation and setting the overflow flag. Under simulation, - malfunctions cannot occur. - - Additional references: - - 12824A Vector Instruction Set User's Manual (12824-90001, Jun-1979). - - VIS Microcode Source (12824-18059, revision 3). -*/ - -static const OP_PAT op_vis[16] = { - OP_N, OP_AAKAKAKK,OP_AKAKK, OP_AAKK, /* .VECT VPIV VABS VSUM */ - OP_AAKK, OP_AAKAKK, OP_AAKK, OP_AAKK, /* VNRM VDOT VMAX VMAB */ - OP_AAKK, OP_AAKK, OP_AKAKK, OP_AKAKK, /* VMIN VMIB VMOV VSWP */ - OP_AA, OP_A, OP_AAACCC,OP_N /* .ERES .ESEG .VSET [test] */ - }; - -static const t_bool op_ftnret[16] = { - FALSE, TRUE, TRUE, TRUE, - TRUE, TRUE, TRUE, TRUE, - TRUE, TRUE, TRUE, TRUE, - FALSE, TRUE, TRUE, FALSE, - }; - - -/* handle the scalar/vector base ops */ -static void vis_svop(uint32 subcode, OPS op, OPSIZE opsize) -{ -OP v1,v2; -int16 delta = opsize==fp_f ? 2 : 4; -OP s = ReadOp(op[0].word,opsize); -uint32 v1addr = op[1].word; -int16 ix1 = INT16(op[2].word) * delta; -uint32 v2addr = op[3].word; -int16 ix2 = INT16(op[4].word) * delta; -int16 i, n = INT16(op[5].word); -uint16 fpuop = (uint16) (subcode & 060) | (opsize==fp_f ? 0 : 2); - -if (n <= 0) return; -for (i=0; ifpk[0] & 0100000) - -static void vis_abs(OP* in, OPSIZE opsize) -{ -uint32 sign = GET_MSIGN(in); /* get sign */ -if (sign) (void)fp_pcom(in, opsize); /* if negative, make positive */ -} - -static void vis_minmax(OPS op,OPSIZE opsize,t_bool domax,t_bool doabs) -{ -OP v1,vmxmn,res; -int16 delta = opsize==fp_f ? 2 : 4; -uint32 mxmnaddr = op[0].word; -uint32 v1addr = op[1].word; -int16 ix1 = INT16(op[2].word) * delta; -int16 n = INT16(op[3].word); -int16 i,mxmn,sign; -uint16 subop = 020 | (opsize==fp_f ? 0 : 2); - -if (n <= 0) return; -mxmn = 0; /* index of maxmin element */ -vmxmn = ReadOp(v1addr,opsize); /* initialize with first element */ -if (doabs) vis_abs(&vmxmn,opsize); /* ABS(v[1]) if requested */ - -for (i = 0; ifpk[0] = in.fpk[0]; -out->fpk[1] = (in.fpk[1] & 0177400) | (in.fpk[3] & 0377); -} - -static void vis_vsmnm(OPS op,OPSIZE opsize,t_bool doabs) -{ -uint16 fpuop; -OP v1,sumnrm = zero; -int16 delta = opsize==fp_f ? 2 : 4; -uint32 saddr = op[0].word; -uint32 v1addr = op[1].word; -int16 ix1 = INT16(op[2].word) * delta; -int16 i,n = INT16(op[3].word); - -if (n <= 0) return; -/* calculates sumnrm = sumnrm + DBLE(v1[i]) resp DBLE(ABS(v1[i])) for incrementing i */ -for (i=0; i>CPU VIS: IR = %06o/%06o (", /* print preamble and IR */ - IR, subcode); - fprint_sym (sim_deb, err_PC, (t_value *) &IR, /* print instruction mnemonic */ - NULL, SWMASK('M')); - fprintf (sim_deb, "), P = %06o", err_PC); /* print location */ - fprint_ops (pattern, op); /* print operands */ - fputc ('\n', sim_deb); /* terminate line */ - } - -switch (entry) { /* decode IR<3:0> */ - case 000: /* .VECT (OP_special) */ - if (subcode & 0400) - vis_svop(subcode,op,opsize); /* scalar/vector op */ - else - vis_vvop(subcode,op,opsize); /* vector/vector op */ - break; - case 001: /* VPIV (OP_(A)AAKAKAKK) */ - vis_vpiv(op,opsize); - break; - case 002: /* VABS (OP_(A)AKAKK) */ - vis_vabs(op,opsize); - break; - case 003: /* VSUM (OP_(A)AAKK) */ - vis_vsmnm(op,opsize,FALSE); - break; - case 004: /* VNRM (OP_(A)AAKK) */ - vis_vsmnm(op,opsize,TRUE); - break; - case 005: /* VDOT (OP_(A)AAKAKK) */ - vis_vdot(op,opsize); - break; - case 006: /* VMAX (OP_(A)AAKK) */ - vis_minmax(op,opsize,TRUE,FALSE); - break; - case 007: /* VMAB (OP_(A)AAKK) */ - vis_minmax(op,opsize,TRUE,TRUE); - break; - case 010: /* VMIN (OP_(A)AAKK) */ - vis_minmax(op,opsize,FALSE,FALSE); - break; - case 011: /* VMIB (OP_(A)AAKK) */ - vis_minmax(op,opsize,FALSE,TRUE); - break; - case 012: /* VMOV (OP_(A)AKAKK) */ - vis_movswp(op,opsize,FALSE); - break; - case 013: /* VSWP (OP_(A)AKAKK) */ - vis_movswp(op,opsize,TRUE); - break; - case 014: /* .ERES (OP_(A)AA) */ - reason = cpu_ema_eres(&rtn,op[2].word,PC,debug); /* handle the ERES instruction */ - PC = rtn; - if (debug) - fprintf (sim_deb, - ">>CPU VIS: return .ERES: AR = %06o, BR = %06o, rtn=%s\n", - AR, BR, PC==op[0].word ? "error" : "good"); - break; - - case 015: /* .ESEG (OP_(A)A) */ - reason = cpu_ema_eseg(&rtn,IR,op[0].word,debug); /* handle the ESEG instruction */ - PC = rtn; - if (debug) - fprintf (sim_deb, - ">>CPU VIS: return .ESEG: AR = %06o , BR = %06o, rtn=%s\n", - AR, BR, rtn==rtn1 ? "error" : "good"); - break; - - case 016: /* .VSET (OP_(A)AAACCC) */ - reason = cpu_ema_vset(&rtn,op,debug); - PC = rtn; - if (debug) - fprintf (sim_deb, ">>CPU VIS: return .VSET: AR = %06o BR = %06o, rtn=%s\n", - AR, BR, - rtn==rtn1 ? "error" : (rtn==(rtn1+1) ? "hard" : "easy") ); - break; - - case 017: /* [test] (OP_N) */ - XR = 3; /* firmware revision */ - SR = 0102077; /* test passed code */ - PC = (PC + 1) & VAMASK; /* P+2 return for firmware w/VIS */ - break; - default: /* others undefined */ - reason = stop_inst; - } - -return reason; -} - - -/* SIGNAL/1000 Instructions - - The SIGNAL/1000 instructions provide fast Fourier transforms and complex - arithmetic. They utilize the F-Series floating-point processor and the - Vector Instruction Set. - - Option implementation by CPU was as follows: - - 2114 2115 2116 2100 1000-M 1000-E 1000-F - ------ ------ ------ ------ ------ ------ ------ - N/A N/A N/A N/A N/A N/A 92835A - - The routines are mapped to instruction codes as follows: - - Instr. 1000-F Description - ------ ------ ---------------------------------------------- - BITRV 105600 Bit reversal - BTRFY 105601 Butterfly algorithm - UNSCR 105602 Unscramble for phasor MPY - PRSCR 105603 Unscramble for phasor MPY - BITR1 105604 Swap two elements in array (alternate format) - BTRF1 105605 Butterfly algorithm (alternate format) - .CADD 105606 Complex number addition - .CSUB 105607 Complex number subtraction - .CMPY 105610 Complex number multiplication - .CDIV 105611 Complex number division - CONJG 105612 Complex conjugate - ..CCM 105613 Complex complement - AIMAG 105614 Return imaginary part - CMPLX 105615 Form complex number - [nop] 105616 [no operation] - [test] 105617 [self test] - - Notes: - - 1. SIGNAL/1000 ROM data are available from Bitsavers. - - Additional references (documents unavailable): - - HP Signal/1000 User Reference and Installation Manual (92835-90002). - - SIGNAL/1000 Microcode Source (92835-18075, revision 2). -*/ - -#define RE(x) (x+0) -#define IM(x) (x+2) - -static const OP_PAT op_signal[16] = { - OP_AAKK, OP_AAFFKK, OP_AAFFKK,OP_AAFFKK, /* BITRV BTRFY UNSCR PRSCR */ - OP_AAAKK, OP_AAAFFKK,OP_AAA, OP_AAA, /* BITR1 BTRF1 .CADD .CSUB */ - OP_AAA, OP_AAA, OP_AAA, OP_A, /* .CMPY .CDIV CONJG ..CCM */ - OP_AA, OP_AAFF, OP_N, OP_N /* AIMAG CMPLX --- [test]*/ - }; - -/* complex addition helper */ -static void sig_caddsub(uint16 addsub,OPS op) -{ -OP a,b,c,d,p1,p2; - -a = ReadOp(RE(op[1].word), fp_f); /* read 1st op */ -b = ReadOp(IM(op[1].word), fp_f); -c = ReadOp(RE(op[2].word), fp_f); /* read 2nd op */ -d = ReadOp(IM(op[2].word), fp_f); -(void)fp_exec(addsub,&p1, a, c); /* add real */ -(void)fp_exec(addsub,&p2, b, d); /* add imag */ -WriteOp(RE(op[0].word), p1, fp_f); /* write result */ -WriteOp(IM(op[0].word), p2, fp_f); /* write result */ -} - -/* butterfly operation helper */ -static void sig_btrfy(uint32 re,uint32 im,OP wr,OP wi,uint32 k, uint32 n2) -{ -/* - * v(k)-------->o-->o----> v(k) - * \ / - * x - * / \ - * v(k+N/2)---->o-->o----> v(k+N/2) - * Wn -1 - * - */ - -OP p1,p2,p3,p4; -OP v1r = ReadOp(re+k, fp_f); /* read v1 */ -OP v1i = ReadOp(im+k, fp_f); -OP v2r = ReadOp(re+k+n2, fp_f); /* read v2 */ -OP v2i = ReadOp(im+k+n2, fp_f); - -/* (p1,p2) := cmul(w,v2) */ -(void)fp_exec(040, &p1, wr, v2r); /* S7,8 p1 := wr*v2r */ -(void)fp_exec(040, ACCUM, wi, v2i); /* ACCUM := wi*v2i */ -(void)fp_exec(024, &p1, p1, NOP); /* S7,S8 p1 := wr*v2r-wi*v2i ==real(w*v2) */ -(void)fp_exec(040, &p2, wi, v2r); /* S9,10 p2 := wi*v2r */ -(void)fp_exec(040, ACCUM, wr, v2i); /* ACCUM := wr*v2i */ -(void)fp_exec(004, &p2, p2, NOP); /* S9,10 p2 := wi*v2r+wr*v2i ==imag(w*v2) */ -/* v2 := v1 - (p1,p2) */ -(void)fp_exec(020, &p3, v1r, p1); /* v2r := v1r-real(w*v2) */ -(void)fp_exec(020, &p4, v1i, p2); /* v2i := v1i-imag(w*v2) */ -WriteOp(re+k+n2, p3, fp_f); /* write v2r */ -WriteOp(im+k+n2, p4, fp_f); /* write v2i */ -/* v1 := v1 + (p1,p2) */ -(void)fp_exec(0, &p3, v1r, p1); /* v1r := v1r+real(w*v2) */ -(void)fp_exec(0, &p4, v1i, p2); /* v1i := v1i+imag(w*v2) */ -WriteOp(re+k, p3, fp_f); /* write v1r */ -WriteOp(im+k, p4, fp_f); /* write v1i */ -O = 0; -} - -/* helper for bit reversal - * idx is 0-based already */ -static void sig_bitrev(uint32 re,uint32 im, uint32 idx, uint32 log2n, int sz) -{ -uint32 i, org=idx, rev = 0; -OP v1r,v1i,v2r,v2i; - -for (i=0; i>= 1; -} - -if (rev < idx) return; /* avoid swapping same pair twice in loop */ - -idx *= sz; /* adjust for element size */ -rev *= sz; /* (REAL*4 vs COMPLEX*8) */ - -v1r = ReadOp(re+idx, fp_f); /* read 1st element */ -v1i = ReadOp(im+idx, fp_f); -v2r = ReadOp(re+rev, fp_f); /* read 2nd element */ -v2i = ReadOp(im+rev, fp_f); -WriteOp(re+idx, v2r, fp_f); /* swap elements */ -WriteOp(im+idx, v2i, fp_f); -WriteOp(re+rev, v1r, fp_f); -WriteOp(im+rev, v1i, fp_f); -} - -/* helper for PRSCR/UNSCR */ -static OP sig_scadd(uint16 oper,t_bool addh, OP a, OP b) -{ -OP r; -static const OP plus_half = { { 0040000, 0000000 } }; /* DEC +0.5 */ - -(void)fp_exec(oper,&r,a,b); /* calculate r := a +/- b */ -if (addh) (void)fp_exec(044,&r,plus_half,NOP); /* if addh set, multiply by 0.5 */ -return r; -} - -/* complex multiply helper */ -static void sig_cmul(OP *r, OP *i, OP a, OP b, OP c, OP d) -{ -OP p; -(void)fp_exec(040, &p , a, c); /* p := ac */ -(void)fp_exec(040, ACCUM, b, d); /* ACCUM := bd */ -(void)fp_exec(024, r, p , NOP); /* real := ac-bd */ -(void)fp_exec(040, &p, a, d); /* p := ad */ -(void)fp_exec(040, ACCUM, b, c); /* ACCUM := bc */ -(void)fp_exec(004, i, p, NOP); /* imag := ad+bc */ -} - -t_stat cpu_signal (uint32 IR, uint32 intrq) -{ -t_stat reason = SCPE_OK; -OPS op; -OP a,b,c,d,p1,p2,p3,p4,m1,m2,wr,wi; -uint32 entry, v, idx1, idx2; -int32 exc, exd; - -t_bool debug = DEBUG_PRI (cpu_dev, DEB_SIG); - -entry = IR & 017; /* mask to entry point */ - -if (op_signal [entry] != OP_N) { - reason = cpu_ops (op_signal [entry], op, intrq); /* get instruction operands */ - if (reason != SCPE_OK) /* evaluation failed? */ - return reason; /* return reason for failure */ - } - -if (debug) { /* debugging? */ - fprintf (sim_deb, ">>CPU SIG: IR = %06o (", IR); /* print preamble and IR */ - fprint_sym (sim_deb, err_PC, (t_value *) &IR, /* print instruction mnemonic */ - NULL, SWMASK('M')); - fprintf (sim_deb, "), P = %06o", err_PC); /* print location */ - fprint_ops (op_signal[entry], op); /* print operands */ - fputc ('\n', sim_deb); /* terminate line */ - } - -switch (entry) { /* decode IR<3:0> */ - case 000: /* BITRV (OP_AAKK) */ - /* BITRV - * bit reversal for FFT - * JSB BITRV - * DEF ret(,I) return address - * DEF vect,I base address of array - * DEF idx,I index bitmap to be reversed (one-based) - * DEF nbits,I number of bits of index - * - * Given a complex*8 vector of nbits (power of 2), this calculates: - * swap( vect[idx], vect[rev(idx)]) where rev(i) is the bitreversed value of i */ - sig_bitrev(op[1].word, op[1].word+2, op[2].word-1, op[3].word, 4); - PC = op[0].word & VAMASK; - break; - - case 001: /* BTRFY (OP_AAFFKK) */ - /* BTRFY - butterfly operation - * JSB BTRFY - * DEF ret(,I) return address - * DEF vect(,I) complex*8 vector - * DEF wr,I real part of W - * DEF wi,I imag part of W - * DEF node,I index of 1st op (1 based) - * DEF lmax,I offset to 2nd op (0 based) */ - sig_btrfy(op[1].word, op[1].word+2, - op[2], op[3], - 2*(op[4].word-1), 2*op[5].word); - PC = op[0].word & VAMASK; - break; - - case 002: /* UNSCR (OP_AAFFKK) */ - /* UNSCR unscramble for phasor MPY - * JSB UNSCR - * DEF ret(,I) - * DEF vector,I - * DEF WR - * DEF WI - * DEF idx1,I - * DEF idx2,I */ - v = op[1].word; - idx1 = 2 * (op[4].word - 1); - idx2 = 2 * (op[5].word - 1); - wr = op[2]; /* read WR */ - wi = op[3]; /* read WI */ - p1 = ReadOp(RE(v + idx1), fp_f); /* S1 VR[idx1] */ - p2 = ReadOp(RE(v + idx2), fp_f); /* S2 VR[idx2] */ - p3 = ReadOp(IM(v + idx1), fp_f); /* S9 VI[idx1] */ - p4 = ReadOp(IM(v + idx2), fp_f); /* S10 VI[idx2] */ - c = sig_scadd(000, TRUE, p3, p4); /* S5,6 0.5*(p3+p4) */ - d = sig_scadd(020, TRUE, p2, p1); /* S7,8 0.5*(p2-p1) */ - sig_cmul(&m1, &m2, wr, wi, c, d); /* (WR,WI) * (c,d) */ - c = sig_scadd(000, TRUE, p1, p2); /* 0.5*(p1+p2) */ - d = sig_scadd(020, TRUE, p3, p4); /* 0.5*(p3-p4) */ - (void)fp_exec(000, &p1, c, m1); /* VR[idx1] := 0.5*(p1+p2) + real(W*(c,d)) */ - WriteOp(RE(v + idx1), p1, fp_f); - (void)fp_exec(000, &p2, d, m2); /* VI[idx1] := 0.5*(p3-p4) + imag(W*(c,d)) */ - WriteOp(IM(v + idx1), p2, fp_f); - (void)fp_exec(020, &p1, c, m1); /* VR[idx2] := 0.5*(p1+p2) - imag(W*(c,d)) */ - WriteOp(RE(v + idx2), p1, fp_f); - (void)fp_exec(020, &p2, d, m2); /* VI[idx2] := 0.5*(p3-p4) - imag(W*(c,d)) */ - WriteOp(IM(v + idx2), p2, fp_f); - PC = op[0].word & VAMASK; - break; - - case 003: /* PRSCR (OP_AAFFKK) */ - /* PRSCR unscramble for phasor MPY - * JSB PRSCR - * DEF ret(,I) - * DEF vector,I - * DEF WR - * DEF WI - * DEF idx1,I - * DEF idx2,I */ - v = op[1].word; - idx1 = 2 * (op[4].word - 1); - idx2 = 2 * (op[5].word - 1); - wr = op[2]; /* read WR */ - wi = op[3]; /* read WI */ - p1 = ReadOp(RE(v + idx1), fp_f); /* VR[idx1] */ - p2 = ReadOp(RE(v + idx2), fp_f); /* VR[idx2] */ - p3 = ReadOp(IM(v + idx1), fp_f); /* VI[idx1] */ - p4 = ReadOp(IM(v + idx2), fp_f); /* VI[idx2] */ - c = sig_scadd(020, FALSE, p1, p2); /* p1-p2 */ - d = sig_scadd(000, FALSE, p3, p4); /* p3+p4 */ - sig_cmul(&m1,&m2, wr, wi, c, d); /* (WR,WI) * (c,d) */ - c = sig_scadd(000, FALSE, p1, p2); /* p1+p2 */ - d = sig_scadd(020, FALSE, p3,p4); /* p3-p4 */ - (void)fp_exec(020, &p1, c, m2); /* VR[idx1] := (p1-p2) - imag(W*(c,d)) */ - WriteOp(RE(v + idx1), p1, fp_f); - (void)fp_exec(000, &p2, d, m1); /* VI[idx1] := (p3-p4) + real(W*(c,d)) */ - WriteOp(IM(v + idx1), p2, fp_f); - (void)fp_exec(000, &p1, c, m2); /* VR[idx2] := (p1+p2) + imag(W*(c,d)) */ - WriteOp(RE(v + idx2), p1, fp_f); - (void)fp_exec(020, &p2, m1, d); /* VI[idx2] := imag(W*(c,d)) - (p3-p4) */ - WriteOp(IM(v + idx2), p2, fp_f); - PC = op[0].word & VAMASK; - break; - - case 004: /* BITR1 (OP_AAAKK) */ - /* BITR1 - * bit reversal for FFT, alternative version - * JSB BITR1 - * DEF ret(,I) return address if already swapped - * DEF revect,I base address of real vect - * DEF imvect,I base address of imag vect - * DEF idx,I index bitmap to be reversed (one-based) - * DEF nbits,I number of bits of index - * - * Given a complex*8 vector of nbits (power of 2), this calculates: - * swap( vect[idx], vect[rev(idx)]) where rev(i) is the bitreversed value of i - * - * difference to BITRV is that BITRV uses complex*8, and BITR1 uses separate real*4 - * vectors for Real and Imag parts */ - sig_bitrev(op[1].word, op[2].word, op[3].word-1, op[4].word, 2); - PC = op[0].word & VAMASK; - break; - - - case 005: /* BTRF1 (OP_AAAFFKK) */ - /* BTRF1 - butterfly operation with real*4 vectors - * JSB BTRF1 - * DEF ret(,I) return address - * DEF rvect,I real part of vector - * DEF ivect,I imag part of vector - * DEF wr,I real part of W - * DEF wi,I imag part of W - * DEF node,I index (1 based) - * DEF lmax,I index (0 based) */ - sig_btrfy(op[1].word, op[2].word, - op[3], op[4], - op[5].word-1, op[6].word); - PC = op[0].word & VAMASK; - break; - - case 006: /* .CADD (OP_AAA) */ - /* .CADD Complex addition - * JSB .CADD - * DEF result,I - * DEF oprd1,I - * DEF oprd2,I - * complex addition is: (a+bi) + (c+di) => (a+c) + (b+d)i */ - sig_caddsub(000,op); - break; - - case 007: /* .CSUB (OP_AAA) */ - /* .CSUB Complex subtraction - * JSB .CSUB - * DEF result,I - * DEF oprd1,I - * DEF oprd2,I - * complex subtraction is: (a+bi) - (c+di) => (a - c) + (b - d)i */ - sig_caddsub(020,op); - break; - - case 010: /* .CMUL (OP_AAA) */ - /* .CMPY Complex multiplication - * call: - * JSB .CMPY - * DEF result,I - * DEF oprd1,I - * DEF oprd2,I - * complex multiply is: (a+bi)*(c+di) => (ac-bd) + (ad+bc)i */ - a = ReadOp(RE(op[1].word), fp_f); /* read 1st op */ - b = ReadOp(IM(op[1].word), fp_f); - c = ReadOp(RE(op[2].word), fp_f); /* read 2nd op */ - d = ReadOp(IM(op[2].word), fp_f); - sig_cmul(&p1, &p2, a, b, c, d); - WriteOp(RE(op[0].word), p1, fp_f); /* write real result */ - WriteOp(IM(op[0].word), p2, fp_f); /* write imag result */ - break; - - case 011: /* .CDIV (OP_AAA) */ - /* .CDIV Complex division - * call: - * JSB .CDIV - * DEF result,I - * DEF oprd1,I - * DEF oprd2,I - * complex division is: (a+bi)/(c+di) => ((ac+bd) + (bc-ad)i)/(c^2+d^2) */ - a = ReadOp(RE(op[1].word), fp_f); /* read 1st op */ - b = ReadOp(IM(op[1].word), fp_f); - c = ReadOp(RE(op[2].word), fp_f); /* read 2nd op */ - d = ReadOp(IM(op[2].word), fp_f); - (void)fp_unpack (NULL, &exc, c, fp_f); /* get exponents */ - (void)fp_unpack (NULL, &exd, d, fp_f); - if (exc < exd) { /* ensure c/d < 1 */ - p1 = a; a = c; c = p1; /* swap dividend and divisor */ - p1 = b; b = d; d = p1; - } - (void)fp_exec(060, &p1, d, c); /* p1,accu := d/c */ - (void)fp_exec(044, ACCUM, d, NOP); /* ACCUM := dd/c */ - (void)fp_exec(004, &p2, c, NOP); /* p2 := c + dd/c */ - (void)fp_exec(040, ACCUM, b, p1); /* ACCUM := bd/c */ - (void)fp_exec(004, ACCUM, a, NOP); /* ACCUM := a + bd/c */ - (void)fp_exec(070, &p3, NOP, p2); /* p3 := (a+bd/c)/(c+dd/c) == (ac+bd)/(cc+dd) */ - WriteOp(RE(op[0].word), p3, fp_f); /* Write real result */ - (void)fp_exec(040, ACCUM, a, p1); /* ACCUM := ad/c */ - (void)fp_exec(030, ACCUM, NOP, b); /* ACCUM := ad/c - b */ - if (exd < exc) { /* was not swapped? */ - (void)fp_exec(024, ACCUM, zero, NOP); /* ACCUM := -ACCUM */ - } - (void)fp_exec(070, &p3, NOP, p2); /* p3 := (b-ad/c)/(c+dd/c) == (bc-ad)/cc+dd) */ - WriteOp(IM(op[0].word), p3, fp_f); /* Write imag result */ - break; - - case 012: /* CONJG (OP_AAA) */ - /* CONJG build A-Bi from A+Bi - * call: - * JSB CONJG - * DEF RTN - * DEF res,I result - * DEF arg,I input argument */ - a = ReadOp(RE(op[2].word), fp_f); /* read real */ - b = ReadOp(IM(op[2].word), fp_f); /* read imag */ - (void)fp_pcom(&b, fp_f); /* negate imag */ - WriteOp(RE(op[1].word), a, fp_f); /* write real */ - WriteOp(IM(op[1].word), b, fp_f); /* write imag */ - break; - - case 013: /* ..CCM (OP_A) */ - /* ..CCM complement complex - * call - * JSB ..CCM - * DEF arg - * build (-RE,-IM) - */ - v = op[0].word; - a = ReadOp(RE(v), fp_f); /* read real */ - b = ReadOp(IM(v), fp_f); /* read imag */ - (void)fp_pcom(&a, fp_f); /* negate real */ - (void)fp_pcom(&b, fp_f); /* negate imag */ - WriteOp(RE(v), a, fp_f); /* write real */ - WriteOp(IM(v), b, fp_f); /* write imag */ - break; - - case 014: /* AIMAG (OP_AA) */ - /* AIMAG return the imaginary part in AB - * JSB AIMAG - * DEF *+2 - * DEF cplx(,I) - * returns: AB imaginary part of complex number */ - a = ReadOp(IM(op[1].word), fp_f); /* read imag */ - AR = a.fpk[0]; /* move MSB to A */ - BR = a.fpk[1]; /* move LSB to B */ - break; - - case 015: /* CMPLX (OP_AFF) */ - /* CMPLX form a complex number - * JSB CMPLX - * DEF *+4 - * DEF result,I complex number - * DEF repart,I real value - * DEF impart,I imaginary value */ - WriteOp(RE(op[1].word), op[2], fp_f); /* write real part */ - WriteOp(IM(op[1].word), op[3], fp_f); /* write imag part */ - break; - - case 017: /* [slftst] (OP_N) */ - XR = 2; /* firmware revision */ - SR = 0102077; /* test passed code */ - PC = (PC + 1) & VAMASK; /* P+2 return for firmware w/SIGNAL1000 */ - break; - - case 016: /* invalid */ - default: /* others undefined */ - reason = stop_inst; - } - -return reason; -} - -#endif /* end of int64 support */ +/* hp2100_cpu7.c: HP 1000 VIS and SIGNAL/1000 microcode + + Copyright (c) 2008, Holger Veit + Copyright (c) 2006-2016, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. + + CPU7 Vector Instruction Set and SIGNAL firmware + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 24-Dec-14 JDB Added casts for explicit downward conversions + 18-Mar-13 JDB Moved EMA helper declarations to hp2100_cpu1.h + 09-May-12 JDB Separated assignments from conditional expressions + 06-Feb-12 JDB Corrected "opsize" parameter type in vis_abs + 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h + 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) + 30-Apr-08 JDB Updated SIGNAL code from Holger + 24-Apr-08 HV Implemented SIGNAL + 20-Apr-08 JDB Updated comments + 26-Feb-08 HV Implemented VIS + + Primary references: + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - Macro/1000 Reference Manual (92059-90001, Dec-1992) + + Additional references are listed with the associated firmware + implementations, as are the HP option model numbers pertaining to the + applicable CPUs. +*/ + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" + +#if defined (HAVE_INT64) /* int64 support available */ + +#include "hp2100_fp1.h" + + +static const OP zero = { { 0, 0, 0, 0, 0 } }; /* DEC 0.0D0 */ + + +/* Vector Instruction Set + + The VIS provides instructions that operate on one-dimensional arrays of + floating-point values. Both single- and double-precision operations are + supported. VIS uses the F-Series floating-point processor to handle the + floating-point math. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A N/A N/A 12824A + + The routines are mapped to instruction codes as follows: + + Single-Precision Double-Precision + Instr. Opcode Subcod Instr. Opcode Subcod Description + ------ ------ ------ ------ ------ ------ ----------------------------- + VADD 101460 000000 DVADD 105460 004002 Vector add + VSUB 101460 000020 DVSUB 105460 004022 Vector subtract + VMPY 101460 000040 DVMPY 105460 004042 Vector multiply + VDIV 101460 000060 DVDIV 105460 004062 Vector divide + VSAD 101460 000400 DVSAD 105460 004402 Scalar-vector add + VSSB 101460 000420 DVSSB 105460 004422 Scalar-vector subtract + VSMY 101460 000440 DVSMY 105460 004442 Scalar-vector multiply + VSDV 101460 000460 DVSDV 105460 004462 Scalar-vector divide + VPIV 101461 0xxxxx DVPIV 105461 0xxxxx Vector pivot + VABS 101462 0xxxxx DVABS 105462 0xxxxx Vector absolute value + VSUM 101463 0xxxxx DVSUM 105463 0xxxxx Vector sum + VNRM 101464 0xxxxx DVNRM 105464 0xxxxx Vector norm + VDOT 101465 0xxxxx DVDOT 105465 0xxxxx Vector dot product + VMAX 101466 0xxxxx DVMAX 105466 0xxxxx Vector maximum value + VMAB 101467 0xxxxx DVMAB 105467 0xxxxx Vector maximum absolute value + VMIN 101470 0xxxxx DVMIN 105470 0xxxxx Vector minimum value + VMIB 101471 0xxxxx DVMIB 105471 0xxxxx Vector minimum absolute value + VMOV 101472 0xxxxx DVMOV 105472 0xxxxx Vector move + VSWP 101473 0xxxxx DVSWP 105473 0xxxxx Vector swap + .ERES 101474 -- -- -- -- Resolve array element address + .ESEG 101475 -- -- -- -- Load MSEG maps + .VSET 101476 -- -- -- -- Vector setup + [test] -- -- -- 105477 -- [self test] + + Instructions use IR bit 11 to select single- or double-precision format. The + double-precision instruction names begin with "D" (e.g., DVADD vs. VADD). + Most VIS instructions are two words in length, with a sub-opcode immediately + following the primary opcode. + + Notes: + + 1. The .VECT (101460) and .DVCT (105460) opcodes preface a single- or + double-precision arithmetic operation that is determined by the + sub-opcode value. The remainder of the dual-precision sub-opcode values + are "don't care," except for requiring a zero in bit 15. + + 2. The VIS uses the hardware FPP of the F-Series. FPP malfunctions are + detected by the VIS firmware and are indicated by a memory-protect + violation and setting the overflow flag. Under simulation, + malfunctions cannot occur. + + Additional references: + - 12824A Vector Instruction Set User's Manual (12824-90001, Jun-1979). + - VIS Microcode Source (12824-18059, revision 3). +*/ + +static const OP_PAT op_vis[16] = { + OP_N, OP_AAKAKAKK,OP_AKAKK, OP_AAKK, /* .VECT VPIV VABS VSUM */ + OP_AAKK, OP_AAKAKK, OP_AAKK, OP_AAKK, /* VNRM VDOT VMAX VMAB */ + OP_AAKK, OP_AAKK, OP_AKAKK, OP_AKAKK, /* VMIN VMIB VMOV VSWP */ + OP_AA, OP_A, OP_AAACCC,OP_N /* .ERES .ESEG .VSET [test] */ + }; + +static const t_bool op_ftnret[16] = { + FALSE, TRUE, TRUE, TRUE, + TRUE, TRUE, TRUE, TRUE, + TRUE, TRUE, TRUE, TRUE, + FALSE, TRUE, TRUE, FALSE, + }; + + +/* handle the scalar/vector base ops */ +static void vis_svop(uint32 subcode, OPS op, OPSIZE opsize) +{ +OP v1,v2; +int16 delta = opsize==fp_f ? 2 : 4; +OP s = ReadOp(op[0].word,opsize); +uint32 v1addr = op[1].word; +int16 ix1 = INT16(op[2].word) * delta; +uint32 v2addr = op[3].word; +int16 ix2 = INT16(op[4].word) * delta; +int16 i, n = INT16(op[5].word); +uint16 fpuop = (uint16) (subcode & 060) | (opsize==fp_f ? 0 : 2); + +if (n <= 0) return; +for (i=0; ifpk[0] & 0100000) + +static void vis_abs(OP* in, OPSIZE opsize) +{ +uint32 sign = GET_MSIGN(in); /* get sign */ +if (sign) (void)fp_pcom(in, opsize); /* if negative, make positive */ +} + +static void vis_minmax(OPS op,OPSIZE opsize,t_bool domax,t_bool doabs) +{ +OP v1,vmxmn,res; +int16 delta = opsize==fp_f ? 2 : 4; +uint32 mxmnaddr = op[0].word; +uint32 v1addr = op[1].word; +int16 ix1 = INT16(op[2].word) * delta; +int16 n = INT16(op[3].word); +int16 i,mxmn,sign; +uint16 subop = 020 | (opsize==fp_f ? 0 : 2); + +if (n <= 0) return; +mxmn = 0; /* index of maxmin element */ +vmxmn = ReadOp(v1addr,opsize); /* initialize with first element */ +if (doabs) vis_abs(&vmxmn,opsize); /* ABS(v[1]) if requested */ + +for (i = 0; ifpk[0] = in.fpk[0]; +out->fpk[1] = (in.fpk[1] & 0177400) | (in.fpk[3] & 0377); +} + +static void vis_vsmnm(OPS op,OPSIZE opsize,t_bool doabs) +{ +uint16 fpuop; +OP v1,sumnrm = zero; +int16 delta = opsize==fp_f ? 2 : 4; +uint32 saddr = op[0].word; +uint32 v1addr = op[1].word; +int16 ix1 = INT16(op[2].word) * delta; +int16 i,n = INT16(op[3].word); + +if (n <= 0) return; +/* calculates sumnrm = sumnrm + DBLE(v1[i]) resp DBLE(ABS(v1[i])) for incrementing i */ +for (i=0; i>CPU VIS: IR = %06o/%06o (", /* print preamble and IR */ + IR, subcode); + fprint_sym (sim_deb, err_PC, (t_value *) &IR, /* print instruction mnemonic */ + NULL, SWMASK('M')); + fprintf (sim_deb, "), P = %06o", err_PC); /* print location */ + fprint_ops (pattern, op); /* print operands */ + fputc ('\n', sim_deb); /* terminate line */ + } + +switch (entry) { /* decode IR<3:0> */ + case 000: /* .VECT (OP_special) */ + if (subcode & 0400) + vis_svop(subcode,op,opsize); /* scalar/vector op */ + else + vis_vvop(subcode,op,opsize); /* vector/vector op */ + break; + case 001: /* VPIV (OP_(A)AAKAKAKK) */ + vis_vpiv(op,opsize); + break; + case 002: /* VABS (OP_(A)AKAKK) */ + vis_vabs(op,opsize); + break; + case 003: /* VSUM (OP_(A)AAKK) */ + vis_vsmnm(op,opsize,FALSE); + break; + case 004: /* VNRM (OP_(A)AAKK) */ + vis_vsmnm(op,opsize,TRUE); + break; + case 005: /* VDOT (OP_(A)AAKAKK) */ + vis_vdot(op,opsize); + break; + case 006: /* VMAX (OP_(A)AAKK) */ + vis_minmax(op,opsize,TRUE,FALSE); + break; + case 007: /* VMAB (OP_(A)AAKK) */ + vis_minmax(op,opsize,TRUE,TRUE); + break; + case 010: /* VMIN (OP_(A)AAKK) */ + vis_minmax(op,opsize,FALSE,FALSE); + break; + case 011: /* VMIB (OP_(A)AAKK) */ + vis_minmax(op,opsize,FALSE,TRUE); + break; + case 012: /* VMOV (OP_(A)AKAKK) */ + vis_movswp(op,opsize,FALSE); + break; + case 013: /* VSWP (OP_(A)AKAKK) */ + vis_movswp(op,opsize,TRUE); + break; + case 014: /* .ERES (OP_(A)AA) */ + reason = cpu_ema_eres(&rtn,op[2].word,PR,debug); /* handle the ERES instruction */ + PR = rtn; + if (debug) + fprintf (sim_deb, + ">>CPU VIS: return .ERES: AR = %06o, BR = %06o, rtn=%s\n", + AR, BR, PR==op[0].word ? "error" : "good"); + break; + + case 015: /* .ESEG (OP_(A)A) */ + reason = cpu_ema_eseg(&rtn,IR,op[0].word,debug); /* handle the ESEG instruction */ + PR = rtn; + if (debug) + fprintf (sim_deb, + ">>CPU VIS: return .ESEG: AR = %06o , BR = %06o, rtn=%s\n", + AR, BR, rtn==rtn1 ? "error" : "good"); + break; + + case 016: /* .VSET (OP_(A)AAACCC) */ + reason = cpu_ema_vset(&rtn,op,debug); + PR = rtn; + if (debug) + fprintf (sim_deb, ">>CPU VIS: return .VSET: AR = %06o BR = %06o, rtn=%s\n", + AR, BR, + rtn==rtn1 ? "error" : (rtn==(rtn1+1) ? "hard" : "easy") ); + break; + + case 017: /* [test] (OP_N) */ + XR = 3; /* firmware revision */ + SR = 0102077; /* test passed code */ + PR = (PR + 1) & VAMASK; /* P+2 return for firmware w/VIS */ + break; + default: /* others undefined */ + reason = stop_inst; + } + +return reason; +} + + +/* SIGNAL/1000 Instructions + + The SIGNAL/1000 instructions provide fast Fourier transforms and complex + arithmetic. They utilize the F-Series floating-point processor and the + Vector Instruction Set. + + Option implementation by CPU was as follows: + + 2114 2115 2116 2100 1000-M 1000-E 1000-F + ------ ------ ------ ------ ------ ------ ------ + N/A N/A N/A N/A N/A N/A 92835A + + The routines are mapped to instruction codes as follows: + + Instr. 1000-F Description + ------ ------ ---------------------------------------------- + BITRV 105600 Bit reversal + BTRFY 105601 Butterfly algorithm + UNSCR 105602 Unscramble for phasor MPY + PRSCR 105603 Unscramble for phasor MPY + BITR1 105604 Swap two elements in array (alternate format) + BTRF1 105605 Butterfly algorithm (alternate format) + .CADD 105606 Complex number addition + .CSUB 105607 Complex number subtraction + .CMPY 105610 Complex number multiplication + .CDIV 105611 Complex number division + CONJG 105612 Complex conjugate + ..CCM 105613 Complex complement + AIMAG 105614 Return imaginary part + CMPLX 105615 Form complex number + [nop] 105616 [no operation] + [test] 105617 [self test] + + Notes: + + 1. SIGNAL/1000 ROM data are available from Bitsavers. + + Additional references (documents unavailable): + - HP Signal/1000 User Reference and Installation Manual (92835-90002). + - SIGNAL/1000 Microcode Source (92835-18075, revision 2). +*/ + +#define RE(x) (x+0) +#define IM(x) (x+2) + +static const OP_PAT op_signal[16] = { + OP_AAKK, OP_AAFFKK, OP_AAFFKK,OP_AAFFKK, /* BITRV BTRFY UNSCR PRSCR */ + OP_AAAKK, OP_AAAFFKK,OP_AAA, OP_AAA, /* BITR1 BTRF1 .CADD .CSUB */ + OP_AAA, OP_AAA, OP_AAA, OP_A, /* .CMPY .CDIV CONJG ..CCM */ + OP_AA, OP_AAFF, OP_N, OP_N /* AIMAG CMPLX --- [test]*/ + }; + +/* complex addition helper */ +static void sig_caddsub(uint16 addsub,OPS op) +{ +OP a,b,c,d,p1,p2; + +a = ReadOp(RE(op[1].word), fp_f); /* read 1st op */ +b = ReadOp(IM(op[1].word), fp_f); +c = ReadOp(RE(op[2].word), fp_f); /* read 2nd op */ +d = ReadOp(IM(op[2].word), fp_f); +(void)fp_exec(addsub,&p1, a, c); /* add real */ +(void)fp_exec(addsub,&p2, b, d); /* add imag */ +WriteOp(RE(op[0].word), p1, fp_f); /* write result */ +WriteOp(IM(op[0].word), p2, fp_f); /* write result */ +} + +/* butterfly operation helper */ +static void sig_btrfy(uint32 re,uint32 im,OP wr,OP wi,uint32 k, uint32 n2) +{ +/* + * v(k)-------->o-->o----> v(k) + * \ / + * x + * / \ + * v(k+N/2)---->o-->o----> v(k+N/2) + * Wn -1 + * + */ + +OP p1,p2,p3,p4; +OP v1r = ReadOp(re+k, fp_f); /* read v1 */ +OP v1i = ReadOp(im+k, fp_f); +OP v2r = ReadOp(re+k+n2, fp_f); /* read v2 */ +OP v2i = ReadOp(im+k+n2, fp_f); + +/* (p1,p2) := cmul(w,v2) */ +(void)fp_exec(040, &p1, wr, v2r); /* S7,8 p1 := wr*v2r */ +(void)fp_exec(040, ACCUM, wi, v2i); /* ACCUM := wi*v2i */ +(void)fp_exec(024, &p1, p1, NOP); /* S7,S8 p1 := wr*v2r-wi*v2i ==real(w*v2) */ +(void)fp_exec(040, &p2, wi, v2r); /* S9,10 p2 := wi*v2r */ +(void)fp_exec(040, ACCUM, wr, v2i); /* ACCUM := wr*v2i */ +(void)fp_exec(004, &p2, p2, NOP); /* S9,10 p2 := wi*v2r+wr*v2i ==imag(w*v2) */ +/* v2 := v1 - (p1,p2) */ +(void)fp_exec(020, &p3, v1r, p1); /* v2r := v1r-real(w*v2) */ +(void)fp_exec(020, &p4, v1i, p2); /* v2i := v1i-imag(w*v2) */ +WriteOp(re+k+n2, p3, fp_f); /* write v2r */ +WriteOp(im+k+n2, p4, fp_f); /* write v2i */ +/* v1 := v1 + (p1,p2) */ +(void)fp_exec(0, &p3, v1r, p1); /* v1r := v1r+real(w*v2) */ +(void)fp_exec(0, &p4, v1i, p2); /* v1i := v1i+imag(w*v2) */ +WriteOp(re+k, p3, fp_f); /* write v1r */ +WriteOp(im+k, p4, fp_f); /* write v1i */ +O = 0; +} + +/* helper for bit reversal + * idx is 0-based already */ +static void sig_bitrev(uint32 re,uint32 im, uint32 idx, uint32 log2n, int sz) +{ +uint32 i, org=idx, rev = 0; +OP v1r,v1i,v2r,v2i; + +for (i=0; i>= 1; +} + +if (rev < idx) return; /* avoid swapping same pair twice in loop */ + +idx *= sz; /* adjust for element size */ +rev *= sz; /* (REAL*4 vs COMPLEX*8) */ + +v1r = ReadOp(re+idx, fp_f); /* read 1st element */ +v1i = ReadOp(im+idx, fp_f); +v2r = ReadOp(re+rev, fp_f); /* read 2nd element */ +v2i = ReadOp(im+rev, fp_f); +WriteOp(re+idx, v2r, fp_f); /* swap elements */ +WriteOp(im+idx, v2i, fp_f); +WriteOp(re+rev, v1r, fp_f); +WriteOp(im+rev, v1i, fp_f); +} + +/* helper for PRSCR/UNSCR */ +static OP sig_scadd(uint16 oper,t_bool addh, OP a, OP b) +{ +OP r; +static const OP plus_half = { { 0040000, 0000000 } }; /* DEC +0.5 */ + +(void)fp_exec(oper,&r,a,b); /* calculate r := a +/- b */ +if (addh) (void)fp_exec(044,&r,plus_half,NOP); /* if addh set, multiply by 0.5 */ +return r; +} + +/* complex multiply helper */ +static void sig_cmul(OP *r, OP *i, OP a, OP b, OP c, OP d) +{ +OP p; +(void)fp_exec(040, &p , a, c); /* p := ac */ +(void)fp_exec(040, ACCUM, b, d); /* ACCUM := bd */ +(void)fp_exec(024, r, p , NOP); /* real := ac-bd */ +(void)fp_exec(040, &p, a, d); /* p := ad */ +(void)fp_exec(040, ACCUM, b, c); /* ACCUM := bc */ +(void)fp_exec(004, i, p, NOP); /* imag := ad+bc */ +} + +t_stat cpu_signal (uint32 IR, uint32 intrq) +{ +t_stat reason = SCPE_OK; +OPS op; +OP a,b,c,d,p1,p2,p3,p4,m1,m2,wr,wi; +uint32 entry, v, idx1, idx2; +int32 exc, exd; + +t_bool debug = DEBUG_PRI (cpu_dev, DEB_SIG); + +entry = IR & 017; /* mask to entry point */ + +if (op_signal [entry] != OP_N) { + reason = cpu_ops (op_signal [entry], op, intrq); /* get instruction operands */ + if (reason != SCPE_OK) /* evaluation failed? */ + return reason; /* return reason for failure */ + } + +if (debug) { /* debugging? */ + fprintf (sim_deb, ">>CPU SIG: IR = %06o (", IR); /* print preamble and IR */ + fprint_sym (sim_deb, err_PC, (t_value *) &IR, /* print instruction mnemonic */ + NULL, SWMASK('M')); + fprintf (sim_deb, "), P = %06o", err_PC); /* print location */ + fprint_ops (op_signal[entry], op); /* print operands */ + fputc ('\n', sim_deb); /* terminate line */ + } + +switch (entry) { /* decode IR<3:0> */ + case 000: /* BITRV (OP_AAKK) */ + /* BITRV + * bit reversal for FFT + * JSB BITRV + * DEF ret(,I) return address + * DEF vect,I base address of array + * DEF idx,I index bitmap to be reversed (one-based) + * DEF nbits,I number of bits of index + * + * Given a complex*8 vector of nbits (power of 2), this calculates: + * swap( vect[idx], vect[rev(idx)]) where rev(i) is the bitreversed value of i */ + sig_bitrev(op[1].word, op[1].word+2, op[2].word-1, op[3].word, 4); + PR = op[0].word & VAMASK; + break; + + case 001: /* BTRFY (OP_AAFFKK) */ + /* BTRFY - butterfly operation + * JSB BTRFY + * DEF ret(,I) return address + * DEF vect(,I) complex*8 vector + * DEF wr,I real part of W + * DEF wi,I imag part of W + * DEF node,I index of 1st op (1 based) + * DEF lmax,I offset to 2nd op (0 based) */ + sig_btrfy(op[1].word, op[1].word+2, + op[2], op[3], + 2*(op[4].word-1), 2*op[5].word); + PR = op[0].word & VAMASK; + break; + + case 002: /* UNSCR (OP_AAFFKK) */ + /* UNSCR unscramble for phasor MPY + * JSB UNSCR + * DEF ret(,I) + * DEF vector,I + * DEF WR + * DEF WI + * DEF idx1,I + * DEF idx2,I */ + v = op[1].word; + idx1 = 2 * (op[4].word - 1); + idx2 = 2 * (op[5].word - 1); + wr = op[2]; /* read WR */ + wi = op[3]; /* read WI */ + p1 = ReadOp(RE(v + idx1), fp_f); /* S1 VR[idx1] */ + p2 = ReadOp(RE(v + idx2), fp_f); /* S2 VR[idx2] */ + p3 = ReadOp(IM(v + idx1), fp_f); /* S9 VI[idx1] */ + p4 = ReadOp(IM(v + idx2), fp_f); /* S10 VI[idx2] */ + c = sig_scadd(000, TRUE, p3, p4); /* S5,6 0.5*(p3+p4) */ + d = sig_scadd(020, TRUE, p2, p1); /* S7,8 0.5*(p2-p1) */ + sig_cmul(&m1, &m2, wr, wi, c, d); /* (WR,WI) * (c,d) */ + c = sig_scadd(000, TRUE, p1, p2); /* 0.5*(p1+p2) */ + d = sig_scadd(020, TRUE, p3, p4); /* 0.5*(p3-p4) */ + (void)fp_exec(000, &p1, c, m1); /* VR[idx1] := 0.5*(p1+p2) + real(W*(c,d)) */ + WriteOp(RE(v + idx1), p1, fp_f); + (void)fp_exec(000, &p2, d, m2); /* VI[idx1] := 0.5*(p3-p4) + imag(W*(c,d)) */ + WriteOp(IM(v + idx1), p2, fp_f); + (void)fp_exec(020, &p1, c, m1); /* VR[idx2] := 0.5*(p1+p2) - imag(W*(c,d)) */ + WriteOp(RE(v + idx2), p1, fp_f); + (void)fp_exec(020, &p2, d, m2); /* VI[idx2] := 0.5*(p3-p4) - imag(W*(c,d)) */ + WriteOp(IM(v + idx2), p2, fp_f); + PR = op[0].word & VAMASK; + break; + + case 003: /* PRSCR (OP_AAFFKK) */ + /* PRSCR unscramble for phasor MPY + * JSB PRSCR + * DEF ret(,I) + * DEF vector,I + * DEF WR + * DEF WI + * DEF idx1,I + * DEF idx2,I */ + v = op[1].word; + idx1 = 2 * (op[4].word - 1); + idx2 = 2 * (op[5].word - 1); + wr = op[2]; /* read WR */ + wi = op[3]; /* read WI */ + p1 = ReadOp(RE(v + idx1), fp_f); /* VR[idx1] */ + p2 = ReadOp(RE(v + idx2), fp_f); /* VR[idx2] */ + p3 = ReadOp(IM(v + idx1), fp_f); /* VI[idx1] */ + p4 = ReadOp(IM(v + idx2), fp_f); /* VI[idx2] */ + c = sig_scadd(020, FALSE, p1, p2); /* p1-p2 */ + d = sig_scadd(000, FALSE, p3, p4); /* p3+p4 */ + sig_cmul(&m1,&m2, wr, wi, c, d); /* (WR,WI) * (c,d) */ + c = sig_scadd(000, FALSE, p1, p2); /* p1+p2 */ + d = sig_scadd(020, FALSE, p3,p4); /* p3-p4 */ + (void)fp_exec(020, &p1, c, m2); /* VR[idx1] := (p1-p2) - imag(W*(c,d)) */ + WriteOp(RE(v + idx1), p1, fp_f); + (void)fp_exec(000, &p2, d, m1); /* VI[idx1] := (p3-p4) + real(W*(c,d)) */ + WriteOp(IM(v + idx1), p2, fp_f); + (void)fp_exec(000, &p1, c, m2); /* VR[idx2] := (p1+p2) + imag(W*(c,d)) */ + WriteOp(RE(v + idx2), p1, fp_f); + (void)fp_exec(020, &p2, m1, d); /* VI[idx2] := imag(W*(c,d)) - (p3-p4) */ + WriteOp(IM(v + idx2), p2, fp_f); + PR = op[0].word & VAMASK; + break; + + case 004: /* BITR1 (OP_AAAKK) */ + /* BITR1 + * bit reversal for FFT, alternative version + * JSB BITR1 + * DEF ret(,I) return address if already swapped + * DEF revect,I base address of real vect + * DEF imvect,I base address of imag vect + * DEF idx,I index bitmap to be reversed (one-based) + * DEF nbits,I number of bits of index + * + * Given a complex*8 vector of nbits (power of 2), this calculates: + * swap( vect[idx], vect[rev(idx)]) where rev(i) is the bitreversed value of i + * + * difference to BITRV is that BITRV uses complex*8, and BITR1 uses separate real*4 + * vectors for Real and Imag parts */ + sig_bitrev(op[1].word, op[2].word, op[3].word-1, op[4].word, 2); + PR = op[0].word & VAMASK; + break; + + + case 005: /* BTRF1 (OP_AAAFFKK) */ + /* BTRF1 - butterfly operation with real*4 vectors + * JSB BTRF1 + * DEF ret(,I) return address + * DEF rvect,I real part of vector + * DEF ivect,I imag part of vector + * DEF wr,I real part of W + * DEF wi,I imag part of W + * DEF node,I index (1 based) + * DEF lmax,I index (0 based) */ + sig_btrfy(op[1].word, op[2].word, + op[3], op[4], + op[5].word-1, op[6].word); + PR = op[0].word & VAMASK; + break; + + case 006: /* .CADD (OP_AAA) */ + /* .CADD Complex addition + * JSB .CADD + * DEF result,I + * DEF oprd1,I + * DEF oprd2,I + * complex addition is: (a+bi) + (c+di) => (a+c) + (b+d)i */ + sig_caddsub(000,op); + break; + + case 007: /* .CSUB (OP_AAA) */ + /* .CSUB Complex subtraction + * JSB .CSUB + * DEF result,I + * DEF oprd1,I + * DEF oprd2,I + * complex subtraction is: (a+bi) - (c+di) => (a - c) + (b - d)i */ + sig_caddsub(020,op); + break; + + case 010: /* .CMUL (OP_AAA) */ + /* .CMPY Complex multiplication + * call: + * JSB .CMPY + * DEF result,I + * DEF oprd1,I + * DEF oprd2,I + * complex multiply is: (a+bi)*(c+di) => (ac-bd) + (ad+bc)i */ + a = ReadOp(RE(op[1].word), fp_f); /* read 1st op */ + b = ReadOp(IM(op[1].word), fp_f); + c = ReadOp(RE(op[2].word), fp_f); /* read 2nd op */ + d = ReadOp(IM(op[2].word), fp_f); + sig_cmul(&p1, &p2, a, b, c, d); + WriteOp(RE(op[0].word), p1, fp_f); /* write real result */ + WriteOp(IM(op[0].word), p2, fp_f); /* write imag result */ + break; + + case 011: /* .CDIV (OP_AAA) */ + /* .CDIV Complex division + * call: + * JSB .CDIV + * DEF result,I + * DEF oprd1,I + * DEF oprd2,I + * complex division is: (a+bi)/(c+di) => ((ac+bd) + (bc-ad)i)/(c^2+d^2) */ + a = ReadOp(RE(op[1].word), fp_f); /* read 1st op */ + b = ReadOp(IM(op[1].word), fp_f); + c = ReadOp(RE(op[2].word), fp_f); /* read 2nd op */ + d = ReadOp(IM(op[2].word), fp_f); + (void)fp_unpack (NULL, &exc, c, fp_f); /* get exponents */ + (void)fp_unpack (NULL, &exd, d, fp_f); + if (exc < exd) { /* ensure c/d < 1 */ + p1 = a; a = c; c = p1; /* swap dividend and divisor */ + p1 = b; b = d; d = p1; + } + (void)fp_exec(060, &p1, d, c); /* p1,accu := d/c */ + (void)fp_exec(044, ACCUM, d, NOP); /* ACCUM := dd/c */ + (void)fp_exec(004, &p2, c, NOP); /* p2 := c + dd/c */ + (void)fp_exec(040, ACCUM, b, p1); /* ACCUM := bd/c */ + (void)fp_exec(004, ACCUM, a, NOP); /* ACCUM := a + bd/c */ + (void)fp_exec(070, &p3, NOP, p2); /* p3 := (a+bd/c)/(c+dd/c) == (ac+bd)/(cc+dd) */ + WriteOp(RE(op[0].word), p3, fp_f); /* Write real result */ + (void)fp_exec(040, ACCUM, a, p1); /* ACCUM := ad/c */ + (void)fp_exec(030, ACCUM, NOP, b); /* ACCUM := ad/c - b */ + if (exd < exc) { /* was not swapped? */ + (void)fp_exec(024, ACCUM, zero, NOP); /* ACCUM := -ACCUM */ + } + (void)fp_exec(070, &p3, NOP, p2); /* p3 := (b-ad/c)/(c+dd/c) == (bc-ad)/cc+dd) */ + WriteOp(IM(op[0].word), p3, fp_f); /* Write imag result */ + break; + + case 012: /* CONJG (OP_AAA) */ + /* CONJG build A-Bi from A+Bi + * call: + * JSB CONJG + * DEF RTN + * DEF res,I result + * DEF arg,I input argument */ + a = ReadOp(RE(op[2].word), fp_f); /* read real */ + b = ReadOp(IM(op[2].word), fp_f); /* read imag */ + (void)fp_pcom(&b, fp_f); /* negate imag */ + WriteOp(RE(op[1].word), a, fp_f); /* write real */ + WriteOp(IM(op[1].word), b, fp_f); /* write imag */ + break; + + case 013: /* ..CCM (OP_A) */ + /* ..CCM complement complex + * call + * JSB ..CCM + * DEF arg + * build (-RE,-IM) + */ + v = op[0].word; + a = ReadOp(RE(v), fp_f); /* read real */ + b = ReadOp(IM(v), fp_f); /* read imag */ + (void)fp_pcom(&a, fp_f); /* negate real */ + (void)fp_pcom(&b, fp_f); /* negate imag */ + WriteOp(RE(v), a, fp_f); /* write real */ + WriteOp(IM(v), b, fp_f); /* write imag */ + break; + + case 014: /* AIMAG (OP_AA) */ + /* AIMAG return the imaginary part in AB + * JSB AIMAG + * DEF *+2 + * DEF cplx(,I) + * returns: AB imaginary part of complex number */ + a = ReadOp(IM(op[1].word), fp_f); /* read imag */ + AR = a.fpk[0]; /* move MSB to A */ + BR = a.fpk[1]; /* move LSB to B */ + break; + + case 015: /* CMPLX (OP_AFF) */ + /* CMPLX form a complex number + * JSB CMPLX + * DEF *+4 + * DEF result,I complex number + * DEF repart,I real value + * DEF impart,I imaginary value */ + WriteOp(RE(op[1].word), op[2], fp_f); /* write real part */ + WriteOp(IM(op[1].word), op[3], fp_f); /* write imag part */ + break; + + case 017: /* [slftst] (OP_N) */ + XR = 2; /* firmware revision */ + SR = 0102077; /* test passed code */ + PR = (PR + 1) & VAMASK; /* P+2 return for firmware w/SIGNAL1000 */ + break; + + case 016: /* invalid */ + default: /* others undefined */ + reason = stop_inst; + } + +return reason; +} + +#endif /* end of int64 support */ diff --git a/HP2100/hp2100_defs.h b/HP2100/hp2100_defs.h index 1a57a58d..6a7e0ce5 100644 --- a/HP2100/hp2100_defs.h +++ b/HP2100/hp2100_defs.h @@ -1,490 +1,761 @@ -/* hp2100_defs.h: HP 2100 simulator definitions - - Copyright (c) 1993-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not - be used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - 30-Dec-14 JDB Added S-register parameters to ibl_copy, more IBL constants - 28-Dec-14 JDB Changed suppression from #pragma GCC to #pragma clang - 05-Feb-13 JDB Added declaration for hp_fprint_stopped - 18-Mar-13 JDB Added "-Wdangling-else" to the suppression pragmas - Removed redundant extern declarations - 14-Mar-13 MP Changed guard macro name to avoid reserved namespace - 14-Dec-12 JDB Added "-Wbitwise-op-parentheses" to the suppression pragmas - 12-May-12 JDB Added pragmas to suppress logical operator precedence warnings - 10-Feb-12 JDB Added hp_setsc, hp_showsc functions to support SC modifier - 28-Mar-11 JDB Tidied up signal handling - 29-Oct-10 JDB DMA channels renamed from 0,1 to 1,2 to match documentation - 27-Oct-10 JDB Revised I/O signal enum values for concurrent signals - Revised I/O macros for new signal handling - 09-Oct-10 JDB Added DA and DC device select code assignments - 07-Sep-08 JDB Added POLL_FIRST to indicate immediate connection attempt - 15-Jul-08 JDB Rearranged declarations with hp2100_cpu.h - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 25-Jun-08 JDB Added PIF device - 17-Jun-08 JDB Declared fmt_char() function - 26-May-08 JDB Added MPX device - 24-Apr-08 JDB Added I_MRG_I, I_JSB, I_JSB_I, and I_JMP instruction masks - 14-Apr-08 JDB Changed TMR_MUX to TMR_POLL for idle support - Added POLLMODE, sync_poll() declaration - Added I_MRG, I_ISZ, I_IOG, I_STF, and I_SFS instruction masks - 07-Dec-07 JDB Added BACI device - 10-Nov-07 JDB Added 16/32-bit unsigned-to-signed conversions - 11-Jan-07 JDB Added 12578A DMA byte packing to DMA structure - 28-Dec-06 JDB Added CRS backplane signal as I/O pseudo-opcode - Added DMASK32 32-bit mask value - 21-Dec-06 JDB Changed MEM_ADDR_OK for 21xx loader support - 12-Sep-06 JDB Define NOTE_IOG to recalc interrupts after instr exec - Rename STOP_INDINT to NOTE_INDINT (not a stop condition) - 30-Dec-04 JDB Added IBL_DS_HEAD head number mask - 19-Nov-04 JDB Added STOP_OFFLINE, STOP_PWROFF stop codes - 25-Apr-04 RMS Added additional IBL definitions - Added DMA EDT I/O pseudo-opcode - 25-Apr-03 RMS Revised for extended file support - 24-Oct-02 RMS Added indirect address interrupt - 08-Feb-02 RMS Added DMS definitions - 01-Feb-02 RMS Added terminal multiplexor support - 16-Jan-02 RMS Added additional device support - 30-Nov-01 RMS Added extended SET/SHOW support - 15-Oct-00 RMS Added dynamic device numbers - 14-Apr-99 RMS Changed t_addr to unsigned - - The author gratefully acknowledges the help of Jeff Moffat in answering - questions about the HP2100; and of Dave Bryan in adding features and - correcting errors throughout the simulator. -*/ - - -#ifndef HP2100_DEFS_H_ -#define HP2100_DEFS_H_ 0 - -#include "sim_defs.h" /* simulator defns */ - - -/* The following pragmas quell clang warnings that are on by default but should - not be, in my opinion. They warn about the use of perfectly valid code and - require the addition of redundant parentheses and braces to silence them. - Rather than clutter up the code with scores of extra symbols that, in my - view, make the code harder to read and maintain, I elect to suppress these - warnings. -*/ - -#if defined (__clang__) -#pragma clang diagnostic ignored "-Wlogical-op-parentheses" -#pragma clang diagnostic ignored "-Wbitwise-op-parentheses" -#pragma clang diagnostic ignored "-Wdangling-else" -#endif - - -/* Simulator stop and notification codes */ - -#define STOP_RSRV 1 /* must be 1 */ -#define STOP_IODV 2 /* must be 2 */ -#define STOP_HALT 3 /* HALT */ -#define STOP_IBKPT 4 /* breakpoint */ -#define STOP_IND 5 /* indirect loop */ -#define NOTE_INDINT 6 /* indirect intr */ -#define STOP_NOCONN 7 /* no connection */ -#define STOP_OFFLINE 8 /* device offline */ -#define STOP_PWROFF 9 /* device powered off */ -#define NOTE_IOG 10 /* I/O instr executed */ - -/* Memory */ - -#define MEMSIZE (cpu_unit.capac) /* actual memory size */ -#define VA_N_SIZE 15 /* virtual addr size */ -#define VASIZE (1 << VA_N_SIZE) -#define VAMASK 077777 /* virt addr mask */ -#define PA_N_SIZE 20 /* phys addr size */ -#define PASIZE (1 << PA_N_SIZE) -#define PAMASK (PASIZE - 1) /* phys addr mask */ - -/* Architectural constants */ - -#define SIGN32 020000000000 /* 32b sign */ -#define DMASK32 037777777777 /* 32b data mask/maximum value */ -#define DMAX32 017777777777 /* 32b maximum signed value */ -#define SIGN 0100000 /* 16b sign */ -#define DMASK 0177777 /* 16b data mask/maximum value */ -#define DMAX 0077777 /* 16b maximum signed value */ -#define DMASK8 0377 /* 8b data mask/maximum value */ - -/* Portable conversions (sign-extension, unsigned-to-signed) */ - -#define SEXT(x) ((int32) (((x) & SIGN)? ((x) | ~DMASK): ((x) & DMASK))) - -#define INT16(u) ((u) > DMAX ? (-(int16) (DMASK - (u)) - 1) : (int16) (u)) -#define INT32(u) ((u) > DMAX32 ? (-(int32) (DMASK32 - (u)) - 1) : (int32) (u)) - -/* Timers */ - -#define TMR_CLK 0 /* clock */ -#define TMR_POLL 1 /* input polling */ - -#define POLL_RATE 100 /* poll 100 times per second */ -#define POLL_FIRST 1 /* first poll is "immediate" */ -#define POLL_WAIT 15800 /* initial poll ~ 10 msec. */ - -typedef enum { INITIAL, SERVICE } POLLMODE; /* poll synchronization modes */ - -/* I/O instruction sub-opcodes */ - -#define soHLT 0 /* halt */ -#define soFLG 1 /* set/clear flag */ -#define soSFC 2 /* skip on flag clear */ -#define soSFS 3 /* skip on flag set */ -#define soMIX 4 /* merge into A/B */ -#define soLIX 5 /* load into A/B */ -#define soOTX 6 /* output from A/B */ -#define soCTL 7 /* set/clear control */ - -/* I/O devices - fixed select code assignments */ - -#define CPU 000 /* interrupt control */ -#define OVF 001 /* overflow */ -#define DMALT1 002 /* DMA 1 alternate */ -#define DMALT2 003 /* DMA 2 alternate */ -#define PWR 004 /* power fail */ -#define PRO 005 /* parity/mem protect */ -#define DMA1 006 /* DMA channel 1 */ -#define DMA2 007 /* DMA channel 2 */ - -/* I/O devices - variable select code assignment defaults */ - -#define PTR 010 /* 12597A-002 paper tape reader */ -#define TTY 011 /* 12531C teleprinter */ -#define PTP 012 /* 12597A-005 paper tape punch */ -#define CLK 013 /* 12539C time-base generator */ -#define LPS 014 /* 12653A line printer */ -#define LPT 015 /* 12845A line printer */ -#define MTD 020 /* 12559A data */ -#define MTC 021 /* 12559A control */ -#define DPD 022 /* 12557A data */ -#define DPC 023 /* 12557A control */ -#define DQD 024 /* 12565A data */ -#define DQC 025 /* 12565A control */ -#define DRD 026 /* 12610A data */ -#define DRC 027 /* 12610A control */ -#define MSD 030 /* 13181A data */ -#define MSC 031 /* 13181A control */ -#define IPLI 032 /* 12566B link in */ -#define IPLO 033 /* 12566B link out */ -#define DS 034 /* 13037A control */ -#define BACI 035 /* 12966A Buffered Async Comm Interface */ -#define MPX 036 /* 12792A/B/C 8-channel multiplexer */ -#define PIF 037 /* 12620A/12936A Privileged Interrupt Fence */ -#define MUXL 040 /* 12920A lower data */ -#define MUXU 041 /* 12920A upper data */ -#define MUXC 042 /* 12920A control */ -#define DI_DA 043 /* 12821A Disc Interface with Amigo disc devices */ -#define DI_DC 044 /* 12821A Disc Interface with CS/80 disc and tape devices */ - -#define OPTDEV 002 /* start of optional devices */ -#define CRSDEV 006 /* start of devices that receive CRS */ -#define VARDEV 010 /* start of variable assignments */ -#define MAXDEV 077 /* end of select code range */ - -/* IBL assignments */ - -#define IBL_V_SEL 14 /* ROM select <15:14> */ -#define IBL_M_SEL 03 -#define IBL_PTR 0000000 /* ROM 0: 12992K paper tape reader (PTR) */ -#define IBL_DP 0040000 /* ROM 1: 12992A 7900 disc (DP) */ -#define IBL_DQ 0060000 /* ROM 1: 12992A 2883 disc (DQ) */ -#define IBL_MS 0100000 /* ROM 2: 12992D 7970 tape (MS) */ -#define IBL_DS 0140000 /* ROM 3: 12992B 7905/06/20/25 disc (DS) */ -#define IBL_MAN 0010000 /* RPL/manual boot <13:12> */ -#define IBL_V_DEV 6 /* select code <11:6> */ -#define IBL_OPT 0000070 /* options in <5:3> */ -#define IBL_DP_REM 0000001 /* DP removable <0:0> */ -#define IBL_DS_HEAD 0000003 /* DS head number <1:0> */ -#define IBL_LNT 64 /* boot ROM length in words */ -#define IBL_MASK (IBL_LNT - 1) /* boot length mask */ -#define IBL_DPC (IBL_LNT - 2) /* DMA ctrl word */ -#define IBL_END (IBL_LNT - 1) /* last location */ - -#define IBL_S_CLR 0000000 /* ibl_copy mask to clear the S register */ -#define IBL_S_NOCLR 0177777 /* ibl_copy mask to preserve the S register */ -#define IBL_S_NOSET 0000000 /* ibl_copy mask to preserve the S register */ - -#define IBL_SET_SC(s) ((s) << IBL_V_DEV) /* position the select code in the S register */ - -typedef uint16 BOOT_ROM [IBL_LNT]; /* boot ROM data */ - - -/* I/O backplane signals. - - The IOSIGNAL declarations mirror the hardware I/O backplane signals. A set - of one or more signals forms an IOCYCLE that is sent to a device IOHANDLER - for action. The CPU and DMA dispatch one signal set to the target device - handler per I/O cycle. A CPU cycle consists of either one or two signals; if - present, the second signal will be CLF. A DMA cycle consists of from two to - five signals. In addition, a front-panel PRESET or power-on reset dispatches - two or three signals, respectively. - - In hardware, signals are assigned to one or more specific I/O T-periods, and - some signals are asserted concurrently. For example, a programmed STC sc,C - instruction asserts the STC and CLF signals together in period T4. Under - simulation, signals are ORed to form an I/O cycle; in this example, the - signal handler would receive an IOCYCLE value of "ioSTC | ioCLF". - - Hardware allows parallel action for concurrent signals. Under simulation, a - "concurrent" set of signals is processed sequentially by the signal handler - in order of ascending numerical value. Although assigned T-periods differ - between programmed I/O and DMA I/O cycles, a single processing order is used. - The order of execution generally follows the order of T-period assertion, - except that ioSIR is processed after all other signals that may affect the - interrupt request chain. - - Implementation notes: - - 1. The ioCLF signal must be processed after ioSFS/ioSFC to ensure that a - true skip test generates ioSKF before the flag is cleared, and after - ioIOI/ioIOO/ioSTC/ioCLC to meet the requirement that executing an - instruction having the H/C bit set is equivalent to executing the same - instruction with the H/C bit clear and then a CLF instruction. - - 2. The ioSKF signal is never sent to an I/O handler. Rather, it is returned - from the handler if the SFC or SFS condition is true. If the condition - is false, ioNONE is returned instead. As these two values are returned - in the 16-bit data portion of the returned value, their assigned values - must be <= 100000 octal. - - 3. An I/O handler will receive ioCRS as a result of a CLC 0 instruction, - ioPOPIO and ioCRS as a result of a RESET command, and ioPON, ioPOPIO, and - ioCRS as a result of a RESET -P command. - - 4. An I/O handler will receive ioNONE when a HLT instruction is executed - that has the H/C bit clear (i.e., no CLF generated). - - 5. In hardware, the SIR signal is generated unconditionally every T5 period - to time the setting of the IRQ flip-flop. Under simulation, ioSIR - indicates that the I/O handler must set the PRL, IRQ, and SRQ signals as - required by the interface logic. ioSIR must be included in the I/O cycle - if any of the flip-flops affecting these signals are changed and the - interface supports interrupts or DMA transfers. - - 6. In hardware, the ENF signal is unconditionally generated every T2 period - to time the setting of the flag flip-flop and to reset the IRQ flip-flop. - If the flag buffer flip-flip is set, then flag will be set by ENF. If - the flag buffer is clear, ENF will not affect flag. Under simulation, - ioENF is sent to set the flag buffer and flag flip-flops. For those - interfaces where this action is identical to that provided by STF, the - ioENF handler may simply fall into the ioSTF handler. - - 7. In hardware, the PON signal is asserted continuously while the CPU is - operating. Under simulation, ioPON is asserted only at simulator - initialization or when processing a RESET -P command. -*/ - -typedef enum { ioNONE = 0000000, /* -- -- -- -- -- no signal asserted */ - ioPON = 0000001, /* T2 T3 T4 T5 T6 power on normal */ - ioENF = 0000002, /* T2 -- -- -- -- enable flag */ - ioIOI = 0000004, /* -- -- T4 T5 -- I/O data input (CPU) - T2 T3 -- -- -- I/O data input (DMA) */ - ioIOO = 0000010, /* -- T3 T4 -- -- I/O data output */ - ioSKF = 0000020, /* -- T3 T4 T5 -- skip on flag */ - ioSFS = 0000040, /* -- T3 T4 T5 -- skip if flag is set */ - ioSFC = 0000100, /* -- T3 T4 T5 -- skip if flag is clear */ - ioSTC = 0000200, /* -- -- T4 -- -- set control flip-flop (CPU) - -- T3 -- -- -- set control flip-flop (DMA) */ - ioCLC = 0000400, /* -- -- T4 -- -- clear control flip-flop (CPU) - -- T3 T4 -- -- clear control flip-flop (DMA) */ - ioSTF = 0001000, /* -- T3 -- -- -- set flag flip-flop */ - ioCLF = 0002000, /* -- -- T4 -- -- clear flag flip-flop (CPU) - -- T3 -- -- -- clear flag flip-flop (DMA) */ - ioEDT = 0004000, /* -- -- T4 -- -- end data transfer */ - ioCRS = 0010000, /* -- -- -- T5 -- control reset */ - ioPOPIO = 0020000, /* -- -- -- T5 -- power-on preset to I/O */ - ioIAK = 0040000, /* -- -- -- -- T6 interrupt acknowledge */ - ioSIR = 0100000 } IOSIGNAL; /* -- -- -- T5 -- set interrupt request */ - - -typedef uint32 IOCYCLE; /* a set of signals forming one I/O cycle */ - -#define IOIRQSET (ioSTC | ioCLC | ioENF | \ - ioSTF | ioCLF | ioIAK | \ - ioCRS | ioPOPIO | ioPON) /* signals that may affect interrupt state */ - - -/* I/O structures */ - -typedef enum { CLEAR, SET } FLIP_FLOP; /* flip-flop type and values */ - -typedef struct dib DIB; /* incomplete definition */ - -typedef uint32 IOHANDLER (DIB *dibptr, /* I/O signal handler prototype */ - IOCYCLE signal_set, - uint32 stat_data); - -struct dib { /* Device information block */ - IOHANDLER *io_handler; /* pointer to device's I/O signal handler */ - uint32 select_code; /* device's select code */ - uint32 card_index; /* device's card index for state variables */ - }; - - -/* I/O signal and status macros. - - The following macros are useful in I/O signal handlers and unit service - routines. The parameter definition symbols employed are: - - I = an IOCYCLE value - E = a t_stat error status value - D = a uint16 data value - C = a uint32 combined status and data value - P = a pointer to a DIB structure - B = a Boolean test value - - Implementation notes: - - 1. The IONEXT macro isolates the next signal in sequence to process from the - I/O cycle I. - - 2. The IOADDSIR macro adds an ioSIR signal to the I/O cycle I if it - contains signals that might change the interrupt state. - - 3. The IORETURN macro forms the combined status and data value to be - returned by a handler from the t_stat error code E and the 16-bit data - value D. - - 4. The IOSTATUS macro isolates the t_stat error code from a combined status - and data value value C. - - 5. The IODATA macro isolates the 16-bit data value from a combined status - and data value value C. - - 6. The IOPOWERON macro calls signal handler P->H with DIB pointer P to - process a power-on reset action. - - 7. The IOPRESET macro calls signal handler P->H with DIB pointer P to - process a front-panel PRESET action. - - 8. The IOERROR macro returns t_stat error code E from a unit service routine - if the Boolean test B is true. -*/ - -#define IONEXT(I) (IOSIGNAL) ((I) & (IOCYCLE) (- (int32) (I))) /* extract next I/O signal to handle */ -#define IOADDSIR(I) ((I) & IOIRQSET ? (I) | ioSIR : (I)) /* add SIR if IRQ state might change */ - -#define IORETURN(E,D) ((uint32) ((E) << 16 | (D) & DMASK)) /* form I/O handler return value */ -#define IOSTATUS(C) ((t_stat) ((C) >> 16) & DMASK) /* extract I/O status from combined value */ -#define IODATA(C) ((uint16) ((C) & DMASK)) /* extract data from combined value */ - -#define IOPOWERON(P) (P)->io_handler ((P), ioPON | ioPOPIO | ioCRS, 0) /* send power-on signals to handler */ -#define IOPRESET(P) (P)->io_handler ((P), ioPOPIO | ioCRS, 0) /* send PRESET signals to handler */ -#define IOERROR(B,E) ((B) ? (E) : SCPE_OK) /* stop on I/O error if enabled */ - - -/* I/O signal logic macros. - - The following macros implement the logic for the SKF, PRL, IRQ, and SRQ - signals. Both standard and general logic macros are provided. The parameter - definition symbols employed are: - - S = a uint32 select code value - B = a Boolean test value - N = a name of a structure containing the standard flip-flops - - Implementation notes: - - 1. The setSKF macro sets the Skip on Flag signal in the return data value if - the Boolean value B is true. - - 2. The setPRL macro sets the Priority Low signal for select code S to the - Boolean value B. - - 3. The setIRQ macro sets the Interrupt Request signal for select code S to - the Boolean value B. - - 4. The setSRQ macro sets the Service Request signal for select code S to the - Boolean value B. - - 5. The PRL macro returns the Priority Low signal for select code S as a - Boolean value. - - 6. The IRQ macro returns the Interrupt Request signal for select code S as a - Boolean value. - - 7. The SRQ macro returns the Service Request signal for select code S as a - Boolean value. - - 8. The setstdSKF macro sets Skip on Flag signal in the return data value if - the flag state in structure N matches the current skip test condition. - - 9. The setstdPRL macro sets the Priority Low signal for the select code - referenced by "dibptr" using the standard logic and the control and flag - states in structure N. - - 10. The setstdIRQ macro sets the Interrupt Request signal for the select code - referenced by "dibptr" using the standard logic and the control, flag, - and flag buffer states in structure N. - - 11. The setstdSRQ macro sets the Service Request signal for the select code - referenced by "dibptr" using the standard logic and the flag state in - structure N. -*/ - -#define BIT_V(S) ((S) & 037) /* convert select code to bit position */ -#define BIT_M(S) (1u << BIT_V (S)) /* convert select code to bit mask */ - -#define setSKF(B) stat_data = IORETURN (SCPE_OK, (uint16) ((B) ? ioSKF : ioNONE)) - -#define setPRL(S,B) dev_prl[(S)/32] = dev_prl[(S)/32] & ~BIT_M (S) | (((B) & 1) << BIT_V (S)) -#define setIRQ(S,B) dev_irq[(S)/32] = dev_irq[(S)/32] & ~BIT_M (S) | (((B) & 1) << BIT_V (S)) -#define setSRQ(S,B) dev_srq[(S)/32] = dev_srq[(S)/32] & ~BIT_M (S) | (((B) & 1) << BIT_V (S)) - -#define PRL(S) ((dev_prl[(S)/32] >> BIT_V (S)) & 1) -#define IRQ(S) ((dev_irq[(S)/32] >> BIT_V (S)) & 1) -#define SRQ(S) ((dev_srq[(S)/32] >> BIT_V (S)) & 1) - -#define setstdSKF(N) setSKF ((signal == ioSFC) && !N.flag || \ - (signal == ioSFS) && N.flag) - -#define setstdPRL(N) setPRL (dibptr->select_code, !(N.control & N.flag)); -#define setstdIRQ(N) setIRQ (dibptr->select_code, N.control & N.flag & N.flagbuf); -#define setstdSRQ(N) setSRQ (dibptr->select_code, N.flag); - - -/* CPU state */ - -extern uint32 dev_prl [2], dev_irq [2], dev_srq [2]; /* I/O signal vectors */ - -/* CPU functions */ - -extern t_stat ibl_copy (const BOOT_ROM rom, int32 dev, uint32 sr_clear, uint32 sr_set); -extern void hp_enbdis_pair (DEVICE *ccp, DEVICE *dcp); - -/* System functions */ - -extern const char *fmt_char (uint8 ch); -extern t_stat hp_setsc (UNIT *uptr, int32 val, char *cptr, void *desc); -extern t_stat hp_showsc (FILE *st, UNIT *uptr, int32 val, void *desc); -extern t_stat hp_setdev (UNIT *uptr, int32 val, char *cptr, void *desc); -extern t_stat hp_showdev (FILE *st, UNIT *uptr, int32 val, void *desc); -extern t_bool hp_fprint_stopped (FILE *st, t_stat reason); - -/* Device-specific functions */ - -extern int32 sync_poll (POLLMODE poll_mode); - -#endif +/* hp2100_defs.h: HP 2100 System architectural declarations + + Copyright (c) 1993-2016, Robert M. Supnik + Copyright (c) 2017 J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + 10-Jan-17 JDB Added architectural constants + 05-Aug-16 JDB Removed PC_Global renaming; P register is now "PR" + 13-May-16 JDB Modified for revised SCP API function parameter types + 19-Jun-15 JDB Conditionally use PC_Global for PC for version 4.0 and on + 30-Dec-14 JDB Added S-register parameters to ibl_copy, more IBL constants + 28-Dec-14 JDB Changed suppression from #pragma GCC to #pragma clang + 05-Feb-13 JDB Added declaration for hp_fprint_stopped + 18-Mar-13 JDB Added "-Wdangling-else" to the suppression pragmas + Removed redundant extern declarations + 14-Mar-13 MP Changed guard macro name to avoid reserved namespace + 14-Dec-12 JDB Added "-Wbitwise-op-parentheses" to the suppression pragmas + 12-May-12 JDB Added pragmas to suppress logical operator precedence warnings + 10-Feb-12 JDB Added hp_setsc, hp_showsc functions to support SC modifier + 28-Mar-11 JDB Tidied up signal handling + 29-Oct-10 JDB DMA channels renamed from 0,1 to 1,2 to match documentation + 27-Oct-10 JDB Revised I/O signal enum values for concurrent signals + Revised I/O macros for new signal handling + 09-Oct-10 JDB Added DA and DC device select code assignments + 07-Sep-08 JDB Added POLL_FIRST to indicate immediate connection attempt + 15-Jul-08 JDB Rearranged declarations with hp2100_cpu.h + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 25-Jun-08 JDB Added PIF device + 17-Jun-08 JDB Declared fmt_char() function + 26-May-08 JDB Added MPX device + 24-Apr-08 JDB Added I_MRG_I, I_JSB, I_JSB_I, and I_JMP instruction masks + 14-Apr-08 JDB Changed TMR_MUX to TMR_POLL for idle support + Added POLLMODE, sync_poll() declaration + Added I_MRG, I_ISZ, I_IOG, I_STF, and I_SFS instruction masks + 07-Dec-07 JDB Added BACI device + 10-Nov-07 JDB Added 16/32-bit unsigned-to-signed conversions + 11-Jan-07 JDB Added 12578A DMA byte packing to DMA structure + 28-Dec-06 JDB Added CRS backplane signal as I/O pseudo-opcode + Added DMASK32 32-bit mask value + 21-Dec-06 JDB Changed MEM_ADDR_OK for 21xx loader support + 12-Sep-06 JDB Define NOTE_IOG to recalc interrupts after instr exec + Rename STOP_INDINT to NOTE_INDINT (not a stop condition) + 30-Dec-04 JDB Added IBL_DS_HEAD head number mask + 19-Nov-04 JDB Added STOP_OFFLINE, STOP_PWROFF stop codes + 25-Apr-04 RMS Added additional IBL definitions + Added DMA EDT I/O pseudo-opcode + 25-Apr-03 RMS Revised for extended file support + 24-Oct-02 RMS Added indirect address interrupt + 08-Feb-02 RMS Added DMS definitions + 01-Feb-02 RMS Added terminal multiplexor support + 16-Jan-02 RMS Added additional device support + 30-Nov-01 RMS Added extended SET/SHOW support + 15-Oct-00 RMS Added dynamic device numbers + 14-Apr-99 RMS Changed t_addr to unsigned + + The [original] author gratefully acknowledges the help of Jeff Moffat in + answering questions about the HP2100; and of Dave Bryan in adding features + and correcting errors throughout the simulator. + + + This file provides the general declarations used throughout the HP 2100 + simulator. It is required by all modules. + + + ----------------------------------------------------- + Implementation Note -- Compiling the Simulator as C++ + ----------------------------------------------------- + + Although simulators are written in C, the SIMH project encourages developers + to compile them with a C++ compiler to obtain the more careful type checking + provided. To obtain successful compilations, the simulator must be written + in the subset of C that is also valid C++. Using valid C features beyond + that subset, as the HP 2100 simulator does, will produce C++ compiler errors. + + The standard C features used by the simulator that prevent error-free C++ + compilation are: + + 1. Incomplete types. + + In C, mutually recursive type definitions are allowed by the use of + incomplete type declarations, such as "DEVICE ms_dev;" followed later by + "DEVICE ms_dev {...};". Several HP device simulators use this feature to + place a pointer to the device structure in the "desc" field of an MTAB + array element, typically when the associated validation or display + routine handles multiple devices. As the DEVICE contains a pointer to + the MTAB array, and an MTAB array element contains a pointer to the + DEVICE, the definitions are mutually recursive, and incomplete types are + employed. C++ does not permit incomplete types. + + 2. Implicit conversion of ints to enums. + + In C, enumeration types are compatible with integer types, and its + members are constants having type "int". As such, they are semantically + equivalent to and may be used interchangeably with integers. For the + developer, though, C enumerations have some advantages. In particular, + the compiler may check a "switch" statement to ensure that all of the + enumeration cases are covered. Also, a mathematical set may be modeled + by an enumeration type with disjoint enumerator values, with the bitwise + integer OR and AND operators modeling the set union and intersection + operations. The latter has direct support in the "gdb" debugger, which + will display an enumerated type value as a union of the various + enumerators. The HP simulator makes extensive use of both features to + model hardware signal buses (e.g., INBOUND_SET, OUTBOUND_SET) and so + performs bitwise integer operations on the enumerations to model signal + assertion and denial. In C++, implicit conversion from enumerations to + integers is allowed, but conversion from integers to enumerations is + illegal without explicit casts. Therefore, the idiom employed by the + simulator to assert a signal (e.g., "outbound_signals |= INTREQ") is + rejected by the C++ compiler. + + 3. Implicit increment operations on enums. + + Because enums are compatible with integers in C, no special enumerator + increment operator is provided. To cycle through the range of an + enumeration type, e.g. in a "for" statement, the standard integer + increment operator, "++", is used. In C++, the "++" operator must be + overloaded with a version specific to the enumeration type; applying the + integer "++" to an enumeration is illegal. + + 4. Use of C++ keywords as variable names. + + C++ reserves a number of additional keywords beyond those reserved by C. + Use of any of these keywords as a variable or type name is legal C but + illegal C++. The HP simulator uses variables named "class" and + "operator", which are keywords in C++. + + The HP simulator is written in ISO standard C and will compile cleanly with a + compiler implementing the 1999 C standard. Compilation as C++ is not a goal + of the simulator and cannot work, given the incompatibilities listed above. +*/ + + +#ifndef HP2100_DEFS_H_ +#define HP2100_DEFS_H_ 0 + + +#include "sim_rev.h" +#include "sim_defs.h" + + + +/* The following pragmas quell clang and Microsoft Visual C++ warnings that are + on by default but should not be, in my opinion. They warn about the use of + perfectly valid code and require the addition of redundant parentheses and + braces to silence them. Rather than clutter up the code with scores of extra + symbols that, in my view, make the code harder to read and maintain, I elect + to suppress these warnings. + + VC++ 2008 warning descriptions: + + - 4114: "same type qualifier used more than once" [legal per C99] + + - 4554: "check operator precedence for possible error; use parentheses to + clarify precedence" + + - 4996: "function was declared deprecated" +*/ + +#if defined (__clang__) + #pragma clang diagnostic ignored "-Wlogical-op-parentheses" + #pragma clang diagnostic ignored "-Wbitwise-op-parentheses" + #pragma clang diagnostic ignored "-Wshift-op-parentheses" + #pragma clang diagnostic ignored "-Wdangling-else" + +#elif defined (_MSC_VER) + #pragma warning (disable: 4114 4554 4996) + +#endif + + +/* Simulator stop and notification codes */ + +#define STOP_RSRV 1 /* must be 1 */ +#define STOP_IODV 2 /* must be 2 */ +#define STOP_HALT 3 /* HALT */ +#define STOP_IBKPT 4 /* breakpoint */ +#define STOP_IND 5 /* indirect loop */ +#define NOTE_INDINT 6 /* indirect intr */ +#define STOP_NOCONN 7 /* no connection */ +#define STOP_OFFLINE 8 /* device offline */ +#define STOP_PWROFF 9 /* device powered off */ +#define NOTE_IOG 10 /* I/O instr executed */ + + +/* Modifier validation identifiers */ + +#define MTAB_XDV (MTAB_XTD | MTAB_VDV) +#define MTAB_XUN (MTAB_XTD | MTAB_VUN) + + +/* Architectural constants. + + These macros specify the width, sign location, value mask, and minimum and + maximum signed and unsigned values for the data sizes supported by the + simulator. In addition, masks for 16-bit and 32-bit overflow are defined (an + overflow is indicated if the masked bits are not all ones or all zeros). + + The HP_WORD type is used to declare variables that represent 16-bit registers + or buses in hardware. +*/ + +typedef uint16 HP_WORD; /* HP 16-bit data word representation */ + +#define R_MASK 0177777u /* 16-bit register mask */ + +#define D4_WIDTH 4 /* 4-bit data bit width */ +#define D4_MASK 0017u /* 4-bit data mask */ + +#define D8_WIDTH 8 /* 8-bit data bit width */ +#define D8_MASK 0377u /* 8-bit data mask */ +#define D8_UMAX 0377u /* 8-bit unsigned maximum value */ +#define D8_SMAX 0177u /* 8-bit signed maximum value */ +#define D8_SMIN 0200u /* 8-bit signed minimum value */ +#define D8_SIGN 0200u /* 8-bit sign */ + +#define D16_WIDTH 16 /* 16-bit data bit width */ +#define D16_MASK 0177777u /* 16-bit data mask */ +#define D16_UMAX 0177777u /* 16-bit unsigned maximum value */ +#define D16_SMAX 0077777u /* 16-bit signed maximum value */ +#define D16_SMIN 0100000u /* 16-bit signed minimum value */ +#define D16_SIGN 0100000u /* 16-bit sign */ + +#define D32_WIDTH 32 /* 32-bit data bit width */ +#define D32_MASK 037777777777u /* 32-bit data mask */ +#define D32_UMAX 037777777777u /* 32-bit unsigned maximum value */ +#define D32_SMAX 017777777777u /* 32-bit signed maximum value */ +#define D32_SMIN 020000000000u /* 32-bit signed minimum value */ +#define D32_SIGN 020000000000u /* 32-bit sign */ + +#define D48_WIDTH 48 /* 48-bit data bit width */ +#define D48_MASK 07777777777777777uL /* 48-bit data mask */ +#define D48_UMAX 07777777777777777uL /* 48-bit unsigned maximum value */ +#define D48_SMAX 03777777777777777uL /* 48-bit signed maximum value */ +#define D48_SMIN 04000000000000000uL /* 48-bit signed minimum value */ +#define D48_SIGN 04000000000000000uL /* 48-bit sign */ + +#define D64_WIDTH 64 /* 64-bit data bit width */ +#define D64_MASK 01777777777777777777777uL /* 64-bit data mask */ +#define D64_UMAX 01777777777777777777777uL /* 64-bit unsigned maximum value */ +#define D64_SMAX 00777777777777777777777uL /* 64-bit signed maximum value */ +#define D64_SMIN 01000000000000000000000uL /* 64-bit signed minimum value */ +#define D64_SIGN 01000000000000000000000uL /* 64-bit sign */ + +#define S16_OVFL_MASK ((uint32) D16_UMAX << D16_WIDTH | \ + D16_SIGN) /* 16-bit signed overflow mask */ + +#define S32_OVFL_MASK ((t_uint64) D32_UMAX << D32_WIDTH | \ + D32_SIGN) /* 32-bit signed overflow mask */ + + +/* Memory constants */ + +#define OF_WIDTH 10 /* offset bit width */ +#define OF_MASK ((1u << OF_WIDTH) - 1) /* offset mask (2 ** 10 - 1) */ +#define OF_MAX ((1u << OF_WIDTH) - 1) /* offset maximum (2 ** 10 - 1) */ + +#define PG_WIDTH 10 /* page bit width */ +#define PG_MASK ((1u << PG_WIDTH) - 1) /* page mask (2 ** 10 - 1) */ +#define PG_MAX ((1u << PG_WIDTH) - 1) /* page maximum (2 ** 10 - 1) */ + +#define LA_WIDTH 15 /* logical address bit width */ +#define LA_MASK ((1u << LA_WIDTH) - 1) /* logical address mask (2 ** 15 - 1) */ +#define LA_MAX ((1u << LA_WIDTH) - 1) /* logical address maximum (2 ** 15 - 1) */ + +#define PA_WIDTH 20 /* physical address bit width */ +#define PA_MASK ((1u << PA_WIDTH) - 1) /* physical address mask (2 ** 20 - 1) */ +#define PA_MAX ((1u << PA_WIDTH) - 1) /* physical address maximum (2 ** 20 - 1) */ + +#define DV_WIDTH 16 /* data value bit width */ +#define DV_MASK ((1u << DV_WIDTH) - 1) /* data value mask (2 ** 16 - 1) */ +#define DV_SIGN ( 1u << (DV_WIDTH - 1)) /* data value sign (2 ** 15) */ +#define DV_UMAX ((1u << DV_WIDTH) - 1) /* data value unsigned maximum (2 ** 16 - 1) */ +#define DV_SMAX ((1u << (DV_WIDTH - 1)) - 1) /* data value signed maximum (2 ** 15 - 1) */ + + +/* Portable conversions. + + SIMH is written with the assumption that the defined-size types (e.g., + uint16) are at least the required number of bits but may be larger. + Conversions that otherwise would make inherent size assumptions must instead + be coded explicitly. For example, doing: + + negative_value_32 = (int32) negative_value_16; + + ...will not guarantee that bits 0-15 of "negative_value_32" are ones, whereas + the supplied sign-extension macro will. + + The conversions available are: + + - SEXT8 -- int8 sign-extended to int32 + - SEXT16 -- int16 sign-extended to int32 + - NEG16 -- int8 negated + - NEG16 -- int16 negated + - NEG32 -- int32 negated + - INT16 -- uint16 to int16 + - INT32 -- uint32 to int32 + + + Implementation notes: + + 1. The routines assume that 16-bit values are masked to exactly 16 bits + before invoking. +*/ + +#define SEXT8(x) (int32) ((x) & D8_SIGN ? (x) | ~D8_MASK : (x)) +#define SEXT16(x) (int32) ((x) & D16_SIGN ? (x) | ~D16_MASK : (x)) + +#define NEG8(x) ((~(x) + 1) & D8_MASK) +#define NEG16(x) ((~(x) + 1) & D16_MASK) +#define NEG32(x) ((~(x) + 1) & D32_MASK) + +#define INT16(u) ((u) > D16_SMAX ? (-(int16) (D16_UMAX - (u)) - 1) : (int16) (u)) +#define INT32(u) ((u) > D32_SMAX ? (-(int32) (D32_UMAX - (u)) - 1) : (int32) (u)) + + +/* Byte accessors. + + These macros extract the upper and lower bytes from a word and form a word + from upper and lower bytes. Replacement of a byte within a word is also + provided, as is an enumeration type that defines byte selection. + + The accessors are: + + - UPPER_BYTE -- return the byte from the upper position of a word value + - LOWER_BYTE -- return the byte from the lower position of a word value + - TO_WORD -- return a word with the specified upper and lower bytes + + - REPLACE_UPPER -- replace the upper byte of the word value + - REPLACE_LOWER -- replace the lower byte of the word value + +*/ + +typedef enum { + upper, /* upper byte selected */ + lower /* lower byte selected */ + } BYTE_SELECTOR; + +#define UPPER_BYTE(w) (uint8) ((w) >> D8_WIDTH & D8_MASK) +#define LOWER_BYTE(w) (uint8) ((w) & D8_MASK) +#define TO_WORD(u,l) (HP_WORD) (((u) & D8_MASK) << D8_WIDTH | (l) & D8_MASK) + +#define REPLACE_UPPER(w,b) ((w) & D8_MASK | ((b) & D8_MASK) << D8_WIDTH) +#define REPLACE_LOWER(w,b) ((w) & D8_MASK << D8_WIDTH | (b) & D8_MASK) + + +/* Double-word accessors */ + +#define UPPER_WORD(d) (HP_WORD) ((d) >> D16_WIDTH & D16_MASK) +#define LOWER_WORD(d) (HP_WORD) ((d) & D16_MASK) + +#define TO_DWORD(u,l) ((uint32) (u) << D16_WIDTH | (l)) + + +/* Portable conversions (sign-extension, unsigned-to-signed) */ + +#define SEXT(x) ((int32) (((x) & SIGN)? ((x) | ~DMASK): ((x) & DMASK))) + + +/* Memory */ + +#define MEMSIZE (cpu_unit.capac) /* actual memory size */ +#define VA_N_SIZE 15 /* virtual addr size */ +#define VASIZE (1 << VA_N_SIZE) +#define VAMASK 077777 /* virt addr mask */ +#define PA_N_SIZE 20 /* phys addr size */ +#define PASIZE (1 << PA_N_SIZE) +#define PAMASK (PASIZE - 1) /* phys addr mask */ + +/* Architectural constants */ + +#define SIGN32 020000000000 /* 32b sign */ +#define DMASK32 037777777777 /* 32b data mask/maximum value */ +#define DMAX32 017777777777 /* 32b maximum signed value */ +#define SIGN 0100000 /* 16b sign */ +#define DMASK 0177777 /* 16b data mask/maximum value */ +#define DMAX 0077777 /* 16b maximum signed value */ +#define DMASK8 0377 /* 8b data mask/maximum value */ + +/* Timers */ + +#define TMR_CLK 0 /* clock */ +#define TMR_POLL 1 /* input polling */ + +#define POLL_RATE 100 /* poll 100 times per second */ +#define POLL_FIRST 1 /* first poll is "immediate" */ +#define POLL_WAIT 15800 /* initial poll ~ 10 msec. */ + +typedef enum { INITIAL, SERVICE } POLLMODE; /* poll synchronization modes */ + +/* I/O instruction sub-opcodes */ + +#define soHLT 0 /* halt */ +#define soFLG 1 /* set/clear flag */ +#define soSFC 2 /* skip on flag clear */ +#define soSFS 3 /* skip on flag set */ +#define soMIX 4 /* merge into A/B */ +#define soLIX 5 /* load into A/B */ +#define soOTX 6 /* output from A/B */ +#define soCTL 7 /* set/clear control */ + +/* I/O devices - fixed select code assignments */ + +#define CPU 000 /* interrupt control */ +#define OVF 001 /* overflow */ +#define DMALT1 002 /* DMA 1 alternate */ +#define DMALT2 003 /* DMA 2 alternate */ +#define PWR 004 /* power fail */ +#define PRO 005 /* parity/mem protect */ +#define DMA1 006 /* DMA channel 1 */ +#define DMA2 007 /* DMA channel 2 */ + +/* I/O devices - variable select code assignment defaults */ + +#define PTR 010 /* 12597A-002 paper tape reader */ +#define TTY 011 /* 12531C teleprinter */ +#define PTP 012 /* 12597A-005 paper tape punch */ +#define CLK 013 /* 12539C time-base generator */ +#define LPS 014 /* 12653A line printer */ +#define LPT 015 /* 12845A line printer */ +#define MTD 020 /* 12559A data */ +#define MTC 021 /* 12559A control */ +#define DPD 022 /* 12557A data */ +#define DPC 023 /* 12557A control */ +#define DQD 024 /* 12565A data */ +#define DQC 025 /* 12565A control */ +#define DRD 026 /* 12610A data */ +#define DRC 027 /* 12610A control */ +#define MSD 030 /* 13181A data */ +#define MSC 031 /* 13181A control */ +#define IPLI 032 /* 12566B link in */ +#define IPLO 033 /* 12566B link out */ +#define DS 034 /* 13037A control */ +#define BACI 035 /* 12966A Buffered Async Comm Interface */ +#define MPX 036 /* 12792A/B/C 8-channel multiplexer */ +#define PIF 037 /* 12620A/12936A Privileged Interrupt Fence */ +#define MUXL 040 /* 12920A lower data */ +#define MUXU 041 /* 12920A upper data */ +#define MUXC 042 /* 12920A control */ +#define DI_DA 043 /* 12821A Disc Interface with Amigo disc devices */ +#define DI_DC 044 /* 12821A Disc Interface with CS/80 disc and tape devices */ + +#define OPTDEV 002 /* start of optional devices */ +#define CRSDEV 006 /* start of devices that receive CRS */ +#define VARDEV 010 /* start of variable assignments */ +#define MAXDEV 077 /* end of select code range */ + +/* IBL assignments */ + +#define IBL_V_SEL 14 /* ROM select <15:14> */ +#define IBL_M_SEL 03 +#define IBL_PTR 0000000 /* ROM 0: 12992K paper tape reader (PTR) */ +#define IBL_DP 0040000 /* ROM 1: 12992A 7900 disc (DP) */ +#define IBL_DQ 0060000 /* ROM 1: 12992A 2883 disc (DQ) */ +#define IBL_MS 0100000 /* ROM 2: 12992D 7970 tape (MS) */ +#define IBL_DS 0140000 /* ROM 3: 12992B 7905/06/20/25 disc (DS) */ +#define IBL_MAN 0010000 /* RPL/manual boot <13:12> */ +#define IBL_V_DEV 6 /* select code <11:6> */ +#define IBL_OPT 0000070 /* options in <5:3> */ +#define IBL_DP_REM 0000001 /* DP removable <0:0> */ +#define IBL_DS_HEAD 0000003 /* DS head number <1:0> */ +#define IBL_LNT 64 /* boot ROM length in words */ +#define IBL_MASK (IBL_LNT - 1) /* boot length mask */ +#define IBL_DPC (IBL_LNT - 2) /* DMA ctrl word */ +#define IBL_END (IBL_LNT - 1) /* last location */ + +#define IBL_S_CLR 0000000 /* ibl_copy mask to clear the S register */ +#define IBL_S_NOCLR 0177777 /* ibl_copy mask to preserve the S register */ +#define IBL_S_NOSET 0000000 /* ibl_copy mask to preserve the S register */ + +#define IBL_SET_SC(s) ((s) << IBL_V_DEV) /* position the select code in the S register */ + +typedef uint16 BOOT_ROM [IBL_LNT]; /* boot ROM data */ + + +/* I/O backplane signals. + + The IOSIGNAL declarations mirror the hardware I/O backplane signals. A set + of one or more signals forms an IOCYCLE that is sent to a device IOHANDLER + for action. The CPU and DMA dispatch one signal set to the target device + handler per I/O cycle. A CPU cycle consists of either one or two signals; if + present, the second signal will be CLF. A DMA cycle consists of from two to + five signals. In addition, a front-panel PRESET or power-on reset dispatches + two or three signals, respectively. + + In hardware, signals are assigned to one or more specific I/O T-periods, and + some signals are asserted concurrently. For example, a programmed STC sc,C + instruction asserts the STC and CLF signals together in period T4. Under + simulation, signals are ORed to form an I/O cycle; in this example, the + signal handler would receive an IOCYCLE value of "ioSTC | ioCLF". + + Hardware allows parallel action for concurrent signals. Under simulation, a + "concurrent" set of signals is processed sequentially by the signal handler + in order of ascending numerical value. Although assigned T-periods differ + between programmed I/O and DMA I/O cycles, a single processing order is used. + The order of execution generally follows the order of T-period assertion, + except that ioSIR is processed after all other signals that may affect the + interrupt request chain. + + Implementation notes: + + 1. The ioCLF signal must be processed after ioSFS/ioSFC to ensure that a + true skip test generates ioSKF before the flag is cleared, and after + ioIOI/ioIOO/ioSTC/ioCLC to meet the requirement that executing an + instruction having the H/C bit set is equivalent to executing the same + instruction with the H/C bit clear and then a CLF instruction. + + 2. The ioSKF signal is never sent to an I/O handler. Rather, it is returned + from the handler if the SFC or SFS condition is true. If the condition + is false, ioNONE is returned instead. As these two values are returned + in the 16-bit data portion of the returned value, their assigned values + must be <= 100000 octal. + + 3. An I/O handler will receive ioCRS as a result of a CLC 0 instruction, + ioPOPIO and ioCRS as a result of a RESET command, and ioPON, ioPOPIO, and + ioCRS as a result of a RESET -P command. + + 4. An I/O handler will receive ioNONE when a HLT instruction is executed + that has the H/C bit clear (i.e., no CLF generated). + + 5. In hardware, the SIR signal is generated unconditionally every T5 period + to time the setting of the IRQ flip-flop. Under simulation, ioSIR + indicates that the I/O handler must set the PRL, IRQ, and SRQ signals as + required by the interface logic. ioSIR must be included in the I/O cycle + if any of the flip-flops affecting these signals are changed and the + interface supports interrupts or DMA transfers. + + 6. In hardware, the ENF signal is unconditionally generated every T2 period + to time the setting of the flag flip-flop and to reset the IRQ flip-flop. + If the flag buffer flip-flip is set, then flag will be set by ENF. If + the flag buffer is clear, ENF will not affect flag. Under simulation, + ioENF is sent to set the flag buffer and flag flip-flops. For those + interfaces where this action is identical to that provided by STF, the + ioENF handler may simply fall into the ioSTF handler. + + 7. In hardware, the PON signal is asserted continuously while the CPU is + operating. Under simulation, ioPON is asserted only at simulator + initialization or when processing a RESET -P command. +*/ + +typedef enum { ioNONE = 0000000, /* -- -- -- -- -- no signal asserted */ + ioPON = 0000001, /* T2 T3 T4 T5 T6 power on normal */ + ioENF = 0000002, /* T2 -- -- -- -- enable flag */ + ioIOI = 0000004, /* -- -- T4 T5 -- I/O data input (CPU) + T2 T3 -- -- -- I/O data input (DMA) */ + ioIOO = 0000010, /* -- T3 T4 -- -- I/O data output */ + ioSKF = 0000020, /* -- T3 T4 T5 -- skip on flag */ + ioSFS = 0000040, /* -- T3 T4 T5 -- skip if flag is set */ + ioSFC = 0000100, /* -- T3 T4 T5 -- skip if flag is clear */ + ioSTC = 0000200, /* -- -- T4 -- -- set control flip-flop (CPU) + -- T3 -- -- -- set control flip-flop (DMA) */ + ioCLC = 0000400, /* -- -- T4 -- -- clear control flip-flop (CPU) + -- T3 T4 -- -- clear control flip-flop (DMA) */ + ioSTF = 0001000, /* -- T3 -- -- -- set flag flip-flop */ + ioCLF = 0002000, /* -- -- T4 -- -- clear flag flip-flop (CPU) + -- T3 -- -- -- clear flag flip-flop (DMA) */ + ioEDT = 0004000, /* -- -- T4 -- -- end data transfer */ + ioCRS = 0010000, /* -- -- -- T5 -- control reset */ + ioPOPIO = 0020000, /* -- -- -- T5 -- power-on preset to I/O */ + ioIAK = 0040000, /* -- -- -- -- T6 interrupt acknowledge */ + ioSIR = 0100000 } IOSIGNAL; /* -- -- -- T5 -- set interrupt request */ + + +typedef uint32 IOCYCLE; /* a set of signals forming one I/O cycle */ + +#define IOIRQSET (ioSTC | ioCLC | ioENF | \ + ioSTF | ioCLF | ioIAK | \ + ioCRS | ioPOPIO | ioPON) /* signals that may affect interrupt state */ + + +/* Flip-flops */ + +typedef enum { + CLEAR = 0, /* the flip-flop is clear */ + SET = 1 /* the flip-flop is set */ + } FLIP_FLOP; + +#define TOGGLE(ff) ff = (FLIP_FLOP) (ff ^ 1) /* toggle a flip-flop variable */ + +#define D_FF(b) (FLIP_FLOP) ((b) != 0) /* use a Boolean expression for a D flip-flop */ + + +/* I/O structures */ + +typedef struct dib DIB; /* incomplete definition */ + +typedef uint32 IOHANDLER (DIB *dibptr, /* I/O signal handler prototype */ + IOCYCLE signal_set, + uint32 stat_data); + +struct dib { /* Device information block */ + IOHANDLER *io_handler; /* pointer to device's I/O signal handler */ + uint32 select_code; /* device's select code */ + uint32 card_index; /* device's card index for state variables */ + }; + + +/* I/O signal and status macros. + + The following macros are useful in I/O signal handlers and unit service + routines. The parameter definition symbols employed are: + + I = an IOCYCLE value + E = a t_stat error status value + D = a uint16 data value + C = a uint32 combined status and data value + P = a pointer to a DIB structure + B = a Boolean test value + + Implementation notes: + + 1. The IONEXT macro isolates the next signal in sequence to process from the + I/O cycle I. + + 2. The IOADDSIR macro adds an ioSIR signal to the I/O cycle I if it + contains signals that might change the interrupt state. + + 3. The IORETURN macro forms the combined status and data value to be + returned by a handler from the t_stat error code E and the 16-bit data + value D. + + 4. The IOSTATUS macro isolates the t_stat error code from a combined status + and data value value C. + + 5. The IODATA macro isolates the 16-bit data value from a combined status + and data value value C. + + 6. The IOPOWERON macro calls signal handler P->H with DIB pointer P to + process a power-on reset action. + + 7. The IOPRESET macro calls signal handler P->H with DIB pointer P to + process a front-panel PRESET action. + + 8. The IOERROR macro returns t_stat error code E from a unit service routine + if the Boolean test B is true. +*/ + +#define IONEXT(I) (IOSIGNAL) ((I) & (IOCYCLE) (- (int32) (I))) /* extract next I/O signal to handle */ +#define IOADDSIR(I) ((I) & IOIRQSET ? (I) | ioSIR : (I)) /* add SIR if IRQ state might change */ + +#define IORETURN(E,D) ((uint32) ((E) << 16 | (D) & DMASK)) /* form I/O handler return value */ +#define IOSTATUS(C) ((t_stat) ((C) >> 16) & DMASK) /* extract I/O status from combined value */ +#define IODATA(C) ((uint16) ((C) & DMASK)) /* extract data from combined value */ + +#define IOPOWERON(P) (P)->io_handler ((P), ioPON | ioPOPIO | ioCRS, 0) /* send power-on signals to handler */ +#define IOPRESET(P) (P)->io_handler ((P), ioPOPIO | ioCRS, 0) /* send PRESET signals to handler */ +#define IOERROR(B,E) ((B) ? (E) : SCPE_OK) /* stop on I/O error if enabled */ + + +/* I/O signal logic macros. + + The following macros implement the logic for the SKF, PRL, IRQ, and SRQ + signals. Both standard and general logic macros are provided. The parameter + definition symbols employed are: + + S = a uint32 select code value + B = a Boolean test value + N = a name of a structure containing the standard flip-flops + + Implementation notes: + + 1. The setSKF macro sets the Skip on Flag signal in the return data value if + the Boolean value B is true. + + 2. The setPRL macro sets the Priority Low signal for select code S to the + Boolean value B. + + 3. The setIRQ macro sets the Interrupt Request signal for select code S to + the Boolean value B. + + 4. The setSRQ macro sets the Service Request signal for select code S to the + Boolean value B. + + 5. The PRL macro returns the Priority Low signal for select code S as a + Boolean value. + + 6. The IRQ macro returns the Interrupt Request signal for select code S as a + Boolean value. + + 7. The SRQ macro returns the Service Request signal for select code S as a + Boolean value. + + 8. The setstdSKF macro sets Skip on Flag signal in the return data value if + the flag state in structure N matches the current skip test condition. + + 9. The setstdPRL macro sets the Priority Low signal for the select code + referenced by "dibptr" using the standard logic and the control and flag + states in structure N. + + 10. The setstdIRQ macro sets the Interrupt Request signal for the select code + referenced by "dibptr" using the standard logic and the control, flag, + and flag buffer states in structure N. + + 11. The setstdSRQ macro sets the Service Request signal for the select code + referenced by "dibptr" using the standard logic and the flag state in + structure N. +*/ + +#define BIT_V(S) ((S) & 037) /* convert select code to bit position */ +#define BIT_M(S) (1u << BIT_V (S)) /* convert select code to bit mask */ + +#define setSKF(B) stat_data = IORETURN (SCPE_OK, (uint16) ((B) ? ioSKF : ioNONE)) + +#define setPRL(S,B) dev_prl[(S)/32] = dev_prl[(S)/32] & ~BIT_M (S) | (((B) & 1) << BIT_V (S)) +#define setIRQ(S,B) dev_irq[(S)/32] = dev_irq[(S)/32] & ~BIT_M (S) | (((B) & 1) << BIT_V (S)) +#define setSRQ(S,B) dev_srq[(S)/32] = dev_srq[(S)/32] & ~BIT_M (S) | (((B) & 1) << BIT_V (S)) + +#define PRL(S) ((dev_prl[(S)/32] >> BIT_V (S)) & 1) +#define IRQ(S) ((dev_irq[(S)/32] >> BIT_V (S)) & 1) +#define SRQ(S) ((dev_srq[(S)/32] >> BIT_V (S)) & 1) + +#define setstdSKF(N) setSKF ((signal == ioSFC) && !N.flag || \ + (signal == ioSFS) && N.flag) + +#define setstdPRL(N) setPRL (dibptr->select_code, !(N.control & N.flag)); +#define setstdIRQ(N) setIRQ (dibptr->select_code, N.control & N.flag & N.flagbuf); +#define setstdSRQ(N) setSRQ (dibptr->select_code, N.flag); + + +/* CPU state */ + +extern uint32 dev_prl [2], dev_irq [2], dev_srq [2]; /* I/O signal vectors */ + +/* CPU functions */ + +extern t_stat ibl_copy (const BOOT_ROM rom, int32 dev, uint32 sr_clear, uint32 sr_set); +extern void hp_enbdis_pair (DEVICE *ccp, DEVICE *dcp); + +/* System functions */ + +extern const char *fmt_char (uint8 ch); +extern t_stat hp_setsc (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +extern t_stat hp_setdev (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +extern t_stat hp_showsc (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +extern t_stat hp_showdev (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +extern t_bool hp_fprint_stopped (FILE *st, t_stat reason); + +/* Device-specific functions */ + +extern int32 sync_poll (POLLMODE poll_mode); + +#endif diff --git a/HP2100/hp2100_di.c b/HP2100/hp2100_di.c index 14c401aa..625920e7 100644 --- a/HP2100/hp2100_di.c +++ b/HP2100/hp2100_di.c @@ -1,1911 +1,1917 @@ -/* hp2100_di.c: HP 12821A HP-IB Disc Interface simulator - - Copyright (c) 2010-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - DI 12821A Disc Interface - - 24-Dec-14 JDB Added casts for explicit downward conversions - Removed redundant global declarations - 13-Feb-12 JDB First release - 15-Dec-11 JDB Added dummy DC device for diagnostics - 09-Oct-10 JDB Created DI simulation - - References: - - HP 12821A Disc Interface Installation and Service Manual (12821-90006, - Feb-1985) - - IEEE Standard Digital Interface for Programmable Instrumentation - (IEEE-488A-1980, Sep-1979) - - - The 12821A was a high-speed implementation of the Hewlett-Packard Interface - Bus (HP-IB, formalized as IEEE Std. 488-1978). It was used to interface - HP-IB disc and tape devices, such as the HP 7906H, 7908A, and 7974A, to the - HP 1000 running RTE-IVB or RTE-6/VM. Three device command protocols were - supported by the I/O drivers: Amigo discs by driver DVA32, CS/80 discs by - DVM33, and Amigo tapes by DVS23. - - In an RTE environment, the 12821A was the system controller. While - electrically compatible with the HP-IB specification and capable of receiving - addressing commands from the bus, the 12821A did not use the full IEEE-488 - protocol. Card talker and listener states were set by bits in the control - register, rather than by receiving talk and listen commands over the bus. - The bus address of the card could be set via DIP switches, but this feature - was only used by the diagnostic. - - The card supported packed and unpacked transfers across the bus. Up to four - devices could be connected to each card; this limit was imposed by the - maximum electrical loading on the bus compatible with the high data rate. - - The 12821A had a 16-word FIFO buffer and could sustain DCPC transfers of one - megabyte per second. Burst transfers by the CPU to fill or empty the FIFO - could run at the full bandwidth of the I/O backplane. This could hold off - lower-priority devices for 10-15 microseconds until the card slowed down to - the rate of the disc or tape. - - Card assembly 12821-60003 was revised to add a DCPC pacing option. Placing - jumper W1 in position A inhibited SRQ for one I/O cycle in six to allow a - lower-priority interface card to transfer one word. Position B allowed SRQ - to assert continuously as it did on the earlier card assembly 12821-60001. - - The simulator is logically partitioned into three sets of functions: the - interface card simulation, the HP-IB bus simulation, and the device - simulation. This is the card simulation and the card portion of the HP-IB - simulation. Separate modules for the tape and disc devices contain the - device simulations and the device portions of the HP-IB simulations. - - This simulator is written to allow the definition of multiple DI cards in a - system. The RTE operating system provided separate I/O drivers for the Amigo - disc, Amigo tape, and CS/80 disc devices. As only one I/O driver could - control a given interface, separate interfaces were required if more than one - device class was installed. For example, it was not possible to control an - Amigo disc and an Amigo tape connected to the same interface card. - - - Implementation notes: - - 1. The simulator behaves as though card switches S1-S7 are initially closed, - providing a card bus address of 0. The address may be changed with the - SET ADDRESS=n command. Only addresses 0-7 are supported, and the - address may duplicate a device bus address without conflict, as the - address is only used during the diagnostic when devices are disconnected. - - 2. The simulator behaves as though card switch S8 is open, enabling the card - to be the system controller. This cannot be changed by the user. - - 3. The simulator behaves as though card jumper W1 (DCPC pacing) is in - position B. This currently cannot be changed by the user. -*/ - - - -#include "hp2100_defs.h" -#include "hp2100_di.h" - - - -/* Program constants */ - -#define SW8_SYSCTL 1 /* card is always the system controller (switch 8) */ - -#define IFC_TIMEOUT 157 /* 157 instructions = ~ 100 microseconds */ - -#define CONTROLLER 31 /* dummy unit number for DI */ - - -/* Character constants */ - -#define LF '\012' - - -/* Control Word Register */ - -#define CNTL_SRQ 0100000 /* enable service request interrupt */ -#define CNTL_IFC 0040000 /* assert IFC or enable IFC interrupt */ -#define CNTL_REN 0020000 /* assert remote enable */ -#define CNTL_IRL 0010000 /* enable input-register-loaded interrupt */ -#define CNTL_LBO 0004000 /* enable last-byte-out interrupt */ -#define CNTL_LF 0002000 /* enable line feed terminator */ -#define CNTL_EOI 0001000 /* assert end or identify */ -#define CNTL_ATN 0000400 /* assert attention */ -#define CNTL_DIAG 0000200 /* diagnostic loopback */ -#define CNTL_NRFD 0000100 /* assert not ready for data */ -#define CNTL_PPE 0000040 /* parallel poll enable */ -#define CNTL_ODD 0000020 /* odd number of bytes */ -#define CNTL_PACK 0000010 /* packed data transfer */ -#define CNTL_LSTN 0000004 /* listen */ -#define CNTL_TALK 0000002 /* talk */ -#define CNTL_CIC 0000001 /* controller in charge */ - - -/* Status Word Register */ - -#define STAT_SRQBUS 0100000 /* service request bus state */ -#define STAT_IFCBUS 0040000 /* interface clear bus state */ -#define STAT_RENBUS 0020000 /* remote enable bus state */ -#define STAT_IRL 0010000 /* input register loaded */ -#define STAT_LBO 0004000 /* last byte out */ -#define STAT_LBI 0002000 /* last byte in */ -#define STAT_EOIBUS 0001000 /* end or identify bus state */ -#define STAT_ATNBUS 0000400 /* attention bus state */ -#define STAT_IFC 0000200 /* interface clear seen */ -#define STAT_ODD 0000020 /* odd number of bytes */ -#define STAT_SYSCTL 0000010 /* system controller */ -#define STAT_LSTN 0000004 /* listener */ -#define STAT_TALK 0000002 /* talker */ -#define STAT_CIC 0000001 /* controller in charge */ - - -/* Data word */ - -#define DATA_LBO 0100000 /* last byte out */ -#define DATA_EOI 0001000 /* end or identify */ -#define DATA_ATN 0000400 /* attention */ - - -/* Tag word */ - -#define BUS_SHIFT 16 /* left shift count to align BUS_ATN, EOI with tag */ -#define DATA_SHIFT 8 /* left shift count to align DATA_ATN, EOI with tag */ - -#define TAG_ATN 0000200000 /* bit 16: attention */ -#define TAG_EOI 0000400000 /* bit 17: end or identify */ -#define TAG_EDT 0001000000 /* bit 18: end of data transfer */ -#define TAG_LBR 0002000000 /* bit 19: last byte received */ - -#define TAG_MASK (TAG_ATN | TAG_EOI | TAG_EDT | TAG_LBR) - - -/* FIFO access modes */ - -#define FIFO_EMPTY (di_card->fifo_count == 0) /* FIFO empty test */ -#define FIFO_FULL (di_card->fifo_count == FIFO_SIZE) /* FIFO full test */ - -typedef enum { - bus_access, /* per-byte access */ - cpu_access, /* per-word access */ - diag_access /* mixed access */ - } FIFO_ACCESS; - - -/* Disc interface state variables */ - -DI_STATE di [card_count]; /* per-card state */ - - -/* Disc interface local bus routines */ - -static t_bool di_bus_accept (CARD_ID card, uint8 data); -static void di_bus_respond (CARD_ID card, uint8 cntl); -static void di_bus_poll (CARD_ID card); - -/* Disc interface local utility routines */ - -static void master_reset (CARD_ID card); -static void update_state (CARD_ID card); -static void fifo_load (CARD_ID card, uint16 data, FIFO_ACCESS access); -static uint16 fifo_unload (CARD_ID card, FIFO_ACCESS access); -static void fprint_bus (FILE *file, char *format, uint8 cntl); - - - -/* Dummy DC device. - - This temporary dummy device allows the DI diagnostic to test inter-card - signals. Test 15 can only be performed if there are two DIs available. - - This device provides a second "bare" card. Normally, it is disabled and - cannot be enabled by the user. Enabling or disabling DIAG mode on the DA - device automatically enables or disables the DC device. The select code of - the DC device is fixed at 45B and cannot be changed. -*/ - -DIB dc_dib = { &di_io, DI_DC, dc }; - -REG dc_reg [] = { - { BRDATA (FIFO, di [dc].fifo, 8, 20, FIFO_SIZE), REG_CIRC }, /* needed for "qptr" */ - { NULL } - }; - -DEVICE dc_dev = { - "DC", /* device name */ - NULL, /* unit array */ - dc_reg, /* register array */ - NULL, /* modifier array */ - 0, /* number of units */ - 10, /* address radix */ - 31, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 8, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &di_reset, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - &dc_dib, /* device information block */ - DEV_DEBUG | DEV_DIS, /* device flags */ - 0, /* debug control flags */ - di_deb, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL }; /* logical device name */ - - - -/* DI data structures. - - *dptrs device pointers - *bus_accept device acceptor function pointers - *bus_respond device responder function pointers - - di_deb DI debug table - - The first three pointer arrays have elements that correspond one-for-one with - the supported devices. These allow the DI simulator to work with multiple - cards. The actual devices are defined in the individual device simulators. - - Note that the DC and MA devices are reserved for future use. Until one or - the other is fully implemented, a dummy DC device is provided above for use - by the diagnostic only. -*/ - -extern DEVICE da_dev; - -static DEVICE *dptrs [card_count] = { &da_dev, &dc_dev, NULL }; -static ACCEPTOR *bus_accept [card_count] = { &da_bus_accept, NULL, NULL }; -static RESPONDER *bus_respond [card_count] = { &da_bus_respond, NULL, NULL }; - - -DEBTAB di_deb [] = { - { "CPU", DEB_CPU }, - { "CMDS", DEB_CMDS }, - { "BUF", DEB_BUF }, - { "XFER", DEB_XFER }, - { "RWSC", DEB_RWSC }, - { "SERV", DEB_SERV }, - { NULL, 0 } - }; - - - -/* Disc interface global VM routines */ - - -/* I/O signal handler. - - The card has two input and two output registers. The Input Data Register and - Output Data Register are addressed when the control flip-flop is set. The - Status Word and the Control Word Register are addressed when the control - flip-flop is clear. The card has the usual control, flag buffer, flag, and - SRQ flip-flops, though flag and SRQ are decoupled to allow the full DCPC - transfer rate. - - In hardware, the presence of the card FIFO, which is necessary to obtain full - DCPC bandwidth, implies a delay between CPU actions, such as outputting the - last word in a data transfer, and device actions, such as accepting the last - word of a disc write. Four flip-flops are used to monitor FIFO status: - - - EDT (End of Data Transfer) - - LBO (Last Byte Out) - - LBI (Last Byte In) - - EOR (End of Record) - - The EDT signal indicates that the final data word of a transfer is being - written to the FIFO. The flip-flop is set by the EDT backplane signal when - the last cycle of a DCPC transfer is executing, or during programmed output - transfers when CLF does not accompany IOO in packed mode, or when bit 15 of - the data word is set in unpacked mode. It remains set until it is cleared by - a master reset. The output of the EDT flip-flop drives the EDT tag input of - the FIFO. - - The LBO signal indicates that the final data byte of a transfer has been - sourced to the bus. The flip-flop is set when the last byte of the entry - tagged with EDT has been unloaded from the FIFO. It is cleared by a master - reset or when an entry not tagged with EDT is unloaded. The output of the - LBO flip-flop drives the LBO bit in the Status Word. - - The LBI signal indicates that the final byte of an input transfer has been - accepted from the bus. The flip-flop is set when a byte tagged with EOI is - received and the EOI bit in the control register is set, or a line-feed byte - is received and the LF bit in the control register is set. It is cleared by - a master reset or when neither of these conditions is true. The input of the - LBI flip-flop also drives the LBR (last byte received) tag input of the FIFO, - and the output of the flip-flop drives the LBI bit in the Status Word. - - The EOR signal indicates that the final data word of a transfer is available - in the Input Data Register. The flip-flop is set when the last byte of the - entry tagged with LBR has been unloaded from the FIFO and written to the IDR. - It is cleared by a master reset or when an entry not tagged with LBR is - unloaded and written to the IDR. The output of the EOR flip-flop sets the - flag flip-flop when the IDR is unloaded. - - - Implementation notes: - - 1. In hardware, the Status Word consists of individual flip-flops and status - signals that are enabled onto the I/O backplane. In simulation, the - individual status values are collected into a Status Word Register, and - the Output Data Register does not exist (output data is written directly - to the FIFO buffer). - - 2. The DIAG, T, and L control bits enable a data loopback path on the card. - An IOO issued to the card unloads a word from the FIFO and then loads the - lower byte back into both bytes of the FIFO. The data word output with - the IOO instruction is not used. - - In hardware, IOO triggers the FIFO unload and reload; T and L are - required only for the loopback path. If L is not asserted, then the FIFO - is loaded with 177777 due to the floating bus. If L is asserted and T is - not, then the FIFO is loaded with 000000 due to pullups on the DIO lines. - In simulation, we look only for DIAG and assume that T/L are set - properly, i.e., unloaded data is reloaded. - - 3. In hardware, the SRQ and NRFD lines are open-collector and may be driven - simultaneously from several bus devices. Simulating this fully would - require keeping the state of the lines for each device and deriving the - common bus signals from the logical OR of the state values. Fortunately, - some simplifications are possible. - - The DI asserts SRQ only if control word bit 15 is 1 and bit 0 is 0. - Other bit combinations deny SRQ; as neither the Amigo nor CS/80 protocols - use SRQ and serial polls, there will be no other driver. - - In hardware, every listener drives NRFD, but in practice there is only - one listener at a time. When the card is the listener, it asserts NRFD - if the FIFO becomes full. In simulation, we assert NRFD on the bus if - NRFD is set in the control register, or we are listening and the FIFO is - full. We deny NRFD if NRFD had been set in the control register but is - no longer, or if we had been a listener but are no longer. That is, we - assume that if we have forced NRFD or set it as a listener, then no one - else will be asserting NRFD, so it's safe for us to deny NRFD when the - override is removed or we are no longer a listener. - - We also deny NRFD when a CRS is issued if NRFD had been explicitly - requested or the card had been listening. The rationale is the same: - only a listener can assert NRFD, so if we were listening, it's safe to - deny it, because only we could have set it. - - 4. In hardware, the IRL, LBO, LBI, and IFC status bits are driven by - corresponding flip-flops. In simulation, the status bits themselves hold - the equivalent states and are set and cleared as indicated. - - 5. The card state must be updated during status read (IOI) processing - because the 7974 boot ROM watches the IFC line to determine when IFC - assertion ends. - - 6. DCPC performance is optimized by recognizing that the normal cases (an - input that empties the FIFO or an output that fills the FIFO) do not - alter the card state, and so the usual update_state call may be omitted. - - 7. The gcc compiler (at least as of version 4.6.2) does not optimize - repeated use of array-of-structures accesses. Instead, it recalculates - the index each time, even though the index is a constant within the - function. To avoid this performance penalty, we use a pointer to the - selected DI_STATE structure. Note that VC++ 2008 does perform this - optimization. -*/ - - -uint32 di_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -static const char * const output_state [] = { "Control", "Data" }; -static const char * const input_state [] = { "Status", "Data" }; - -const char * const hold_or_clear = (signal_set & ioCLF ? ",C" : ""); -const CARD_ID card = (CARD_ID) (dibptr->card_index); -DI_STATE * const di_card = &di [card]; - -uint8 assert, deny; /* new bus control states */ -uint16 data; -t_bool update_required = TRUE; /* TRUE if CLF must update the card state */ - -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate the next signal */ - - switch (signal) { /* dispatch an I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - di_card->flag = CLEAR; /* clear the flag */ - di_card->flagbuf = CLEAR; /* and flag buffer */ - - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: [CLF] Flag cleared\n", - dptrs [card]->name); - - if (update_required) /* if the card state has changed */ - update_state (card); /* then update the state */ - break; - - - case ioSTF: /* set flag flip-flop */ - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: [STF] Flag set\n", - dptrs [card]->name); - - /* fall into ENF handler */ - - case ioENF: /* enable flag */ - di_card->flag = SET; /* set the flag */ - di_card->flagbuf = SET; /* and flag buffer */ - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (di [card]); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (di [card]); - break; - - - case ioIOI: /* I/O data input */ - if (di_card->control == SET) { /* is the card in data mode? */ - data = di_card->input_data_register; /* read the input data register */ - di_card->status_register &= ~STAT_IRL; /* clear the input register loaded status */ - - if (FIFO_EMPTY && di_card->eor == CLEAR) { /* is the FIFO empty and end of record not seen? */ - if (di_card->srq == SET && DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: SRQ cleared\n", - dptrs [card]->name); - - di_card->srq = CLEAR; /* clear SRQ */ - update_required = FALSE; /* the card state does not change */ - } - } - - else { /* the card is in status mode */ - di_card->status_register &= /* clear the values to be computed, */ - STAT_IRL | STAT_LBO /* preserving those set elsewhere */ - | STAT_LBI | STAT_IFC; - - di_card->status_register |= /* set T/L/C status from control register */ - di_card->cntl_register /* (T/L are ORed, as MTA or MLA can also set) */ - & (CNTL_CIC | CNTL_TALK | CNTL_LSTN); - - - if (SW8_SYSCTL) /* if SW8 is set, */ - di_card->status_register |= STAT_SYSCTL; /* the card is the system controller */ - - if (di_card->ibp == lower) /* if lower byte input is next */ - di_card->status_register |= STAT_ODD; /* then the last transfer was odd */ - - di_card->status_register |= /* set the bus status bits */ - (di_card->bus_cntl /* from the corresponding bus control lines */ - & (BUS_SRQ | BUS_IFC | BUS_REN - | BUS_EOI | BUS_ATN)) << DATA_SHIFT; - - data = di_card->status_register; /* return the status word */ - } - - if (DEBUG_PRJ (dptrs [card], DEB_CPU)) - fprintf (sim_deb, ">>%s cpu: [LIx%s] %s = %06o\n", - dptrs [card]->name, hold_or_clear, - input_state [di_card->control], data); - - if (update_required && !(signal_set & ioCLF)) /* if an update is required and CLF is not present, */ - update_state (card); /* update the state, else ioCLF will update it */ - - stat_data = IORETURN (SCPE_OK, data); /* merge in the return status */ - break; - - - case ioIOO: /* I/O data output */ - data = IODATA (stat_data); /* get the data value */ - - if (DEBUG_PRJ (dptrs [card], DEB_CPU)) - fprintf (sim_deb, ">>%s cpu: [OTx%s] %s = %06o\n", - dptrs [card]->name, hold_or_clear, - output_state [di_card->control], data); - - if (di_card->control == SET) { /* is the card in data mode? */ - if (signal_set & ioEDT) /* if end of DCPC transfer */ - di_card->edt = SET; /* set the EDT flip-flop */ - - else if (di_card->cntl_register & CNTL_PACK) { /* is this a packed transfer? */ - if (!(signal_set & ioCLF)) /* and CLF not given? */ - di_card->edt = SET; /* set the EDT flip-flop */ - } - - else /* it's an unpacked transfer */ - if (data & DATA_LBO) /* is the last byte out? */ - di_card->edt = SET; /* set the EDT flip-flop */ - - if (di_card->cntl_register & CNTL_DIAG) { /* set for DIAG loopback? */ - data = fifo_unload (card, diag_access); /* unload data from the FIFO */ - fifo_load (card, data, diag_access); /* and load it back in */ - } - - else { /* the card is set for normal operation */ - fifo_load (card, data, cpu_access); /* load the data word into the FIFO */ - - if (FIFO_FULL && (di_card->bus_cntl & BUS_NRFD)) { /* FIFO full and listener not ready? */ - if (di_card->srq == SET && DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: SRQ cleared\n", - dptrs [card]->name); - - di_card->srq = CLEAR; /* clear SRQ */ - update_required = FALSE; /* the card state does not change */ - } - } - } - - else { /* the card is in control mode */ - assert = 0; /* initialize bus control assertions */ - deny = 0; /* and denials */ - - if (!(data & CNTL_PACK)) /* unpacked mode always sets */ - di_card->ibp = di_card->obp = lower; /* byte selectors to the lower byte */ - - if (data & CNTL_TALK) { /* talking enables ATN and EOI outputs */ - if ((data & (CNTL_PPE | CNTL_CIC)) /* if parallel poll is enabled */ - == (CNTL_PPE | CNTL_CIC)) /* and the card is CIC */ - assert = BUS_PPOLL; /* then conduct a parallel poll */ - - else if ((di_card->cntl_register /* if PP was enabled */ - & (CNTL_PPE | CNTL_CIC)) /* but is not now */ - == (CNTL_PPE | CNTL_CIC)) - deny = BUS_PPOLL; /* then end the parallel poll */ - - else if ((data /* if packed mode */ - & (CNTL_PACK | CNTL_CIC | CNTL_ATN)) /* and the card is CIC */ - == (CNTL_PACK | CNTL_CIC | CNTL_ATN)) /* then the ATN control output */ - assert = BUS_ATN; /* is coupled to the bus */ - - else /* if none of the above */ - deny = BUS_ATN; /* then ATN is not driven */ - } - - else /* the card is not talking */ - deny = BUS_ATN | BUS_EOI; /* so ATN and EOI are disabled */ - - - if (data & CNTL_NRFD) /* is card not ready set explicitly? */ - assert |= BUS_NRFD; /* assert NRFD on the bus */ - - else if (di_card->cntl_register & CNTL_NRFD) /* NRFD was set but is not now? */ - deny |= BUS_NRFD; /* deny NRFD on the bus */ - - if (FIFO_FULL) /* is the FIFO full? */ - if (data & CNTL_LSTN) /* is card now listening? */ - assert |= BUS_NRFD; /* listener and a full FIFO asserts NRFD */ - - else if (di_card->cntl_register & CNTL_LSTN) /* was card a listener but is not now? */ - deny |= BUS_NRFD; /* deny NRFD on the bus */ - - - if (SW8_SYSCTL) { /* system controller drives REN and IFC */ - if (data & CNTL_REN) /* REN control */ - assert |= BUS_REN; /* output is */ - else /* coupled to */ - deny |= BUS_REN; /* the bus */ - - if (data & CNTL_IFC) { /* is IFC set? */ - assert |= BUS_IFC; /* assert IFC on the bus */ - - di_card->status_register = - di_card->status_register - & ~(STAT_LSTN | STAT_TALK) /* clear listen and talk status */ - | STAT_IFC; /* and set IFC status */ - - di_card->ifc_timer = /* start the IFC timer by calculating */ - sim_gtime () + IFC_TIMEOUT; /* the IFC stop time (now + 100 microseconds) */ - } - } - - if ((data & (CNTL_SRQ | CNTL_CIC)) == CNTL_SRQ) /* if service request and not the controller */ - assert |= BUS_SRQ; /* then assert SRQ on the bus */ - else /* else */ - deny |= BUS_SRQ; /* deny SRQ on the bus */ - - di_card->cntl_register = data; /* save the control word */ - di_bus_control (card, CONTROLLER, assert, deny); /* update the bus control state */ - } - - if (update_required && !(signal_set & ioCLF)) /* if update required and CLF is not present, */ - update_state (card); /* update the state, else ioCLF will update it */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - di_card->flag = SET; /* set the flag */ - di_card->flagbuf = SET; /* and flag buffer */ - - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: [POPIO] Flag set\n", - dptrs [card]->name); - break; - - - case ioCRS: /* control reset */ - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: [CRS] Master reset\n", - dptrs [card]->name); - - di_card->status_register &= /* clear listen and talk status */ - ~(STAT_LSTN | STAT_TALK); - - deny = BUS_SRQ | BUS_REN | BUS_ATN | BUS_EOI; /* clear the lines driven by the control register */ - - if (di_card->cntl_register & (CNTL_NRFD | CNTL_LSTN)) /* if asserting NRFD or listening */ - deny |= BUS_NRFD; /* then deny because we're clearing */ - - di_card->cntl_register = 0; /* clear the control word register */ - di_card->control = CLEAR; /* clear control */ - di_card->srq = CLEAR; /* clear SRQ */ - - master_reset (card); /* perform a master reset */ - - di_bus_control (card, CONTROLLER, 0, deny); /* update the bus control state */ - update_state (card); /* update the card state */ - break; - - - case ioCLC: /* clear control flip-flop */ - di_card->control = CLEAR; /* clear control */ - - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) { - fprintf (sim_deb, ">>%s cmds: [CLC%s] Control cleared (configure mode)", - dptrs [card]->name, hold_or_clear); - - if (signal_set & ioCLF) /* if ioCLF is given, */ - fputs (", master reset\n", sim_deb); /* then report a master reset */ - else - fputc ('\n', sim_deb); - } - - if (signal_set & ioCLF) /* if ioCLF is given, */ - master_reset (card); /* then do a master reset */ - break; /* (ioCLF will call update_state for us) */ - - - case ioSTC: /* set control flip-flop */ - di_card->control = SET; /* set control */ - - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: [STC%s] Control set (data mode)\n", - dptrs [card]->name, hold_or_clear); - break; - - - case ioEDT: /* end data transfer */ - if (DEBUG_PRJ (dptrs [card], DEB_CPU)) - fprintf (sim_deb, ">>%s cpu: [EDT] DCPC transfer ended\n", - dptrs [card]->name); - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (di [card]); /* set the standard PRL signal */ - setstdIRQ (di [card]); /* set the standard IRQ signal */ - - setSRQ (dibptr->select_code, /* set the SRQ signal if control and SRQ are set */ - di_card->srq == SET && di_card->control == SET); - break; - - - case ioIAK: /* interrupt acknowledge */ - di_card->flagbuf = CLEAR; /* clear the flag buffer */ - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove the current signal from the set */ - } - -return stat_data; -} - - -/* Reset the simulator. - - During a hardware PRESET, POPIO sets the flag buffer and flag flip-flops, and - CRS clears the control flip-flop and Control Word Register. In addition, CRS - performs a master reset on the card. - - PON is not used by the card. - - - Implementation notes: - - 1. During a power-on reset, a pointer to the FIFO simulation register is - saved to allow access to the "qptr" field during FIFO loading and - unloading. This enables SCP to view the FIFO as a circular queue, so - that the bottom word of the FIFO is always displayed as FIFO[0], - regardless of where it is in the actual FIFO array. -*/ - -t_stat di_reset (DEVICE *dptr) -{ -DIB *dibptr = (DIB *) dptr->ctxt; /* get the DIB pointer */ -const CARD_ID card = (CARD_ID) (dibptr->card_index); /* get the card number */ - -if (sim_switches & SWMASK ('P')) { /* is this a power-on reset? */ - di [card].fifo_reg = find_reg ("FIFO", NULL, dptr); /* find the FIFO register entry */ - - if (di [card].fifo_reg == NULL) /* if not there */ - return SCPE_IERR; /* then this is a programming error! */ - else /* found it */ - di [card].fifo_reg->qptr = 0; /* so reset the FIFO bottom index */ - - di [card].status_register = 0; /* clear the status word */ - - di [card].bus_cntl = 0; /* deny the HP-IB control lines */ - - di [card].listeners = 0; /* clear the map of listeners */ - di [card].talker = 0; /* clear the map of talker */ - di [card].poll_response = 0; /* clear the map of parallel poll responses */ - - di [card].ifc_timer = 0.0; /* clear the IFC timer */ - } - -IOPRESET (dibptr); /* PRESET the device */ - -return SCPE_OK; -} - - - -/* Disc interface global SCP routines */ - - -/* Set a unit's bus address. - - Bus addresses range from 0-7 and are initialized to the unit number. All - units of a device must have unique bus addresses. In addition, the card also - has a bus address, although this is only used for the diagnostic. The card - address may be the same as a unit address, as all units are disconnected - during a diagnostic run. - - The "value" parameter indicates whether the routine is setting a unit's bus - address (0) or a card's bus address (1). - - - Implementation notes: - - 1. To ensure that each address is unique, a check is made of the other units - for conflicting addresses. An "invalid argument" error is returned if - the desired address duplicates another. This means that addresses cannot - be exchanged without first assigning one of them to an unused address. - Also, an address cannot be set that duplicates the address of a disabled - unit (which cannot be displayed without enabling it). - - An alternate implementation would be to set the new assignments into a - "shadow array" that is set into the unit flags (and checked for validity) - only when a power-on reset is done. This would follow the disc and tape - controller hardware, which reads the HP-IB address switch settings only - at power-up. -*/ - -t_stat di_set_address (UNIT *uptr, int32 value, char *cptr, void *desc) -{ -t_stat status; -uint32 index, new_address; -uint32 old_address = GET_BUSADR (uptr->flags); -DEVICE *dptr = (DEVICE *) desc; - -if (cptr == NULL) /* if the address is not given */ - return SCPE_ARG; /* report a missing argument */ - -new_address = (uint32) get_uint (cptr, 10, 7, &status); /* parse the address value */ - -if (status == SCPE_OK) { /* is the parse OK? */ - if (value) /* are we setting the card address? */ - dptr->flags = dptr->flags & ~DEV_BUSADR /* store the new address in the device flags */ - | SET_DIADR (new_address); - - else { /* we are setting a unit address */ - for (index = 0; index < dptr->numunits; index++) /* look through the units */ - if (new_address != old_address /* to ensure that the address is unique */ - && new_address == GET_BUSADR (dptr->units [index].flags)) { - printf ("Bus address conflict: DA%d\n", index); - - if (sim_log) - fprintf (sim_log, "Bus address conflict: DA%d\n", index); - - return SCPE_NOFNC; /* a duplicate address gives an error */ - } - - uptr->flags = uptr->flags & ~UNIT_BUSADR /* the address is valid; change it */ - | SET_BUSADR (new_address); /* in the unit flags */ - } - } - -return status; /* return the result of the parse */ -} - - -/* Show a unit's bus address. - - The "value" parameter indicates whether the routine is showing a unit's bus - address (0) or a card's bus address (1). -*/ - -t_stat di_show_address (FILE *st, UNIT *uptr, int32 value, void *desc) -{ -DEVICE *dptr = (DEVICE *) desc; - -if (value) /* do we want the card address? */ - fprintf (st, "address=%d", GET_DIADR (dptr->flags)); /* get it from the device flags */ -else /* we want the unit address */ - fprintf (st, "bus=%d", GET_BUSADR (uptr->flags)); /* get it from the unit flags */ - -return SCPE_OK; -} - - -/* Set the bus cable connection. - - In normal use, the various tape and disc devices are connected together and - to the disc interface card by HP-IB cables. For the diagnostic, two disc - interface cards are connected by a single cable. - - The "value" parameter indicates whether the routine is connecting the - cable to devices for normal use (0) or to another card for diagnostics (1). - - - Implementation notes: - - 1. Initially, only one card and peripheral set is simulated: the ICD disc - family (DA device). For diagnostic use, a second, dummy card is enabled - (DC device). Once a second card simulation is implemented, this code - will no longer be necessary. -*/ - -t_stat di_set_cable (UNIT *uptr, int32 value, char *cptr, void *desc) -{ -if (value) { /* is the diagnostic cable selected? */ - ((DEVICE *) desc)->flags |= DEV_DIAG; /* set the diagnostic flag */ - dc_dev.flags &= ~DEV_DIS; /* enable the dummy device */ - dc_dev.flags |= DEV_DIAG; /* and set its flag as well */ - } -else { /* the peripheral cable is selected */ - ((DEVICE *) desc)->flags &= ~DEV_DIAG; /* clear the diagnostic flag */ - dc_dev.flags |= DEV_DIS; /* disable the dummy device */ - dc_dev.flags &= ~DEV_DIAG; /* and clear its flag */ - } - -return SCPE_OK; -} - - -/* Show the bus cable connection. - - The "value" parameter indicates whether the cable is connected to devices for - normal use (0) or to another card for diagnostics (1). -*/ - -t_stat di_show_cable (FILE *st, UNIT *uptr, int32 value, void *desc) -{ -if (((DEVICE *) desc)->flags & DEV_DIAG) /* is the cable connected for diagnostics? */ - fputs ("diagnostic cable", st); /* report it */ -else /* the cable is connected for device use */ - fputs ("HP-IB cable", st); /* report the condition */ - -return SCPE_OK; -} - - - -/* Disc interface global bus routines. - - In hardware, the HP-IB bus consists of eight control lines and eight data - lines. Signals are asserted on the control lines to establish communication - between a source and one or more acceptors. For commands, the source is - always the controller (the 12821A card), and the acceptors are all of the - connected devices. For data, the source is the current talker, and the - acceptors are one or more current listeners. A three-wire interlocking - handshake enables communication at the rate of the slowest of the multiple - acceptors. The controller conducts a parallel poll by asserting ATN and EOI - together. Devices whose parallel poll responses are enabled each assert one - of the data lines to indicate that service is required. - - In simulation, a disabled or detached unit logically is not connected to the - bus. The card maintains a bitmap of acceptors (all devices currently - attached), listeners (all devices currently addressed to listen), the talker - (the device currently addressed to talk), and the enabled parallel poll - responses. Changes in control line state are communicated to all acceptors - via control/respond function calls, and data is exchanged between talker and - listeners via source/acceptor function calls. Data bytes are sent to all - current listeners in bus-address order. The card conducts a parallel poll by - checking the response bitmap; devices must set and clear their poll responses - appropriately in advance of the poll. - - Not all of the HP-IB control lines are simulated. The DAV and NDAC handshake - lines are never asserted; instead, they are simulated by the bus source - function calling one or more bus acceptor functions. SRQ and REN are - asserted as directed by the system controller but are not otherwise used (no - HP disc or tape devices assert SRQ or respond to REN). IFC, ATN, EOI, and - NRFD are asserted and tested by the controller and devices. In particular, - asserting NRFD will hold off a pending data transmission until it is denied. - - The functions that simulate the HP-IB (where "*" is "di", "da", etc.) are: - - di_bus_source -- 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. - - *_bus_accept -- 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. - - di_bus_control -- 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. - - *_bus_respond -- 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. - - di_poll_response -- Set a device's poll response. Called by a device to - enable or disable its response to a future parallel - poll. -*/ - - -/* Source a byte to the bus. - - This routine is called to send bytes to devices on the bus connected to the - specified card. If the card is in diagnostic mode, which simulate two cards - connected by an HP-IB cable, then the byte is sent to another card in the - card cage that is also in diagnostic mode and enabled to receive. If the - card is not in diagnostic mode, then the byte is sent to all acceptors (if a - command) or to all listeners (if data) on the bus. - - The return value indicates whether or not there were any acceptors on the - bus. - - - Implementation notes: - - 1. If the responses from a previously conducted parallel poll are not - cleared from the FIFO before enabling the card to transmit, the card will - appear to conduct a new parallel poll because the FIFO tags cause ATN and - EOI to be asserted. This "fake" parallel poll is ignored (a real - parallel poll does not source data onto the bus). -*/ - -t_bool di_bus_source (CARD_ID card, uint8 data) -{ -CARD_ID other; -uint32 acceptors, unit; -t_bool accepted = FALSE; - -if (DEBUG_PRJ (dptrs [card], DEB_XFER)) { - fprintf (sim_deb, ">>%s xfer: HP-IB DIO %03o available ", dptrs [card]->name, data); - fprint_bus (sim_deb, "[%s]\n", di [card].bus_cntl); - } - -if (dptrs [card]->flags & DEV_DIAG) /* is this a diagnostic run? */ - for (other = first_card; other <= last_card; other++) { /* look through the list of cards */ - if (other != card && dptrs [other] /* for the other card */ - && (dptrs [other]->flags & DEV_DIAG) /* that is configured for diagnostic mode */ - && (di [other].cntl_register & CNTL_LSTN)) /* and is listening */ - accepted = di_bus_accept (other, data); /* call the interface acceptor for the other card */ - } - -else if ((di [card].bus_cntl & BUS_PPOLL) != BUS_PPOLL) { /* this is a normal run; not a fake poll? */ - if (di [card].cntl_register & CNTL_LSTN) /* is the card a listener? */ - accepted = di_bus_accept (card, data); /* call the interface acceptor for this card */ - - acceptors = di [card].acceptors; /* get the map of acceptors */ - - if (!(di [card].bus_cntl & BUS_ATN) /* if a data transfer, */ - || (data & BUS_COMMAND) == BUS_ACG) /* or an addressed command, e.g., SDC */ - acceptors = di [card].listeners; /* then limit just to listeners */ - - for (unit = 0; acceptors; unit++) { /* loop through the units */ - if (acceptors & 1) /* is the current unit accepting? */ - accepted |= (*bus_accept [card]) (unit, data); /* call the acceptor for this card */ - - acceptors = acceptors >> 1; /* move to the next acceptor */ - } - } - -if (DEBUG_PRJ (dptrs [card], DEB_XFER) && !accepted) - fprintf (sim_deb, ">>%s xfer: HP-IB no acceptors\n", - dptrs [card]->name); - -return accepted; -} - - -/* Assert or deny control on the bus. - - This routine is called by the indicated unit to assert or deny the HP-IB - control lines on the bus connected to the specified card. Separate sets of - signals to assert and deny are provided. - - If the bus state after modification did not change, the routine returns with - no further action. Otherwise, if the card is in diagnostic mode, then - notification of the bus change is sent to another card in the card cage that - is also in diagnostic mode. - - If the card is not in diagnostic mode, then the set of control lines that - are changing is checked to determine whether notification is necessary. If - not, then the change is not broadcast to improve performance. However, if - notification is required, then all acceptors on the bus are informed of the - change. - - - Implementation notes: - - 1. If a signal is asserted and denied in the same call, the assertion takes - precedence. - - 2. Of the sixteen potential control line state changes, only IFC assertion - and ATN and NRFD denial must be broadcast. Asserting IFC unaddresses all - devices, and denying ATN or NRFD allows a waiting talker to source a data - byte to the bus. Devices do not act upon the remaining thirteen state - changes, and a considerable performance improvement is obtained by - omitting the notification calls. - - 3. All control line state notifications are sent in diagnostic mode, as the - responses of the other card are specifically tested by the diagnostic. - - 4. Asserting ATN and EOI will conduct a parallel poll. Devices are not - notified of the poll. Instead, the previously stored parallel poll - responses will be used. -*/ - -#define ASSERT_SET (BUS_IFC) -#define DENY_SET (BUS_ATN | BUS_NRFD) - -void di_bus_control (CARD_ID card, uint32 unit, uint8 assert, uint8 deny) -{ -CARD_ID other; -uint32 acceptors, responder; -t_bool responded; -uint8 new_state, new_assertions, new_denials; - -new_state = di [card].bus_cntl & ~deny | assert; /* set up the new control state */ - -if (new_state == di [card].bus_cntl) /* if the control state did not change */ - return; /* return now */ - -new_assertions = ~di [card].bus_cntl & assert; /* get the changing assertions */ -new_denials = di [card].bus_cntl & deny; /* get the changing denials */ - -di [card].bus_cntl = new_state; /* establish the new control state */ - -if (DEBUG_PRJ (dptrs [card], DEB_XFER)) { - if (unit == CONTROLLER) - fprintf (sim_deb, ">>%s xfer: HP-IB card %d", dptrs [card]->name, card); - else - fprintf (sim_deb, ">>%s xfer: HP-IB address %d", - dptrs [card]->name, GET_BUSADR (dptrs [card]->units [unit].flags)); - - if (new_assertions) - fprint_bus (sim_deb, " asserted [%s]", new_assertions); - - if (new_denials) - fprint_bus (sim_deb, " denied [%s]", new_denials); - - fprint_bus (sim_deb, ", bus is [%s]\n", new_state); - } - -if ((dptrs [card]->flags & DEV_DIAG) /* is the card in diagnostic mode? */ - || (new_assertions & ASSERT_SET) /* or are changed signals in the */ - || (new_denials & DENY_SET)) { /* set that must be broadcast? */ - responded = FALSE; /* assume no response was received */ - - if (dptrs [card]->flags & DEV_DIAG) { /* is this a diagnostic run? */ - for (other = first_card; other <= last_card; other++) /* look through the list of cards */ - if (other != card && dptrs [other] /* for the other card */ - && (dptrs [other]->flags & DEV_DIAG)) { /* that is configured for diagnostic */ - di_bus_respond (other, new_state); /* notify the other card of the new control state */ - responded = TRUE; /* and note that there was a responder */ - } - } - - else { /* this is a normal run */ - update_state (card); /* update the card for the new control state */ - - acceptors = di [card].acceptors; /* get the map of acceptors */ - responded = (acceptors != 0); /* set response if there are any acceptors */ - - for (responder = 0; acceptors; responder++) { /* loop the through units */ - if ((acceptors & 1) && responder != unit) /* is the current unit accepting? */ - (*bus_respond [card]) (card, responder, new_state); /* call the responder for this card */ - - acceptors = acceptors >> 1; /* move to the next acceptor */ - } - } - - if (DEBUG_PRJ (dptrs [card], DEB_XFER) & !responded) - fprintf (sim_deb, ">>%s xfer: HP-IB no responders\n", - dptrs [card]->name); -} - -if ((new_state & BUS_PPOLL) == BUS_PPOLL) /* was a parallel poll requested? */ - di_bus_poll (card); /* conduct the poll */ - -return; -} - - -/* Enable or disable a unit's parallel poll response. - - The poll response for a unit connected to a specified card is set or cleared - as indicated. If a parallel poll is in progress when a poll response is set, - the poll is conducted again to reflect the new response. -*/ - -void di_poll_response (CARD_ID card, uint32 unit, FLIP_FLOP response) -{ -const uint32 address = GET_BUSADR (dptrs [card]->units [unit].flags); -uint32 previous_response = di [card].poll_response; - -if (response == SET) { /* enable the poll response? */ - di [card].poll_response |= PPR (address); /* set the response bit */ - - if ((di [card].bus_cntl & BUS_PPOLL) == BUS_PPOLL) /* is a parallel poll in progress? */ - di_bus_poll (card); /* conduct again with the new response */ - } -else /* disable the poll response */ - di [card].poll_response &= ~PPR (address); /* by clearing the response bit */ - -if (DEBUG_PRJ (dptrs [card], DEB_XFER) - && previous_response != di [card].poll_response) - fprintf (sim_deb, ">>%s xfer: HP-IB address %d parallel poll response %s\n", - dptrs [card]->name, address, (response == SET ? "enabled" : "disabled")); - -return; -} - - - -/* Disc interface local bus routines */ - - -/* Conduct a parallel poll on the bus. - - A controller asserting ATN and EOI simultaneously on the bus is conducting a - parallel poll. In hardware, each device whose poll response is enabled - asserts the data line corresponding to its bus address. The controller - terminates the poll by denying ATN and EOI. - - Setting the CIC (controller in charge) and PPE (parallel poll enable) bits in - the Control Word Register direct the disc interface to conduct a poll. - Setting PPE without CIC enables the poll response for the interface. - - In the diagnostic mode, one card is set to conduct the poll, and the other is - set to respond to it. In the normal mode, connected devices have set or - cleared their respective poll responses before this routine is called. - - - Implementation notes: - - 1. The card hardware fills the upper and lower bytes of the FIFO with the - response byte. In simulation, we use the diag_access mode to do the same - thing (diagnostic loopback also fills both bytes with the lower byte). -*/ - -static void di_bus_poll (CARD_ID card) -{ -CARD_ID other; -uint8 response; - -if ((di [card].cntl_register - & (CNTL_PPE | CNTL_CIC)) == CNTL_PPE) /* is the card's poll response enabled? */ - response = di [card].poll_response /* add the card's response */ - | PPR (GET_DIADR (dptrs [card]->flags)); /* to the devices' responses */ -else - response = di [card].poll_response; /* the card response is disabled, so just use devices */ - -if (dptrs [card]->flags & DEV_DIAG) /* is this a diagnostic run? */ - for (other = first_card; other <= last_card; other++) /* look through the list of cards */ - if (other != card && dptrs [other] /* for another card */ - && (dptrs [other]->flags & DEV_DIAG) /* that is configured for the diagnostic */ - && (di [other].cntl_register /* and has PPE asserted */ - & (CNTL_PPE | CNTL_CIC)) == CNTL_PPE) - response |= /* merge its poll response */ - PPR (GET_DIADR (dptrs [other]->flags)); - -if (response) { /* is a poll response indicated? */ - if (DEBUG_PRJ (dptrs [card], DEB_XFER)) - fprintf (sim_deb, ">>%s xfer: HP-IB parallel poll DIO %03o\n", - dptrs [card]->name, response); - - while (di [card].fifo_count != FIFO_SIZE) /* fill the card FIFO with the responses */ - fifo_load (card, (uint16) response, diag_access); /* (hardware feature) */ - - update_state (card); /* update the card state */ - } - -return; -} - - -/* Accept a data byte from the bus. - - The indicated card accepts a byte that has been sourced to the bus. The byte - is loaded into the FIFO, and the card state is updated to reflect the load. - - Bus acceptors return TRUE to indicate that the byte was accepted. A card - always accepts a byte, so the routine always returns TRUE. -*/ - -static t_bool di_bus_accept (CARD_ID card, uint8 data) -{ -if (DEBUG_PRJ (dptrs [card], DEB_XFER)) - fprintf (sim_deb, ">>%s xfer: HP-IB card %d accepted data %03o \n", - dptrs [card]->name, card, data); - -fifo_load (card, data, bus_access); /* load the data byte into the FIFO */ -update_state (card); /* and update the card state */ -return TRUE; /* indicate that the byte was accepted */ -} - - -/* Respond to the bus control lines. - - The indicated card is notified of the new control state on the bus. The - routine establishes the new bus state and updates the card state to reflect - the change. -*/ - -static void di_bus_respond (CARD_ID card, uint8 new_cntl) -{ -di [card].bus_cntl = new_cntl; /* update the bus control lines */ -update_state (card); /* update the card state */ -return; -} - - - -/* Disc interface local utility routines */ - - -/* Master reset the interface. - - This is the programmed card master reset, not the simulator reset routine. - Master reset initializes a number of flip-flops and data paths on the card. - The primary use, other than during a PRESET, is to clear the FIFO in - preparation to changing the card from a listener to a talker or vice versa. - This ensures that unneeded FIFO data is not transmitted inadvertently to the - bus or to the CPU. It is also used when changing the data mode from unpacked - to packed to release the byte pointer flip-flops, which are held in the - "lower byte" position during unpacked transfers. - - In hardware, a master reset: - - clears the EDT, EOR, IRL, LBO, LBI, and IFC flip-flops - - clears the Input Data Register - - clears the FIFO - - sets or clears the odd/even input and output byte pointer flip-flops, - depending on whether the P (packed transfer) bit is set in the Control - Word Register -*/ - -static void master_reset (CARD_ID card) -{ -di [card].edt = CLEAR; /* clear the EDT flip-flop */ -di [card].eor = CLEAR; /* clear the EOR flip-flop */ - -if (di [card].cntl_register & CNTL_PACK) /* if packed mode is set, */ - di [card].ibp = di [card].obp = upper; /* MR sets the selectors to the upper byte */ -else /* otherwise, unpacked mode overrides */ - di [card].ibp = di [card].obp = lower; /* and sets the selectors to the lower byte */ - -di [card].status_register &= /* clear the status flip-flops */ - ~(STAT_IRL | STAT_LBO | STAT_LBI | STAT_IFC); - -di [card].input_data_register = 0; /* clear the input data register */ -di [card].fifo_count = 0; /* clear the FIFO */ - -if (DEBUG_PRJ (dptrs [card], DEB_BUF)) - fprintf (sim_deb, ">>%s buf: FIFO cleared\n", - dptrs [card]->name); - -return; -} - - -/* Update the interface state. - - In hardware, certain external operations cause automatic responses by the - disc interface card. For example, when the Input Data Register is unloaded - by an LIx instruction, it is automatically reloaded with the next word from - the FIFO. Also, the card may be set to interrupt in response to the - assertion of certain bus control lines. - - In simulation, this routine must be called whenever the FIFO, card control, - or bus control state changes. It determines whether: - - 1. ...the next word from the FIFO should be unloaded into the IDR. If the - card is listening, and the IDR is empty, and the FIFO contains data, then - a word is unloaded and stored in the IDR, and the Input Register Loaded - status bit is set. - - 2. ...the next word from the FIFO should be unloaded and sourced to the bus. - If the card is talking (but not polling), and the listener is ready to - accept data, and the last byte has not been sent, and the FIFO contains - data, then a word is unloaded and sourced to the bus. This occurs - regardless of whether or not there are any listeners. - - 3. ...an interface clear operation has completed. If IFC is asserted, and - the current simulation time is later than the IFC expiration time, then - IFC is denied, and the timer is reset. - - 4. ...the card should assert NRFD to prevent FIFO overflow. If the card is - listening, and the FIFO is full, or the last byte has been received, or a - pause has been explicitly requested, then NRFD is asserted. - - 5. ...the SRQ flip-flop should be set or cleared. If the card is listening - and the Input Data Register has been loaded, or the card is talking and - the FIFO is not full, then SRQ is asserted to request a DCPC transfer. - - 6. ...the flag flip-flop should be set or cleared. If the Input Data - Register has been loaded or the Last Byte Out flip-flop is set and the - corresponding Control Word Register IRL or LBO bits are set, or the End - of Record flip-flop is set and the Input Data Register has been unloaded, - or SRQ is asserted on the bus and the corresponding Control Word Register - bit is set when the card is not the controller-in-charge, or REN or IFC - is asserted on the bus and the corresponding Control Word Register bits - are set when the card is not the system controller, then the flag is set - to request an interrupt. - - - Implementation notes: - - 1. The fifo_unload routine may set STAT_LBO, so the flag test must be done - after unloading. - - 2. The gcc compiler (at least as of version 4.6.2) does not optimize - repeated use of array-of-structures accesses. Instead, it recalculates - the index each time, even though the index is a constant within the - function. To avoid this performance penalty, we use a pointer to the - selected DI_STATE structure. Note that VC++ 2008 does perform this - optimization. - */ - -static void update_state (CARD_ID card) -{ -DIB * const dibptr = (DIB *) dptrs [card]->ctxt; -DI_STATE * const di_card = &di [card]; -uint8 assert = 0; -uint8 deny = 0; -uint16 data; -FLIP_FLOP previous_state; - -if (di_card->cntl_register & CNTL_LSTN) { /* is the card a listener? */ - if (!(di_card->status_register & STAT_IRL) /* is the IDR empty? */ - && ! FIFO_EMPTY) { /* and data remains in the FIFO? */ - data = fifo_unload (card, cpu_access); /* unload the FIFO */ - di_card->input_data_register = data; /* into the IDR */ - di_card->status_register |= STAT_IRL; /* set the input register loaded status */ - } - } - -else if ((di_card->cntl_register /* is the card a talker? */ - & (CNTL_TALK | CNTL_PPE)) == CNTL_TALK) /* and not polling? */ - while (! FIFO_EMPTY /* is data remaining in FIFO? */ - && !(di_card->bus_cntl & BUS_NRFD) /* and NRFD is denied? */ - && !(di_card->status_register & STAT_LBO)) { /* and the last byte has not been sent? */ - data = fifo_unload (card, bus_access); /* unload a FIFO byte */ - di_bus_source (card, (uint8) data); /* source it to the bus */ - } - - -if (di_card->bus_cntl & BUS_IFC /* is an IFC in progress? */ - && di_card->ifc_timer != 0.0 /* and I am timing? */ - && sim_gtime () > di_card->ifc_timer) { /* and has the timeout elapsed? */ - deny = BUS_IFC; /* deny IFC on the bus */ - di_card->ifc_timer = 0.0; /* clear the IFC timer */ - di_card->status_register &= ~STAT_IFC; /* and clear IFC status */ - } - - -if (di_card->cntl_register & CNTL_LSTN) /* is the card a listener? */ - if (di_card->cntl_register & CNTL_NRFD /* if explicitly requested */ - || di_card->status_register & STAT_LBI /* or the last byte is in */ - || FIFO_FULL) /* or the FIFO is full */ - assert = BUS_NRFD; /* then assert NRFD */ - else /* otherwise the card is ready for data */ - deny |= BUS_NRFD; /* so deny NRFD */ - -if (assert != deny) /* was there any change in bus state? */ - di_bus_control (card, CONTROLLER, assert, deny); /* update the bus control */ - - -previous_state = di_card->srq; /* save the current SRQ state */ - -if (di_card->cntl_register & CNTL_LSTN /* if the card is a listener */ - && di_card->status_register & STAT_IRL /* and the input register is loaded, */ - || di_card->cntl_register & CNTL_TALK /* or the card is a talker */ - && ! FIFO_FULL) /* and the FIFO is not full */ - di_card->srq = SET; /* then request a DCPC cycle */ -else - di_card->srq = CLEAR; /* otherwise, DCPC service is not needed */ - - -if (DEBUG_PRJ (dptrs [card], DEB_CMDS) - && di_card->srq != previous_state) - fprintf (sim_deb, ">>%s cmds: SRQ %s\n", - dptrs [card]->name, di_card->srq == SET ? "set" : "cleared"); - - -if (di_card->status_register & STAT_IRL /* is the input register loaded */ - && di_card->cntl_register & CNTL_IRL /* and notification is wanted? */ - || di_card->status_register & STAT_LBO /* or is the last byte out */ - && di_card->cntl_register & CNTL_LBO /* and notification is wanted? */ - || di_card->eor == SET /* or was the end of record seen */ - && !(di_card->status_register & STAT_IRL) /* and the input register was unloaded? */ - || di_card->bus_cntl & BUS_SRQ /* or is SRQ asserted on the bus */ - && di_card->cntl_register & CNTL_SRQ /* and notification is wanted */ - && di_card->cntl_register & CNTL_CIC /* and the card is not controller? */ - || !SW8_SYSCTL /* or is the card not the system controller */ - && di_card->bus_cntl & BUS_REN /* and REN is asserted on the bus */ - && di_card->cntl_register & CNTL_REN /* and notification is wanted? */ - || !SW8_SYSCTL /* or is the card not the system controller */ - && di_card->status_register & STAT_IFC /* and IFC is asserted on the bus */ - && di_card->cntl_register & CNTL_IFC) { /* and notification is wanted? */ - - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: Flag set\n", - dptrs [card]->name); - - di_io (dibptr, ioENF, 0); /* set the flag and recalculate interrupts */ - } - -else if (di_card->srq != previous_state) /* if SRQ changed state, */ - di_io (dibptr, ioSIR, 0); /* then recalculate interrupts */ - -return; -} - - -/* Load a word or byte into the FIFO. - - A word or byte is loaded into the next available location in the FIFO. The - significance of the data parameter is indicated by the access mode as - follows: - - - For CPU access, the parameter is a 16-bit value. - - - For bus access, the parameter is an 8-bit value in the lower byte and a - zero in the upper byte. - - - For diagnostic access, the parameter is an 8-bit value in the lower byte - that will be duplicated in the upper byte. - - For bus access, byte loading into the FIFO is controlled by the value of the - Input Buffer Pointer (IBP) selector. - - In addition to data words, the FIFO holds tags that mark the last byte - received or to be transmitted and that indicate the state of the ATN and EOI - bus lines (if listening) or the states to assert (if talking). The tag is - assembled into the upper word, the data is assembled into the lower word, and - then the 32-bit value is stored in the next available FIFO location. - - If data is coming from the CPU, the 16-bit value is loaded into the next FIFO - location, and the occupancy count is incremented. - - If the data is coming from the bus, and the input mode is unpacked, the 8-bit - value is loaded into the lower byte of the next FIFO location, and the - occupancy count is incremented. In hardware, the upper FIFO is not clocked; - in simulation, the upper byte is set to zero. The IBP always points at the - lower byte in unpacked mode. - - If the data is coming from the bus, and the input mode is packed, the 8-bit - value is loaded into either the upper or lower byte of the next FIFO - location, depending on the value of the IBP, and the IBP is toggled. If the - value was stored in the lower byte, the occupancy count is incremented. - - A special case occurs when the value is to be stored in the upper byte, and - the LBR tag is set to indicate that this is the last byte to be received. In - this case, the value is stored in both bytes of the next FIFO location, and - the occupancy counter is incremented. - - If data is coming from the diagnostic FIFO loopback, the 8-bit value in the - lower byte is copied to the upper byte, the resulting 16-bit value is loaded - into the next FIFO location, and the occupancy count is incremented. - - - Implementation notes: - - 1. Four tag bits are loaded into the upper word of each FIFO entry: - - - Last Byte Received (while receiving, a line feed is received and the - LF bit is set in the Control Word Register, or a byte with EOI - asserted is received and the EOI bit is set). - - - End of Data Transfer (while transmitting, DCPC asserts the EDT - backplane signal, or an unpacked-mode data word has the LBO bit set, - or a packed-mode OTx is issued without an accompanying CLF). - - - ATN (the state of ATN on the bus if receiving, or the ATN bit in the - unpacked data word if transmitting). - - - EOI (the state of EOI on the bus if receiving, or the EOI bit in the - unpacked data word if transmitting). - - 2. The FIFO is implemented as circular queue to take advantage of REG_CIRC - EXAMINE semantics. REG->qptr is the index of the first word currently in - the FIFO. By specifying REG_CIRC, examining FIFO[0-n] will always - display the words in load order, regardless of the actual array index of - the start of the list. The number of words currently present in the FIFO - is kept in fifo_count (0 = empty, 1-16 = number of words available). - - If fifo_count < FIFO_SIZE, (REG->qptr + fifo_count) mod FIFO_SIZE is the - index of the new word location. Loading stores the word there and then - increments fifo_count. - - 3. Because the load and unload routines need access to qptr in the REG - structure for the FIFO array, pointers to the REG for each card are - stored in the fifo_reg array during device reset. - - 4. The gcc compiler (at least as of version 4.6.2) does not optimize - repeated use of array-of-structures accesses. Instead, it recalculates - the index each time, even though the index is a constant within the - function. To avoid this performance penalty, we use a pointer to the - selected DI_STATE structure. Note that VC++ 2008 does perform this - optimization. -*/ - -static void fifo_load (CARD_ID card, uint16 data, FIFO_ACCESS access) -{ -uint32 tag, index; -t_bool add_word = TRUE; -DI_STATE * const di_card = &di [card]; - -if (FIFO_FULL) { /* is the FIFO already full? */ - if (DEBUG_PRJ (dptrs [card], DEB_BUF)) - fprintf (sim_deb, ">>%s buf: Attempted load to full FIFO, data %0*o\n", - dptrs [card]->name, (access == bus_access ? 3 : 6), data); - - return; /* return with the load ignored */ - } - -if (di_card->cntl_register & CNTL_LSTN) { /* is the card receiving? */ - tag = (di_card->bus_cntl /* set the tag from the bus signals */ - & (BUS_ATN | BUS_EOI)) << BUS_SHIFT; /* shifted to the tag locations */ - - if ((di_card->cntl_register & CNTL_EOI /* EOI detection is enabled, */ - && di_card->bus_cntl & BUS_EOI) /* and data was tagged with EOI? */ - || (di_card->cntl_register & CNTL_LF /* or LF detection is enabled, */ - && GET_LOWER (data) == LF)) { /* and the byte is a line feed? */ - tag = tag | TAG_LBR; /* tag as the last byte received */ - di_card->status_register |= STAT_LBI; /* set the last byte in status */ - } - else /* neither termination condition was seen */ - di_card->status_register &= ~STAT_LBI; /* so clear the last byte in status */ - } - -else /* the card is transmitting */ - tag = (data & (DATA_ATN | DATA_EOI)) << DATA_SHIFT; /* set the tag from the data shifted to the tag location */ - -if (di_card->edt == SET) /* is this the end of the data transfer? */ - tag = tag | TAG_EDT; /* set the EDT tag */ - - -index = (di_card->fifo_reg->qptr /* calculate the index */ - + di_card->fifo_count) % FIFO_SIZE; /* of the next available location */ - -if (access == bus_access) { /* is this a bus access */ - if (di_card->ibp == upper) { /* in packed mode for the upper byte? */ - di_card->ibp = lower; /* set the lower byte as next */ - - if (tag & TAG_LBR) /* is this the last byte? */ - di_card->fifo [index] = /* copy to both bytes of the FIFO */ - tag | SET_BOTH (data); /* and store with the tag */ - else { /* more bytes are expected */ - di_card->fifo [index] = /* so position this byte */ - tag | SET_UPPER (data); /* and store it with the tag */ - add_word = FALSE; /* wait for the second byte before adding */ - } - } - - else /* this is the lower byte */ - if (di_card->cntl_register & CNTL_PACK) { /* is the card in packed mode? */ - di_card->ibp = upper; /* set the upper byte as next */ - - di_card->fifo [index] = /* merge the data and tag values */ - tag | di_card->fifo [index] | SET_LOWER (data); - } - else /* the card is in unpacked mode */ - di_card->fifo [index] = /* position this byte */ - tag | SET_LOWER (data); /* and store with the tag */ - } - -else if (access == cpu_access) /* is this a cpu access? */ - di_card->fifo [index] = tag | data; /* store the tag and full word in the FIFO */ - -else { /* must be diagnostic access */ - data = SET_BOTH (GET_LOWER (data)); /* copy the lower byte to the upper byte */ - di_card->fifo [index] = tag | data; /* and store the tag and full word in the FIFO */ - } - -if (add_word) /* did we add a word to the FIFO? */ - di_card->fifo_count = di_card->fifo_count + 1; /* increment the count of words stored */ - -if (DEBUG_PRJ (dptrs [card], DEB_BUF)) { - fprintf (sim_deb, ">>%s buf: Data %0*o tag ", - dptrs [card]->name, (access == bus_access ? 3 : 6), data); - fprint_val (sim_deb, tag >> BUS_SHIFT, 2, 4, PV_RZRO); - fprintf (sim_deb, " loaded into FIFO (%d)\n", di_card->fifo_count); - } - -return; -} - - -/* Unload a word or byte from the FIFO. - - A word or byte is unloaded from the first location in the FIFO. The - significance of the returned value is indicated by the access mode as - follows: - - - For CPU access, a 16-bit value is unloaded and returned. - - - For bus access, an 8-bit value is unloaded and returned. - - - For diagnostic access, an 16-bit value is unloaded, and the lower byte - is returned. - - For bus access, byte unloading from the FIFO is controlled by the value of - the Output Buffer Pointer (OBP) selector. - - If the FIFO is not empty, the first entry is obtained and split into tag and - data words. The LBR tag value is loaded into the EOR flip-flop if the CPU is - accessing. The EDT tag sets Last Byte Out status if the last byte is being - unloaded. - - If the data is going to the CPU, the 16-bit packed data value is returned as - is, or the lower byte of the unpacked value is merged with the tags for ATN - and EOI and returned. The occupancy count is decremented to unload the FIFO - entry. - - If the data is going to the bus, and the input mode is unpacked, the 8-bit - value is returned in the lower byte, and the occupancy count is decremented. - In hardware, the upper FIFO is not clocked; in simulation, the upper byte is - ignored. The OBP always points at the lower byte in unpacked mode. - - If the data is going to the bus, and the input mode is packed, the 8-bit - value is unloaded from either the upper or lower byte of the data word, - depending on the value of the OBP, and returned in the lower byte. The OBP - value is toggled. If the value was obtained from the lower byte, the - occupancy count is decremented to unload the FIFO. Otherwise, the count is - not altered, so that the lower-byte access will be from the same FIFO entry. - - If data is going to the diagnostic FIFO loopback, the lower byte of the - 16-bit value is returned; the upper byte of the returned value is zero. - - - Implementation notes: - - 1. Four tag bits are unloaded from the upper word of each FIFO entry: - - - Last Byte Received (sets the End of Record flip-flop when the last - byte received is loaded into the Input Data Register). - - - End of Data Transfer (sets the LBO bit in the Status Word Register - when the last byte is unloaded from the FIFO). - - - ATN (in unpacked mode, sets the ATN bit in the returned data word - if listening, or controls the bus ATN line if talking; in packed mode, - the tag is ignored). - - - EOI (in unpacked mode, sets the EOI bit in the returned data word if - listening, or asserts the bus EOI line if talking; in packed mode, the - tag is ignored). - - ATN and EOI tag handling is complex. If the card is listening in the - unpacked mode, the ATN tag substitutes for bit 8 of the data word, and - the EOI tag substitutes for bit 9. In the packed mode, bits 8 and 9 are - as stored in the FIFO (they are upper-byte data bits). - - If the card is talking in the unpacked mode, the ATN tag asserts or - denies ATN on the bus if the card is the CIC, and the EOI tag asserts or - denies EOI on the bus. In the packed mode, the ATN bit in the Control - Word Register asserts or denies ATN on the bus if the card is the CIC, - and the EOI bit asserts EOI on the bus if the last byte of the entry - tagged with EDT has been unloaded from the FIFO (which sets LBO status) - or denies EOI otherwise. - - 2. In hardware, the EOR flip-flop is clocked with the Input Data Register. - Therefore, when the card is listening, EOR is set not when the last byte - is unloaded from the FIFO, but rather when that byte is loaded into the - IDR. These two actions occur together when the IDR is empty. - - However, during diagnostic access, data unloaded from the FIFO is - reloaded, and the IDR is never clocked. As the T and L bits must be set - with DIAG in the Control Word Register to enable the loopback path, the - LBR tag will be entered into the FIFO if EOI or LF detection is enabled, - but the EOR flip-flop will not be set when that word falls through to be - unloaded. - - In simulation, EOR is set whenever the LBR tag is unloaded from the FIFO - during CPU access, as a CPU unload is always followed by an IDR store. - - 3. If fifo_count > 0, REG->qptr is the index of the word to remove. Removal - gets the word and then increments qptr (mod FIFO_SIZE) and decrements - fifo_count. - - 4. The gcc compiler (at least as of version 4.6.2) does not optimize - repeated use of array-of-structures accesses. Instead, it recalculates - the index each time, even though the index is a constant within the - function. To avoid this performance penalty, we use a pointer to the - selected DI_STATE structure. Note that VC++ 2008 does perform this - optimization. -*/ - -static uint16 fifo_unload (CARD_ID card, FIFO_ACCESS access) -{ -uint32 data, tag; -t_bool remove_word = TRUE; -DI_STATE * const di_card = &di [card]; - -if (FIFO_EMPTY) { /* is the FIFO already empty? */ - if (DEBUG_PRJ (dptrs [card], DEB_BUF)) - fprintf (sim_deb, ">>%s buf: Attempted unload from empty FIFO\n", - dptrs [card]->name); - - return 0; /* return with no data */ - } - -data = di_card->fifo [di_card->fifo_reg->qptr]; /* get the tag and data from the FIFO */ - -tag = data & TAG_MASK; /* mask the tag to just the tag bits */ -data = data & DMASK; /* and the data to just the data bits */ - -if (tag & TAG_EDT /* is this the end of a data transfer */ - && (di_card->obp == lower /* and the lower byte is next */ - || di_card->cntl_register & CNTL_ODD)) /* or we are sending an odd number of bytes? */ - di_card->status_register |= STAT_LBO; /* set the last byte out status */ - - -if (access == cpu_access) { /* is this a cpu access? */ - if (!(di_card->cntl_register & CNTL_PACK)) /* in unpacked mode? */ - data = data & ~(DATA_ATN | DATA_EOI) /* substitute the ATN/EOI tag values */ - | (tag & (TAG_ATN | TAG_EOI)) >> DATA_SHIFT; /* into the data word */ - - if (tag & TAG_LBR) /* is this the last byte? */ - di_card->eor = SET; /* set */ - else /* or clear */ - di_card->eor = CLEAR; /* the end-of-record flip-flop */ - } - -else if (access == bus_access) /* is this a bus access? */ - if (di_card->obp == upper) { /* is this the upper byte? */ - di_card->obp = lower; /* set the lower byte as next */ - data = GET_UPPER (data); /* mask and position the upper byte in the data word */ - remove_word = FALSE; /* do not unload the FIFO until the next byte */ - } - - else { /* this is the lower byte */ - data = GET_LOWER (data); /* mask and position it in the data word */ - - if (di_card->cntl_register & CNTL_PACK) /* is the card in the packed mode? */ - di_card->obp = upper; /* set the upper byte as next */ - } - -else /* must be a diagnostic access */ - data = GET_LOWER (data); /* access is to the lower byte only */ - - -if (remove_word) { /* remove the word from the FIFO? */ - di_card->fifo_reg->qptr = /* update the FIFO queue pointer */ - (di_card->fifo_reg->qptr + 1) % FIFO_SIZE; /* and wrap around as needed */ - - di_card->fifo_count = di_card->fifo_count - 1; /* decrement the FIFO count */ - } - - -if (DEBUG_PRJ (dptrs [card], DEB_BUF)) { - fprintf (sim_deb, ">>%s buf: Data %0*o tag ", - dptrs [card]->name, (access == cpu_access ? 6 : 3), data); - fprint_val (sim_deb, tag >> BUS_SHIFT, 2, 4, PV_RZRO); - fprintf (sim_deb, " unloaded from FIFO (%d)\n", di_card->fifo_count); - } - - -if (di_card->cntl_register & CNTL_TALK) /* is the card talking? */ - if (di_card->cntl_register & CNTL_PACK) /* is it in the packed mode? */ - if (di_card->status_register & STAT_LBO /* yes, is the last byte out? */ - && di_card->cntl_register & CNTL_EOI) /* and is EOI control enabled? */ - di_card->bus_cntl |= BUS_EOI; /* assert EOI on the bus */ - else - di_card->bus_cntl &= ~BUS_EOI; /* deny EOI on the bus */ - - else { /* the card is in the unpacked mode */ - if (di_card->cntl_register & CNTL_CIC) /* is the card the controller in charge? */ - di_card->bus_cntl = /* assert or deny the ATN bus line */ - di_card->bus_cntl & ~BUS_ATN /* from the ATN tag value */ - | (uint8) ((tag & TAG_ATN) >> BUS_SHIFT); - - di_card->bus_cntl = /* assert or deny the EOI bus line */ - di_card->bus_cntl & ~BUS_EOI /* from the EOI tag value */ - | (uint8) ((tag & TAG_EOI) >> BUS_SHIFT); - } - -return (uint16) data; /* return the data value */ -} - - -/* Print the bus state for debugging. - - The states of the supplied bus control lines are decoded and printed in - mnemonic form to the specified file using the indicated format string. An - asserted bus signal is indicated by its name; a denied signal is omitted. - - - Implementation notes: - - 1. The strings in the cntl_names array must appear in BUS_xxx order. The - first element corresponds to bus bit 0, etc. -*/ - -static void fprint_bus (FILE *file, char *format, uint8 cntl) -{ -static const char *cntl_names [] = { - "ATN", /* bit 0: attention */ - "EOI", /* bit 1: end or identify */ - "DAV", /* bit 2: data available */ - "NRFD", /* bit 3: not ready for data */ - "NDAC", /* bit 4: not data accepted */ - "REN", /* bit 5: remote enable */ - "IFC", /* bit 6: interface clear */ - "SRQ" /* bit 7: service request */ - }; - -uint32 signal; -char mnemonics [40]; - -if (cntl == 0) /* are any control signals asserted? */ - strcpy (mnemonics, "---"); /* no; use dashes in lieu of an empty string */ - -else { /* one or more signals are asserted */ - mnemonics [0] = '\0'; - - for (signal = 0; signal <= 7; signal++) /* loop though the set of signals */ - if (cntl & (1 << signal)) { /* is this signal asserted? */ - if (strlen (mnemonics) > 0) /* yes; is it the first one asserted? */ - strcat (mnemonics, " "); /* no, so append a space to separate */ - strcat (mnemonics, cntl_names [signal]); /* append the name of the asserted signal */ - } - } - -fprintf (file, format, mnemonics); /* print the bus state */ -return; -} +/* hp2100_di.c: HP 12821A HP-IB Disc Interface simulator + + Copyright (c) 2010-2017, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + DI 12821A Disc Interface + + 17-Jan-17 JDB Changed to use new byte accessors in hp2100_defs.h + 13-May-16 JDB Modified for revised SCP API function parameter types + 24-Dec-14 JDB Added casts for explicit downward conversions + Removed redundant global declarations + 13-Feb-12 JDB First release + 15-Dec-11 JDB Added dummy DC device for diagnostics + 09-Oct-10 JDB Created DI simulation + + References: + - HP 12821A Disc Interface Installation and Service Manual + (12821-90006, February 1985) + - IEEE Standard Digital Interface for Programmable Instrumentation + (IEEE-488A-1980, September 1979) + + + The 12821A was a high-speed implementation of the Hewlett-Packard Interface + Bus (HP-IB, formalized as IEEE Std. 488-1978). It was used to interface + HP-IB disc and tape devices, such as the HP 7906H, 7908A, and 7974A, to the + HP 1000 running RTE-IVB or RTE-6/VM. Three device command protocols were + supported by the I/O drivers: Amigo discs by driver DVA32, CS/80 discs by + DVM33, and Amigo tapes by DVS23. + + In an RTE environment, the 12821A was the system controller. While + electrically compatible with the HP-IB specification and capable of receiving + addressing commands from the bus, the 12821A did not use the full IEEE-488 + protocol. Card talker and listener states were set by bits in the control + register, rather than by receiving talk and listen commands over the bus. + The bus address of the card could be set via DIP switches, but this feature + was only used by the diagnostic. + + The card supported packed and unpacked transfers across the bus. Up to four + devices could be connected to each card; this limit was imposed by the + maximum electrical loading on the bus compatible with the high data rate. + + The 12821A had a 16-word FIFO buffer and could sustain DCPC transfers of one + megabyte per second. Burst transfers by the CPU to fill or empty the FIFO + could run at the full bandwidth of the I/O backplane. This could hold off + lower-priority devices for 10-15 microseconds until the card slowed down to + the rate of the disc or tape. + + Card assembly 12821-60003 was revised to add a DCPC pacing option. Placing + jumper W1 in position A inhibited SRQ for one I/O cycle in six to allow a + lower-priority interface card to transfer one word. Position B allowed SRQ + to assert continuously as it did on the earlier card assembly 12821-60001. + + The simulator is logically partitioned into three sets of functions: the + interface card simulation, the HP-IB bus simulation, and the device + simulation. This is the card simulation and the card portion of the HP-IB + simulation. Separate modules for the tape and disc devices contain the + device simulations and the device portions of the HP-IB simulations. + + This simulator is written to allow the definition of multiple DI cards in a + system. The RTE operating system provided separate I/O drivers for the Amigo + disc, Amigo tape, and CS/80 disc devices. As only one I/O driver could + control a given interface, separate interfaces were required if more than one + device class was installed. For example, it was not possible to control an + Amigo disc and an Amigo tape connected to the same interface card. + + + Implementation notes: + + 1. The simulator behaves as though card switches S1-S7 are initially closed, + providing a card bus address of 0. The address may be changed with the + SET ADDRESS=n command. Only addresses 0-7 are supported, and the + address may duplicate a device bus address without conflict, as the + address is only used during the diagnostic when devices are disconnected. + + 2. The simulator behaves as though card switch S8 is open, enabling the card + to be the system controller. This cannot be changed by the user. + + 3. The simulator behaves as though card jumper W1 (DCPC pacing) is in + position B. This currently cannot be changed by the user. +*/ + + + +#include "hp2100_defs.h" +#include "hp2100_di.h" + + + +/* Program constants */ + +#define SW8_SYSCTL 1 /* card is always the system controller (switch 8) */ + +#define IFC_TIMEOUT 157 /* 157 instructions = ~ 100 microseconds */ + +#define CONTROLLER 31 /* dummy unit number for DI */ + + +/* Character constants */ + +#define LF '\012' + + +/* Control Word Register */ + +#define CNTL_SRQ 0100000 /* enable service request interrupt */ +#define CNTL_IFC 0040000 /* assert IFC or enable IFC interrupt */ +#define CNTL_REN 0020000 /* assert remote enable */ +#define CNTL_IRL 0010000 /* enable input-register-loaded interrupt */ +#define CNTL_LBO 0004000 /* enable last-byte-out interrupt */ +#define CNTL_LF 0002000 /* enable line feed terminator */ +#define CNTL_EOI 0001000 /* assert end or identify */ +#define CNTL_ATN 0000400 /* assert attention */ +#define CNTL_DIAG 0000200 /* diagnostic loopback */ +#define CNTL_NRFD 0000100 /* assert not ready for data */ +#define CNTL_PPE 0000040 /* parallel poll enable */ +#define CNTL_ODD 0000020 /* odd number of bytes */ +#define CNTL_PACK 0000010 /* packed data transfer */ +#define CNTL_LSTN 0000004 /* listen */ +#define CNTL_TALK 0000002 /* talk */ +#define CNTL_CIC 0000001 /* controller in charge */ + + +/* Status Word Register */ + +#define STAT_SRQBUS 0100000 /* service request bus state */ +#define STAT_IFCBUS 0040000 /* interface clear bus state */ +#define STAT_RENBUS 0020000 /* remote enable bus state */ +#define STAT_IRL 0010000 /* input register loaded */ +#define STAT_LBO 0004000 /* last byte out */ +#define STAT_LBI 0002000 /* last byte in */ +#define STAT_EOIBUS 0001000 /* end or identify bus state */ +#define STAT_ATNBUS 0000400 /* attention bus state */ +#define STAT_IFC 0000200 /* interface clear seen */ +#define STAT_ODD 0000020 /* odd number of bytes */ +#define STAT_SYSCTL 0000010 /* system controller */ +#define STAT_LSTN 0000004 /* listener */ +#define STAT_TALK 0000002 /* talker */ +#define STAT_CIC 0000001 /* controller in charge */ + + +/* Data word */ + +#define DATA_LBO 0100000 /* last byte out */ +#define DATA_EOI 0001000 /* end or identify */ +#define DATA_ATN 0000400 /* attention */ + + +/* Tag word */ + +#define BUS_SHIFT 16 /* left shift count to align BUS_ATN, EOI with tag */ +#define DATA_SHIFT 8 /* left shift count to align DATA_ATN, EOI with tag */ + +#define TAG_ATN 0000200000 /* bit 16: attention */ +#define TAG_EOI 0000400000 /* bit 17: end or identify */ +#define TAG_EDT 0001000000 /* bit 18: end of data transfer */ +#define TAG_LBR 0002000000 /* bit 19: last byte received */ + +#define TAG_MASK (TAG_ATN | TAG_EOI | TAG_EDT | TAG_LBR) + + +/* FIFO access modes */ + +#define FIFO_EMPTY (di_card->fifo_count == 0) /* FIFO empty test */ +#define FIFO_FULL (di_card->fifo_count == FIFO_SIZE) /* FIFO full test */ + +typedef enum { + bus_access, /* per-byte access */ + cpu_access, /* per-word access */ + diag_access /* mixed access */ + } FIFO_ACCESS; + + +/* Disc interface state variables */ + +DI_STATE di [card_count]; /* per-card state */ + + +/* Disc interface local bus routines */ + +static t_bool di_bus_accept (CARD_ID card, uint8 data); +static void di_bus_respond (CARD_ID card, uint8 cntl); +static void di_bus_poll (CARD_ID card); + +/* Disc interface local utility routines */ + +static void master_reset (CARD_ID card); +static void update_state (CARD_ID card); +static void fifo_load (CARD_ID card, uint16 data, FIFO_ACCESS access); +static uint16 fifo_unload (CARD_ID card, FIFO_ACCESS access); +static void fprint_bus (FILE *file, char *format, uint8 cntl); + + + +/* Dummy DC device. + + This temporary dummy device allows the DI diagnostic to test inter-card + signals. Test 15 can only be performed if there are two DIs available. + + This device provides a second "bare" card. Normally, it is disabled and + cannot be enabled by the user. Enabling or disabling DIAG mode on the DA + device automatically enables or disables the DC device. The select code of + the DC device is fixed at 45B and cannot be changed. +*/ + +DIB dc_dib = { &di_io, DI_DC, dc }; + +REG dc_reg [] = { + { BRDATA (FIFO, di [dc].fifo, 8, 20, FIFO_SIZE), REG_CIRC }, /* needed for "qptr" */ + { NULL } + }; + +DEVICE dc_dev = { + "DC", /* device name */ + NULL, /* unit array */ + dc_reg, /* register array */ + NULL, /* modifier array */ + 0, /* number of units */ + 10, /* address radix */ + 31, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &di_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &dc_dib, /* device information block */ + DEV_DEBUG | DEV_DIS, /* device flags */ + 0, /* debug control flags */ + di_deb, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + + + +/* DI data structures. + + *dptrs device pointers + *bus_accept device acceptor function pointers + *bus_respond device responder function pointers + + di_deb DI debug table + + The first three pointer arrays have elements that correspond one-for-one with + the supported devices. These allow the DI simulator to work with multiple + cards. The actual devices are defined in the individual device simulators. + + Note that the DC and MA devices are reserved for future use. Until one or + the other is fully implemented, a dummy DC device is provided above for use + by the diagnostic only. +*/ + +extern DEVICE da_dev; + +static DEVICE *dptrs [card_count] = { &da_dev, &dc_dev, NULL }; +static ACCEPTOR *bus_accept [card_count] = { &da_bus_accept, NULL, NULL }; +static RESPONDER *bus_respond [card_count] = { &da_bus_respond, NULL, NULL }; + + +DEBTAB di_deb [] = { + { "CPU", DEB_CPU }, + { "CMDS", DEB_CMDS }, + { "BUF", DEB_BUF }, + { "XFER", DEB_XFER }, + { "RWSC", DEB_RWSC }, + { "SERV", DEB_SERV }, + { NULL, 0 } + }; + + + +/* Disc interface global VM routines */ + + +/* I/O signal handler. + + The card has two input and two output registers. The Input Data Register and + Output Data Register are addressed when the control flip-flop is set. The + Status Word and the Control Word Register are addressed when the control + flip-flop is clear. The card has the usual control, flag buffer, flag, and + SRQ flip-flops, though flag and SRQ are decoupled to allow the full DCPC + transfer rate. + + In hardware, the presence of the card FIFO, which is necessary to obtain full + DCPC bandwidth, implies a delay between CPU actions, such as outputting the + last word in a data transfer, and device actions, such as accepting the last + word of a disc write. Four flip-flops are used to monitor FIFO status: + + - EDT (End of Data Transfer) + - LBO (Last Byte Out) + - LBI (Last Byte In) + - EOR (End of Record) + + The EDT signal indicates that the final data word of a transfer is being + written to the FIFO. The flip-flop is set by the EDT backplane signal when + the last cycle of a DCPC transfer is executing, or during programmed output + transfers when CLF does not accompany IOO in packed mode, or when bit 15 of + the data word is set in unpacked mode. It remains set until it is cleared by + a master reset. The output of the EDT flip-flop drives the EDT tag input of + the FIFO. + + The LBO signal indicates that the final data byte of a transfer has been + sourced to the bus. The flip-flop is set when the last byte of the entry + tagged with EDT has been unloaded from the FIFO. It is cleared by a master + reset or when an entry not tagged with EDT is unloaded. The output of the + LBO flip-flop drives the LBO bit in the Status Word. + + The LBI signal indicates that the final byte of an input transfer has been + accepted from the bus. The flip-flop is set when a byte tagged with EOI is + received and the EOI bit in the control register is set, or a line-feed byte + is received and the LF bit in the control register is set. It is cleared by + a master reset or when neither of these conditions is true. The input of the + LBI flip-flop also drives the LBR (last byte received) tag input of the FIFO, + and the output of the flip-flop drives the LBI bit in the Status Word. + + The EOR signal indicates that the final data word of a transfer is available + in the Input Data Register. The flip-flop is set when the last byte of the + entry tagged with LBR has been unloaded from the FIFO and written to the IDR. + It is cleared by a master reset or when an entry not tagged with LBR is + unloaded and written to the IDR. The output of the EOR flip-flop sets the + flag flip-flop when the IDR is unloaded. + + + Implementation notes: + + 1. In hardware, the Status Word consists of individual flip-flops and status + signals that are enabled onto the I/O backplane. In simulation, the + individual status values are collected into a Status Word Register, and + the Output Data Register does not exist (output data is written directly + to the FIFO buffer). + + 2. The DIAG, T, and L control bits enable a data loopback path on the card. + An IOO issued to the card unloads a word from the FIFO and then loads the + lower byte back into both bytes of the FIFO. The data word output with + the IOO instruction is not used. + + In hardware, IOO triggers the FIFO unload and reload; T and L are + required only for the loopback path. If L is not asserted, then the FIFO + is loaded with 177777 due to the floating bus. If L is asserted and T is + not, then the FIFO is loaded with 000000 due to pullups on the DIO lines. + In simulation, we look only for DIAG and assume that T/L are set + properly, i.e., unloaded data is reloaded. + + 3. In hardware, the SRQ and NRFD lines are open-collector and may be driven + simultaneously from several bus devices. Simulating this fully would + require keeping the state of the lines for each device and deriving the + common bus signals from the logical OR of the state values. Fortunately, + some simplifications are possible. + + The DI asserts SRQ only if control word bit 15 is 1 and bit 0 is 0. + Other bit combinations deny SRQ; as neither the Amigo nor CS/80 protocols + use SRQ and serial polls, there will be no other driver. + + In hardware, every listener drives NRFD, but in practice there is only + one listener at a time. When the card is the listener, it asserts NRFD + if the FIFO becomes full. In simulation, we assert NRFD on the bus if + NRFD is set in the control register, or we are listening and the FIFO is + full. We deny NRFD if NRFD had been set in the control register but is + no longer, or if we had been a listener but are no longer. That is, we + assume that if we have forced NRFD or set it as a listener, then no one + else will be asserting NRFD, so it's safe for us to deny NRFD when the + override is removed or we are no longer a listener. + + We also deny NRFD when a CRS is issued if NRFD had been explicitly + requested or the card had been listening. The rationale is the same: + only a listener can assert NRFD, so if we were listening, it's safe to + deny it, because only we could have set it. + + 4. In hardware, the IRL, LBO, LBI, and IFC status bits are driven by + corresponding flip-flops. In simulation, the status bits themselves hold + the equivalent states and are set and cleared as indicated. + + 5. The card state must be updated during status read (IOI) processing + because the 7974 boot ROM watches the IFC line to determine when IFC + assertion ends. + + 6. DCPC performance is optimized by recognizing that the normal cases (an + input that empties the FIFO or an output that fills the FIFO) do not + alter the card state, and so the usual update_state call may be omitted. + + 7. The gcc compiler (at least as of version 4.6.2) does not optimize + repeated use of array-of-structures accesses. Instead, it recalculates + the index each time, even though the index is a constant within the + function. To avoid this performance penalty, we use a pointer to the + selected DI_STATE structure. Note that VC++ 2008 does perform this + optimization. +*/ + + +uint32 di_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +static const char * const output_state [] = { "Control", "Data" }; +static const char * const input_state [] = { "Status", "Data" }; + +const char * const hold_or_clear = (signal_set & ioCLF ? ",C" : ""); +const CARD_ID card = (CARD_ID) (dibptr->card_index); +DI_STATE * const di_card = &di [card]; + +uint8 assert, deny; /* new bus control states */ +uint16 data; +t_bool update_required = TRUE; /* TRUE if CLF must update the card state */ + +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate the next signal */ + + switch (signal) { /* dispatch an I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + di_card->flag = CLEAR; /* clear the flag */ + di_card->flagbuf = CLEAR; /* and flag buffer */ + + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: [CLF] Flag cleared\n", + dptrs [card]->name); + + if (update_required) /* if the card state has changed */ + update_state (card); /* then update the state */ + break; + + + case ioSTF: /* set flag flip-flop */ + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: [STF] Flag set\n", + dptrs [card]->name); + + /* fall into ENF handler */ + + case ioENF: /* enable flag */ + di_card->flag = SET; /* set the flag */ + di_card->flagbuf = SET; /* and flag buffer */ + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (di [card]); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (di [card]); + break; + + + case ioIOI: /* I/O data input */ + if (di_card->control == SET) { /* is the card in data mode? */ + data = di_card->input_data_register; /* read the input data register */ + di_card->status_register &= ~STAT_IRL; /* clear the input register loaded status */ + + if (FIFO_EMPTY && di_card->eor == CLEAR) { /* is the FIFO empty and end of record not seen? */ + if (di_card->srq == SET && DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: SRQ cleared\n", + dptrs [card]->name); + + di_card->srq = CLEAR; /* clear SRQ */ + update_required = FALSE; /* the card state does not change */ + } + } + + else { /* the card is in status mode */ + di_card->status_register &= /* clear the values to be computed, */ + STAT_IRL | STAT_LBO /* preserving those set elsewhere */ + | STAT_LBI | STAT_IFC; + + di_card->status_register |= /* set T/L/C status from control register */ + di_card->cntl_register /* (T/L are ORed, as MTA or MLA can also set) */ + & (CNTL_CIC | CNTL_TALK | CNTL_LSTN); + + + if (SW8_SYSCTL) /* if SW8 is set, */ + di_card->status_register |= STAT_SYSCTL; /* the card is the system controller */ + + if (di_card->ibp == lower) /* if lower byte input is next */ + di_card->status_register |= STAT_ODD; /* then the last transfer was odd */ + + di_card->status_register |= /* set the bus status bits */ + (di_card->bus_cntl /* from the corresponding bus control lines */ + & (BUS_SRQ | BUS_IFC | BUS_REN + | BUS_EOI | BUS_ATN)) << DATA_SHIFT; + + data = di_card->status_register; /* return the status word */ + } + + if (DEBUG_PRJ (dptrs [card], DEB_CPU)) + fprintf (sim_deb, ">>%s cpu: [LIx%s] %s = %06o\n", + dptrs [card]->name, hold_or_clear, + input_state [di_card->control], data); + + if (update_required && !(signal_set & ioCLF)) /* if an update is required and CLF is not present, */ + update_state (card); /* update the state, else ioCLF will update it */ + + stat_data = IORETURN (SCPE_OK, data); /* merge in the return status */ + break; + + + case ioIOO: /* I/O data output */ + data = IODATA (stat_data); /* get the data value */ + + if (DEBUG_PRJ (dptrs [card], DEB_CPU)) + fprintf (sim_deb, ">>%s cpu: [OTx%s] %s = %06o\n", + dptrs [card]->name, hold_or_clear, + output_state [di_card->control], data); + + if (di_card->control == SET) { /* is the card in data mode? */ + if (signal_set & ioEDT) /* if end of DCPC transfer */ + di_card->edt = SET; /* set the EDT flip-flop */ + + else if (di_card->cntl_register & CNTL_PACK) { /* is this a packed transfer? */ + if (!(signal_set & ioCLF)) /* and CLF not given? */ + di_card->edt = SET; /* set the EDT flip-flop */ + } + + else /* it's an unpacked transfer */ + if (data & DATA_LBO) /* is the last byte out? */ + di_card->edt = SET; /* set the EDT flip-flop */ + + if (di_card->cntl_register & CNTL_DIAG) { /* set for DIAG loopback? */ + data = fifo_unload (card, diag_access); /* unload data from the FIFO */ + fifo_load (card, data, diag_access); /* and load it back in */ + } + + else { /* the card is set for normal operation */ + fifo_load (card, data, cpu_access); /* load the data word into the FIFO */ + + if (FIFO_FULL && (di_card->bus_cntl & BUS_NRFD)) { /* FIFO full and listener not ready? */ + if (di_card->srq == SET && DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: SRQ cleared\n", + dptrs [card]->name); + + di_card->srq = CLEAR; /* clear SRQ */ + update_required = FALSE; /* the card state does not change */ + } + } + } + + else { /* the card is in control mode */ + assert = 0; /* initialize bus control assertions */ + deny = 0; /* and denials */ + + if (!(data & CNTL_PACK)) /* unpacked mode always sets */ + di_card->ibp = di_card->obp = lower; /* byte selectors to the lower byte */ + + if (data & CNTL_TALK) { /* talking enables ATN and EOI outputs */ + if ((data & (CNTL_PPE | CNTL_CIC)) /* if parallel poll is enabled */ + == (CNTL_PPE | CNTL_CIC)) /* and the card is CIC */ + assert = BUS_PPOLL; /* then conduct a parallel poll */ + + else if ((di_card->cntl_register /* if PP was enabled */ + & (CNTL_PPE | CNTL_CIC)) /* but is not now */ + == (CNTL_PPE | CNTL_CIC)) + deny = BUS_PPOLL; /* then end the parallel poll */ + + else if ((data /* if packed mode */ + & (CNTL_PACK | CNTL_CIC | CNTL_ATN)) /* and the card is CIC */ + == (CNTL_PACK | CNTL_CIC | CNTL_ATN)) /* then the ATN control output */ + assert = BUS_ATN; /* is coupled to the bus */ + + else /* if none of the above */ + deny = BUS_ATN; /* then ATN is not driven */ + } + + else /* the card is not talking */ + deny = BUS_ATN | BUS_EOI; /* so ATN and EOI are disabled */ + + + if (data & CNTL_NRFD) /* is card not ready set explicitly? */ + assert |= BUS_NRFD; /* assert NRFD on the bus */ + + else if (di_card->cntl_register & CNTL_NRFD) /* NRFD was set but is not now? */ + deny |= BUS_NRFD; /* deny NRFD on the bus */ + + if (FIFO_FULL) /* is the FIFO full? */ + if (data & CNTL_LSTN) /* is card now listening? */ + assert |= BUS_NRFD; /* listener and a full FIFO asserts NRFD */ + + else if (di_card->cntl_register & CNTL_LSTN) /* was card a listener but is not now? */ + deny |= BUS_NRFD; /* deny NRFD on the bus */ + + + if (SW8_SYSCTL) { /* system controller drives REN and IFC */ + if (data & CNTL_REN) /* REN control */ + assert |= BUS_REN; /* output is */ + else /* coupled to */ + deny |= BUS_REN; /* the bus */ + + if (data & CNTL_IFC) { /* is IFC set? */ + assert |= BUS_IFC; /* assert IFC on the bus */ + + di_card->status_register = + di_card->status_register + & ~(STAT_LSTN | STAT_TALK) /* clear listen and talk status */ + | STAT_IFC; /* and set IFC status */ + + di_card->ifc_timer = /* start the IFC timer by calculating */ + sim_gtime () + IFC_TIMEOUT; /* the IFC stop time (now + 100 microseconds) */ + } + } + + if ((data & (CNTL_SRQ | CNTL_CIC)) == CNTL_SRQ) /* if service request and not the controller */ + assert |= BUS_SRQ; /* then assert SRQ on the bus */ + else /* else */ + deny |= BUS_SRQ; /* deny SRQ on the bus */ + + di_card->cntl_register = data; /* save the control word */ + di_bus_control (card, CONTROLLER, assert, deny); /* update the bus control state */ + } + + if (update_required && !(signal_set & ioCLF)) /* if update required and CLF is not present, */ + update_state (card); /* update the state, else ioCLF will update it */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + di_card->flag = SET; /* set the flag */ + di_card->flagbuf = SET; /* and flag buffer */ + + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: [POPIO] Flag set\n", + dptrs [card]->name); + break; + + + case ioCRS: /* control reset */ + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: [CRS] Master reset\n", + dptrs [card]->name); + + di_card->status_register &= /* clear listen and talk status */ + ~(STAT_LSTN | STAT_TALK); + + deny = BUS_SRQ | BUS_REN | BUS_ATN | BUS_EOI; /* clear the lines driven by the control register */ + + if (di_card->cntl_register & (CNTL_NRFD | CNTL_LSTN)) /* if asserting NRFD or listening */ + deny |= BUS_NRFD; /* then deny because we're clearing */ + + di_card->cntl_register = 0; /* clear the control word register */ + di_card->control = CLEAR; /* clear control */ + di_card->srq = CLEAR; /* clear SRQ */ + + master_reset (card); /* perform a master reset */ + + di_bus_control (card, CONTROLLER, 0, deny); /* update the bus control state */ + update_state (card); /* update the card state */ + break; + + + case ioCLC: /* clear control flip-flop */ + di_card->control = CLEAR; /* clear control */ + + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) { + fprintf (sim_deb, ">>%s cmds: [CLC%s] Control cleared (configure mode)", + dptrs [card]->name, hold_or_clear); + + if (signal_set & ioCLF) /* if ioCLF is given, */ + fputs (", master reset\n", sim_deb); /* then report a master reset */ + else + fputc ('\n', sim_deb); + } + + if (signal_set & ioCLF) /* if ioCLF is given, */ + master_reset (card); /* then do a master reset */ + break; /* (ioCLF will call update_state for us) */ + + + case ioSTC: /* set control flip-flop */ + di_card->control = SET; /* set control */ + + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: [STC%s] Control set (data mode)\n", + dptrs [card]->name, hold_or_clear); + break; + + + case ioEDT: /* end data transfer */ + if (DEBUG_PRJ (dptrs [card], DEB_CPU)) + fprintf (sim_deb, ">>%s cpu: [EDT] DCPC transfer ended\n", + dptrs [card]->name); + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (di [card]); /* set the standard PRL signal */ + setstdIRQ (di [card]); /* set the standard IRQ signal */ + + setSRQ (dibptr->select_code, /* set the SRQ signal if control and SRQ are set */ + di_card->srq == SET && di_card->control == SET); + break; + + + case ioIAK: /* interrupt acknowledge */ + di_card->flagbuf = CLEAR; /* clear the flag buffer */ + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove the current signal from the set */ + } + +return stat_data; +} + + +/* Reset the simulator. + + During a hardware PRESET, POPIO sets the flag buffer and flag flip-flops, and + CRS clears the control flip-flop and Control Word Register. In addition, CRS + performs a master reset on the card. + + PON is not used by the card. + + + Implementation notes: + + 1. During a power-on reset, a pointer to the FIFO simulation register is + saved to allow access to the "qptr" field during FIFO loading and + unloading. This enables SCP to view the FIFO as a circular queue, so + that the bottom word of the FIFO is always displayed as FIFO[0], + regardless of where it is in the actual FIFO array. +*/ + +t_stat di_reset (DEVICE *dptr) +{ +DIB *dibptr = (DIB *) dptr->ctxt; /* get the DIB pointer */ +const CARD_ID card = (CARD_ID) (dibptr->card_index); /* get the card number */ + +if (sim_switches & SWMASK ('P')) { /* is this a power-on reset? */ + di [card].fifo_reg = find_reg ("FIFO", NULL, dptr); /* find the FIFO register entry */ + + if (di [card].fifo_reg == NULL) /* if not there */ + return SCPE_IERR; /* then this is a programming error! */ + else /* found it */ + di [card].fifo_reg->qptr = 0; /* so reset the FIFO bottom index */ + + di [card].status_register = 0; /* clear the status word */ + + di [card].bus_cntl = 0; /* deny the HP-IB control lines */ + + di [card].listeners = 0; /* clear the map of listeners */ + di [card].talker = 0; /* clear the map of talker */ + di [card].poll_response = 0; /* clear the map of parallel poll responses */ + + di [card].ifc_timer = 0.0; /* clear the IFC timer */ + } + +IOPRESET (dibptr); /* PRESET the device */ + +return SCPE_OK; +} + + + +/* Disc interface global SCP routines */ + + +/* Set a unit's bus address. + + Bus addresses range from 0-7 and are initialized to the unit number. All + units of a device must have unique bus addresses. In addition, the card also + has a bus address, although this is only used for the diagnostic. The card + address may be the same as a unit address, as all units are disconnected + during a diagnostic run. + + The "value" parameter indicates whether the routine is setting a unit's bus + address (0) or a card's bus address (1). + + + Implementation notes: + + 1. To ensure that each address is unique, a check is made of the other units + for conflicting addresses. An "invalid argument" error is returned if + the desired address duplicates another. This means that addresses cannot + be exchanged without first assigning one of them to an unused address. + Also, an address cannot be set that duplicates the address of a disabled + unit (which cannot be displayed without enabling it). + + An alternate implementation would be to set the new assignments into a + "shadow array" that is set into the unit flags (and checked for validity) + only when a power-on reset is done. This would follow the disc and tape + controller hardware, which reads the HP-IB address switch settings only + at power-up. +*/ + +t_stat di_set_address (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +t_stat status; +uint32 index, new_address; +uint32 old_address = GET_BUSADR (uptr->flags); +DEVICE *dptr = (DEVICE *) desc; + +if (cptr == NULL) /* if the address is not given */ + return SCPE_ARG; /* report a missing argument */ + +new_address = (uint32) get_uint (cptr, 10, 7, &status); /* parse the address value */ + +if (status == SCPE_OK) { /* is the parse OK? */ + if (value) /* are we setting the card address? */ + dptr->flags = dptr->flags & ~DEV_BUSADR /* store the new address in the device flags */ + | SET_DIADR (new_address); + + else { /* we are setting a unit address */ + for (index = 0; index < dptr->numunits; index++) /* look through the units */ + if (new_address != old_address /* to ensure that the address is unique */ + && new_address == GET_BUSADR (dptr->units [index].flags)) { + printf ("Bus address conflict: DA%d\n", index); + + if (sim_log) + fprintf (sim_log, "Bus address conflict: DA%d\n", index); + + return SCPE_NOFNC; /* a duplicate address gives an error */ + } + + uptr->flags = uptr->flags & ~UNIT_BUSADR /* the address is valid; change it */ + | SET_BUSADR (new_address); /* in the unit flags */ + } + } + +return status; /* return the result of the parse */ +} + + +/* Show a unit's bus address. + + The "value" parameter indicates whether the routine is showing a unit's bus + address (0) or a card's bus address (1). +*/ + +t_stat di_show_address (FILE *st, UNIT *uptr, int32 value, CONST void *desc) +{ +const DEVICE *dptr = (const DEVICE *) desc; + +if (value) /* do we want the card address? */ + fprintf (st, "address=%d", GET_DIADR (dptr->flags)); /* get it from the device flags */ +else /* we want the unit address */ + fprintf (st, "bus=%d", GET_BUSADR (uptr->flags)); /* get it from the unit flags */ + +return SCPE_OK; +} + + +/* Set the bus cable connection. + + In normal use, the various tape and disc devices are connected together and + to the disc interface card by HP-IB cables. For the diagnostic, two disc + interface cards are connected by a single cable. + + The "value" parameter indicates whether the routine is connecting the + cable to devices for normal use (0) or to another card for diagnostics (1). + + + Implementation notes: + + 1. Initially, only one card and peripheral set is simulated: the ICD disc + family (DA device). For diagnostic use, a second, dummy card is enabled + (DC device). Once a second card simulation is implemented, this code + will no longer be necessary. +*/ + +t_stat di_set_cable (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +DEVICE *dptr = (DEVICE *) desc; + +if (value) { /* is the diagnostic cable selected? */ + dptr->flags |= DEV_DIAG; /* set the diagnostic flag */ + dc_dev.flags &= ~DEV_DIS; /* enable the dummy device */ + dc_dev.flags |= DEV_DIAG; /* and set its flag as well */ + } +else { /* the peripheral cable is selected */ + dptr->flags &= ~DEV_DIAG; /* clear the diagnostic flag */ + dc_dev.flags |= DEV_DIS; /* disable the dummy device */ + dc_dev.flags &= ~DEV_DIAG; /* and clear its flag */ + } + +return SCPE_OK; +} + + +/* Show the bus cable connection. + + The "value" parameter indicates whether the cable is connected to devices for + normal use (0) or to another card for diagnostics (1). +*/ + +t_stat di_show_cable (FILE *st, UNIT *uptr, int32 value, CONST void *desc) +{ +const DEVICE *dptr = (const DEVICE *) desc; + +if (dptr->flags & DEV_DIAG) /* is the cable connected for diagnostics? */ + fputs ("diagnostic cable", st); /* report it */ +else /* the cable is connected for device use */ + fputs ("HP-IB cable", st); /* report the condition */ + +return SCPE_OK; +} + + + +/* Disc interface global bus routines. + + In hardware, the HP-IB bus consists of eight control lines and eight data + lines. Signals are asserted on the control lines to establish communication + between a source and one or more acceptors. For commands, the source is + always the controller (the 12821A card), and the acceptors are all of the + connected devices. For data, the source is the current talker, and the + acceptors are one or more current listeners. A three-wire interlocking + handshake enables communication at the rate of the slowest of the multiple + acceptors. The controller conducts a parallel poll by asserting ATN and EOI + together. Devices whose parallel poll responses are enabled each assert one + of the data lines to indicate that service is required. + + In simulation, a disabled or detached unit logically is not connected to the + bus. The card maintains a bitmap of acceptors (all devices currently + attached), listeners (all devices currently addressed to listen), the talker + (the device currently addressed to talk), and the enabled parallel poll + responses. Changes in control line state are communicated to all acceptors + via control/respond function calls, and data is exchanged between talker and + listeners via source/acceptor function calls. Data bytes are sent to all + current listeners in bus-address order. The card conducts a parallel poll by + checking the response bitmap; devices must set and clear their poll responses + appropriately in advance of the poll. + + Not all of the HP-IB control lines are simulated. The DAV and NDAC handshake + lines are never asserted; instead, they are simulated by the bus source + function calling one or more bus acceptor functions. SRQ and REN are + asserted as directed by the system controller but are not otherwise used (no + HP disc or tape devices assert SRQ or respond to REN). IFC, ATN, EOI, and + NRFD are asserted and tested by the controller and devices. In particular, + asserting NRFD will hold off a pending data transmission until it is denied. + + The functions that simulate the HP-IB (where "*" is "di", "da", etc.) are: + + di_bus_source -- 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. + + *_bus_accept -- 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. + + di_bus_control -- 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. + + *_bus_respond -- 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. + + di_poll_response -- Set a device's poll response. Called by a device to + enable or disable its response to a future parallel + poll. +*/ + + +/* Source a byte to the bus. + + This routine is called to send bytes to devices on the bus connected to the + specified card. If the card is in diagnostic mode, which simulate two cards + connected by an HP-IB cable, then the byte is sent to another card in the + card cage that is also in diagnostic mode and enabled to receive. If the + card is not in diagnostic mode, then the byte is sent to all acceptors (if a + command) or to all listeners (if data) on the bus. + + The return value indicates whether or not there were any acceptors on the + bus. + + + Implementation notes: + + 1. If the responses from a previously conducted parallel poll are not + cleared from the FIFO before enabling the card to transmit, the card will + appear to conduct a new parallel poll because the FIFO tags cause ATN and + EOI to be asserted. This "fake" parallel poll is ignored (a real + parallel poll does not source data onto the bus). +*/ + +t_bool di_bus_source (CARD_ID card, uint8 data) +{ +CARD_ID other; +uint32 acceptors, unit; +t_bool accepted = FALSE; + +if (DEBUG_PRJ (dptrs [card], DEB_XFER)) { + fprintf (sim_deb, ">>%s xfer: HP-IB DIO %03o available ", dptrs [card]->name, data); + fprint_bus (sim_deb, "[%s]\n", di [card].bus_cntl); + } + +if (dptrs [card]->flags & DEV_DIAG) /* is this a diagnostic run? */ + for (other = first_card; other <= last_card; other++) { /* look through the list of cards */ + if (other != card && dptrs [other] /* for the other card */ + && (dptrs [other]->flags & DEV_DIAG) /* that is configured for diagnostic mode */ + && (di [other].cntl_register & CNTL_LSTN)) /* and is listening */ + accepted = di_bus_accept (other, data); /* call the interface acceptor for the other card */ + } + +else if ((di [card].bus_cntl & BUS_PPOLL) != BUS_PPOLL) { /* this is a normal run; not a fake poll? */ + if (di [card].cntl_register & CNTL_LSTN) /* is the card a listener? */ + accepted = di_bus_accept (card, data); /* call the interface acceptor for this card */ + + acceptors = di [card].acceptors; /* get the map of acceptors */ + + if (!(di [card].bus_cntl & BUS_ATN) /* if a data transfer, */ + || (data & BUS_COMMAND) == BUS_ACG) /* or an addressed command, e.g., SDC */ + acceptors = di [card].listeners; /* then limit just to listeners */ + + for (unit = 0; acceptors; unit++) { /* loop through the units */ + if (acceptors & 1) /* is the current unit accepting? */ + accepted |= (*bus_accept [card]) (unit, data); /* call the acceptor for this card */ + + acceptors = acceptors >> 1; /* move to the next acceptor */ + } + } + +if (DEBUG_PRJ (dptrs [card], DEB_XFER) && !accepted) + fprintf (sim_deb, ">>%s xfer: HP-IB no acceptors\n", + dptrs [card]->name); + +return accepted; +} + + +/* Assert or deny control on the bus. + + This routine is called by the indicated unit to assert or deny the HP-IB + control lines on the bus connected to the specified card. Separate sets of + signals to assert and deny are provided. + + If the bus state after modification did not change, the routine returns with + no further action. Otherwise, if the card is in diagnostic mode, then + notification of the bus change is sent to another card in the card cage that + is also in diagnostic mode. + + If the card is not in diagnostic mode, then the set of control lines that + are changing is checked to determine whether notification is necessary. If + not, then the change is not broadcast to improve performance. However, if + notification is required, then all acceptors on the bus are informed of the + change. + + + Implementation notes: + + 1. If a signal is asserted and denied in the same call, the assertion takes + precedence. + + 2. Of the sixteen potential control line state changes, only IFC assertion + and ATN and NRFD denial must be broadcast. Asserting IFC unaddresses all + devices, and denying ATN or NRFD allows a waiting talker to source a data + byte to the bus. Devices do not act upon the remaining thirteen state + changes, and a considerable performance improvement is obtained by + omitting the notification calls. + + 3. All control line state notifications are sent in diagnostic mode, as the + responses of the other card are specifically tested by the diagnostic. + + 4. Asserting ATN and EOI will conduct a parallel poll. Devices are not + notified of the poll. Instead, the previously stored parallel poll + responses will be used. +*/ + +#define ASSERT_SET (BUS_IFC) +#define DENY_SET (BUS_ATN | BUS_NRFD) + +void di_bus_control (CARD_ID card, uint32 unit, uint8 assert, uint8 deny) +{ +CARD_ID other; +uint32 acceptors, responder; +t_bool responded; +uint8 new_state, new_assertions, new_denials; + +new_state = di [card].bus_cntl & ~deny | assert; /* set up the new control state */ + +if (new_state == di [card].bus_cntl) /* if the control state did not change */ + return; /* return now */ + +new_assertions = ~di [card].bus_cntl & assert; /* get the changing assertions */ +new_denials = di [card].bus_cntl & deny; /* get the changing denials */ + +di [card].bus_cntl = new_state; /* establish the new control state */ + +if (DEBUG_PRJ (dptrs [card], DEB_XFER)) { + if (unit == CONTROLLER) + fprintf (sim_deb, ">>%s xfer: HP-IB card %d", dptrs [card]->name, card); + else + fprintf (sim_deb, ">>%s xfer: HP-IB address %d", + dptrs [card]->name, GET_BUSADR (dptrs [card]->units [unit].flags)); + + if (new_assertions) + fprint_bus (sim_deb, " asserted [%s]", new_assertions); + + if (new_denials) + fprint_bus (sim_deb, " denied [%s]", new_denials); + + fprint_bus (sim_deb, ", bus is [%s]\n", new_state); + } + +if ((dptrs [card]->flags & DEV_DIAG) /* is the card in diagnostic mode? */ + || (new_assertions & ASSERT_SET) /* or are changed signals in the */ + || (new_denials & DENY_SET)) { /* set that must be broadcast? */ + responded = FALSE; /* assume no response was received */ + + if (dptrs [card]->flags & DEV_DIAG) { /* is this a diagnostic run? */ + for (other = first_card; other <= last_card; other++) /* look through the list of cards */ + if (other != card && dptrs [other] /* for the other card */ + && (dptrs [other]->flags & DEV_DIAG)) { /* that is configured for diagnostic */ + di_bus_respond (other, new_state); /* notify the other card of the new control state */ + responded = TRUE; /* and note that there was a responder */ + } + } + + else { /* this is a normal run */ + update_state (card); /* update the card for the new control state */ + + acceptors = di [card].acceptors; /* get the map of acceptors */ + responded = (acceptors != 0); /* set response if there are any acceptors */ + + for (responder = 0; acceptors; responder++) { /* loop the through units */ + if ((acceptors & 1) && responder != unit) /* is the current unit accepting? */ + (*bus_respond [card]) (card, responder, new_state); /* call the responder for this card */ + + acceptors = acceptors >> 1; /* move to the next acceptor */ + } + } + + if (DEBUG_PRJ (dptrs [card], DEB_XFER) & !responded) + fprintf (sim_deb, ">>%s xfer: HP-IB no responders\n", + dptrs [card]->name); +} + +if ((new_state & BUS_PPOLL) == BUS_PPOLL) /* was a parallel poll requested? */ + di_bus_poll (card); /* conduct the poll */ + +return; +} + + +/* Enable or disable a unit's parallel poll response. + + The poll response for a unit connected to a specified card is set or cleared + as indicated. If a parallel poll is in progress when a poll response is set, + the poll is conducted again to reflect the new response. +*/ + +void di_poll_response (CARD_ID card, uint32 unit, FLIP_FLOP response) +{ +const uint32 address = GET_BUSADR (dptrs [card]->units [unit].flags); +uint32 previous_response = di [card].poll_response; + +if (response == SET) { /* enable the poll response? */ + di [card].poll_response |= PPR (address); /* set the response bit */ + + if ((di [card].bus_cntl & BUS_PPOLL) == BUS_PPOLL) /* is a parallel poll in progress? */ + di_bus_poll (card); /* conduct again with the new response */ + } +else /* disable the poll response */ + di [card].poll_response &= ~PPR (address); /* by clearing the response bit */ + +if (DEBUG_PRJ (dptrs [card], DEB_XFER) + && previous_response != di [card].poll_response) + fprintf (sim_deb, ">>%s xfer: HP-IB address %d parallel poll response %s\n", + dptrs [card]->name, address, (response == SET ? "enabled" : "disabled")); + +return; +} + + + +/* Disc interface local bus routines */ + + +/* Conduct a parallel poll on the bus. + + A controller asserting ATN and EOI simultaneously on the bus is conducting a + parallel poll. In hardware, each device whose poll response is enabled + asserts the data line corresponding to its bus address. The controller + terminates the poll by denying ATN and EOI. + + Setting the CIC (controller in charge) and PPE (parallel poll enable) bits in + the Control Word Register direct the disc interface to conduct a poll. + Setting PPE without CIC enables the poll response for the interface. + + In the diagnostic mode, one card is set to conduct the poll, and the other is + set to respond to it. In the normal mode, connected devices have set or + cleared their respective poll responses before this routine is called. + + + Implementation notes: + + 1. The card hardware fills the upper and lower bytes of the FIFO with the + response byte. In simulation, we use the diag_access mode to do the same + thing (diagnostic loopback also fills both bytes with the lower byte). +*/ + +static void di_bus_poll (CARD_ID card) +{ +CARD_ID other; +uint8 response; + +if ((di [card].cntl_register + & (CNTL_PPE | CNTL_CIC)) == CNTL_PPE) /* is the card's poll response enabled? */ + response = di [card].poll_response /* add the card's response */ + | PPR (GET_DIADR (dptrs [card]->flags)); /* to the devices' responses */ +else + response = di [card].poll_response; /* the card response is disabled, so just use devices */ + +if (dptrs [card]->flags & DEV_DIAG) /* is this a diagnostic run? */ + for (other = first_card; other <= last_card; other++) /* look through the list of cards */ + if (other != card && dptrs [other] /* for another card */ + && (dptrs [other]->flags & DEV_DIAG) /* that is configured for the diagnostic */ + && (di [other].cntl_register /* and has PPE asserted */ + & (CNTL_PPE | CNTL_CIC)) == CNTL_PPE) + response |= /* merge its poll response */ + PPR (GET_DIADR (dptrs [other]->flags)); + +if (response) { /* is a poll response indicated? */ + if (DEBUG_PRJ (dptrs [card], DEB_XFER)) + fprintf (sim_deb, ">>%s xfer: HP-IB parallel poll DIO %03o\n", + dptrs [card]->name, response); + + while (di [card].fifo_count != FIFO_SIZE) /* fill the card FIFO with the responses */ + fifo_load (card, (uint16) response, diag_access); /* (hardware feature) */ + + update_state (card); /* update the card state */ + } + +return; +} + + +/* Accept a data byte from the bus. + + The indicated card accepts a byte that has been sourced to the bus. The byte + is loaded into the FIFO, and the card state is updated to reflect the load. + + Bus acceptors return TRUE to indicate that the byte was accepted. A card + always accepts a byte, so the routine always returns TRUE. +*/ + +static t_bool di_bus_accept (CARD_ID card, uint8 data) +{ +if (DEBUG_PRJ (dptrs [card], DEB_XFER)) + fprintf (sim_deb, ">>%s xfer: HP-IB card %d accepted data %03o \n", + dptrs [card]->name, card, data); + +fifo_load (card, data, bus_access); /* load the data byte into the FIFO */ +update_state (card); /* and update the card state */ +return TRUE; /* indicate that the byte was accepted */ +} + + +/* Respond to the bus control lines. + + The indicated card is notified of the new control state on the bus. The + routine establishes the new bus state and updates the card state to reflect + the change. +*/ + +static void di_bus_respond (CARD_ID card, uint8 new_cntl) +{ +di [card].bus_cntl = new_cntl; /* update the bus control lines */ +update_state (card); /* update the card state */ +return; +} + + + +/* Disc interface local utility routines */ + + +/* Master reset the interface. + + This is the programmed card master reset, not the simulator reset routine. + Master reset initializes a number of flip-flops and data paths on the card. + The primary use, other than during a PRESET, is to clear the FIFO in + preparation to changing the card from a listener to a talker or vice versa. + This ensures that unneeded FIFO data is not transmitted inadvertently to the + bus or to the CPU. It is also used when changing the data mode from unpacked + to packed to release the byte pointer flip-flops, which are held in the + "lower byte" position during unpacked transfers. + + In hardware, a master reset: + - clears the EDT, EOR, IRL, LBO, LBI, and IFC flip-flops + - clears the Input Data Register + - clears the FIFO + - sets or clears the odd/even input and output byte pointer flip-flops, + depending on whether the P (packed transfer) bit is set in the Control + Word Register +*/ + +static void master_reset (CARD_ID card) +{ +di [card].edt = CLEAR; /* clear the EDT flip-flop */ +di [card].eor = CLEAR; /* clear the EOR flip-flop */ + +if (di [card].cntl_register & CNTL_PACK) /* if packed mode is set, */ + di [card].ibp = di [card].obp = upper; /* MR sets the selectors to the upper byte */ +else /* otherwise, unpacked mode overrides */ + di [card].ibp = di [card].obp = lower; /* and sets the selectors to the lower byte */ + +di [card].status_register &= /* clear the status flip-flops */ + ~(STAT_IRL | STAT_LBO | STAT_LBI | STAT_IFC); + +di [card].input_data_register = 0; /* clear the input data register */ +di [card].fifo_count = 0; /* clear the FIFO */ + +if (DEBUG_PRJ (dptrs [card], DEB_BUF)) + fprintf (sim_deb, ">>%s buf: FIFO cleared\n", + dptrs [card]->name); + +return; +} + + +/* Update the interface state. + + In hardware, certain external operations cause automatic responses by the + disc interface card. For example, when the Input Data Register is unloaded + by an LIx instruction, it is automatically reloaded with the next word from + the FIFO. Also, the card may be set to interrupt in response to the + assertion of certain bus control lines. + + In simulation, this routine must be called whenever the FIFO, card control, + or bus control state changes. It determines whether: + + 1. ...the next word from the FIFO should be unloaded into the IDR. If the + card is listening, and the IDR is empty, and the FIFO contains data, then + a word is unloaded and stored in the IDR, and the Input Register Loaded + status bit is set. + + 2. ...the next word from the FIFO should be unloaded and sourced to the bus. + If the card is talking (but not polling), and the listener is ready to + accept data, and the last byte has not been sent, and the FIFO contains + data, then a word is unloaded and sourced to the bus. This occurs + regardless of whether or not there are any listeners. + + 3. ...an interface clear operation has completed. If IFC is asserted, and + the current simulation time is later than the IFC expiration time, then + IFC is denied, and the timer is reset. + + 4. ...the card should assert NRFD to prevent FIFO overflow. If the card is + listening, and the FIFO is full, or the last byte has been received, or a + pause has been explicitly requested, then NRFD is asserted. + + 5. ...the SRQ flip-flop should be set or cleared. If the card is listening + and the Input Data Register has been loaded, or the card is talking and + the FIFO is not full, then SRQ is asserted to request a DCPC transfer. + + 6. ...the flag flip-flop should be set or cleared. If the Input Data + Register has been loaded or the Last Byte Out flip-flop is set and the + corresponding Control Word Register IRL or LBO bits are set, or the End + of Record flip-flop is set and the Input Data Register has been unloaded, + or SRQ is asserted on the bus and the corresponding Control Word Register + bit is set when the card is not the controller-in-charge, or REN or IFC + is asserted on the bus and the corresponding Control Word Register bits + are set when the card is not the system controller, then the flag is set + to request an interrupt. + + + Implementation notes: + + 1. The fifo_unload routine may set STAT_LBO, so the flag test must be done + after unloading. + + 2. The gcc compiler (at least as of version 4.6.2) does not optimize + repeated use of array-of-structures accesses. Instead, it recalculates + the index each time, even though the index is a constant within the + function. To avoid this performance penalty, we use a pointer to the + selected DI_STATE structure. Note that VC++ 2008 does perform this + optimization. + */ + +static void update_state (CARD_ID card) +{ +DIB * const dibptr = (DIB *) dptrs [card]->ctxt; +DI_STATE * const di_card = &di [card]; +uint8 assert = 0; +uint8 deny = 0; +uint16 data; +FLIP_FLOP previous_state; + +if (di_card->cntl_register & CNTL_LSTN) { /* is the card a listener? */ + if (!(di_card->status_register & STAT_IRL) /* is the IDR empty? */ + && ! FIFO_EMPTY) { /* and data remains in the FIFO? */ + data = fifo_unload (card, cpu_access); /* unload the FIFO */ + di_card->input_data_register = data; /* into the IDR */ + di_card->status_register |= STAT_IRL; /* set the input register loaded status */ + } + } + +else if ((di_card->cntl_register /* is the card a talker? */ + & (CNTL_TALK | CNTL_PPE)) == CNTL_TALK) /* and not polling? */ + while (! FIFO_EMPTY /* is data remaining in FIFO? */ + && !(di_card->bus_cntl & BUS_NRFD) /* and NRFD is denied? */ + && !(di_card->status_register & STAT_LBO)) { /* and the last byte has not been sent? */ + data = fifo_unload (card, bus_access); /* unload a FIFO byte */ + di_bus_source (card, (uint8) data); /* source it to the bus */ + } + + +if (di_card->bus_cntl & BUS_IFC /* is an IFC in progress? */ + && di_card->ifc_timer != 0.0 /* and I am timing? */ + && sim_gtime () > di_card->ifc_timer) { /* and has the timeout elapsed? */ + deny = BUS_IFC; /* deny IFC on the bus */ + di_card->ifc_timer = 0.0; /* clear the IFC timer */ + di_card->status_register &= ~STAT_IFC; /* and clear IFC status */ + } + + +if (di_card->cntl_register & CNTL_LSTN) /* is the card a listener? */ + if (di_card->cntl_register & CNTL_NRFD /* if explicitly requested */ + || di_card->status_register & STAT_LBI /* or the last byte is in */ + || FIFO_FULL) /* or the FIFO is full */ + assert = BUS_NRFD; /* then assert NRFD */ + else /* otherwise the card is ready for data */ + deny |= BUS_NRFD; /* so deny NRFD */ + +if (assert != deny) /* was there any change in bus state? */ + di_bus_control (card, CONTROLLER, assert, deny); /* update the bus control */ + + +previous_state = di_card->srq; /* save the current SRQ state */ + +if (di_card->cntl_register & CNTL_LSTN /* if the card is a listener */ + && di_card->status_register & STAT_IRL /* and the input register is loaded, */ + || di_card->cntl_register & CNTL_TALK /* or the card is a talker */ + && ! FIFO_FULL) /* and the FIFO is not full */ + di_card->srq = SET; /* then request a DCPC cycle */ +else + di_card->srq = CLEAR; /* otherwise, DCPC service is not needed */ + + +if (DEBUG_PRJ (dptrs [card], DEB_CMDS) + && di_card->srq != previous_state) + fprintf (sim_deb, ">>%s cmds: SRQ %s\n", + dptrs [card]->name, di_card->srq == SET ? "set" : "cleared"); + + +if (di_card->status_register & STAT_IRL /* is the input register loaded */ + && di_card->cntl_register & CNTL_IRL /* and notification is wanted? */ + || di_card->status_register & STAT_LBO /* or is the last byte out */ + && di_card->cntl_register & CNTL_LBO /* and notification is wanted? */ + || di_card->eor == SET /* or was the end of record seen */ + && !(di_card->status_register & STAT_IRL) /* and the input register was unloaded? */ + || di_card->bus_cntl & BUS_SRQ /* or is SRQ asserted on the bus */ + && di_card->cntl_register & CNTL_SRQ /* and notification is wanted */ + && di_card->cntl_register & CNTL_CIC /* and the card is not controller? */ + || !SW8_SYSCTL /* or is the card not the system controller */ + && di_card->bus_cntl & BUS_REN /* and REN is asserted on the bus */ + && di_card->cntl_register & CNTL_REN /* and notification is wanted? */ + || !SW8_SYSCTL /* or is the card not the system controller */ + && di_card->status_register & STAT_IFC /* and IFC is asserted on the bus */ + && di_card->cntl_register & CNTL_IFC) { /* and notification is wanted? */ + + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: Flag set\n", + dptrs [card]->name); + + di_io (dibptr, ioENF, 0); /* set the flag and recalculate interrupts */ + } + +else if (di_card->srq != previous_state) /* if SRQ changed state, */ + di_io (dibptr, ioSIR, 0); /* then recalculate interrupts */ + +return; +} + + +/* Load a word or byte into the FIFO. + + A word or byte is loaded into the next available location in the FIFO. The + significance of the data parameter is indicated by the access mode as + follows: + + - For CPU access, the parameter is a 16-bit value. + + - For bus access, the parameter is an 8-bit value in the lower byte and a + zero in the upper byte. + + - For diagnostic access, the parameter is an 8-bit value in the lower byte + that will be duplicated in the upper byte. + + For bus access, byte loading into the FIFO is controlled by the value of the + Input Buffer Pointer (IBP) selector. + + In addition to data words, the FIFO holds tags that mark the last byte + received or to be transmitted and that indicate the state of the ATN and EOI + bus lines (if listening) or the states to assert (if talking). The tag is + assembled into the upper word, the data is assembled into the lower word, and + then the 32-bit value is stored in the next available FIFO location. + + If data is coming from the CPU, the 16-bit value is loaded into the next FIFO + location, and the occupancy count is incremented. + + If the data is coming from the bus, and the input mode is unpacked, the 8-bit + value is loaded into the lower byte of the next FIFO location, and the + occupancy count is incremented. In hardware, the upper FIFO is not clocked; + in simulation, the upper byte is set to zero. The IBP always points at the + lower byte in unpacked mode. + + If the data is coming from the bus, and the input mode is packed, the 8-bit + value is loaded into either the upper or lower byte of the next FIFO + location, depending on the value of the IBP, and the IBP is toggled. If the + value was stored in the lower byte, the occupancy count is incremented. + + A special case occurs when the value is to be stored in the upper byte, and + the LBR tag is set to indicate that this is the last byte to be received. In + this case, the value is stored in both bytes of the next FIFO location, and + the occupancy counter is incremented. + + If data is coming from the diagnostic FIFO loopback, the 8-bit value in the + lower byte is copied to the upper byte, the resulting 16-bit value is loaded + into the next FIFO location, and the occupancy count is incremented. + + + Implementation notes: + + 1. Four tag bits are loaded into the upper word of each FIFO entry: + + - Last Byte Received (while receiving, a line feed is received and the + LF bit is set in the Control Word Register, or a byte with EOI + asserted is received and the EOI bit is set). + + - End of Data Transfer (while transmitting, DCPC asserts the EDT + backplane signal, or an unpacked-mode data word has the LBO bit set, + or a packed-mode OTx is issued without an accompanying CLF). + + - ATN (the state of ATN on the bus if receiving, or the ATN bit in the + unpacked data word if transmitting). + + - EOI (the state of EOI on the bus if receiving, or the EOI bit in the + unpacked data word if transmitting). + + 2. The FIFO is implemented as circular queue to take advantage of REG_CIRC + EXAMINE semantics. REG->qptr is the index of the first word currently in + the FIFO. By specifying REG_CIRC, examining FIFO[0-n] will always + display the words in load order, regardless of the actual array index of + the start of the list. The number of words currently present in the FIFO + is kept in fifo_count (0 = empty, 1-16 = number of words available). + + If fifo_count < FIFO_SIZE, (REG->qptr + fifo_count) mod FIFO_SIZE is the + index of the new word location. Loading stores the word there and then + increments fifo_count. + + 3. Because the load and unload routines need access to qptr in the REG + structure for the FIFO array, pointers to the REG for each card are + stored in the fifo_reg array during device reset. + + 4. The gcc compiler (at least as of version 4.6.2) does not optimize + repeated use of array-of-structures accesses. Instead, it recalculates + the index each time, even though the index is a constant within the + function. To avoid this performance penalty, we use a pointer to the + selected DI_STATE structure. Note that VC++ 2008 does perform this + optimization. +*/ + +static void fifo_load (CARD_ID card, uint16 data, FIFO_ACCESS access) +{ +uint32 tag, index; +t_bool add_word = TRUE; +DI_STATE * const di_card = &di [card]; + +if (FIFO_FULL) { /* is the FIFO already full? */ + if (DEBUG_PRJ (dptrs [card], DEB_BUF)) + fprintf (sim_deb, ">>%s buf: Attempted load to full FIFO, data %0*o\n", + dptrs [card]->name, (access == bus_access ? 3 : 6), data); + + return; /* return with the load ignored */ + } + +if (di_card->cntl_register & CNTL_LSTN) { /* is the card receiving? */ + tag = (di_card->bus_cntl /* set the tag from the bus signals */ + & (BUS_ATN | BUS_EOI)) << BUS_SHIFT; /* shifted to the tag locations */ + + if ((di_card->cntl_register & CNTL_EOI /* EOI detection is enabled, */ + && di_card->bus_cntl & BUS_EOI) /* and data was tagged with EOI? */ + || (di_card->cntl_register & CNTL_LF /* or LF detection is enabled, */ + && LOWER_BYTE (data) == LF)) { /* and the byte is a line feed? */ + tag = tag | TAG_LBR; /* tag as the last byte received */ + di_card->status_register |= STAT_LBI; /* set the last byte in status */ + } + else /* neither termination condition was seen */ + di_card->status_register &= ~STAT_LBI; /* so clear the last byte in status */ + } + +else /* the card is transmitting */ + tag = (data & (DATA_ATN | DATA_EOI)) << DATA_SHIFT; /* set the tag from the data shifted to the tag location */ + +if (di_card->edt == SET) /* is this the end of the data transfer? */ + tag = tag | TAG_EDT; /* set the EDT tag */ + + +index = (di_card->fifo_reg->qptr /* calculate the index */ + + di_card->fifo_count) % FIFO_SIZE; /* of the next available location */ + +if (access == bus_access) { /* is this a bus access */ + if (di_card->ibp == upper) { /* in packed mode for the upper byte? */ + di_card->ibp = lower; /* set the lower byte as next */ + + if (tag & TAG_LBR) /* is this the last byte? */ + di_card->fifo [index] = /* copy to both bytes of the FIFO */ + tag | TO_WORD (data, data); /* and store with the tag */ + else { /* more bytes are expected */ + di_card->fifo [index] = /* so position this byte */ + tag | TO_WORD (data, 0); /* and store it with the tag */ + add_word = FALSE; /* wait for the second byte before adding */ + } + } + + else /* this is the lower byte */ + if (di_card->cntl_register & CNTL_PACK) { /* is the card in packed mode? */ + di_card->ibp = upper; /* set the upper byte as next */ + + di_card->fifo [index] = /* merge the data and tag values */ + tag | di_card->fifo [index] | TO_WORD (0, data); + } + else /* the card is in unpacked mode */ + di_card->fifo [index] = /* position this byte */ + tag | TO_WORD (0, data); /* and store with the tag */ + } + +else if (access == cpu_access) /* is this a cpu access? */ + di_card->fifo [index] = tag | data; /* store the tag and full word in the FIFO */ + +else { /* must be diagnostic access */ + data = TO_WORD (data, data); /* copy the lower byte to the upper byte */ + di_card->fifo [index] = tag | data; /* and store the tag and full word in the FIFO */ + } + +if (add_word) /* did we add a word to the FIFO? */ + di_card->fifo_count = di_card->fifo_count + 1; /* increment the count of words stored */ + +if (DEBUG_PRJ (dptrs [card], DEB_BUF)) { + fprintf (sim_deb, ">>%s buf: Data %0*o tag ", + dptrs [card]->name, (access == bus_access ? 3 : 6), data); + fprint_val (sim_deb, tag >> BUS_SHIFT, 2, 4, PV_RZRO); + fprintf (sim_deb, " loaded into FIFO (%d)\n", di_card->fifo_count); + } + +return; +} + + +/* Unload a word or byte from the FIFO. + + A word or byte is unloaded from the first location in the FIFO. The + significance of the returned value is indicated by the access mode as + follows: + + - For CPU access, a 16-bit value is unloaded and returned. + + - For bus access, an 8-bit value is unloaded and returned. + + - For diagnostic access, an 16-bit value is unloaded, and the lower byte + is returned. + + For bus access, byte unloading from the FIFO is controlled by the value of + the Output Buffer Pointer (OBP) selector. + + If the FIFO is not empty, the first entry is obtained and split into tag and + data words. The LBR tag value is loaded into the EOR flip-flop if the CPU is + accessing. The EDT tag sets Last Byte Out status if the last byte is being + unloaded. + + If the data is going to the CPU, the 16-bit packed data value is returned as + is, or the lower byte of the unpacked value is merged with the tags for ATN + and EOI and returned. The occupancy count is decremented to unload the FIFO + entry. + + If the data is going to the bus, and the input mode is unpacked, the 8-bit + value is returned in the lower byte, and the occupancy count is decremented. + In hardware, the upper FIFO is not clocked; in simulation, the upper byte is + ignored. The OBP always points at the lower byte in unpacked mode. + + If the data is going to the bus, and the input mode is packed, the 8-bit + value is unloaded from either the upper or lower byte of the data word, + depending on the value of the OBP, and returned in the lower byte. The OBP + value is toggled. If the value was obtained from the lower byte, the + occupancy count is decremented to unload the FIFO. Otherwise, the count is + not altered, so that the lower-byte access will be from the same FIFO entry. + + If data is going to the diagnostic FIFO loopback, the lower byte of the + 16-bit value is returned; the upper byte of the returned value is zero. + + + Implementation notes: + + 1. Four tag bits are unloaded from the upper word of each FIFO entry: + + - Last Byte Received (sets the End of Record flip-flop when the last + byte received is loaded into the Input Data Register). + + - End of Data Transfer (sets the LBO bit in the Status Word Register + when the last byte is unloaded from the FIFO). + + - ATN (in unpacked mode, sets the ATN bit in the returned data word + if listening, or controls the bus ATN line if talking; in packed mode, + the tag is ignored). + + - EOI (in unpacked mode, sets the EOI bit in the returned data word if + listening, or asserts the bus EOI line if talking; in packed mode, the + tag is ignored). + + ATN and EOI tag handling is complex. If the card is listening in the + unpacked mode, the ATN tag substitutes for bit 8 of the data word, and + the EOI tag substitutes for bit 9. In the packed mode, bits 8 and 9 are + as stored in the FIFO (they are upper-byte data bits). + + If the card is talking in the unpacked mode, the ATN tag asserts or + denies ATN on the bus if the card is the CIC, and the EOI tag asserts or + denies EOI on the bus. In the packed mode, the ATN bit in the Control + Word Register asserts or denies ATN on the bus if the card is the CIC, + and the EOI bit asserts EOI on the bus if the last byte of the entry + tagged with EDT has been unloaded from the FIFO (which sets LBO status) + or denies EOI otherwise. + + 2. In hardware, the EOR flip-flop is clocked with the Input Data Register. + Therefore, when the card is listening, EOR is set not when the last byte + is unloaded from the FIFO, but rather when that byte is loaded into the + IDR. These two actions occur together when the IDR is empty. + + However, during diagnostic access, data unloaded from the FIFO is + reloaded, and the IDR is never clocked. As the T and L bits must be set + with DIAG in the Control Word Register to enable the loopback path, the + LBR tag will be entered into the FIFO if EOI or LF detection is enabled, + but the EOR flip-flop will not be set when that word falls through to be + unloaded. + + In simulation, EOR is set whenever the LBR tag is unloaded from the FIFO + during CPU access, as a CPU unload is always followed by an IDR store. + + 3. If fifo_count > 0, REG->qptr is the index of the word to remove. Removal + gets the word and then increments qptr (mod FIFO_SIZE) and decrements + fifo_count. + + 4. The gcc compiler (at least as of version 4.6.2) does not optimize + repeated use of array-of-structures accesses. Instead, it recalculates + the index each time, even though the index is a constant within the + function. To avoid this performance penalty, we use a pointer to the + selected DI_STATE structure. Note that VC++ 2008 does perform this + optimization. +*/ + +static uint16 fifo_unload (CARD_ID card, FIFO_ACCESS access) +{ +uint32 data, tag; +t_bool remove_word = TRUE; +DI_STATE * const di_card = &di [card]; + +if (FIFO_EMPTY) { /* is the FIFO already empty? */ + if (DEBUG_PRJ (dptrs [card], DEB_BUF)) + fprintf (sim_deb, ">>%s buf: Attempted unload from empty FIFO\n", + dptrs [card]->name); + + return 0; /* return with no data */ + } + +data = di_card->fifo [di_card->fifo_reg->qptr]; /* get the tag and data from the FIFO */ + +tag = data & TAG_MASK; /* mask the tag to just the tag bits */ +data = data & DMASK; /* and the data to just the data bits */ + +if (tag & TAG_EDT /* is this the end of a data transfer */ + && (di_card->obp == lower /* and the lower byte is next */ + || di_card->cntl_register & CNTL_ODD)) /* or we are sending an odd number of bytes? */ + di_card->status_register |= STAT_LBO; /* set the last byte out status */ + + +if (access == cpu_access) { /* is this a cpu access? */ + if (!(di_card->cntl_register & CNTL_PACK)) /* in unpacked mode? */ + data = data & ~(DATA_ATN | DATA_EOI) /* substitute the ATN/EOI tag values */ + | (tag & (TAG_ATN | TAG_EOI)) >> DATA_SHIFT; /* into the data word */ + + if (tag & TAG_LBR) /* is this the last byte? */ + di_card->eor = SET; /* set */ + else /* or clear */ + di_card->eor = CLEAR; /* the end-of-record flip-flop */ + } + +else if (access == bus_access) /* is this a bus access? */ + if (di_card->obp == upper) { /* is this the upper byte? */ + di_card->obp = lower; /* set the lower byte as next */ + data = UPPER_BYTE (data); /* mask and position the upper byte in the data word */ + remove_word = FALSE; /* do not unload the FIFO until the next byte */ + } + + else { /* this is the lower byte */ + data = LOWER_BYTE (data); /* mask and position it in the data word */ + + if (di_card->cntl_register & CNTL_PACK) /* is the card in the packed mode? */ + di_card->obp = upper; /* set the upper byte as next */ + } + +else /* must be a diagnostic access */ + data = LOWER_BYTE (data); /* access is to the lower byte only */ + + +if (remove_word) { /* remove the word from the FIFO? */ + di_card->fifo_reg->qptr = /* update the FIFO queue pointer */ + (di_card->fifo_reg->qptr + 1) % FIFO_SIZE; /* and wrap around as needed */ + + di_card->fifo_count = di_card->fifo_count - 1; /* decrement the FIFO count */ + } + + +if (DEBUG_PRJ (dptrs [card], DEB_BUF)) { + fprintf (sim_deb, ">>%s buf: Data %0*o tag ", + dptrs [card]->name, (access == cpu_access ? 6 : 3), data); + fprint_val (sim_deb, tag >> BUS_SHIFT, 2, 4, PV_RZRO); + fprintf (sim_deb, " unloaded from FIFO (%d)\n", di_card->fifo_count); + } + + +if (di_card->cntl_register & CNTL_TALK) /* is the card talking? */ + if (di_card->cntl_register & CNTL_PACK) /* is it in the packed mode? */ + if (di_card->status_register & STAT_LBO /* yes, is the last byte out? */ + && di_card->cntl_register & CNTL_EOI) /* and is EOI control enabled? */ + di_card->bus_cntl |= BUS_EOI; /* assert EOI on the bus */ + else + di_card->bus_cntl &= ~BUS_EOI; /* deny EOI on the bus */ + + else { /* the card is in the unpacked mode */ + if (di_card->cntl_register & CNTL_CIC) /* is the card the controller in charge? */ + di_card->bus_cntl = /* assert or deny the ATN bus line */ + di_card->bus_cntl & ~BUS_ATN /* from the ATN tag value */ + | (uint8) ((tag & TAG_ATN) >> BUS_SHIFT); + + di_card->bus_cntl = /* assert or deny the EOI bus line */ + di_card->bus_cntl & ~BUS_EOI /* from the EOI tag value */ + | (uint8) ((tag & TAG_EOI) >> BUS_SHIFT); + } + +return (uint16) data; /* return the data value */ +} + + +/* Print the bus state for debugging. + + The states of the supplied bus control lines are decoded and printed in + mnemonic form to the specified file using the indicated format string. An + asserted bus signal is indicated by its name; a denied signal is omitted. + + + Implementation notes: + + 1. The strings in the cntl_names array must appear in BUS_xxx order. The + first element corresponds to bus bit 0, etc. +*/ + +static void fprint_bus (FILE *file, char *format, uint8 cntl) +{ +static const char *cntl_names [] = { + "ATN", /* bit 0: attention */ + "EOI", /* bit 1: end or identify */ + "DAV", /* bit 2: data available */ + "NRFD", /* bit 3: not ready for data */ + "NDAC", /* bit 4: not data accepted */ + "REN", /* bit 5: remote enable */ + "IFC", /* bit 6: interface clear */ + "SRQ" /* bit 7: service request */ + }; + +uint32 signal; +char mnemonics [40]; + +if (cntl == 0) /* are any control signals asserted? */ + strcpy (mnemonics, "---"); /* no; use dashes in lieu of an empty string */ + +else { /* one or more signals are asserted */ + mnemonics [0] = '\0'; + + for (signal = 0; signal <= 7; signal++) /* loop though the set of signals */ + if (cntl & (1 << signal)) { /* is this signal asserted? */ + if (strlen (mnemonics) > 0) /* yes; is it the first one asserted? */ + strcat (mnemonics, " "); /* no, so append a space to separate */ + strcat (mnemonics, cntl_names [signal]); /* append the name of the asserted signal */ + } + } + +fprintf (file, format, mnemonics); /* print the bus state */ +return; +} diff --git a/HP2100/hp2100_di.h b/HP2100/hp2100_di.h index 5763ed4a..c2b89fe7 100644 --- a/HP2100/hp2100_di.h +++ b/HP2100/hp2100_di.h @@ -1,300 +1,284 @@ -/* hp2100_di.h: HP 12821A HP-IB Disc Interface simulator definitions - - Copyright (c) 2010-2012, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - DI 12821A Disc Interface - - 14-Feb-12 JDB First release - 16-Nov-10 JDB Created DI common definitions file - - - This file defines the interface between HP-IB device simulators and the - 12821A Disc Interface simulator. It must be included by the device-specific - modules (hp2100_di_da.c, etc.). - - - Implementation notes: - - 1. Three CARD_ID values are defined, corresponding to the Amigo disc (DA), - CS/80 disc (DC), and Amigo mag tape (MA) simulators. At first release, - only the DA device is implemented. However, as the 12821A diagnostic - requires two cards to test I/O fully, a dummy DC device is provided by - the DA simulator. It is enabled only when the DA card is configured for - diagnostic mode. This dummy device should be removed when either the DC - or MA device is implemented. -*/ - - - -/* Program constants */ - -#define FIFO_SIZE 16 /* FIFO depth */ - -typedef enum { - da, dc, ma, /* card IDs */ - first_card = da, /* first card ID */ - last_card = ma, /* last card ID */ - card_count /* count of card IDs */ - } CARD_ID; - - -/* Device flags and accessors (bits 7-0 are reserved for disc/tape flags) */ - -#define DEV_V_BUSADR (DEV_V_UF + 8) /* bits 10-8: interface HP-IB address */ -#define DEV_V_DIAG (DEV_V_UF + 11) /* bit 11: diagnostic mode */ -#define DEV_V_W1 (DEV_V_UF + 12) /* bit 12: DCPC pacing jumper */ - -#define DEV_M_BUSADR 07 /* bus address mask */ - -#define DEV_BUSADR (DEV_M_BUSADR << DEV_V_BUSADR) -#define DEV_DIAG (1 << DEV_V_DIAG) -#define DEV_W1 (1 << DEV_V_W1) - -#define GET_DIADR(f) (((f) >> DEV_V_BUSADR) & DEV_M_BUSADR) -#define SET_DIADR(f) (((f) & DEV_M_BUSADR) << DEV_V_BUSADR) - - -/* Unit flags and accessors (bits 7-0 are reserved for disc/tape flags) */ - -#define UNIT_V_BUSADR (UNIT_V_UF + 8) /* bits 10-8: unit HP-IB address */ - -#define UNIT_M_BUSADR 07 /* bus address mask */ - -#define UNIT_BUSADR (UNIT_M_BUSADR << UNIT_V_BUSADR) - -#define GET_BUSADR(f) (((f) >> UNIT_V_BUSADR) & UNIT_M_BUSADR) -#define SET_BUSADR(f) (((f) & UNIT_M_BUSADR) << UNIT_V_BUSADR) - - -/* Debug flags */ - -#define DEB_CPU (1 << 0) /* words received from and sent to the CPU */ -#define DEB_CMDS (1 << 1) /* interface commands received from the CPU */ -#define DEB_BUF (1 << 2) /* data read from and written to the card FIFO */ -#define DEB_XFER (1 << 3) /* data received and transmitted via HP-IB */ -#define DEB_RWSC (1 << 4) /* device read/write/status/control commands */ -#define DEB_SERV (1 << 5) /* unit service scheduling calls */ - - -/* HP-IB control line state bit flags. - - NOTE that these flags align with the corresponding flags in the DI status - register, so don't change the numerical values! -*/ - -#define BUS_ATN 0001 /* attention */ -#define BUS_EOI 0002 /* end or identify */ -#define BUS_DAV 0004 /* data available */ -#define BUS_NRFD 0010 /* not ready for data */ -#define BUS_NDAC 0020 /* not data accepted */ -#define BUS_REN 0040 /* remote enable */ -#define BUS_IFC 0100 /* interface clear */ -#define BUS_SRQ 0200 /* service request */ - -#define BUS_PPOLL (BUS_ATN | BUS_EOI) /* parallel poll */ - -/* HP-IB data */ - -#define BUS_ADDRESS 0037 /* bus address mask */ -#define BUS_GROUP 0140 /* bus group mask */ -#define BUS_COMMAND 0160 /* bus command type mask */ -#define BUS_DATA 0177 /* bus data mask */ -#define BUS_PARITY 0200 /* bus parity mask */ - -#define BUS_PCG 0000 /* primary command group */ -#define BUS_LAG 0040 /* listen address group */ -#define BUS_TAG 0100 /* talk address group */ -#define BUS_SCG 0140 /* secondary command group */ - -#define BUS_UCG 0020 /* universal command group */ -#define BUS_ACG 0000 /* addressed command group */ - -#define BUS_UNADDRESS 0037 /* unlisten and untalk addresses */ - -#define PPR(a) (uint8) (1 << (7 - (a))) /* parallel poll response */ - - -/* Byte accessors */ - -#define BYTE_SHIFT 8 /* byte shift count */ -#define UPPER_BYTE 0177400 /* high-order byte mask */ -#define LOWER_BYTE 0000377 /* low-order byte mask */ - -#define GET_UPPER(w) (uint8) (((w) & UPPER_BYTE) >> BYTE_SHIFT) -#define GET_LOWER(w) (uint8) ((w) & LOWER_BYTE) - -#define SET_UPPER(b) (uint16) ((b) << BYTE_SHIFT) -#define SET_LOWER(b) (uint16) (b) -#define SET_BOTH(b) (SET_UPPER (b) | SET_LOWER (b)) - -typedef enum { - upper, /* upper byte selected */ - lower /* lower byte selected */ - } SELECTOR; - - -/* Per-card state variables */ - -typedef struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - FLIP_FLOP srq; /* SRQ flip-flop */ - FLIP_FLOP edt; /* EDT flip-flop */ - FLIP_FLOP eor; /* EOR flip-flop */ - SELECTOR ibp; /* input byte pointer selector */ - SELECTOR obp; /* output byte pointer selector */ - - uint16 cntl_register; /* control word register */ - uint16 status_register; /* status word register */ - uint16 input_data_register; /* input data register */ - - uint32 fifo [FIFO_SIZE]; /* FIFO buffer */ - uint32 fifo_count; /* FIFO occupancy counter */ - REG *fifo_reg; /* FIFO register pointer */ - - uint32 acceptors; /* unit bitmap of the bus acceptors */ - uint32 listeners; /* unit bitmap of the bus listeners */ - uint32 talker; /* unit bitmap of the bus talker */ - - uint8 bus_cntl; /* HP-IB bus control state (ATN, EOI, etc.) */ - uint8 poll_response; /* address bitmap of parallel poll responses */ - - double ifc_timer; /* 100 microsecond IFC timer */ - } DI_STATE; - - -/* Disc interface VM global register definitions. - - These definitions should be included before any device-specific registers. - - - Implementation notes: - - 1. The TMR register is included to ensure that the IFC timer is saved by a - SAVE command. It is declared as a hidden, read-only byte array of a size - compatible with a double-precision floating-point value, as there is no - appropriate macro for the double type. -*/ - -#define DI_REGS(dev) \ - { ORDATA (CWR, di [dev].cntl_register, 16), REG_FIT }, \ - { ORDATA (SWR, di [dev].status_register, 16), REG_FIT }, \ - { ORDATA (IDR, di [dev].input_data_register, 16), REG_FIT }, \ - \ - { DRDATA (FCNT, di [dev].fifo_count, 5) }, \ - { BRDATA (FIFO, di [dev].fifo, 8, 20, FIFO_SIZE), REG_CIRC }, \ - \ - { GRDATA (ACPT, di [dev].acceptors, 2, 4, 0) }, \ - { GRDATA (LSTN, di [dev].listeners, 2, 4, 0) }, \ - { GRDATA (TALK, di [dev].talker, 2, 4, 0) }, \ - { GRDATA (PPR, di [dev].poll_response, 2, 8, 0), REG_FIT }, \ - { GRDATA (BUSCTL, di [dev].bus_cntl, 2, 8, 0), REG_FIT }, \ - \ - { FLDATA (CTL, di [dev].control, 0) }, \ - { FLDATA (FLG, di [dev].flag, 0) }, \ - { FLDATA (FBF, di [dev].flagbuf, 0) }, \ - { FLDATA (SRQ, di [dev].srq, 0) }, \ - { FLDATA (EDT, di [dev].edt, 0) }, \ - { FLDATA (EOR, di [dev].eor, 0) }, \ - \ - { BRDATA (TMR, &di [dev].ifc_timer, 10, CHAR_BIT, sizeof (double)), REG_HRO }, \ - \ - { ORDATA (SC, dev##_dib.select_code, 6), REG_HRO } - - -/* Disc interface VM global modifier definitions. - - These definitions should be included before any device-specific modifiers. -*/ - -#define DI_MODS(dev) \ - { MTAB_XTD | MTAB_VDV, 1, "ADDRESS", "ADDRESS", &di_set_address, &di_show_address, &dev }, \ - \ - { MTAB_XTD | MTAB_VDV, 1, NULL, "DIAG", &di_set_cable, NULL, &dev }, \ - { MTAB_XTD | MTAB_VDV, 0, NULL, "HPIB", &di_set_cable, NULL, &dev }, \ - { MTAB_XTD | MTAB_VDV, 0, "CABLE", NULL, NULL, &di_show_cable, &dev }, \ - \ - { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &dev }, \ - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dev }, \ - \ - { MTAB_XTD | MTAB_VUN, 0, "BUS", "BUS", &di_set_address, &di_show_address, &dev } - - -/* Disc interface global bus routine definitions */ - -typedef t_bool ACCEPTOR (uint32 unit, uint8 data); -typedef void RESPONDER (CARD_ID card, uint32 unit, uint8 new_cntl); - - -/* Disc interface global variables */ - -extern DI_STATE di []; -extern DEBTAB di_deb []; - - -/* Disc interface global VM routines */ - -extern IOHANDLER di_io; -extern t_stat di_reset (DEVICE *dptr); - -/* Disc interface global SCP routines */ - -extern t_stat di_set_address (UNIT *uptr, int32 value, char *cptr, void *desc); -extern t_stat di_show_address (FILE *st, UNIT *uptr, int32 value, void *desc); -extern t_stat di_set_cable (UNIT *uptr, int32 value, char *cptr, void *desc); -extern t_stat di_show_cable (FILE *st, UNIT *uptr, int32 value, void *desc); - -/* Disc interface global bus routines */ - -extern t_bool di_bus_source (CARD_ID card, uint8 data); -extern void di_bus_control (CARD_ID card, uint32 unit, uint8 assert, uint8 deny); -extern void di_poll_response (CARD_ID card, uint32 unit, FLIP_FLOP response); - - -/* Amigo disc global VM routines */ - -extern t_stat da_service (UNIT *uptr); -extern t_stat da_boot (int32 unitno, DEVICE *dptr); - -/* Amigo disc global bus routines */ - -extern ACCEPTOR da_bus_accept; -extern RESPONDER da_bus_respond; - - -/* Amigo mag tape global VM routines */ - -extern t_stat ma_service (UNIT *uptr); -extern t_stat ma_boot (int32 unitno, DEVICE *dptr); - -/* Amigo mag tape global SCP routines */ - -extern t_stat ma_set_timing (UNIT *uptr, int32 val, char *cptr, void *desc); -extern t_stat ma_show_timing (FILE *st, UNIT *uptr, int32 val, void *desc); - -/* Amigo mag tape global bus routines */ - -extern ACCEPTOR ma_bus_accept; -extern RESPONDER ma_bus_respond; +/* hp2100_di.h: HP 12821A HP-IB Disc Interface simulator declarations + + Copyright (c) 2010-2017, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + DI 12821A Disc Interface + + 10-Jan-17 JDB Moved byte accessors to hp2100_defs.h + 13-May-16 JDB Modified for revised SCP API function parameter types + 14-Feb-12 JDB First release + 16-Nov-10 JDB Created DI common definitions file + + + This file defines the interface between HP-IB device simulators and the + 12821A Disc Interface simulator. It must be included by the device-specific + modules (hp2100_di_da.c, etc.). + + + Implementation notes: + + 1. Three CARD_ID values are defined, corresponding to the Amigo disc (DA), + CS/80 disc (DC), and Amigo mag tape (MA) simulators. At first release, + only the DA device is implemented. However, as the 12821A diagnostic + requires two cards to test I/O fully, a dummy DC device is provided by + the DA simulator. It is enabled only when the DA card is configured for + diagnostic mode. This dummy device should be removed when either the DC + or MA device is implemented. +*/ + + + +/* Program constants */ + +#define FIFO_SIZE 16 /* FIFO depth */ + +typedef enum { + da, dc, ma, /* card IDs */ + first_card = da, /* first card ID */ + last_card = ma, /* last card ID */ + card_count /* count of card IDs */ + } CARD_ID; + + +/* Device flags and accessors (bits 7-0 are reserved for disc/tape flags) */ + +#define DEV_V_BUSADR (DEV_V_UF + 8) /* bits 10-8: interface HP-IB address */ +#define DEV_V_DIAG (DEV_V_UF + 11) /* bit 11: diagnostic mode */ +#define DEV_V_W1 (DEV_V_UF + 12) /* bit 12: DCPC pacing jumper */ + +#define DEV_M_BUSADR 07 /* bus address mask */ + +#define DEV_BUSADR (DEV_M_BUSADR << DEV_V_BUSADR) +#define DEV_DIAG (1 << DEV_V_DIAG) +#define DEV_W1 (1 << DEV_V_W1) + +#define GET_DIADR(f) (((f) >> DEV_V_BUSADR) & DEV_M_BUSADR) +#define SET_DIADR(f) (((f) & DEV_M_BUSADR) << DEV_V_BUSADR) + + +/* Unit flags and accessors (bits 7-0 are reserved for disc/tape flags) */ + +#define UNIT_V_BUSADR (UNIT_V_UF + 8) /* bits 10-8: unit HP-IB address */ + +#define UNIT_M_BUSADR 07 /* bus address mask */ + +#define UNIT_BUSADR (UNIT_M_BUSADR << UNIT_V_BUSADR) + +#define GET_BUSADR(f) (((f) >> UNIT_V_BUSADR) & UNIT_M_BUSADR) +#define SET_BUSADR(f) (((f) & UNIT_M_BUSADR) << UNIT_V_BUSADR) + + +/* Debug flags */ + +#define DEB_CPU (1 << 0) /* words received from and sent to the CPU */ +#define DEB_CMDS (1 << 1) /* interface commands received from the CPU */ +#define DEB_BUF (1 << 2) /* data read from and written to the card FIFO */ +#define DEB_XFER (1 << 3) /* data received and transmitted via HP-IB */ +#define DEB_RWSC (1 << 4) /* device read/write/status/control commands */ +#define DEB_SERV (1 << 5) /* unit service scheduling calls */ + + +/* HP-IB control line state bit flags. + + NOTE that these flags align with the corresponding flags in the DI status + register, so don't change the numerical values! +*/ + +#define BUS_ATN 0001 /* attention */ +#define BUS_EOI 0002 /* end or identify */ +#define BUS_DAV 0004 /* data available */ +#define BUS_NRFD 0010 /* not ready for data */ +#define BUS_NDAC 0020 /* not data accepted */ +#define BUS_REN 0040 /* remote enable */ +#define BUS_IFC 0100 /* interface clear */ +#define BUS_SRQ 0200 /* service request */ + +#define BUS_PPOLL (BUS_ATN | BUS_EOI) /* parallel poll */ + +/* HP-IB data */ + +#define BUS_ADDRESS 0037 /* bus address mask */ +#define BUS_GROUP 0140 /* bus group mask */ +#define BUS_COMMAND 0160 /* bus command type mask */ +#define BUS_DATA 0177 /* bus data mask */ +#define BUS_PARITY 0200 /* bus parity mask */ + +#define BUS_PCG 0000 /* primary command group */ +#define BUS_LAG 0040 /* listen address group */ +#define BUS_TAG 0100 /* talk address group */ +#define BUS_SCG 0140 /* secondary command group */ + +#define BUS_UCG 0020 /* universal command group */ +#define BUS_ACG 0000 /* addressed command group */ + +#define BUS_UNADDRESS 0037 /* unlisten and untalk addresses */ + +#define PPR(a) (uint8) (1 << (7 - (a))) /* parallel poll response */ + + +/* Per-card state variables */ + +typedef struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + FLIP_FLOP srq; /* SRQ flip-flop */ + FLIP_FLOP edt; /* EDT flip-flop */ + FLIP_FLOP eor; /* EOR flip-flop */ + BYTE_SELECTOR ibp; /* input byte pointer selector */ + BYTE_SELECTOR obp; /* output byte pointer selector */ + + uint16 cntl_register; /* control word register */ + uint16 status_register; /* status word register */ + uint16 input_data_register; /* input data register */ + + uint32 fifo [FIFO_SIZE]; /* FIFO buffer */ + uint32 fifo_count; /* FIFO occupancy counter */ + REG *fifo_reg; /* FIFO register pointer */ + + uint32 acceptors; /* unit bitmap of the bus acceptors */ + uint32 listeners; /* unit bitmap of the bus listeners */ + uint32 talker; /* unit bitmap of the bus talker */ + + uint8 bus_cntl; /* HP-IB bus control state (ATN, EOI, etc.) */ + uint8 poll_response; /* address bitmap of parallel poll responses */ + + double ifc_timer; /* 100 microsecond IFC timer */ + } DI_STATE; + + +/* Disc interface VM global register definitions. + + These definitions should be included before any device-specific registers. + + + Implementation notes: + + 1. The TMR register is included to ensure that the IFC timer is saved by a + SAVE command. It is declared as a hidden, read-only byte array of a size + compatible with a double-precision floating-point value, as there is no + appropriate macro for the double type. +*/ + +#define DI_REGS(dev) \ + { ORDATA (CWR, di [dev].cntl_register, 16), REG_FIT }, \ + { ORDATA (SWR, di [dev].status_register, 16), REG_FIT }, \ + { ORDATA (IDR, di [dev].input_data_register, 16), REG_FIT }, \ + \ + { DRDATA (FCNT, di [dev].fifo_count, 5) }, \ + { BRDATA (FIFO, di [dev].fifo, 8, 20, FIFO_SIZE), REG_CIRC }, \ + \ + { GRDATA (ACPT, di [dev].acceptors, 2, 4, 0) }, \ + { GRDATA (LSTN, di [dev].listeners, 2, 4, 0) }, \ + { GRDATA (TALK, di [dev].talker, 2, 4, 0) }, \ + { GRDATA (PPR, di [dev].poll_response, 2, 8, 0), REG_FIT }, \ + { GRDATA (BUSCTL, di [dev].bus_cntl, 2, 8, 0), REG_FIT }, \ + \ + { FLDATA (CTL, di [dev].control, 0) }, \ + { FLDATA (FLG, di [dev].flag, 0) }, \ + { FLDATA (FBF, di [dev].flagbuf, 0) }, \ + { FLDATA (SRQ, di [dev].srq, 0) }, \ + { FLDATA (EDT, di [dev].edt, 0) }, \ + { FLDATA (EOR, di [dev].eor, 0) }, \ + \ + { BRDATA (TMR, &di [dev].ifc_timer, 10, CHAR_BIT, sizeof (double)), REG_HRO }, \ + \ + { ORDATA (SC, dev##_dib.select_code, 6), REG_HRO } + + +/* Disc interface VM global modifier definitions. + + These definitions should be included before any device-specific modifiers. +*/ + +#define DI_MODS(dev) \ + { MTAB_XTD | MTAB_VDV, 1, "ADDRESS", "ADDRESS", &di_set_address, &di_show_address, &dev }, \ + \ + { MTAB_XTD | MTAB_VDV, 1, NULL, "DIAG", &di_set_cable, NULL, &dev }, \ + { MTAB_XTD | MTAB_VDV, 0, NULL, "HPIB", &di_set_cable, NULL, &dev }, \ + { MTAB_XTD | MTAB_VDV, 0, "CABLE", NULL, NULL, &di_show_cable, &dev }, \ + \ + { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &dev }, \ + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dev }, \ + \ + { MTAB_XTD | MTAB_VUN, 0, "BUS", "BUS", &di_set_address, &di_show_address, &dev } + + +/* Disc interface global bus routine definitions */ + +typedef t_bool ACCEPTOR (uint32 unit, uint8 data); +typedef void RESPONDER (CARD_ID card, uint32 unit, uint8 new_cntl); + + +/* Disc interface global variables */ + +extern DI_STATE di []; +extern DEBTAB di_deb []; + + +/* Disc interface global VM routines */ + +extern IOHANDLER di_io; +extern t_stat di_reset (DEVICE *dptr); + +/* Disc interface global SCP routines */ + +extern t_stat di_set_address (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +extern t_stat di_set_cable (UNIT *uptr, int32 value, CONST char *cptr, void *desc); + +extern t_stat di_show_address (FILE *st, UNIT *uptr, int32 value, CONST void *desc); +extern t_stat di_show_cable (FILE *st, UNIT *uptr, int32 value, CONST void *desc); + +/* Disc interface global bus routines */ + +extern t_bool di_bus_source (CARD_ID card, uint8 data); +extern void di_bus_control (CARD_ID card, uint32 unit, uint8 assert, uint8 deny); +extern void di_poll_response (CARD_ID card, uint32 unit, FLIP_FLOP response); + + +/* Amigo disc global VM routines */ + +extern t_stat da_service (UNIT *uptr); +extern t_stat da_boot (int32 unitno, DEVICE *dptr); + +/* Amigo disc global bus routines */ + +extern ACCEPTOR da_bus_accept; +extern RESPONDER da_bus_respond; + + +/* Amigo mag tape global VM routines */ + +extern t_stat ma_service (UNIT *uptr); +extern t_stat ma_boot (int32 unitno, DEVICE *dptr); + +/* Amigo mag tape global SCP routines */ + +extern t_stat ma_set_timing (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +extern t_stat ma_show_timing (FILE *st, UNIT *uptr, int32 val, CONST void *desc); + +/* Amigo mag tape global bus routines */ + +extern ACCEPTOR ma_bus_accept; +extern RESPONDER ma_bus_respond; diff --git a/HP2100/hp2100_di_da.c b/HP2100/hp2100_di_da.c index 7020a0e3..e73f73c0 100644 --- a/HP2100/hp2100_di_da.c +++ b/HP2100/hp2100_di_da.c @@ -1,2133 +1,2140 @@ -/* hp2100_di_da.c: HP 12821A HP-IB Disc Interface simulator for Amigo disc drives - - Copyright (c) 2011-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - DA 12821A Disc Interface with Amigo disc drives - - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 24-Dec-14 JDB Use T_ADDR_FMT with t_addr values for 64-bit compatibility - Removed redundant global declarations - 24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash - 07-May-12 JDB Cancel the intersector delay if an untalk is received - 29-Mar-12 JDB First release - 04-Nov-11 JDB Created DA device - - References: - - HP 13365 Integrated Controller Programming Guide (13365-90901, Feb-1980) - - HP 7910 Disc Drive Service Manual (07910-90903, Apr-1981) - - 12745D Disc Controller (13037) to HP-IB Adapter Kit Installation and - Service Manual (12745-90911, Sep-1983) - - HP's 5 1/4-Inch Winchester Disc Drive Service Documentation - (09134-90032, Aug-1983) - - HP 12992 Loader ROMs Installation Manual (12992-90001, Apr-1986) - - RTE Driver DVA32 Source (92084-18708, revision 2540) - - IEEE Standard Digital Interface for Programmable Instrumentation - (IEEE-488A-1980, Sep-1979) - - - The HP 7906H, 7920H, and 7925H Integrated Controller Disc (ICD) drives were - connected via an 12821A disc interface and provided 20MB, 50MB, and 120MB - capacities. The drives were identical to the 7906M, 7920M, and 7925M - Multi-Access Controller (MAC) units but incorporated internal two-card - controllers in each drive and connected to the CPU interface via the - Hewlett-Packard Interface Bus (HP-IB), HP's implementation of IEEE-488. Each - controller was dedicated to a single drive and operated similarly to the - 12745 Disc Controller to HP-IB Adapter option for the 13037 Disc Controller - chassis. The 7906H was introduced in 1980 (there was no 7905H version, as - the 7905 was obsolete by that time). Up to four ICD drives could be - connected to a single 12821A card. The limitation was imposed by the bus - loading and the target data transfer rate. - - The ICD command set essentially was the MAC command set modified for - single-unit operation. The unit number and CPU hold bit fields in the opcode - words were unused in the ICD implementation. The Load TIO Register, Wakeup, - and Request Syndrome commands were removed, as Load TIO was used with the HP - 3000, Wakeup was used in a multi-CPU environment, and the simpler ICD - controller did not support ECC. Controller status values 02B (Unit - Available) and 27B (Unit Unavailable) were dropped as the controller - supported only single units, 12B (I/O Program Error) was reused to indicate - HP-IB protocol errors, 13B (Sync Not Received) was added, and 17B (Possibly - Correctable Data Error) was removed as error correction was not supported. - - Some minor redefinitions also occurred. For example, status 14B (End of - Cylinder) was expanded to include an auto-seek beyond the drive limits, and - 37B (Drive Attention) was restricted just head unloads from head loads and - unloads. - - The command set was expanded to include several commands related to HP-IB - operation. These were, in large part, adapted from the Amigo disc command - protocol outlined in the service manual for the HP 9133/34/35 series of - 5-1/4" Winchester drives. They include the Amigo Identify and Amigo Clear - sequences, Read and Write Loopback channel tests, and controller Self Test - commands. - - This simulator implements the Amigo disc protocol. It calls the 12821A Disc - Interface (DI) simulator to send and receive bytes across the HP-IB to and - from the CPU, and it calls the HP Disc Library to implement the controller - functions related to disc unit operation (e.g., seek, read, write, etc.). - Four units are provided, and any combination of 7906H/20H/25H drives may be - defined. - - Unfortunately, the primary reference for the ICD controller (the HP 13365 - Integrated Controller Programming Guide) does not indicate parallel poll - responses for these HP-IB commands. Therefore, the responses have been - derived from the sequences in the 7910 and 12745 manuals, although they - sometimes conflict. - - The drives respond to the following commands; the secondary and opcode - numeric values are in hex, and the bus addressing state is indicated by U - [untalk], L [listen], and T [talk]: - - Bus Sec Op Operation - --- --- -- -------------------------------- - U MSA -- Amigo Identify - - L 00 -- Write Data - L 08 00 Cold Load Read - L 08 01 Recalibrate - L 08 02 Seek - L 08 03 Request Status - L 08 04 Request Sector Address - L 08 05 Read - L 08 06 Read Full Sector - L 08 07 Verify - L 08 08 Write - L 08 09 Write Full Sector - L 08 0A Clear - L 08 0B Initialize - L 08 0C Address Record - L 08 0E Read with Offset - L 08 0F Set File Mask - L 08 12 Read without Verify - L 08 14 Request Logical Disc Address - L 08 15 End - L 09 -- Cyclic Redundancy Check - L 10 -- Amigo Clear - L 1E -- Write Loopback - L 1F ss Initiate Self-Test - - T 00 -- Read Data - T 08 -- Read Status - T 09 -- Cyclic Redundancy Check - T 10 -- Device Specified Jump - T 1E -- Read Loopback - T 1F -- Return Self-Test Result - - In addition, the controller responds to the Selected Device Clear primary - (04). - - - HP-IB Transaction Sequences - =========================== - - Amigo Identify - - ATN UNT Untalk - ATN MSA My secondary address - DAB ID data byte #1 = 00H - EOI DAB ID data byte #2 = 03H - ATN OTA Talk 30 - - - Amigo Clear - - ATN MLA My listen address - ATN SCG Secondary command 10H - ppd Parallel poll disabled - EOI DAB Unused data byte - ATN SDC Selected device clear - ATN UNL Unlisten - ... - ppe Parallel poll enabled when clear completes - - - CRC - - ATN MTA My talk address - ATN SCG Secondary command 09H - ppd Parallel poll disabled - DAB Data byte #1 - ... - EOI DAB Data byte #n - ppe Parallel poll enabled - ATN UNT Untalk - - or - - ATN MLA My listen address - ATN SCG Secondary command 09H - ppd Parallel poll disabled - DAB Data byte #1 - ... - EOI DAB Data byte #n - ppe Parallel poll enabled - ATN UNL Unlisten - - - Device Specified Jump - - ATN MTA My talk address - ATN SCG Secondary command 10H - ppd Parallel poll disabled - EOI DAB DSJ data byte - ATN UNT Untalk - - - Initiate Self-Test and Return Self-Test Result - - ATN MLA My listen address - ATN SCG Secondary command 1FH - ppd Parallel poll disabled - EOI DAB Self-test number - ppe Parallel poll enabled - ATN UNL Unlisten - - ATN MTA My talk address - ATN SCG Secondary command 1FH - ppd Parallel poll disabled - EOI DAB Result data byte - ppe Parallel poll enabled - ATN UNT Untalk - - - Write Loopback and Read Loopback - - ATN MLA My listen address - ATN SCG Secondary command 1EH - ppd Parallel poll disabled - DAB Loopback data byte #1 - ... - EOI DAB Loopback data byte #256 - ppe Parallel poll enabled - ATN UNL Unlisten - - ATN MTA My talk address - ATN SCG Secondary command 1EH - ppd Parallel poll disabled - DAB Loopback data byte #1 - ... - EOI DAB Loopback data byte #16 - ppe Parallel poll enabled - ATN UNT Untalk - - - Recalibrate and Seek - - ATN MLA My listen address - ATN SCG Secondary command 08H - ppd Parallel poll disabled - DAB Opcode 01H, 02H - ... (one to five - EOI DAB parameter bytes) - ATN UNL Unlisten - ... - ppe Parallel poll enabled when seek completes - - - Clear, Address Record, and Set File Mask - - ATN MLA My listen address - ATN SCG Secondary command 08H - ppd Parallel poll disabled - DAB Opcode 0AH, 0CH, 0FH - ... (one to five - EOI DAB parameter bytes) - ppe Parallel poll enabled - ATN UNL Unlisten - - - End - - ATN MLA My listen address - ATN SCG Secondary command 08H - ppd Parallel poll disabled - DAB Opcode 15H - EOI DAB Unused data byte - ATN UNL Unlisten - - - Request Status, Request Sector Address, and Request Logical Disc Address - - ATN MLA My listen address - ATN SCG Secondary command 08H - ppd Parallel poll disabled - DAB Opcode 03H, 04H, 14H - EOI DAB Unused data byte - ATN UNL Unlisten - - ATN MTA My talk address - ATN SCG Secondary command 08H - DAB Status byte #1 - ... (two to four - EOI DAB status bytes) - ppe Parallel poll enabled - ATN UNT Untalk - - - Cold Load Read, Read, Read Full Sector, Verify, Read with Offset, and Read - without Verify - - ATN MLA My listen address - ATN SCG Secondary command 08H - ppd Parallel poll disabled - DAB Opcode 00H, 05H, 06H, 07H, 0EH, 12H - EOI DAB Unused data byte - ATN UNL Unlisten - - ATN MTA My talk address - ATN SCG Secondary command 00H - DAB Read data byte #1 - ... - DAB Read data byte #n - ATN UNT Untalk - ... - ppe Parallel poll enabled when sector ends - - - Write, Write Full Sector, and Initialize - - ATN MLA My listen address - ATN SCG Secondary command 08H - ppd Parallel poll disabled - DAB Opcode 08H, 09H, 0BH - EOI DAB Unused data byte - ATN UNL Unlisten - - ATN MLA My listen address - ATN SCG Secondary command 00H - DAB Write data byte #1 - ... - EOI DAB Write data byte #n - ppe Parallel poll enabled - ATN UNL Unlisten - - - Implementation notes: - - 1. The 12745 does not alter the parallel poll response for the - Device-Specified Jump command. - - 2. The 7910 does not perform a parallel poll response enable and disable - between the Initiate Self-Test and Return Self-Test Result commands. - - 3. The 12745 does not disable the parallel poll response for the Read - Loopback command. -*/ - - - -#include "hp2100_defs.h" -#include "hp2100_di.h" -#include "hp_disclib.h" - - - -/* Program constants */ - -#define DA_UNITS 4 /* number of addressable disc units */ - - -/* Interface states */ - -typedef enum { - idle = 0, /* idle = default for reset */ - opcode_wait, /* waiting for opcode reception */ - parameter_wait, /* waiting for parameter reception */ - read_wait, /* waiting for send read data secondary */ - write_wait, /* waiting for receive write data secondary */ - status_wait, /* waiting for send status secondary */ - command_exec, /* executing an interface command */ - command_wait, /* waiting for command completion */ - read_xfer, /* sending read data or status */ - write_xfer, /* receiving write data */ - error_source, /* sending bytes for error recovery */ - error_sink /* receiving bytes for error recovery */ - } IF_STATE; - - -/* Interface state names */ - -static const char *if_state_name [] = { - "idle", - "opcode wait", - "parameter wait", - "read wait", - "write wait", - "status wait", - "command execution", - "command wait", - "read transfer", - "write transfer", - "error source", - "error sink" - }; - - -/* Next interface state after command recognition */ - -static const IF_STATE next_state [] = { - read_wait, /* cold load read */ - command_exec, /* recalibrate */ - command_exec, /* seek */ - status_wait, /* request status */ - status_wait, /* request sector address */ - read_wait, /* read */ - read_wait, /* read full sector */ - command_exec, /* verify */ - write_wait, /* write */ - write_wait, /* write full sector */ - command_exec, /* clear */ - write_wait, /* initialize */ - command_exec, /* address record */ - idle, /* request syndrome */ - read_wait, /* read with offset */ - command_exec, /* set file mask */ - idle, /* invalid */ - idle, /* invalid */ - read_wait, /* read without verify */ - idle, /* load TIO register */ - status_wait, /* request disc address */ - command_exec, /* end */ - idle /* wakeup */ - }; - - -/* Interface commands */ - -typedef enum { - invalid = 0, /* invalid = default for reset */ - disc_command, /* MLA 08 */ - crc_listen, /* MLA 09 */ - amigo_clear, /* MLA 10 */ - write_loopback, /* MLA 1E */ - initiate_self_test, /* MLA 1F */ - crc_talk, /* MTA 09 */ - device_specified_jump, /* MTA 10 */ - read_loopback, /* MTA 1E */ - return_self_test_result, /* MTA 1F */ - amigo_identify /* UNT MSA */ - } IF_COMMAND; - -/* Interface command names */ - -static const char *if_command_name [] = { - "invalid", - "disc command", - "CRC listen", - "Amigo clear", - "write loopback", - "initiate self-test", - "CRC talk", - "device specified jump", - "read loopback", - "return self-test result", - "Amigo identify" - }; - - - -/* Amigo disc state variables */ - -static uint16 buffer [DL_BUFSIZE]; /* command/status/sector buffer */ - -static uint8 if_dsj [DA_UNITS]; /* ICD controller DSJ values */ -static IF_STATE if_state [DA_UNITS]; /* ICD controller state */ -static IF_COMMAND if_command [DA_UNITS]; /* ICD controller command */ - -static CNTLR_VARS icd_cntlr [DA_UNITS] = /* ICD controllers: */ - { { CNTLR_INIT (ICD, buffer, NULL) }, /* unit 0 controller */ - { CNTLR_INIT (ICD, buffer, NULL) }, /* unit 1 controller */ - { CNTLR_INIT (ICD, buffer, NULL) }, /* unit 2 controller */ - { CNTLR_INIT (ICD, buffer, NULL) } }; /* unit 3 controller */ - - - -/* Amigo disc global VM routines */ - -t_stat da_reset (DEVICE *dptr); -t_stat da_attach (UNIT *uptr, char *cptr); -t_stat da_detach (UNIT *uptr); - -/* Amigo disc global SCP routines */ - -t_stat da_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc); - -/* Amigo disc local utility routines */ - -static t_bool start_command (uint32 unit); -static void abort_command (uint32 unit, CNTLR_STATUS status, IF_STATE state); -static void complete_read (uint32 unit); -static void complete_write (uint32 unit); -static void complete_abort (uint32 unit); -static uint8 get_buffer_byte (CVPTR cvptr); -static void put_buffer_byte (CVPTR cvptr, uint8 data); -static t_stat activate_unit (UNIT *uptr); - - - -/* Amigo disc VM global data structures. - - da_dib DA device information block - da_unit DA unit list - da_reg DA register list - da_mod DA modifier list - da_dev DA device descriptor - - - Implementation notes: - - 1. The IFSTAT and IFCMD registers are declared to accommodate the - corresponding arrays of enums. Arrayed registers assume that elements - are allocated space only to the integral number of bytes implied by the - "width" field. The storage size of an enum is implementation-defined, so - we must determine the number of bits for "width" at compile time. - PV_LEFT is used to avoid the large number of leading zeros that would be - displayed if an implementation stored enums in full words. - - 2. The CNVARS register is included to ensure that the controller state - variables array is saved by a SAVE command. It is declared as a hidden, - read-only byte array of a depth compatible with the size of the array. - - There does not appear to be a way to expose the fields of the four - controller state variables as arrayed registers. Access to an array - always assumes that elements appear at memory offsets equal to the - element size, i.e., a 32-bit arrayed register has elements at four-byte - offsets. There's no way to specify an array of structure elements where - a given 32-bit field appears at, say, 92-byte offsets (i.e., the size of - the structure). -*/ - -DEVICE da_dev; - -DIB da_dib = { &di_io, DI_DA, da }; - -#define UNIT_FLAGS (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD) - -UNIT da_unit [] = { - { UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (0), D7906_WORDS) }, /* drive unit 0 */ - { UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (1), D7906_WORDS) }, /* drive unit 1 */ - { UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (2), D7906_WORDS) }, /* drive unit 2 */ - { UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (3), D7906_WORDS) } /* drive unit 3 */ - }; - -REG da_reg [] = { - DI_REGS (da), - - { BRDATA (BUFFER, buffer, 8, 16, DL_BUFSIZE) }, - - { BRDATA (DSJ, if_dsj, 10, 2, DA_UNITS) }, - { BRDATA (ISTATE, if_state, 10, sizeof (IF_STATE) * CHAR_BIT, DA_UNITS), PV_LEFT }, - { BRDATA (ICMD, if_command, 10, sizeof (IF_COMMAND) * CHAR_BIT, DA_UNITS), PV_LEFT }, - - { BRDATA (CNVARS, icd_cntlr, 10, CHAR_BIT, sizeof (CNTLR_VARS) * DA_UNITS), REG_HRO }, - - { NULL } - }; - -MTAB da_mod [] = { - DI_MODS (da_dev), - - { UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", &da_load_unload, NULL, NULL }, - { UNIT_UNLOAD, 0, "heads loaded", "LOADED", &da_load_unload, NULL, NULL }, - - { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL, NULL, NULL }, - { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL, NULL, NULL }, - - { UNIT_FMT, UNIT_FMT, "format enabled", "FORMAT", NULL, NULL, NULL }, - { UNIT_FMT, 0, "format disabled", "NOFORMAT", NULL, NULL, NULL }, - - { UNIT_MODEL, MODEL_7906, "7906H", "7906H", &dl_set_model, NULL, NULL }, - { UNIT_MODEL, MODEL_7920, "7920H", "7920H", &dl_set_model, NULL, NULL }, - { UNIT_MODEL, MODEL_7925, "7925H", "7925H", &dl_set_model, NULL, NULL }, - - { 0 } - }; - -DEVICE da_dev = { - "DA", /* device name */ - da_unit, /* unit array */ - da_reg, /* register array */ - da_mod, /* modifier array */ - DA_UNITS, /* number of units */ - 10, /* address radix */ - 26, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 16, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &da_reset, /* reset routine */ - &da_boot, /* boot routine */ - &da_attach, /* attach routine */ - &da_detach, /* detach routine */ - &da_dib, /* device information block */ - DEV_DEBUG | DEV_DISABLE, /* device flags */ - 0, /* debug control flags */ - di_deb, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL /* logical device name */ - }; - - - -/* Amigo disc global VM routines */ - - -/* Service an Amigo disc drive I/O event. - - The service routine is called to execute commands and control the transfer of - data to and from the HP-IB card. The actions to be taken depend on the - current state of the ICD interface. The possibilities are: - - 1. A command is pending on the interface. This occurs only when a command - is received while a Seek or Recalibrate command is in progress. - - 2. A command is executing. - - 3. Data is being sent or received over the HP-IB during command execution. - - 4. Dummy bytes are being sent or received over the HP-IB due to a command - error. - - Entry to the the service routine in any other interface state or to process a - command not allowed in a valid state will return an Internal Error to cause a - simulator stop. Exit from the routine will be either in one of the above - states, or in the idle state if the operation is complete. - - The specific actions taken for the various interface states are as follows: - - command_wait - ============ - - We are entered in this state only if a unit that was busy (still seeking) - was addressed to listen or talk. The card has been held off by asserting - NRFD after receiving MLA or MTA. Upon entry, we complete the seek and then - release the interface by denying NRFD to allow the remainder of the command - sequence to be received from the card. - - command_exec - ============ - - We are entered in this state to initiate, continue, or complete a command. - The command may be a disc command, such as Seek or Read, or an interface - command, such as Amigo Identify or Device-Specified Jump. - - Disc commands call the disc library service routine to perform all of the - common controller actions. Any ICD-specific actions needed, such as - setting the DSJ value, are performed after the call. - - Certain disc commands require multiple execution phases. For example, the - Read command has a start phase that reads data from the disc image file - into the sector buffer, a data phase that transfers bytes from the buffer - to the card, and an end phase that schedules the intersector gap time and - resets to the start phase. Data phase transfers are performed in the - read_xfer or write_xfer interface states. - - The results of the disc library service are inferred by the controller - state. If the controller is busy, then the command continues in a new - phase. Otherwise, the command either has completed normally or has - terminated with an error. If an error has occurred during a disc command - that transfers data, DSJ is set to 1, and the interface state is changed to - source or sink dummy bytes to complete the command sequence. - - Interface commands may either complete immediately (e.g., Amigo Clear) or - transfer data (e.g., DSJ). - - read_xfer - ========= - - Commands that send data to the CPU enter the service routine to source a - byte to the bus. Bytes are transferred only when ATN and NRFD are denied; - if they are not, we simply exit, as we will be rescheduled when the lines - are dropped. Otherwise, we get a byte from the sector buffer and send it - to the card. If the card has stopped listening, or the buffer is now - empty, then we terminate the transfer and move to the end phase of the - command. Otherwise, we reschedule the next data phase byte transfer. - - Disc and interface commands are handled separately, as EOI is always - asserted on the last byte of an interface command transfer and never on a - (good) disc command transfer. - - write_xfer - ========== - - Commands that receive data from the CPU enter the service routine to - determine whether or not to continue the transfer. Our bus accept routine - has already stored the received byte in the sector buffer and has asserted - NRFD to hold off the card. If the buffer is now full, or the byte was - tagged with EOI, then we terminate the transfer and move to the end phase - of the command. Otherwise, we deny NRFD and exit; we will be rescheduled - when the next byte arrives. - - error_source - ============ - - If an error occurred during the data transfer phase of a read or status - command, a dummy byte tagged with EOI is sourced to the bus. This allows - the OS driver for the card to terminate the command and request the - controller's status. - - error_sink - ========== - - If an error occurred during the data transfer phase of a write command, - dummy bytes are sunk from the bus until EOI is seen or the card is - unaddressed. This allows the OS driver to complete the command as expected - and then determine the cause of the failure by requesting the controller's - status. - - - Implementation notes: - - 1. The disc library sets the controller state to idle for a normal End, - Seek, or Recalibrate command and to wait for all other commands that end - normally. So we determine command completion by checking if the - controller is not busy, rather than checking if the controller is idle. - - Drive Attention status is the normal result of the completion of a Seek - or Recalibrate command. Normal Completion status is the normal result of - all other commands. - - 2. The disc library returns the buffer length in words. We double the - return value to count bytes. - - 3. Some commands, such as DSJ, could be completed in the bus accept routine. - They are serviced here instead to avoid presenting a zero execution time - to the CPU. - - 4. The Amigo command set does not provide the disc with the number of bytes - that will be read, and the unit expects to be untalked when the read is - to terminate. The RTE ICD bootstrap extension does not do this. - Instead, it resets the card via CLC 0,C to terminate the Cold Load Read - that was started by the ICD boot loader ROM. - - In hardware, if the LSTN control bit is cleared, e.g., by CRS, - transmission stops because the card denies NDAC and NRFD (the HP-IB - handshake requires NDAC and NRFD to be asserted to start the handshake - sequence; TACS * SDYS * ~NDAC * ~NRFD is an error condition). In - simulation, we handle this by terminating a read transfer if the card - stops accepting. If we did not, then the disc would continue to source - bytes to the bus, overflowing the card FIFO (a FIFO full condition cannot - assert NRFD if the LSTN control bit is clear). -*/ - -t_stat da_service (UNIT *uptr) -{ -uint8 data; -CNTLR_CLASS command_class; -const int32 unit = uptr - da_unit; /* get the disc unit number */ -const CVPTR cvptr = &icd_cntlr [unit]; /* get a pointer to the controller */ -t_stat result = SCPE_OK; -t_bool release_interface = FALSE; - -switch (if_state [unit]) { /* dispatch the interface state */ - - case command_wait: /* command is waiting */ - release_interface = TRUE; /* release the interface at then end if it's idle */ - - /* fall into the command_exec handler to process the current command */ - - case command_exec: /* command is executing */ - switch (if_command [unit]) { /* dispatch the interface command */ - - case disc_command: /* execute a disc command */ - result = dl_service_drive (cvptr, uptr); /* service the disc unit */ - - if (cvptr->opcode == Clear) /* is this a Clear command? */ - if_dsj [unit] = 2; /* indicate that the self test is complete */ - - if (cvptr->state != cntlr_busy) { /* has the controller stopped? */ - if_state [unit] = idle; /* idle the interface */ - - if (cvptr->status == normal_completion || /* do we have normal completion */ - cvptr->status == drive_attention) /* or drive attention? */ - break; /* we're done */ - - else { /* if the status is abnormal */ - if_dsj [unit] = 1; /* an error has occurred */ - - command_class = dl_classify (*cvptr); /* classify the command */ - - if (command_class == class_write) { /* did a write command fail? */ - if_state [unit] = error_sink; /* sink the remaining bytes */ - uptr->wait = cvptr->cmd_time; /* activate to complete processing */ - } - - else if (command_class != class_control) { /* did a read or status command fail? */ - if_state [unit] = error_source; /* source an error byte */ - uptr->wait = cvptr->cmd_time; /* activate to complete processing */ - } - } - } - - else if (uptr->PHASE == data_phase) { /* are we starting the data phase? */ - cvptr->length = cvptr->length * 2; /* convert the buffer length to bytes */ - - if (dl_classify (*cvptr) == class_write) /* is this a write command? */ - if_state [unit] = write_xfer; /* set for a write data transfer */ - else /* it is a read or status command */ - if_state [unit] = read_xfer; /* set for a read data transfer */ - } - - break; - - - case amigo_identify: /* Amigo Identify */ - buffer [0] = 0x0003; /* store the response in the buffer */ - cvptr->length = 2; /* return two bytes */ - - if_state [unit] = read_xfer; /* we are ready to transfer the data */ - uptr->wait = cvptr->cmd_time; /* schedule the transfer */ - - if (DEBUG_PRI (da_dev, DEB_RWSC)) - fprintf (sim_deb, ">>DA rwsc: Unit %d Amigo identify response %04XH\n", - unit, buffer [0]); - break; - - - case initiate_self_test: /* Initiate a self test */ - sim_cancel (&da_unit [unit]); /* cancel any operation in progress */ - dl_clear_controller (cvptr, /* hard-clear the controller */ - &da_unit [unit], - hard_clear); - if_dsj [unit] = 2; /* set DSJ for self test completion */ - if_state [unit] = idle; /* the command is complete */ - di_poll_response (da, unit, SET); /* with PPR enabled */ - break; - - - case amigo_clear: /* Amigo clear */ - dl_idle_controller (cvptr); /* idle the controller */ - if_dsj [unit] = 0; /* clear the DSJ value */ - if_state [unit] = idle; /* the command is complete */ - di_poll_response (da, unit, SET); /* with PPR enabled */ - break; - - - default: /* no other commands are executed */ - result = SCPE_IERR; /* signal an internal error */ - break; - } /* end of command dispatch */ - break; - - - case error_source: /* send data after an error */ - if (! (di [da].bus_cntl & (BUS_ATN | BUS_NRFD))) { /* is the card ready for data? */ - di [da].bus_cntl |= BUS_EOI; /* set EOI */ - di_bus_source (da, 0); /* and send a dummy byte to the card */ - if_state [unit] = idle; /* the command is complete */ - } - break; - - - case read_xfer: /* send read data */ - if (! (di [da].bus_cntl & (BUS_ATN | BUS_NRFD))) /* is the card ready for data? */ - switch (if_command [unit]) { /* dispatch the interface command */ - - case disc_command: /* disc read or status commands */ - data = get_buffer_byte (cvptr); /* get the next byte from the buffer */ - - if (di_bus_source (da, data) == FALSE) /* send the byte to the card; is it listening? */ - cvptr->eod = SET; /* no, so terminate the read */ - - if (cvptr->length == 0 || cvptr->eod == SET) { /* is the data phase complete? */ - uptr->PHASE = end_phase; /* set the end phase */ - - if (cvptr->opcode == Request_Status) /* is it a Request Status command? */ - if_dsj [unit] = 0; /* clear the DSJ value */ - - if_state [unit] = command_exec; /* set to execute the command */ - uptr->wait = cvptr->cmd_time; /* and reschedule the service */ - } - - else /* the data phase continues */ - uptr->wait = cvptr->data_time; /* reschedule the next transfer */ - - break; - - - case amigo_identify: - case read_loopback: - case return_self_test_result: - data = get_buffer_byte (cvptr); /* get the next byte from the buffer */ - - if (cvptr->length == 0) /* is the transfer complete? */ - di [da].bus_cntl |= BUS_EOI; /* set EOI */ - - if (di_bus_source (da, data) /* send the byte to the card; is it listening? */ - && cvptr->length > 0) /* and is there more to transfer? */ - uptr->wait = cvptr->data_time; /* reschedule the next transfer */ - - else { /* the transfer is complete */ - if_state [unit] = idle; /* the command is complete */ - di_poll_response (da, unit, SET); /* enable the PPR */ - } - break; - - - case device_specified_jump: - di [da].bus_cntl |= BUS_EOI; /* set EOI */ - di_bus_source (da, if_dsj [unit]); /* send the DSJ value to the card */ - if_state [unit] = idle; /* the command is complete */ - break; - - - case crc_talk: - di [da].bus_cntl |= BUS_EOI; /* set EOI */ - di_bus_source (da, 0); /* send dummy bytes */ - break; /* until the card untalks */ - - - default: /* no other commands send data */ - result = SCPE_IERR; /* signal an internal error */ - break; - } /* end of read data transfer dispatch */ - break; - - - case error_sink: /* absorb data after an error */ - cvptr->index = 0; /* absorb data until EOI asserts */ - - if (cvptr->eod == SET) /* is the transfer complete? */ - if_state [unit] = idle; /* the command is complete */ - - di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */ - break; - - - case write_xfer: /* receive write data */ - switch (if_command [unit]) { /* dispatch the interface command */ - - case disc_command: /* disc write commands */ - if (cvptr->length == 0 || cvptr->eod == SET) { /* is the data phase complete? */ - uptr->PHASE = end_phase; /* set the end phase */ - - if_state [unit] = command_exec; /* set to execute the command */ - uptr->wait = cvptr->cmd_time; /* and schedule the service */ - - if (cvptr->eod == CLEAR) /* is the transfer continuing? */ - break; /* do not deny NRFD until next service! */ - } - - di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */ - break; - - - case write_loopback: - if (cvptr->eod == SET) { /* is the transfer complete? */ - cvptr->length = 16 - cvptr->length; /* set the count of bytes transferred */ - if_state [unit] = idle; /* the command is complete */ - } - - di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */ - break; - - - default: /* no other commands receive data */ - result = SCPE_IERR; /* signal an internal error */ - break; - } /* end of write data transfer dispatch */ - break; - - - default: /* no other states schedule service */ - result = SCPE_IERR; /* signal an internal error */ - break; - } /* end of interface state dispatch */ - - -if (uptr->wait) /* is service requested? */ - activate_unit (uptr); /* schedule the next event */ - -if (result == SCPE_IERR && DEBUG_PRI (da_dev, DEB_RWSC)) { /* did an internal error occur? */ - fprintf (sim_deb, ">>DA rwsc: Unit %d ", unit); /* report it if debugging */ - - if (if_state [unit] == command_exec - && if_command [unit] == disc_command) - fprintf (sim_deb, "%s command %s phase ", - dl_opcode_name (ICD, (CNTLR_OPCODE) uptr->OP), - dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); - else - fprintf (sim_deb, "%s state %s ", - if_command_name [if_command [unit]], - if_state_name [if_state [unit]]); - - fputs ("service not handled\n", sim_deb); - } - -if (if_state [unit] == idle) { /* is the command now complete? */ - if (if_command [unit] == disc_command) { /* did a disc command complete? */ - if (cvptr->opcode != End) /* yes; if the command was not End, */ - di_poll_response (da, unit, SET); /* then enable PPR */ - - if (DEBUG_PRI (da_dev, DEB_RWSC)) - fprintf (sim_deb, ">>DA rwsc: Unit %d %s disc command completed\n", - unit, dl_opcode_name (ICD, cvptr->opcode)); - } - - else /* an interface command completed */ - if (DEBUG_PRI (da_dev, DEB_RWSC)) - fprintf (sim_deb, ">>DA rwsc: Unit %d %s command completed\n", - unit, if_command_name [if_command [unit]]); - - if (release_interface) /* if the next command is already pending */ - di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */ - } - -return result; /* return the result of the service */ -} - - -/* Reset or preset the simulator. - - In hardware, a self-test is performed by the controller at power-on. When - the self-test completes, the controller sets DSJ = 2 and enables the parallel - poll response. - - A front panel PRESET or programmed CRS has no direct effect on the controller - or drive. However, the card reacts to CRS by clearing its talker and - listener states, so an in-progress read or status command will abort when the - next byte sourced to the bus finds no acceptors. -*/ - -t_stat da_reset (DEVICE *dptr) -{ -uint32 unit; -t_stat status; - -status = di_reset (dptr); /* reset the card */ - -if (status == SCPE_OK && (sim_switches & SWMASK ('P'))) /* is the card OK and is this a power-on reset? */ - for (unit = 0; unit < dptr->numunits; unit++) { /* loop through the units */ - sim_cancel (dptr->units + unit); /* cancel any current activation */ - dptr->units [unit].CYL = 0; /* reset the head position */ - dptr->units [unit].pos = 0; /* to cylinder 0 */ - - dl_clear_controller (&icd_cntlr [unit], /* hard-clear the controller */ - dptr->units + unit, - hard_clear); - - if_state [unit] = idle; /* reset the interface state */ - if_command [unit] = invalid; /* reset the interface command */ - - if_dsj [unit] = 2; /* set the DSJ for power up complete */ - } - -return status; -} - - -/* Attach a unit to a disc image file. - - The simulator considers an attached unit to be connected to the bus and an - unattached unit to be disconnected, so we set the card's acceptor bit for the - selected unit if the attach is successful. An attached unit is ready if the - heads are loaded or not ready if not. - - This model is slightly different than the MAC (DS) simulation, where an - unattached unit is considered "connected but not ready" -- the same - indication returned by an attached unit whose heads are unloaded. Therefore, - the situation when the simulator is started is that all DS units are - "connected to the controller but not ready," whereas all DA units are "not - connected to the bus." This eliminates the overhead of sending HP-IB - messages to unused units. - - In tabular form, the simulator responses are: - - Enabled Loaded Attached DS (MAC) DA (ICD) - ------- ------ -------- ------------ ------------ - N N N disconnected disconnected - N N Y -- -- - N Y N -- -- - N Y Y -- -- - Y N N unloaded disconnected - Y N Y unloaded unloaded - Y Y N -- -- - Y Y Y ready ready - - The unspecified responses are illegal conditions; for example, the simulator - does not allow an attached unit to be disabled. - - - Implementation notes: - - 1. To conform exactly to the MAC responses would have required intercepting - the SET DISABLED/ENABLED commands in order to clear or set the unit - accepting bits. However, short of intercepting the all SET commands with - a custom command table, there is no way to ensure that unit enables are - observed. Adding ENABLED and DISABLED to the modifiers table and - specifying a validation routine works for the DISABLED case but not the - ENABLED case -- set_unit_enbdis returns SCPE_UDIS before calling the - validation routine. -*/ - -t_stat da_attach (UNIT *uptr, char *cptr) -{ -t_stat result; -const int32 unit = uptr - da_unit; /* calculate the unit number */ - -result = dl_attach (&icd_cntlr [unit], uptr, cptr); /* attach the drive */ - -if (result == SCPE_OK) /* was the attach successful? */ - di [da].acceptors |= (1 << unit); /* set the unit's accepting bit */ - -return result; -} - - -/* Detach a disc image file from a unit. - - As explained above, detaching a unit is the hardware equivalent of - disconnecting the drive from the bus, so we clear the unit's acceptor bit if - the detach is successful. -*/ - -t_stat da_detach (UNIT *uptr) -{ -t_stat result; -const int32 unit = uptr - da_unit; /* calculate the unit number */ - -result = dl_detach (&icd_cntlr [unit], uptr); /* detach the drive */ - -if (result == SCPE_OK) { /* was the detach successful? */ - di [da].acceptors &= ~(1 << unit); /* clear the unit's accepting bit */ - di_poll_response (da, unit, CLEAR); /* and its PPR, as it's no longer present */ - } - -return result; -} - - -/* Boot an Amigo disc drive. - - The ICD disc bootstrap program is loaded from the HP 12992H Boot Loader ROM - into memory, the I/O instructions are configured for the interface card's - select code, and the program is run to boot from the specified unit. The - loader supports booting the disc at bus address 0 only. Before execution, - the S register is automatically set as follows: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - ------ ------ ---------------------- ------------- ----- - ROM # 0 1 select code reserved head - - The boot routine sets bits 15-6 of the S register to appropriate values. - Bits 5-3 and 1-0 retain their original values, so S should be set before - booting. These bits are typically set to 0, although bit 5 is set for an RTE - reconfiguration boot, and bits 1-0 may be set if booting from a head other - than 0 is desired. -*/ - -static const BOOT_ROM da_rom = { - 0102501, /* START LIA 1 GET SWITCH REGISTER SETTING */ - 0100044, /* LSL 4 SHIFT A LEFT 4 */ - 0006111, /* CLE,SLB,RSS SR BIT 12 SET FOR MANUAL BOOT? */ - 0100041, /* LSL 1 NO. SHIFT HEAD # FOR RPL BOOT */ - 0001424, /* ALR,ALR SHIFT HEAD 2, CLEAR SIGN */ - 0033744, /* IOR HDSEC SET EOI BIT */ - 0073744, /* STA HDSEC PLACE IN COMMAND BUFFER */ - 0017756, /* JSB BTCTL SEND DUMMY,U-CLR,PP */ - 0102510, /* LIA IBI READ INPUT REGISTER */ - 0101027, /* ASR 7 SHIFT DRIVE 0 RESPONSE TO LSB */ - 0002011, /* SLA,RSS DID DRIVE 0 RESPOND? */ - 0027710, /* JMP *-3 NO, GO LOOK AGAIN */ - 0107700, /* CLC 0,C */ - 0017756, /* JSB BTCTL SEND TALK, CL-RD,BUS HOLDER */ - 0002300, /* CCE */ - 0017756, /* JSB BTCTL TELL CARD TO LISTEN */ - 0063776, /* LDA DMACW LOAD DMA CONTROL WORD */ - 0102606, /* OTA 6 OUTPUT TO DCPC */ - 0106702, /* CLC 2 READY DCPC */ - 0063735, /* LDA ADDR1 LOAD DMA BUFFER ADDRESS */ - 0102602, /* OTA 2 OUTPUT TO DCPC */ - 0063740, /* LDA DMAWC LOAD DMA WORD COUNT */ - 0102702, /* STC 2 READY DCPC */ - 0102602, /* OTA 2 OUTPUT TO DCPC */ - 0103706, /* STC 6,C START DCPC */ - 0102206, /* TEST SFC 6 SKIP IF DMA NOT DONE */ - 0117750, /* JSB ADDR2,I SUCCESSFUL END OF TRANSFER */ - 0102310, /* SFS IBI SKIP IF DISC ABORTED TRANSFER */ - 0027731, /* JMP TEST RECHECK FOR TRANSFER END */ - 0102011, /* ADDR1 HLT 11B ERROR HALT */ - 0000677, /* UNCLR OCT 677 UNLISTEN */ - 0000737, /* OCT 737 UNTALK */ - 0176624, /* DMAWC OCT 176624 UNIVERSAL CLEAR,LBO */ - 0000440, /* LIST OCT 440 LISTEN BUS ADDRESS 0 */ - 0000550, /* CMSEC OCT 550 SECONDARY GET COMMAND */ - 0000000, /* BOOT OCT 0 COLD LOAD READ COMMAND */ - 0001000, /* HDSEC OCT 1000 HEAD,SECTOR PLUS EOI */ - 0000677, /* UNLST OCT 677 ATN,PRIMARY UNLISTEN,PARITY */ - 0000500, /* TALK OCT 500 SEND READ DATA */ - 0100740, /* RDSEC OCT 100740 SECONDARY READ DATA */ - 0102055, /* ADDR2 OCT 102055 BOOT EXTENSION STARTING ADDRESS */ - 0004003, /* CTLP OCT 4003 INT=LBO,T,CIC */ - 0000047, /* OCT 47 PPE,L,T,CIC */ - 0004003, /* OCT 4003 INT=LBO,T,CIC */ - 0000413, /* OCT 413 ATN,P,L,CIC */ - 0001015, /* OCT 1015 INT=EOI,P,L,CIC */ - 0000000, /* BTCTL NOP */ - 0107710, /* CLC IBI,C RESET IBI */ - 0063751, /* BM LDA CTLP LOAD CONTROL WORD */ - 0102610, /* OTA IBI OUTPUT TO CONTROL REGISTER */ - 0102710, /* STC IBI RETURN IBI TO DATA MODE */ - 0037760, /* ISZ BM INCREMENT CONTROL WORD POINTER */ - 0002240, /* SEZ,CME */ - 0127756, /* JMP BTCTL,I RETURN */ - 0063736, /* LABL LDA UNCLR LOAD DATA WORD */ - 0037766, /* ISZ LABL INCREMENT WORD POINTER */ - 0102610, /* OTA IBI OUTPUT TO HPIB */ - 0002021, /* SSA,RSS SKIP IF LAST WORD */ - 0027766, /* JMP LABL GO BACK FOR NEXT WORD */ - 0102310, /* SFS IBI SKIP IF LAST WORD SENT TO BUS */ - 0027773, /* JMP *-1 RECHECK ACCEPTANCE */ - 0027757, /* JMP BTCTL+1 */ - 0000010, /* DMACW ABS IBI */ - 0170100 /* ABS -START */ - }; - -t_stat da_boot (int32 unitno, DEVICE *dptr) -{ -if (GET_BUSADR (da_unit [unitno].flags) != 0) /* booting is supported on bus address 0 only */ - return SCPE_NOFNC; /* report "Command not allowed" if attempted */ - -if (ibl_copy (da_rom, da_dib.select_code, /* copy the boot ROM to memory and configure */ - IBL_OPT | IBL_DS_HEAD, /* the S register accordingly */ - IBL_DS | IBL_MAN | IBL_SET_SC (da_dib.select_code))) - return SCPE_IERR; /* return an internal error if the copy failed */ -else - return SCPE_OK; -} - - - -/* Amigo disc global SCP routines */ - - -/* Load or unload a unit's heads. - - The heads are automatically loaded when a unit is attached and unloaded when - a unit is detached. While a unit is attached, the heads may be manually - unloaded; this yields a "not ready" status if the unit is accessed. An - unloaded drive may be manually loaded, returning the unit to "ready" status. - - The ICD controller sets Drive Attention status when the heads unload and also - asserts a parallel poll response if the heads unload while in idle state 2 - (i.e., after an End command). - - - Implementation notes: - - 1. The 13365 manual says on page 28 that Drive Attention status is - "Generated whenever...the drive unloads and the controller is in Idle - State 2 or 3." However, the ICD diagnostic tests for Drive Attention - status on head unload immediately after the Request Status command that - completes the previous step, which leaves the controller in idle state 1. - - Moreover, the diagnostic does NOT check for Drive Attention status if the - Amigo ID is 2 (MAC controller). But the 12745 manual says on page 3-7 - that the status is returned if "...Drive becomes not ready (heads - unload)" with no mention of controller state. - - It appears as though the diagnostic test is exactly backward. However, - we match the diagnostic expectation below. -*/ - -t_stat da_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc) -{ -const int32 unit = uptr - da_unit; /* calculate the unit number */ -const t_bool load = (value != UNIT_UNLOAD); /* true if the heads are loading */ -t_stat result; - -result = dl_load_unload (&icd_cntlr [unit], uptr, load); /* load or unload the heads */ - -if (result == SCPE_OK && ! load) { /* was the unload successful? */ - icd_cntlr [unit].status = drive_attention; /* set Drive Attention status */ - - if (uptr->OP == End) /* is the controller in idle state 2? */ - di_poll_response (da, unit, SET); /* enable PPR */ - } - -return result; -} - - - -/* Amigo disc global bus routines */ - - -/* Accept a data byte from the bus. - - The indicated unit is offered a byte that has been sourced to the bus. The - routine returns TRUE or FALSE to indicate whether or not it accepted the - byte. - - Commands from the bus may be universal (applying to all acceptors) or - addressed (applying only to those acceptors that have been addressed to - listen). Data bytes are accepted only if the unit has been addressed to - listen. As we are called for a data transfer or an addressed command only if - we are currently listening, the only bytes that we do not accept are primary - talk or listen commands directed to another address, or secondary commands - when we are not addressed to listen. - - This routine handles the HP-IB protocol. The type of byte passed is - determined by the state of the ATN signal and, if ATN is asserted, by the - high-order bits of the value. Most of the work involves decoding secondary - commands and their associated data parameters. The interface state is - changed as needed to track the command protocol. The states processed in - this routine are: - - opcode_wait - =========== - - A Receive Disc Command secondary has been received, and the interface is - waiting for the opcode that should follow. - - parameter_wait - ============== - - A disc opcode or interface command has been received, and the interface is - waiting for a parameter byte that should follow. - - write_wait - ========== - - A disc write command has been received, and the interface is waiting for - the Receive Write Data secondary that should follow. - - read_wait - ========= - - A disc read command has been received, and the interface is waiting for the - Send Read Data secondary that should follow. - - status_wait - =========== - - A disc status command has been received, and the interface is waiting for - the Send Disc Status secondary that should follow. - - write_xfer - ========== - - A disc write is in progress, and the interface is waiting for a data byte - that should follow. - - error_sink - ========== - - A disc write has terminated with an error, and the interface is waiting to - absorb all of the remaining data bytes of the transfer. - - - Disc commands and parameters are assembled in the sector buffer before being - passed to the disc library to start the command. Once the command is - started, the interface state is set either to execute the command or to wait - for the receipt of a data transfer secondary before executing, depending on - the command. - - Two disc command protocol errors are detected. First, an Illegal Opcode is - identified during the check for the expected number of disc command - parameters. This allows us to sink an arbitrary number of parameter bytes. - Second, an I/O Program Error occurs if an unsupported secondary is received - or the HP-IB sequence is incorrect. The latter occurs if a command has the - wrong number of parameters or a secondary data transfer sequence is invalid. - - Disc commands that require data transfers (e.g., Read, Write, Request Status) - involve a pair of secondaries. The first transmits the command, and the - second transmits or receives the data. If one occurs without the other, an - I/O Program Error occurs. - - A secondary or command that generates an I/O Program Error is always ignored. - Error recovery is as follows: - - - An unsupported talk secondary sends a single data byte tagged with EOI. - - - An unsupported listen secondary accepts and discards any accompanying data - bytes until EOI is asserted or an Unlisten is received. - - - A supported command with too few parameter bytes or for which the last - parameter byte is not tagged with EOI (before unlisten) does nothing. - - - A supported command with too many parameter bytes accepts and discards - excess parameter bytes until EOI is asserted or an Unlisten is received. - - - A read or status command that is not followed by a Send Read Data or a - Send Disc Status secondary does nothing. The unexpected secondary is - executed normally. - - - A write command that is not followed by a Receive Write Data secondary - does nothing. The unexpected secondary is executed normally. - - - A Send Read Data or a Send Disc Status secondary that is not preceded by a - read or status command sends a single data byte tagged with EOI. - - - A Receive Write Data secondary that is not preceded by a write command - accepts and discards data bytes until EOI is asserted or an Unlisten is - received. - - The Amigo command sequence does not provide a byte count for disc read and - write commands, so the controller continues to source or accept data bytes - until the device is unaddressed. Normally, this is done by an Unlisten or - Untalk. However, per IEEE-488, a listening device may be unaddressed by IFC, - by an Unlisten, or by addressing the device to talk, and a talking device may - be unaddressed by IFC, by addressing another device to talk (or no device via - Untalk), or by addressing the device to listen. Therefore, we must keep - track of whether the unit stopped talking or listening, and if it has, we - check for command termination. - - If the controller is unaddressed in the middle of a sector transfer, the read - or write must be terminated cleanly to ensure that the disc image is - coherent. It is also permissible to untalk the controller before all of the - requested status bytes are returned. - - In addition, the controller has no way to inform the host that an error has - occurred that prevents the command from continuing. For example, if a data - error is encountered while reading or a protected track is encountered while - writing, the controller must still source or sink data bytes until the - command is terminated by the host. The controller handles read errors by - sourcing a single data byte tagged with EOI and write errors by sinking data - bytes until EOI is seen or the unit is unaddressed. - - Therefore, if the unit is unaddressed while a read, write, or status command - is transferring data, the unit service must be scheduled to end the current - command. Unaddressing while an error condition is present merely terminates - the source or sink operation. - - - Implementation notes: - - 1. The 13365 manual does not indicate that the controller responds to - Universal Clear, but the 12992H loader ROM issues this primary and - expects the controller to initialize. - - 2. It is not necessary to check for listening when processing addressed - commands, as only listeners are called by the bus source. -*/ - -t_bool da_bus_accept (uint32 unit, uint8 data) -{ -const uint8 message_address = data & BUS_ADDRESS; -t_bool accepted = TRUE; -t_bool initiated = FALSE; -t_bool addressed = FALSE; -t_bool stopped_listening = FALSE; -t_bool stopped_talking = FALSE; -char action [40] = ""; -uint32 my_address; - -if (di [da].bus_cntl & BUS_ATN) { /* is it a bus command (ATN asserted)? */ - switch (data & BUS_GROUP) { /* dispatch the bus group */ - - case BUS_PCG: /* primary command group */ - switch (message_address) { - - case 0x04: /* selected device clear */ - case 0x05: /* SDC with parity freeze */ - case 0x14: /* universal clear */ - if (DEBUG_PRI (da_dev, DEB_RWSC)) - fprintf (sim_deb, ">>DA rwsc: Unit %d device cleared\n", unit); - - sim_cancel (&da_unit [unit]); /* cancel any in-progress command */ - dl_idle_controller (&icd_cntlr [unit]); /* idle the controller */ - if_dsj [unit] = 0; /* clear DSJ */ - if_state [unit] = idle; /* idle the interface */ - di_poll_response (da, unit, SET); /* enable PPR */ - - if (DEBUG_PRI (da_dev, DEB_XFER)) - strcpy (action, "device clear"); - break; - - - default: /* unsupported universal command */ - break; /* universals are always accepted */ - } - - break; - - - case BUS_LAG: /* listen address group */ - my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */ - - if (message_address == my_address) { /* is it my listen address? */ - di [da].listeners |= (1 << unit); /* set my listener bit */ - di [da].talker &= ~(1 << unit); /* clear my talker bit */ - - addressed = TRUE; /* unit is now addressed */ - stopped_talking = TRUE; /* MLA stops the unit from talking */ - - if (DEBUG_PRI (da_dev, DEB_XFER)) - sprintf (action, "listen %d", message_address); - } - - else if (message_address == BUS_UNADDRESS) { /* is it an Unlisten? */ - di [da].listeners = 0; /* clear all of the listeners */ - - stopped_listening = TRUE; /* UNL stops the unit from listening */ - - if (DEBUG_PRI (da_dev, DEB_XFER)) - strcpy (action, "unlisten"); - } - - else /* other listen addresses */ - accepted = FALSE; /* are not accepted */ - - break; - - - case BUS_TAG: /* talk address group */ - my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */ - - if (message_address == my_address) { /* is it my talk address? */ - di [da].talker = (1 << unit); /* set my talker bit and clear the others */ - di [da].listeners &= ~(1 << unit); /* clear my listener bit */ - - addressed = TRUE; /* the unit is now addressed */ - stopped_listening = TRUE; /* MTA stops the unit from listening */ - - if (DEBUG_PRI (da_dev, DEB_XFER)) - sprintf (action, "talk %d", message_address); - } - - else { /* it is some other talker (or Untalk) */ - di [da].talker &= ~(1 << unit); /* clear my talker bit */ - - stopped_talking = TRUE; /* UNT or OTA stops the unit from talking */ - - if (message_address != BUS_UNADDRESS) /* other talk addresses */ - accepted = FALSE; /* are not accepted */ - - else /* it's an Untalk */ - if (DEBUG_PRI (da_dev, DEB_XFER)) - strcpy (action, "untalk"); - } - - break; - - - case BUS_SCG: /* secondary command group */ - icd_cntlr [unit].index = 0; /* reset the buffer index */ - - if (di [da].listeners & (1 << unit)) { /* is it a listen secondary? */ - if (if_state [unit] == write_wait /* if we're waiting for a write data secondary */ - && message_address != 0x00) /* but it's not there, */ - abort_command (unit, io_program_error, /* then abort the pending command */ - idle); /* and process the new command */ - - switch (message_address) { /* dispatch the listen secondary */ - - case 0x00: /* Receive Write Data */ - if (if_state [unit] != write_wait) /* if we're not expecting it */ - abort_command (unit, io_program_error, /* abort and sink any data */ - error_sink); - else { /* the sequence is correct */ - if_state [unit] = command_exec; /* the command is ready to execute */ - da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* schedule the unit */ - di_bus_control (da, unit, BUS_NRFD, 0); /* assert NRFD to hold off the card */ - } - - initiated = TRUE; /* log the command or abort initiation */ - break; - - case 0x08: /* disc commands */ - if_command [unit] = disc_command; /* set the command and wait */ - if_state [unit] = opcode_wait; /* for the opcode that must follow */ - break; - - case 0x09: /* CRC (Listen) */ - if_command [unit] = crc_listen; /* set up the command */ - if_state [unit] = error_sink; /* sink any data that will be coming */ - initiated = TRUE; /* log the command initiation */ - break; - - case 0x10: /* Amigo Clear */ - if_command [unit] = amigo_clear; /* set up the command */ - if_state [unit] = parameter_wait; /* a parameter must follow */ - icd_cntlr [unit].length = 1; /* set to expect one (unused) byte */ - break; - - case 0x1E: /* Write Loopback */ - if_command [unit] = write_loopback; /* set up the command */ - if_state [unit] = write_xfer; /* data will be coming */ - icd_cntlr [unit].length = 16; /* accept only the first 16 bytes */ - initiated = TRUE; /* log the command initiation */ - break; - - case 0x1F: /* Initiate Self-Test */ - if_command [unit] = initiate_self_test; /* set up the command */ - if_state [unit] = parameter_wait; /* a parameter must follow */ - icd_cntlr [unit].length = 1; /* set to expect the test ID byte */ - break; - - default: /* an unsupported listen secondary was received */ - abort_command (unit, io_program_error, /* abort and sink any data */ - error_sink); /* that might accompany the command */ - initiated = TRUE; /* log the abort initiation */ - break; - } - } - - - else if (di [da].talker & (1 << unit)) { /* is it a talk secondary? */ - da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* these are always scheduled and */ - initiated = TRUE; /* logged as initiated */ - - if (if_state [unit] == read_wait /* if we're waiting for a send data secondary */ - && message_address != 0x00 /* but it's not there */ - || if_state [unit] == status_wait /* or a send status secondary, */ - && message_address != 0x08) /* but it's not there */ - abort_command (unit, io_program_error, /* then abort the pending command */ - idle); /* and process the new command */ - - switch (message_address) { /* dispatch the talk secondary */ - - case 0x00: /* Send Read Data */ - if (if_state [unit] != read_wait) /* if we're not expecting it */ - abort_command (unit, io_program_error, /* abort and source a data byte */ - error_source); /* tagged with EOI */ - else - if_state [unit] = command_exec; /* the command is ready to execute */ - break; - - case 0x08: /* Read Status */ - if (if_state [unit] != status_wait) /* if we're not expecting it, */ - abort_command (unit, io_program_error, /* abort and source a data byte */ - error_source); /* tagged with EOI */ - else /* all status commands */ - if_state [unit] = read_xfer; /* are ready to transfer data */ - break; - - case 0x09: /* CRC (Talk) */ - if_command [unit] = crc_talk; /* set up the command */ - if_state [unit] = read_xfer; /* data will be going */ - break; - - case 0x10: /* Device-Specified Jump */ - if_command [unit] = device_specified_jump; /* set up the command */ - if_state [unit] = read_xfer; /* data will be going */ - break; - - case 0x1E: /* Read Loopback */ - if_command [unit] = read_loopback; /* set up the command */ - if_state [unit] = read_xfer; /* data will be going */ - break; - - case 0x1F: /* Return Self-Test Result */ - if_command [unit] = return_self_test_result; /* set up the command */ - if_state [unit] = read_xfer; /* data will be going */ - icd_cntlr [unit].length = 1; /* return one byte that indicates */ - buffer [0] = 0; /* that the self-test passed */ - break; - - default: /* an unsupported talk secondary was received */ - abort_command (unit, io_program_error, /* abort and source a data byte */ - error_source); /* tagged with EOI */ - break; - } - } - - - else { /* the unit is not addressed */ - my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */ - - if (di [da].talker == 0 && di [da].listeners == 0 /* if there are no talkers or listeners */ - && message_address == my_address) { /* and this is my secondary address, */ - if_command [unit] = amigo_identify; /* then this is an Amigo ID sequence */ - if_state [unit] = command_exec; /* set up for execution */ - da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* schedule the unit */ - initiated = TRUE; /* log the command initiation */ - } - - else /* unaddressed secondaries */ - accepted = FALSE; /* are not accepted */ - } - - - if (accepted) { /* was the command accepted? */ - if (DEBUG_PRI (da_dev, DEB_XFER)) - sprintf (action, "secondary %02XH", message_address); - - if (if_command [unit] != amigo_identify) /* disable PPR for all commands */ - di_poll_response (da, unit, CLEAR); /* except Amigo ID */ - } - - break; /* end of secondary processing */ - } - - - if (addressed && sim_is_active (&da_unit [unit])) { /* is the unit being addressed while it is busy? */ - if_state [unit] = command_wait; /* change the interface state to wait */ - di_bus_control (da, unit, BUS_NRFD, 0); /* and assert NRFD to hold off the card */ - - if (DEBUG_PRI (da_dev, DEB_RWSC)) - fprintf (sim_deb, ">>DA rwsc: Unit %d addressed while controller is busy\n", unit); - } - - if (stopped_listening) { /* was the unit Unlistened? */ - if (icd_cntlr [unit].state == cntlr_busy) /* if the controller is busy, */ - complete_write (unit); /* then check for write completion */ - - else if (if_command [unit] == invalid) /* if a command was aborting, */ - complete_abort (unit); /* then complete it */ - - else if (if_state [unit] == opcode_wait /* if waiting for an opcode */ - || if_state [unit] == parameter_wait) /* or a parameter, */ - abort_command (unit, io_program_error, idle); /* then abort the pending command */ - } - - else if (stopped_talking) { /* was the unit Untalked? */ - if (icd_cntlr [unit].state == cntlr_busy) /* if the controller is busy, */ - complete_read (unit); /* then check for read completion */ - - else if (if_command [unit] == invalid) /* if a command was aborting, */ - complete_abort (unit); /* then complete it */ - } - } /* end of bus command processing */ - - -else { /* it is bus data (ATN is denied) */ - switch (if_state [unit]) { /* dispatch the interface state */ - - case opcode_wait: /* waiting for an opcode */ - if (DEBUG_PRI (da_dev, DEB_XFER)) - sprintf (action, "opcode %02XH", data & DL_OPCODE_MASK); - - buffer [0] = SET_UPPER (data); /* set the opcode into the buffer */ - - if (dl_prepare_command (&icd_cntlr [unit], /* is the command valid? */ - da_unit, unit)) { - if_state [unit] = parameter_wait; /* set up to get the pad byte */ - icd_cntlr [unit].index = 0; /* reset the word index for the next byte */ - icd_cntlr [unit].length = /* convert the parameter count to bytes */ - icd_cntlr [unit].length * 2 + 1; /* and include the pad byte */ - } - - else { /* the disc command is invalid */ - abort_command (unit, illegal_opcode, /* abort the command */ - error_sink); /* and sink any parameter bytes */ - initiated = TRUE; /* log the abort initiation */ - } /* (the unit cannot be busy) */ - break; - - - case parameter_wait: /* waiting for a parameter */ - if (DEBUG_PRI (da_dev, DEB_XFER)) - sprintf (action, "parameter %02XH", data); - - put_buffer_byte (&icd_cntlr [unit], data); /* add the byte to the buffer */ - - if (icd_cntlr [unit].length == 0) /* is this the last parameter? */ - if (di [da].bus_cntl & BUS_EOI) /* does the host agree? */ - initiated = start_command (unit); /* start the command and log the initiation */ - - else { /* the parameter count is wrong */ - abort_command (unit, io_program_error, /* abort the command and sink */ - error_sink); /* any additional parameter bytes */ - initiated = TRUE; /* log the abort initiation */ - } - break; - - - case write_xfer: /* transferring write data */ - if (icd_cntlr [unit].length > 0) /* if there is more to transfer */ - put_buffer_byte (&icd_cntlr [unit], data); /* then add the byte to the buffer */ - - /* fall into error_sink handler */ - - case error_sink: /* sinking data after an error */ - if (DEBUG_PRI (da_dev, DEB_XFER)) - sprintf (action, "data %03o", data); - - if (di [da].bus_cntl & BUS_EOI) /* is this the last byte from the bus? */ - icd_cntlr [unit].eod = SET; /* indicate EOD to the controller */ - - di_bus_control (da, unit, BUS_NRFD, 0); /* assert NRFD to hold off the card */ - - da_unit [unit].wait = icd_cntlr [unit].data_time; /* schedule the unit */ - break; - - - default: /* data was received in the wrong state */ - abort_command (unit, io_program_error, /* report the error */ - error_sink); /* and sink any data that follows */ - - if (DEBUG_PRI (da_dev, DEB_XFER)) - sprintf (action, "unhandled data %03o", data); - break; - } - } - - -if (accepted && DEBUG_PRI (da_dev, DEB_XFER)) - fprintf (sim_deb, ">>DA xfer: HP-IB address %d accepted %s\n", - GET_BUSADR (da_unit [unit].flags), action); - -if (da_unit [unit].wait > 0) /* was service requested? */ - activate_unit (&da_unit [unit]); /* schedule the unit */ - -if (initiated && DEBUG_PRI (da_dev, DEB_RWSC)) - if (if_command [unit] == disc_command) - fprintf (sim_deb, ">>DA rwsc: Unit %d position %" T_ADDR_FMT "d %s disc command initiated\n", - unit, da_unit [unit].pos, dl_opcode_name (ICD, icd_cntlr [unit].opcode)); - else - fprintf (sim_deb, ">>DA rwsc: Unit %d %s command initiated\n", - unit, if_command_name [if_command [unit]]); - -return accepted; /* indicate the acceptance condition */ -} - - -/* Respond to the bus control lines. - - The indicated unit is notified of the new control state on the bus. There - are two conditions to which we must respond: - - 1. An Interface Clear is initiated. IFC unaddresses all units, so any - in-progress disc command must be terminated as if an Untalk and Unlisten - were accepted from the data bus. - - 2. Attention and Not Ready for Data are denied. A device addressed to talk - must wait for ATN to deny before data may be sent. Also, a listener that - has asserted NRFD must deny it before a talker may send data. If the - interface is sending data and both ATN and NRFD are denied, then we - reschedule the service routine to send the next byte. -*/ - -void da_bus_respond (CARD_ID card, uint32 unit, uint8 new_cntl) -{ -if (new_cntl & BUS_IFC) { /* is interface clear asserted? */ - di [da].listeners = 0; /* perform an Unlisten */ - di [da].talker = 0; /* and an Untalk */ - - if (icd_cntlr [unit].state == cntlr_busy) { /* is the controller busy? */ - complete_write (unit); /* check for write completion */ - complete_read (unit); /* or read completion */ - - if (da_unit [unit].wait > 0) /* is service needed? */ - activate_unit (&da_unit [unit]); /* activate the unit */ - } - - else if (if_command [unit] == invalid) /* if a command was aborting, */ - complete_abort (unit); /* then complete it */ - - else if (if_state [unit] == opcode_wait /* if we're waiting for an opcode */ - || if_state [unit] == parameter_wait) /* or a parameter, */ - abort_command (unit, io_program_error, idle); /* then abort the pending command */ - } - -if (!(new_cntl & (BUS_ATN | BUS_NRFD)) /* is the card in data mode and ready for data? */ - && (if_state [unit] == read_xfer /* is the interface waiting to send data */ - || if_state [unit] == error_source)) /* or source error bytes? */ - da_service (&da_unit [unit]); /* start or resume the transfer */ -} - - - -/* Amigo disc local utility routines */ - - -/* Start a command with parameters. - - A command that has been waiting for all of its parameters to be received is - now ready to start. If this is a disc command, call the disc library to - validate the parameters and, if they are OK, to start the command. Status - commands return the status values in the sector buffer and the number of - words that were returned in the buffer length, which we convert to a byte - count. - - If the disc command was accepted, the library returns a pointer to the unit - to be scheduled. For an ICD controller, the unit is always the one currently - addressed, so we simply test if the return is not NULL. If it isn't, then we - set the next interface state as determined by the command that is executing. - For example, a Read command sets the interface to read_wait status in order - to wait until the accompanying Send Read Data secondary is received. - - If the return is NULL, then the command was rejected, so we set DSJ = 1 and - leave the interface state in parameter_wait; the controller status will have - been set to the reason for the rejection. - - If the next interface state is command_exec, then the disc command is ready - for execution, and we return TRUE to schedule the unit service. Otherwise, - we return FALSE, and the appropriate action will be taken by the caller. - - For all other commands, execution begins as soon as the correct parameters - are received, so we set command_exec state and return TRUE. (Only Amigo - Clear and Initiate Self Test require parameters, so they will be the only - other commands that must be started here.) - - - Implementation notes: - - 1. As the ICD implementation does not need to differentiate between unit and - controller commands, the return value from the dl_start_command routine - is not used other than as an indication of success or failure. -*/ - -static t_bool start_command (uint32 unit) -{ -if (if_command [unit] == disc_command) { /* are we starting a disc command? */ - if (dl_start_command (&icd_cntlr [unit], da_unit, unit)) { /* start the command; was it successful? */ - icd_cntlr [unit].length = icd_cntlr [unit].length * 2; /* convert the return length from words to bytes */ - if_state [unit] = next_state [icd_cntlr [unit].opcode]; /* set the next interface state */ - } - - else /* the command was rejected */ - if_dsj [unit] = 1; /* so indicate an error */ - - if (if_state [unit] == command_exec) /* if the command is executing */ - return TRUE; /* activate the unit */ - - else { /* if we must wait */ - da_unit [unit].wait = 0; /* for another secondary, */ - return FALSE; /* then skip the activation */ - } - } - -else { /* all other commands */ - if_state [unit] = command_exec; /* execute as soon */ - da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* as they */ - return TRUE; /* are received */ - } -} - - -/* Abort an in-process command. - - A command sequence partially received via the bus must be aborted. The cause - might be an unknown secondary, an illegal disc command opcode, an improper - secondary sequence (e.g., a Read not followed by Send Read Data), an - incorrect number of parameters, or unaddressing before the sequence was - complete. In any event, the controller and interface are set to an abort - state, and the DSJ value is set to 1 to indicate an error. -*/ - -static void abort_command (uint32 unit, CNTLR_STATUS status, IF_STATE state) -{ -if_command [unit] = invalid; /* indicate an invalid command */ -if_state [unit] = state; /* set the interface state as directed */ -if_dsj [unit] = 1; /* set DSJ to indicate an error condition */ -dl_end_command (&icd_cntlr [unit], status); /* place the disc controller into the wait state */ -return; -} - - -/* Complete an in-process read command. - - An Untalk terminates a Read, Read Full Sector, Read Without Verify, Read With - Offset, or Cold Load Read command, which must be tied off cleanly by setting - the end-of-data condition and calling the service routine. This is required - only if the read has not already aborted (e.g., for an auto-seek error). - - If a read is in progress, the controller will be busy, and the interface - state will be either command_exec (if between sectors) or read_xfer (if - within a sector). We set up the end phase for the command and schedule the - disc service to tidy up. - - If a read has aborted, the controller will be waiting, and the interface - state will be error_source. In this latter case, we no nothing, as the - controller has already set the required error status. - - We must be careful NOT to trigger on an Untalk that may follow the opcode and - precede the Send Read Data sequence. In this case, the controller will be - busy, but the interface state will be either read_wait or status_wait. - - - Implementation notes: - - 1. The test for controller busy is made before calling this routine. This - saves the call overhead for the most common case, which is the card is - being unaddressed after command completion. - - 2. There is no need to test if we are processing a disc command, as the - controller would not be busy otherwise. - - 3. If an auto-seek will be needed to continue the read, but the seek will - fail, then an extra delay is inserted before the service call to start - the next sector. Once an Untalk is received, this delay is no longer - needed, so it is cancelled before rescheduling the service routine. -*/ - -static void complete_read (uint32 unit) -{ -if ((if_state [unit] == command_exec /* is a command executing */ - || if_state [unit] == read_xfer) /* or is data transferring */ - && (dl_classify (icd_cntlr [unit]) == class_read /* and the controller is executing */ - || dl_classify (icd_cntlr [unit]) == class_status)) { /* a read or status command? */ - icd_cntlr [unit].eod = SET; /* set the end of data flag */ - - if_state [unit] = command_exec; /* set to execute */ - da_unit [unit].PHASE = end_phase; /* the completion phase */ - - sim_cancel (&da_unit [unit]); /* cancel the EOT delay */ - da_unit [unit].wait = icd_cntlr [unit].data_time; /* reschedule for completion */ - } - -return; -} - - -/* Complete an in-process write command. - - Normally, the host sends a byte tagged with EOI to end a Write, Write Full - Sector, or Initialize command. However, an Unlisten may terminate a write, - which must be tied off cleanly by setting the end-of-data condition and - calling the service routine. This is required only if the write has not - already aborted (e.g., for a write-protected disc). - - If a write is in progress, the controller will be busy, and the interface - state will be either command_exec (if between sectors) or write_xfer (if - within a sector). We set up the end phase for the command and schedule the - disc service to tidy up. - - If a write has aborted, the controller will be waiting, and the interface - state will be error_sink. In this latter case, we do nothing, as the - controller has already set the required error status. - - We must be careful NOT to trigger on the Unlisten that may follow the opcode - and precede the Receive Write Data sequence. In this case, the controller - will be busy, but the interface state will be write_wait. - - - Implementation notes: - - 1. The test for controller busy is made before calling this routine. This - saves the call overhead for the most common case, which is the card is - being unaddressed after command completion. - - 2. There is no need to test if we are processing a disc command, as the - controller would not be busy otherwise. -*/ - -static void complete_write (uint32 unit) -{ -if ((if_state [unit] == command_exec /* is a command executing */ - || if_state [unit] == write_xfer) /* or is data transferring */ - && dl_classify (icd_cntlr [unit]) == class_write) { /* and the controller is executing a write? */ - icd_cntlr [unit].eod = SET; /* set the end of data flag */ - - if_state [unit] = command_exec; /* set to execute */ - da_unit [unit].PHASE = end_phase; /* the completion phase */ - da_unit [unit].wait = icd_cntlr [unit].data_time; /* ensure that the controller will finish */ - } - -return; -} - - -/* Complete an in-process command abort. - - Errors in the command protocol begin an abort sequence that may involve - sourcing or sinking bytes to allow the sequence to complete as expected by - the CPU. Unaddressing the unit terminates the aborted command. - - If an abort is in progress, and the interface is not idle, the end-of-data - indication is set, and the disc service routine is called directly to process - the completion of the abort. The service routine will terminate the - error_source or error_sink state cleanly and then idle the interface. - - - Implementation notes: - - 1. The test for an abort-in-progress is made before calling this routine. - This saves the call overhead for the most common case, which is the card - is being unaddressed after normal command completion. -*/ - -static void complete_abort (uint32 unit) -{ -if (if_state [unit] != idle) { /* is the interface busy? */ - icd_cntlr [unit].eod = SET; /* set the end of data flag */ - da_service (&da_unit [unit]); /* and process the abort completion */ - } - -return; -} - - -/* Get a byte from the sector buffer. - - The next available byte in the sector buffer is returned to the caller. The - determination of which byte of the 16-bit buffer word to return is made by - the polarity of the buffer byte count. The count always begins with an even - number, as it is set by doubling the word count returned from the disc - library. Therefore, because we decrement the count first, the upper byte is - indicated by an odd count, and the lower byte is indicated by an even count. - The buffer index is incremented only after the lower byte is returned. -*/ - -static uint8 get_buffer_byte (CVPTR cvptr) -{ -cvptr->length = cvptr->length - 1; /* count the byte */ - -if (cvptr->length & 1) /* is the upper byte next? */ - return GET_UPPER (buffer [cvptr->index]); /* return the byte */ -else /* the lower byte is next */ - return GET_LOWER (buffer [cvptr->index++]); /* return the byte and bump the word index */ -} - - -/* Put a byte into the sector buffer. - - The supplied byte is stored in the sector buffer. The determination of which - byte of the 16-bit buffer word to store is made by the polarity of the buffer - byte count. The count always begins with an even number, as it is set by - doubling the word count returned from the disc library. Therefore, because - we decrement the count first, the upper byte is indicated by an odd count, - and the lower byte is indicated by an even count. The buffer index is - incremented only after the lower byte is stored. -*/ - -static void put_buffer_byte (CVPTR cvptr, uint8 data) -{ -cvptr->length = cvptr->length - 1; /* count the byte */ - -if (cvptr->length & 1) /* is the upper byte next? */ - buffer [cvptr->index] = SET_UPPER (data); /* save the byte */ -else /* the lower byte is next */ - buffer [cvptr->index++] |= SET_LOWER (data); /* merge the byte and bump the word index */ -return; -} - - -/* Activate the unit. - - The specified unit is activated using the unit's "wait" time. If debugging - is enabled, the activation is logged to the debug file. -*/ - -static t_stat activate_unit (UNIT *uptr) -{ -int32 unit; -t_stat result; - -if (DEBUG_PRI (da_dev, DEB_SERV)) { - unit = uptr - da_unit; - - fprintf (sim_deb, ">>DA serv: Unit %d state %s delay %d service scheduled\n", - unit, if_state_name [if_state [unit]], uptr->wait); - } - -result = sim_activate (uptr, uptr->wait); /* activate the unit */ -uptr->wait = 0; /* reset the activation time */ - -return result; /* return the activation status */ -} +/* hp2100_di_da.c: HP 12821A HP-IB Disc Interface simulator for Amigo disc drives + + Copyright (c) 2011-2017, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + DA 12821A Disc Interface with Amigo disc drives + + 17-Jan-17 JDB Changed to use new byte accessors in hp2100_defs.h + 13-May-16 JDB Modified for revised SCP API function parameter types + 04-Mar-16 JDB Name changed to "hp2100_disclib" until HP 3000 integration + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 24-Dec-14 JDB Use T_ADDR_FMT with t_addr values for 64-bit compatibility + Removed redundant global declarations + 24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash + 07-May-12 JDB Cancel the intersector delay if an untalk is received + 29-Mar-12 JDB First release + 04-Nov-11 JDB Created DA device + + References: + - HP 13365 Integrated Controller Programming Guide + (13365-90901, February 1980) + - HP 7910 Disc Drive Service Manual + (07910-90903, April 1981) + - 12745D Disc Controller (13037) to HP-IB Adapter Kit Installation and Service Manual + (12745-90911, September 1983) + - HP's 5 1/4-Inch Winchester Disc Drive Service Documentation + (09134-90032, August 1983) + - HP 12992 Loader ROMs Installation Manual + (12992-90001, April 1986) + - RTE Driver DVA32 Source + (92084-18708, revision 2540) + - IEEE Standard Digital Interface for Programmable Instrumentation + (IEEE-488A-1980, September 1979) + + + The HP 7906H, 7920H, and 7925H Integrated Controller Disc (ICD) drives were + connected via an 12821A disc interface and provided 20MB, 50MB, and 120MB + capacities. The drives were identical to the 7906M, 7920M, and 7925M + Multi-Access Controller (MAC) units but incorporated internal two-card + controllers in each drive and connected to the CPU interface via the + Hewlett-Packard Interface Bus (HP-IB), HP's implementation of IEEE-488. Each + controller was dedicated to a single drive and operated similarly to the + 12745 Disc Controller to HP-IB Adapter option for the 13037 Disc Controller + chassis. The 7906H was introduced in 1980 (there was no 7905H version, as + the 7905 was obsolete by that time). Up to four ICD drives could be + connected to a single 12821A card. The limitation was imposed by the bus + loading and the target data transfer rate. + + The ICD command set essentially was the MAC command set modified for + single-unit operation. The unit number and CPU hold bit fields in the opcode + words were unused in the ICD implementation. The Load TIO Register, Wakeup, + and Request Syndrome commands were removed, as Load TIO was used with the HP + 3000, Wakeup was used in a multi-CPU environment, and the simpler ICD + controller did not support ECC. Controller status values 02B (Unit + Available) and 27B (Unit Unavailable) were dropped as the controller + supported only single units, 12B (I/O Program Error) was reused to indicate + HP-IB protocol errors, 13B (Sync Not Received) was added, and 17B (Possibly + Correctable Data Error) was removed as error correction was not supported. + + Some minor redefinitions also occurred. For example, status 14B (End of + Cylinder) was expanded to include an auto-seek beyond the drive limits, and + 37B (Drive Attention) was restricted just head unloads from head loads and + unloads. + + The command set was expanded to include several commands related to HP-IB + operation. These were, in large part, adapted from the Amigo disc command + protocol outlined in the service manual for the HP 9133/34/35 series of + 5-1/4" Winchester drives. They include the Amigo Identify and Amigo Clear + sequences, Read and Write Loopback channel tests, and controller Self Test + commands. + + This simulator implements the Amigo disc protocol. It calls the 12821A Disc + Interface (DI) simulator to send and receive bytes across the HP-IB to and + from the CPU, and it calls the HP Disc Library to implement the controller + functions related to disc unit operation (e.g., seek, read, write, etc.). + Four units are provided, and any combination of 7906H/20H/25H drives may be + defined. + + Unfortunately, the primary reference for the ICD controller (the HP 13365 + Integrated Controller Programming Guide) does not indicate parallel poll + responses for these HP-IB commands. Therefore, the responses have been + derived from the sequences in the 7910 and 12745 manuals, although they + sometimes conflict. + + The drives respond to the following commands; the secondary and opcode + numeric values are in hex, and the bus addressing state is indicated by U + [untalk], L [listen], and T [talk]: + + Bus Sec Op Operation + --- --- -- -------------------------------- + U MSA -- Amigo Identify + + L 00 -- Write Data + L 08 00 Cold Load Read + L 08 01 Recalibrate + L 08 02 Seek + L 08 03 Request Status + L 08 04 Request Sector Address + L 08 05 Read + L 08 06 Read Full Sector + L 08 07 Verify + L 08 08 Write + L 08 09 Write Full Sector + L 08 0A Clear + L 08 0B Initialize + L 08 0C Address Record + L 08 0E Read with Offset + L 08 0F Set File Mask + L 08 12 Read without Verify + L 08 14 Request Logical Disc Address + L 08 15 End + L 09 -- Cyclic Redundancy Check + L 10 -- Amigo Clear + L 1E -- Write Loopback + L 1F ss Initiate Self-Test + + T 00 -- Read Data + T 08 -- Read Status + T 09 -- Cyclic Redundancy Check + T 10 -- Device Specified Jump + T 1E -- Read Loopback + T 1F -- Return Self-Test Result + + In addition, the controller responds to the Selected Device Clear primary + (04). + + + HP-IB Transaction Sequences + =========================== + + Amigo Identify + + ATN UNT Untalk + ATN MSA My secondary address + DAB ID data byte #1 = 00H + EOI DAB ID data byte #2 = 03H + ATN OTA Talk 30 + + + Amigo Clear + + ATN MLA My listen address + ATN SCG Secondary command 10H + ppd Parallel poll disabled + EOI DAB Unused data byte + ATN SDC Selected device clear + ATN UNL Unlisten + ... + ppe Parallel poll enabled when clear completes + + + CRC + + ATN MTA My talk address + ATN SCG Secondary command 09H + ppd Parallel poll disabled + DAB Data byte #1 + ... + EOI DAB Data byte #n + ppe Parallel poll enabled + ATN UNT Untalk + + or + + ATN MLA My listen address + ATN SCG Secondary command 09H + ppd Parallel poll disabled + DAB Data byte #1 + ... + EOI DAB Data byte #n + ppe Parallel poll enabled + ATN UNL Unlisten + + + Device Specified Jump + + ATN MTA My talk address + ATN SCG Secondary command 10H + ppd Parallel poll disabled + EOI DAB DSJ data byte + ATN UNT Untalk + + + Initiate Self-Test and Return Self-Test Result + + ATN MLA My listen address + ATN SCG Secondary command 1FH + ppd Parallel poll disabled + EOI DAB Self-test number + ppe Parallel poll enabled + ATN UNL Unlisten + + ATN MTA My talk address + ATN SCG Secondary command 1FH + ppd Parallel poll disabled + EOI DAB Result data byte + ppe Parallel poll enabled + ATN UNT Untalk + + + Write Loopback and Read Loopback + + ATN MLA My listen address + ATN SCG Secondary command 1EH + ppd Parallel poll disabled + DAB Loopback data byte #1 + ... + EOI DAB Loopback data byte #256 + ppe Parallel poll enabled + ATN UNL Unlisten + + ATN MTA My talk address + ATN SCG Secondary command 1EH + ppd Parallel poll disabled + DAB Loopback data byte #1 + ... + EOI DAB Loopback data byte #16 + ppe Parallel poll enabled + ATN UNT Untalk + + + Recalibrate and Seek + + ATN MLA My listen address + ATN SCG Secondary command 08H + ppd Parallel poll disabled + DAB Opcode 01H, 02H + ... (one to five + EOI DAB parameter bytes) + ATN UNL Unlisten + ... + ppe Parallel poll enabled when seek completes + + + Clear, Address Record, and Set File Mask + + ATN MLA My listen address + ATN SCG Secondary command 08H + ppd Parallel poll disabled + DAB Opcode 0AH, 0CH, 0FH + ... (one to five + EOI DAB parameter bytes) + ppe Parallel poll enabled + ATN UNL Unlisten + + + End + + ATN MLA My listen address + ATN SCG Secondary command 08H + ppd Parallel poll disabled + DAB Opcode 15H + EOI DAB Unused data byte + ATN UNL Unlisten + + + Request Status, Request Sector Address, and Request Logical Disc Address + + ATN MLA My listen address + ATN SCG Secondary command 08H + ppd Parallel poll disabled + DAB Opcode 03H, 04H, 14H + EOI DAB Unused data byte + ATN UNL Unlisten + + ATN MTA My talk address + ATN SCG Secondary command 08H + DAB Status byte #1 + ... (two to four + EOI DAB status bytes) + ppe Parallel poll enabled + ATN UNT Untalk + + + Cold Load Read, Read, Read Full Sector, Verify, Read with Offset, and Read + without Verify + + ATN MLA My listen address + ATN SCG Secondary command 08H + ppd Parallel poll disabled + DAB Opcode 00H, 05H, 06H, 07H, 0EH, 12H + EOI DAB Unused data byte + ATN UNL Unlisten + + ATN MTA My talk address + ATN SCG Secondary command 00H + DAB Read data byte #1 + ... + DAB Read data byte #n + ATN UNT Untalk + ... + ppe Parallel poll enabled when sector ends + + + Write, Write Full Sector, and Initialize + + ATN MLA My listen address + ATN SCG Secondary command 08H + ppd Parallel poll disabled + DAB Opcode 08H, 09H, 0BH + EOI DAB Unused data byte + ATN UNL Unlisten + + ATN MLA My listen address + ATN SCG Secondary command 00H + DAB Write data byte #1 + ... + EOI DAB Write data byte #n + ppe Parallel poll enabled + ATN UNL Unlisten + + + Implementation notes: + + 1. The 12745 does not alter the parallel poll response for the + Device-Specified Jump command. + + 2. The 7910 does not perform a parallel poll response enable and disable + between the Initiate Self-Test and Return Self-Test Result commands. + + 3. The 12745 does not disable the parallel poll response for the Read + Loopback command. +*/ + + + +#include "hp2100_defs.h" +#include "hp2100_di.h" +#include "hp2100_disclib.h" + + + +/* Program constants */ + +#define DA_UNITS 4 /* number of addressable disc units */ + + +/* Interface states */ + +typedef enum { + idle = 0, /* idle = default for reset */ + opcode_wait, /* waiting for opcode reception */ + parameter_wait, /* waiting for parameter reception */ + read_wait, /* waiting for send read data secondary */ + write_wait, /* waiting for receive write data secondary */ + status_wait, /* waiting for send status secondary */ + command_exec, /* executing an interface command */ + command_wait, /* waiting for command completion */ + read_xfer, /* sending read data or status */ + write_xfer, /* receiving write data */ + error_source, /* sending bytes for error recovery */ + error_sink /* receiving bytes for error recovery */ + } IF_STATE; + + +/* Interface state names */ + +static const char *if_state_name [] = { + "idle", + "opcode wait", + "parameter wait", + "read wait", + "write wait", + "status wait", + "command execution", + "command wait", + "read transfer", + "write transfer", + "error source", + "error sink" + }; + + +/* Next interface state after command recognition */ + +static const IF_STATE next_state [] = { + read_wait, /* cold load read */ + command_exec, /* recalibrate */ + command_exec, /* seek */ + status_wait, /* request status */ + status_wait, /* request sector address */ + read_wait, /* read */ + read_wait, /* read full sector */ + command_exec, /* verify */ + write_wait, /* write */ + write_wait, /* write full sector */ + command_exec, /* clear */ + write_wait, /* initialize */ + command_exec, /* address record */ + idle, /* request syndrome */ + read_wait, /* read with offset */ + command_exec, /* set file mask */ + idle, /* invalid */ + idle, /* invalid */ + read_wait, /* read without verify */ + idle, /* load TIO register */ + status_wait, /* request disc address */ + command_exec, /* end */ + idle /* wakeup */ + }; + + +/* Interface commands */ + +typedef enum { + invalid = 0, /* invalid = default for reset */ + disc_command, /* MLA 08 */ + crc_listen, /* MLA 09 */ + amigo_clear, /* MLA 10 */ + write_loopback, /* MLA 1E */ + initiate_self_test, /* MLA 1F */ + crc_talk, /* MTA 09 */ + device_specified_jump, /* MTA 10 */ + read_loopback, /* MTA 1E */ + return_self_test_result, /* MTA 1F */ + amigo_identify /* UNT MSA */ + } IF_COMMAND; + +/* Interface command names */ + +static const char *if_command_name [] = { + "invalid", + "disc command", + "CRC listen", + "Amigo clear", + "write loopback", + "initiate self-test", + "CRC talk", + "device specified jump", + "read loopback", + "return self-test result", + "Amigo identify" + }; + + + +/* Amigo disc state variables */ + +static uint16 buffer [DL_BUFSIZE]; /* command/status/sector buffer */ + +static uint8 if_dsj [DA_UNITS]; /* ICD controller DSJ values */ +static IF_STATE if_state [DA_UNITS]; /* ICD controller state */ +static IF_COMMAND if_command [DA_UNITS]; /* ICD controller command */ + +static CNTLR_VARS icd_cntlr [DA_UNITS] = /* ICD controllers: */ + { { CNTLR_INIT (ICD, buffer, NULL) }, /* unit 0 controller */ + { CNTLR_INIT (ICD, buffer, NULL) }, /* unit 1 controller */ + { CNTLR_INIT (ICD, buffer, NULL) }, /* unit 2 controller */ + { CNTLR_INIT (ICD, buffer, NULL) } }; /* unit 3 controller */ + + + +/* Amigo disc global VM routines */ + +t_stat da_reset (DEVICE *dptr); +t_stat da_attach (UNIT *uptr, CONST char *cptr); +t_stat da_detach (UNIT *uptr); + +/* Amigo disc global SCP routines */ + +t_stat da_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc); + +/* Amigo disc local utility routines */ + +static t_bool start_command (uint32 unit); +static void abort_command (uint32 unit, CNTLR_STATUS status, IF_STATE state); +static void complete_read (uint32 unit); +static void complete_write (uint32 unit); +static void complete_abort (uint32 unit); +static uint8 get_buffer_byte (CVPTR cvptr); +static void put_buffer_byte (CVPTR cvptr, uint8 data); +static t_stat activate_unit (UNIT *uptr); + + + +/* Amigo disc VM global data structures. + + da_dib DA device information block + da_unit DA unit list + da_reg DA register list + da_mod DA modifier list + da_dev DA device descriptor + + + Implementation notes: + + 1. The IFSTAT and IFCMD registers are declared to accommodate the + corresponding arrays of enums. Arrayed registers assume that elements + are allocated space only to the integral number of bytes implied by the + "width" field. The storage size of an enum is implementation-defined, so + we must determine the number of bits for "width" at compile time. + PV_LEFT is used to avoid the large number of leading zeros that would be + displayed if an implementation stored enums in full words. + + 2. The CNVARS register is included to ensure that the controller state + variables array is saved by a SAVE command. It is declared as a hidden, + read-only byte array of a depth compatible with the size of the array. + + There does not appear to be a way to expose the fields of the four + controller state variables as arrayed registers. Access to an array + always assumes that elements appear at memory offsets equal to the + element size, i.e., a 32-bit arrayed register has elements at four-byte + offsets. There's no way to specify an array of structure elements where + a given 32-bit field appears at, say, 92-byte offsets (i.e., the size of + the structure). +*/ + +DEVICE da_dev; + +DIB da_dib = { &di_io, DI_DA, da }; + +#define UNIT_FLAGS (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD) + +UNIT da_unit [] = { + { UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (0), D7906_WORDS) }, /* drive unit 0 */ + { UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (1), D7906_WORDS) }, /* drive unit 1 */ + { UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (2), D7906_WORDS) }, /* drive unit 2 */ + { UDATA (&da_service, UNIT_FLAGS | MODEL_7906 | SET_BUSADR (3), D7906_WORDS) } /* drive unit 3 */ + }; + +REG da_reg [] = { + DI_REGS (da), + + { BRDATA (BUFFER, buffer, 8, 16, DL_BUFSIZE) }, + + { BRDATA (DSJ, if_dsj, 10, 2, DA_UNITS) }, + { BRDATA (ISTATE, if_state, 10, sizeof (IF_STATE) * CHAR_BIT, DA_UNITS), PV_LEFT }, + { BRDATA (ICMD, if_command, 10, sizeof (IF_COMMAND) * CHAR_BIT, DA_UNITS), PV_LEFT }, + + { BRDATA (CNVARS, icd_cntlr, 10, CHAR_BIT, sizeof (CNTLR_VARS) * DA_UNITS), REG_HRO }, + + { NULL } + }; + +MTAB da_mod [] = { + DI_MODS (da_dev), + + { UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", &da_load_unload, NULL, NULL }, + { UNIT_UNLOAD, 0, "heads loaded", "LOADED", &da_load_unload, NULL, NULL }, + + { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL, NULL, NULL }, + { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL, NULL, NULL }, + + { UNIT_FMT, UNIT_FMT, "format enabled", "FORMAT", NULL, NULL, NULL }, + { UNIT_FMT, 0, "format disabled", "NOFORMAT", NULL, NULL, NULL }, + + { UNIT_MODEL, MODEL_7906, "7906H", "7906H", &dl_set_model, NULL, NULL }, + { UNIT_MODEL, MODEL_7920, "7920H", "7920H", &dl_set_model, NULL, NULL }, + { UNIT_MODEL, MODEL_7925, "7925H", "7925H", &dl_set_model, NULL, NULL }, + + { 0 } + }; + +DEVICE da_dev = { + "DA", /* device name */ + da_unit, /* unit array */ + da_reg, /* register array */ + da_mod, /* modifier array */ + DA_UNITS, /* number of units */ + 10, /* address radix */ + 26, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 16, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &da_reset, /* reset routine */ + &da_boot, /* boot routine */ + &da_attach, /* attach routine */ + &da_detach, /* detach routine */ + &da_dib, /* device information block */ + DEV_DEBUG | DEV_DISABLE, /* device flags */ + 0, /* debug control flags */ + di_deb, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL /* logical device name */ + }; + + + +/* Amigo disc global VM routines */ + + +/* Service an Amigo disc drive I/O event. + + The service routine is called to execute commands and control the transfer of + data to and from the HP-IB card. The actions to be taken depend on the + current state of the ICD interface. The possibilities are: + + 1. A command is pending on the interface. This occurs only when a command + is received while a Seek or Recalibrate command is in progress. + + 2. A command is executing. + + 3. Data is being sent or received over the HP-IB during command execution. + + 4. Dummy bytes are being sent or received over the HP-IB due to a command + error. + + Entry to the the service routine in any other interface state or to process a + command not allowed in a valid state will return an Internal Error to cause a + simulator stop. Exit from the routine will be either in one of the above + states, or in the idle state if the operation is complete. + + The specific actions taken for the various interface states are as follows: + + command_wait + ============ + + We are entered in this state only if a unit that was busy (still seeking) + was addressed to listen or talk. The card has been held off by asserting + NRFD after receiving MLA or MTA. Upon entry, we complete the seek and then + release the interface by denying NRFD to allow the remainder of the command + sequence to be received from the card. + + command_exec + ============ + + We are entered in this state to initiate, continue, or complete a command. + The command may be a disc command, such as Seek or Read, or an interface + command, such as Amigo Identify or Device-Specified Jump. + + Disc commands call the disc library service routine to perform all of the + common controller actions. Any ICD-specific actions needed, such as + setting the DSJ value, are performed after the call. + + Certain disc commands require multiple execution phases. For example, the + Read command has a start phase that reads data from the disc image file + into the sector buffer, a data phase that transfers bytes from the buffer + to the card, and an end phase that schedules the intersector gap time and + resets to the start phase. Data phase transfers are performed in the + read_xfer or write_xfer interface states. + + The results of the disc library service are inferred by the controller + state. If the controller is busy, then the command continues in a new + phase. Otherwise, the command either has completed normally or has + terminated with an error. If an error has occurred during a disc command + that transfers data, DSJ is set to 1, and the interface state is changed to + source or sink dummy bytes to complete the command sequence. + + Interface commands may either complete immediately (e.g., Amigo Clear) or + transfer data (e.g., DSJ). + + read_xfer + ========= + + Commands that send data to the CPU enter the service routine to source a + byte to the bus. Bytes are transferred only when ATN and NRFD are denied; + if they are not, we simply exit, as we will be rescheduled when the lines + are dropped. Otherwise, we get a byte from the sector buffer and send it + to the card. If the card has stopped listening, or the buffer is now + empty, then we terminate the transfer and move to the end phase of the + command. Otherwise, we reschedule the next data phase byte transfer. + + Disc and interface commands are handled separately, as EOI is always + asserted on the last byte of an interface command transfer and never on a + (good) disc command transfer. + + write_xfer + ========== + + Commands that receive data from the CPU enter the service routine to + determine whether or not to continue the transfer. Our bus accept routine + has already stored the received byte in the sector buffer and has asserted + NRFD to hold off the card. If the buffer is now full, or the byte was + tagged with EOI, then we terminate the transfer and move to the end phase + of the command. Otherwise, we deny NRFD and exit; we will be rescheduled + when the next byte arrives. + + error_source + ============ + + If an error occurred during the data transfer phase of a read or status + command, a dummy byte tagged with EOI is sourced to the bus. This allows + the OS driver for the card to terminate the command and request the + controller's status. + + error_sink + ========== + + If an error occurred during the data transfer phase of a write command, + dummy bytes are sunk from the bus until EOI is seen or the card is + unaddressed. This allows the OS driver to complete the command as expected + and then determine the cause of the failure by requesting the controller's + status. + + + Implementation notes: + + 1. The disc library sets the controller state to idle for a normal End, + Seek, or Recalibrate command and to wait for all other commands that end + normally. So we determine command completion by checking if the + controller is not busy, rather than checking if the controller is idle. + + Drive Attention status is the normal result of the completion of a Seek + or Recalibrate command. Normal Completion status is the normal result of + all other commands. + + 2. The disc library returns the buffer length in words. We double the + return value to count bytes. + + 3. Some commands, such as DSJ, could be completed in the bus accept routine. + They are serviced here instead to avoid presenting a zero execution time + to the CPU. + + 4. The Amigo command set does not provide the disc with the number of bytes + that will be read, and the unit expects to be untalked when the read is + to terminate. The RTE ICD bootstrap extension does not do this. + Instead, it resets the card via CLC 0,C to terminate the Cold Load Read + that was started by the ICD boot loader ROM. + + In hardware, if the LSTN control bit is cleared, e.g., by CRS, + transmission stops because the card denies NDAC and NRFD (the HP-IB + handshake requires NDAC and NRFD to be asserted to start the handshake + sequence; TACS * SDYS * ~NDAC * ~NRFD is an error condition). In + simulation, we handle this by terminating a read transfer if the card + stops accepting. If we did not, then the disc would continue to source + bytes to the bus, overflowing the card FIFO (a FIFO full condition cannot + assert NRFD if the LSTN control bit is clear). +*/ + +t_stat da_service (UNIT *uptr) +{ +uint8 data; +CNTLR_CLASS command_class; +const int32 unit = uptr - da_unit; /* get the disc unit number */ +const CVPTR cvptr = &icd_cntlr [unit]; /* get a pointer to the controller */ +t_stat result = SCPE_OK; +t_bool release_interface = FALSE; + +switch (if_state [unit]) { /* dispatch the interface state */ + + case command_wait: /* command is waiting */ + release_interface = TRUE; /* release the interface at then end if it's idle */ + + /* fall into the command_exec handler to process the current command */ + + case command_exec: /* command is executing */ + switch (if_command [unit]) { /* dispatch the interface command */ + + case disc_command: /* execute a disc command */ + result = dl_service_drive (cvptr, uptr); /* service the disc unit */ + + if (cvptr->opcode == Clear) /* is this a Clear command? */ + if_dsj [unit] = 2; /* indicate that the self test is complete */ + + if (cvptr->state != cntlr_busy) { /* has the controller stopped? */ + if_state [unit] = idle; /* idle the interface */ + + if (cvptr->status == normal_completion || /* do we have normal completion */ + cvptr->status == drive_attention) /* or drive attention? */ + break; /* we're done */ + + else { /* if the status is abnormal */ + if_dsj [unit] = 1; /* an error has occurred */ + + command_class = dl_classify (*cvptr); /* classify the command */ + + if (command_class == class_write) { /* did a write command fail? */ + if_state [unit] = error_sink; /* sink the remaining bytes */ + uptr->wait = cvptr->cmd_time; /* activate to complete processing */ + } + + else if (command_class != class_control) { /* did a read or status command fail? */ + if_state [unit] = error_source; /* source an error byte */ + uptr->wait = cvptr->cmd_time; /* activate to complete processing */ + } + } + } + + else if (uptr->PHASE == data_phase) { /* are we starting the data phase? */ + cvptr->length = cvptr->length * 2; /* convert the buffer length to bytes */ + + if (dl_classify (*cvptr) == class_write) /* is this a write command? */ + if_state [unit] = write_xfer; /* set for a write data transfer */ + else /* it is a read or status command */ + if_state [unit] = read_xfer; /* set for a read data transfer */ + } + + break; + + + case amigo_identify: /* Amigo Identify */ + buffer [0] = 0x0003; /* store the response in the buffer */ + cvptr->length = 2; /* return two bytes */ + + if_state [unit] = read_xfer; /* we are ready to transfer the data */ + uptr->wait = cvptr->cmd_time; /* schedule the transfer */ + + if (DEBUG_PRI (da_dev, DEB_RWSC)) + fprintf (sim_deb, ">>DA rwsc: Unit %d Amigo identify response %04XH\n", + unit, buffer [0]); + break; + + + case initiate_self_test: /* Initiate a self test */ + sim_cancel (&da_unit [unit]); /* cancel any operation in progress */ + dl_clear_controller (cvptr, /* hard-clear the controller */ + &da_unit [unit], + hard_clear); + if_dsj [unit] = 2; /* set DSJ for self test completion */ + if_state [unit] = idle; /* the command is complete */ + di_poll_response (da, unit, SET); /* with PPR enabled */ + break; + + + case amigo_clear: /* Amigo clear */ + dl_idle_controller (cvptr); /* idle the controller */ + if_dsj [unit] = 0; /* clear the DSJ value */ + if_state [unit] = idle; /* the command is complete */ + di_poll_response (da, unit, SET); /* with PPR enabled */ + break; + + + default: /* no other commands are executed */ + result = SCPE_IERR; /* signal an internal error */ + break; + } /* end of command dispatch */ + break; + + + case error_source: /* send data after an error */ + if (! (di [da].bus_cntl & (BUS_ATN | BUS_NRFD))) { /* is the card ready for data? */ + di [da].bus_cntl |= BUS_EOI; /* set EOI */ + di_bus_source (da, 0); /* and send a dummy byte to the card */ + if_state [unit] = idle; /* the command is complete */ + } + break; + + + case read_xfer: /* send read data */ + if (! (di [da].bus_cntl & (BUS_ATN | BUS_NRFD))) /* is the card ready for data? */ + switch (if_command [unit]) { /* dispatch the interface command */ + + case disc_command: /* disc read or status commands */ + data = get_buffer_byte (cvptr); /* get the next byte from the buffer */ + + if (di_bus_source (da, data) == FALSE) /* send the byte to the card; is it listening? */ + cvptr->eod = SET; /* no, so terminate the read */ + + if (cvptr->length == 0 || cvptr->eod == SET) { /* is the data phase complete? */ + uptr->PHASE = end_phase; /* set the end phase */ + + if (cvptr->opcode == Request_Status) /* is it a Request Status command? */ + if_dsj [unit] = 0; /* clear the DSJ value */ + + if_state [unit] = command_exec; /* set to execute the command */ + uptr->wait = cvptr->cmd_time; /* and reschedule the service */ + } + + else /* the data phase continues */ + uptr->wait = cvptr->data_time; /* reschedule the next transfer */ + + break; + + + case amigo_identify: + case read_loopback: + case return_self_test_result: + data = get_buffer_byte (cvptr); /* get the next byte from the buffer */ + + if (cvptr->length == 0) /* is the transfer complete? */ + di [da].bus_cntl |= BUS_EOI; /* set EOI */ + + if (di_bus_source (da, data) /* send the byte to the card; is it listening? */ + && cvptr->length > 0) /* and is there more to transfer? */ + uptr->wait = cvptr->data_time; /* reschedule the next transfer */ + + else { /* the transfer is complete */ + if_state [unit] = idle; /* the command is complete */ + di_poll_response (da, unit, SET); /* enable the PPR */ + } + break; + + + case device_specified_jump: + di [da].bus_cntl |= BUS_EOI; /* set EOI */ + di_bus_source (da, if_dsj [unit]); /* send the DSJ value to the card */ + if_state [unit] = idle; /* the command is complete */ + break; + + + case crc_talk: + di [da].bus_cntl |= BUS_EOI; /* set EOI */ + di_bus_source (da, 0); /* send dummy bytes */ + break; /* until the card untalks */ + + + default: /* no other commands send data */ + result = SCPE_IERR; /* signal an internal error */ + break; + } /* end of read data transfer dispatch */ + break; + + + case error_sink: /* absorb data after an error */ + cvptr->index = 0; /* absorb data until EOI asserts */ + + if (cvptr->eod == SET) /* is the transfer complete? */ + if_state [unit] = idle; /* the command is complete */ + + di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */ + break; + + + case write_xfer: /* receive write data */ + switch (if_command [unit]) { /* dispatch the interface command */ + + case disc_command: /* disc write commands */ + if (cvptr->length == 0 || cvptr->eod == SET) { /* is the data phase complete? */ + uptr->PHASE = end_phase; /* set the end phase */ + + if_state [unit] = command_exec; /* set to execute the command */ + uptr->wait = cvptr->cmd_time; /* and schedule the service */ + + if (cvptr->eod == CLEAR) /* is the transfer continuing? */ + break; /* do not deny NRFD until next service! */ + } + + di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */ + break; + + + case write_loopback: + if (cvptr->eod == SET) { /* is the transfer complete? */ + cvptr->length = 16 - cvptr->length; /* set the count of bytes transferred */ + if_state [unit] = idle; /* the command is complete */ + } + + di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */ + break; + + + default: /* no other commands receive data */ + result = SCPE_IERR; /* signal an internal error */ + break; + } /* end of write data transfer dispatch */ + break; + + + default: /* no other states schedule service */ + result = SCPE_IERR; /* signal an internal error */ + break; + } /* end of interface state dispatch */ + + +if (uptr->wait) /* is service requested? */ + activate_unit (uptr); /* schedule the next event */ + +if (result == SCPE_IERR && DEBUG_PRI (da_dev, DEB_RWSC)) { /* did an internal error occur? */ + fprintf (sim_deb, ">>DA rwsc: Unit %d ", unit); /* report it if debugging */ + + if (if_state [unit] == command_exec + && if_command [unit] == disc_command) + fprintf (sim_deb, "%s command %s phase ", + dl_opcode_name (ICD, (CNTLR_OPCODE) uptr->OP), + dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); + else + fprintf (sim_deb, "%s state %s ", + if_command_name [if_command [unit]], + if_state_name [if_state [unit]]); + + fputs ("service not handled\n", sim_deb); + } + +if (if_state [unit] == idle) { /* is the command now complete? */ + if (if_command [unit] == disc_command) { /* did a disc command complete? */ + if (cvptr->opcode != End) /* yes; if the command was not End, */ + di_poll_response (da, unit, SET); /* then enable PPR */ + + if (DEBUG_PRI (da_dev, DEB_RWSC)) + fprintf (sim_deb, ">>DA rwsc: Unit %d %s disc command completed\n", + unit, dl_opcode_name (ICD, cvptr->opcode)); + } + + else /* an interface command completed */ + if (DEBUG_PRI (da_dev, DEB_RWSC)) + fprintf (sim_deb, ">>DA rwsc: Unit %d %s command completed\n", + unit, if_command_name [if_command [unit]]); + + if (release_interface) /* if the next command is already pending */ + di_bus_control (da, unit, 0, BUS_NRFD); /* deny NRFD to allow the card to resume */ + } + +return result; /* return the result of the service */ +} + + +/* Reset or preset the simulator. + + In hardware, a self-test is performed by the controller at power-on. When + the self-test completes, the controller sets DSJ = 2 and enables the parallel + poll response. + + A front panel PRESET or programmed CRS has no direct effect on the controller + or drive. However, the card reacts to CRS by clearing its talker and + listener states, so an in-progress read or status command will abort when the + next byte sourced to the bus finds no acceptors. +*/ + +t_stat da_reset (DEVICE *dptr) +{ +uint32 unit; +t_stat status; + +status = di_reset (dptr); /* reset the card */ + +if (status == SCPE_OK && (sim_switches & SWMASK ('P'))) /* is the card OK and is this a power-on reset? */ + for (unit = 0; unit < dptr->numunits; unit++) { /* loop through the units */ + sim_cancel (dptr->units + unit); /* cancel any current activation */ + dptr->units [unit].CYL = 0; /* reset the head position */ + dptr->units [unit].pos = 0; /* to cylinder 0 */ + + dl_clear_controller (&icd_cntlr [unit], /* hard-clear the controller */ + dptr->units + unit, + hard_clear); + + if_state [unit] = idle; /* reset the interface state */ + if_command [unit] = invalid; /* reset the interface command */ + + if_dsj [unit] = 2; /* set the DSJ for power up complete */ + } + +return status; +} + + +/* Attach a unit to a disc image file. + + The simulator considers an attached unit to be connected to the bus and an + unattached unit to be disconnected, so we set the card's acceptor bit for the + selected unit if the attach is successful. An attached unit is ready if the + heads are loaded or not ready if not. + + This model is slightly different than the MAC (DS) simulation, where an + unattached unit is considered "connected but not ready" -- the same + indication returned by an attached unit whose heads are unloaded. Therefore, + the situation when the simulator is started is that all DS units are + "connected to the controller but not ready," whereas all DA units are "not + connected to the bus." This eliminates the overhead of sending HP-IB + messages to unused units. + + In tabular form, the simulator responses are: + + Enabled Loaded Attached DS (MAC) DA (ICD) + ------- ------ -------- ------------ ------------ + N N N disconnected disconnected + N N Y -- -- + N Y N -- -- + N Y Y -- -- + Y N N unloaded disconnected + Y N Y unloaded unloaded + Y Y N -- -- + Y Y Y ready ready + + The unspecified responses are illegal conditions; for example, the simulator + does not allow an attached unit to be disabled. + + + Implementation notes: + + 1. To conform exactly to the MAC responses would have required intercepting + the SET DISABLED/ENABLED commands in order to clear or set the unit + accepting bits. However, short of intercepting the all SET commands with + a custom command table, there is no way to ensure that unit enables are + observed. Adding ENABLED and DISABLED to the modifiers table and + specifying a validation routine works for the DISABLED case but not the + ENABLED case -- set_unit_enbdis returns SCPE_UDIS before calling the + validation routine. +*/ + +t_stat da_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat result; +const int32 unit = uptr - da_unit; /* calculate the unit number */ + +result = dl_attach (&icd_cntlr [unit], uptr, cptr); /* attach the drive */ + +if (result == SCPE_OK) /* was the attach successful? */ + di [da].acceptors |= (1 << unit); /* set the unit's accepting bit */ + +return result; +} + + +/* Detach a disc image file from a unit. + + As explained above, detaching a unit is the hardware equivalent of + disconnecting the drive from the bus, so we clear the unit's acceptor bit if + the detach is successful. +*/ + +t_stat da_detach (UNIT *uptr) +{ +t_stat result; +const int32 unit = uptr - da_unit; /* calculate the unit number */ + +result = dl_detach (&icd_cntlr [unit], uptr); /* detach the drive */ + +if (result == SCPE_OK) { /* was the detach successful? */ + di [da].acceptors &= ~(1 << unit); /* clear the unit's accepting bit */ + di_poll_response (da, unit, CLEAR); /* and its PPR, as it's no longer present */ + } + +return result; +} + + +/* Boot an Amigo disc drive. + + The ICD disc bootstrap program is loaded from the HP 12992H Boot Loader ROM + into memory, the I/O instructions are configured for the interface card's + select code, and the program is run to boot from the specified unit. The + loader supports booting the disc at bus address 0 only. Before execution, + the S register is automatically set as follows: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + ------ ------ ---------------------- ------------- ----- + ROM # 0 1 select code reserved head + + The boot routine sets bits 15-6 of the S register to appropriate values. + Bits 5-3 and 1-0 retain their original values, so S should be set before + booting. These bits are typically set to 0, although bit 5 is set for an RTE + reconfiguration boot, and bits 1-0 may be set if booting from a head other + than 0 is desired. +*/ + +static const BOOT_ROM da_rom = { + 0102501, /* START LIA 1 GET SWITCH REGISTER SETTING */ + 0100044, /* LSL 4 SHIFT A LEFT 4 */ + 0006111, /* CLE,SLB,RSS SR BIT 12 SET FOR MANUAL BOOT? */ + 0100041, /* LSL 1 NO. SHIFT HEAD # FOR RPL BOOT */ + 0001424, /* ALR,ALR SHIFT HEAD 2, CLEAR SIGN */ + 0033744, /* IOR HDSEC SET EOI BIT */ + 0073744, /* STA HDSEC PLACE IN COMMAND BUFFER */ + 0017756, /* JSB BTCTL SEND DUMMY,U-CLR,PP */ + 0102510, /* LIA IBI READ INPUT REGISTER */ + 0101027, /* ASR 7 SHIFT DRIVE 0 RESPONSE TO LSB */ + 0002011, /* SLA,RSS DID DRIVE 0 RESPOND? */ + 0027710, /* JMP *-3 NO, GO LOOK AGAIN */ + 0107700, /* CLC 0,C */ + 0017756, /* JSB BTCTL SEND TALK, CL-RD,BUS HOLDER */ + 0002300, /* CCE */ + 0017756, /* JSB BTCTL TELL CARD TO LISTEN */ + 0063776, /* LDA DMACW LOAD DMA CONTROL WORD */ + 0102606, /* OTA 6 OUTPUT TO DCPC */ + 0106702, /* CLC 2 READY DCPC */ + 0063735, /* LDA ADDR1 LOAD DMA BUFFER ADDRESS */ + 0102602, /* OTA 2 OUTPUT TO DCPC */ + 0063740, /* LDA DMAWC LOAD DMA WORD COUNT */ + 0102702, /* STC 2 READY DCPC */ + 0102602, /* OTA 2 OUTPUT TO DCPC */ + 0103706, /* STC 6,C START DCPC */ + 0102206, /* TEST SFC 6 SKIP IF DMA NOT DONE */ + 0117750, /* JSB ADDR2,I SUCCESSFUL END OF TRANSFER */ + 0102310, /* SFS IBI SKIP IF DISC ABORTED TRANSFER */ + 0027731, /* JMP TEST RECHECK FOR TRANSFER END */ + 0102011, /* ADDR1 HLT 11B ERROR HALT */ + 0000677, /* UNCLR OCT 677 UNLISTEN */ + 0000737, /* OCT 737 UNTALK */ + 0176624, /* DMAWC OCT 176624 UNIVERSAL CLEAR,LBO */ + 0000440, /* LIST OCT 440 LISTEN BUS ADDRESS 0 */ + 0000550, /* CMSEC OCT 550 SECONDARY GET COMMAND */ + 0000000, /* BOOT OCT 0 COLD LOAD READ COMMAND */ + 0001000, /* HDSEC OCT 1000 HEAD,SECTOR PLUS EOI */ + 0000677, /* UNLST OCT 677 ATN,PRIMARY UNLISTEN,PARITY */ + 0000500, /* TALK OCT 500 SEND READ DATA */ + 0100740, /* RDSEC OCT 100740 SECONDARY READ DATA */ + 0102055, /* ADDR2 OCT 102055 BOOT EXTENSION STARTING ADDRESS */ + 0004003, /* CTLP OCT 4003 INT=LBO,T,CIC */ + 0000047, /* OCT 47 PPE,L,T,CIC */ + 0004003, /* OCT 4003 INT=LBO,T,CIC */ + 0000413, /* OCT 413 ATN,P,L,CIC */ + 0001015, /* OCT 1015 INT=EOI,P,L,CIC */ + 0000000, /* BTCTL NOP */ + 0107710, /* CLC IBI,C RESET IBI */ + 0063751, /* BM LDA CTLP LOAD CONTROL WORD */ + 0102610, /* OTA IBI OUTPUT TO CONTROL REGISTER */ + 0102710, /* STC IBI RETURN IBI TO DATA MODE */ + 0037760, /* ISZ BM INCREMENT CONTROL WORD POINTER */ + 0002240, /* SEZ,CME */ + 0127756, /* JMP BTCTL,I RETURN */ + 0063736, /* LABL LDA UNCLR LOAD DATA WORD */ + 0037766, /* ISZ LABL INCREMENT WORD POINTER */ + 0102610, /* OTA IBI OUTPUT TO HPIB */ + 0002021, /* SSA,RSS SKIP IF LAST WORD */ + 0027766, /* JMP LABL GO BACK FOR NEXT WORD */ + 0102310, /* SFS IBI SKIP IF LAST WORD SENT TO BUS */ + 0027773, /* JMP *-1 RECHECK ACCEPTANCE */ + 0027757, /* JMP BTCTL+1 */ + 0000010, /* DMACW ABS IBI */ + 0170100 /* ABS -START */ + }; + +t_stat da_boot (int32 unitno, DEVICE *dptr) +{ +if (GET_BUSADR (da_unit [unitno].flags) != 0) /* booting is supported on bus address 0 only */ + return SCPE_NOFNC; /* report "Command not allowed" if attempted */ + +if (ibl_copy (da_rom, da_dib.select_code, /* copy the boot ROM to memory and configure */ + IBL_OPT | IBL_DS_HEAD, /* the S register accordingly */ + IBL_DS | IBL_MAN | IBL_SET_SC (da_dib.select_code))) + return SCPE_IERR; /* return an internal error if the copy failed */ +else + return SCPE_OK; +} + + + +/* Amigo disc global SCP routines */ + + +/* Load or unload a unit's heads. + + The heads are automatically loaded when a unit is attached and unloaded when + a unit is detached. While a unit is attached, the heads may be manually + unloaded; this yields a "not ready" status if the unit is accessed. An + unloaded drive may be manually loaded, returning the unit to "ready" status. + + The ICD controller sets Drive Attention status when the heads unload and also + asserts a parallel poll response if the heads unload while in idle state 2 + (i.e., after an End command). + + + Implementation notes: + + 1. The 13365 manual says on page 28 that Drive Attention status is + "Generated whenever...the drive unloads and the controller is in Idle + State 2 or 3." However, the ICD diagnostic tests for Drive Attention + status on head unload immediately after the Request Status command that + completes the previous step, which leaves the controller in idle state 1. + + Moreover, the diagnostic does NOT check for Drive Attention status if the + Amigo ID is 2 (MAC controller). But the 12745 manual says on page 3-7 + that the status is returned if "...Drive becomes not ready (heads + unload)" with no mention of controller state. + + It appears as though the diagnostic test is exactly backward. However, + we match the diagnostic expectation below. +*/ + +t_stat da_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +const int32 unit = uptr - da_unit; /* calculate the unit number */ +const t_bool load = (value != UNIT_UNLOAD); /* true if the heads are loading */ +t_stat result; + +result = dl_load_unload (&icd_cntlr [unit], uptr, load); /* load or unload the heads */ + +if (result == SCPE_OK && ! load) { /* was the unload successful? */ + icd_cntlr [unit].status = drive_attention; /* set Drive Attention status */ + + if (uptr->OP == End) /* is the controller in idle state 2? */ + di_poll_response (da, unit, SET); /* enable PPR */ + } + +return result; +} + + + +/* Amigo disc global bus routines */ + + +/* Accept a data byte from the bus. + + The indicated unit is offered a byte that has been sourced to the bus. The + routine returns TRUE or FALSE to indicate whether or not it accepted the + byte. + + Commands from the bus may be universal (applying to all acceptors) or + addressed (applying only to those acceptors that have been addressed to + listen). Data bytes are accepted only if the unit has been addressed to + listen. As we are called for a data transfer or an addressed command only if + we are currently listening, the only bytes that we do not accept are primary + talk or listen commands directed to another address, or secondary commands + when we are not addressed to listen. + + This routine handles the HP-IB protocol. The type of byte passed is + determined by the state of the ATN signal and, if ATN is asserted, by the + high-order bits of the value. Most of the work involves decoding secondary + commands and their associated data parameters. The interface state is + changed as needed to track the command protocol. The states processed in + this routine are: + + opcode_wait + =========== + + A Receive Disc Command secondary has been received, and the interface is + waiting for the opcode that should follow. + + parameter_wait + ============== + + A disc opcode or interface command has been received, and the interface is + waiting for a parameter byte that should follow. + + write_wait + ========== + + A disc write command has been received, and the interface is waiting for + the Receive Write Data secondary that should follow. + + read_wait + ========= + + A disc read command has been received, and the interface is waiting for the + Send Read Data secondary that should follow. + + status_wait + =========== + + A disc status command has been received, and the interface is waiting for + the Send Disc Status secondary that should follow. + + write_xfer + ========== + + A disc write is in progress, and the interface is waiting for a data byte + that should follow. + + error_sink + ========== + + A disc write has terminated with an error, and the interface is waiting to + absorb all of the remaining data bytes of the transfer. + + + Disc commands and parameters are assembled in the sector buffer before being + passed to the disc library to start the command. Once the command is + started, the interface state is set either to execute the command or to wait + for the receipt of a data transfer secondary before executing, depending on + the command. + + Two disc command protocol errors are detected. First, an Illegal Opcode is + identified during the check for the expected number of disc command + parameters. This allows us to sink an arbitrary number of parameter bytes. + Second, an I/O Program Error occurs if an unsupported secondary is received + or the HP-IB sequence is incorrect. The latter occurs if a command has the + wrong number of parameters or a secondary data transfer sequence is invalid. + + Disc commands that require data transfers (e.g., Read, Write, Request Status) + involve a pair of secondaries. The first transmits the command, and the + second transmits or receives the data. If one occurs without the other, an + I/O Program Error occurs. + + A secondary or command that generates an I/O Program Error is always ignored. + Error recovery is as follows: + + - An unsupported talk secondary sends a single data byte tagged with EOI. + + - An unsupported listen secondary accepts and discards any accompanying data + bytes until EOI is asserted or an Unlisten is received. + + - A supported command with too few parameter bytes or for which the last + parameter byte is not tagged with EOI (before unlisten) does nothing. + + - A supported command with too many parameter bytes accepts and discards + excess parameter bytes until EOI is asserted or an Unlisten is received. + + - A read or status command that is not followed by a Send Read Data or a + Send Disc Status secondary does nothing. The unexpected secondary is + executed normally. + + - A write command that is not followed by a Receive Write Data secondary + does nothing. The unexpected secondary is executed normally. + + - A Send Read Data or a Send Disc Status secondary that is not preceded by a + read or status command sends a single data byte tagged with EOI. + + - A Receive Write Data secondary that is not preceded by a write command + accepts and discards data bytes until EOI is asserted or an Unlisten is + received. + + The Amigo command sequence does not provide a byte count for disc read and + write commands, so the controller continues to source or accept data bytes + until the device is unaddressed. Normally, this is done by an Unlisten or + Untalk. However, per IEEE-488, a listening device may be unaddressed by IFC, + by an Unlisten, or by addressing the device to talk, and a talking device may + be unaddressed by IFC, by addressing another device to talk (or no device via + Untalk), or by addressing the device to listen. Therefore, we must keep + track of whether the unit stopped talking or listening, and if it has, we + check for command termination. + + If the controller is unaddressed in the middle of a sector transfer, the read + or write must be terminated cleanly to ensure that the disc image is + coherent. It is also permissible to untalk the controller before all of the + requested status bytes are returned. + + In addition, the controller has no way to inform the host that an error has + occurred that prevents the command from continuing. For example, if a data + error is encountered while reading or a protected track is encountered while + writing, the controller must still source or sink data bytes until the + command is terminated by the host. The controller handles read errors by + sourcing a single data byte tagged with EOI and write errors by sinking data + bytes until EOI is seen or the unit is unaddressed. + + Therefore, if the unit is unaddressed while a read, write, or status command + is transferring data, the unit service must be scheduled to end the current + command. Unaddressing while an error condition is present merely terminates + the source or sink operation. + + + Implementation notes: + + 1. The 13365 manual does not indicate that the controller responds to + Universal Clear, but the 12992H loader ROM issues this primary and + expects the controller to initialize. + + 2. It is not necessary to check for listening when processing addressed + commands, as only listeners are called by the bus source. +*/ + +t_bool da_bus_accept (uint32 unit, uint8 data) +{ +const uint8 message_address = data & BUS_ADDRESS; +t_bool accepted = TRUE; +t_bool initiated = FALSE; +t_bool addressed = FALSE; +t_bool stopped_listening = FALSE; +t_bool stopped_talking = FALSE; +char action [40] = ""; +uint32 my_address; + +if (di [da].bus_cntl & BUS_ATN) { /* is it a bus command (ATN asserted)? */ + switch (data & BUS_GROUP) { /* dispatch the bus group */ + + case BUS_PCG: /* primary command group */ + switch (message_address) { + + case 0x04: /* selected device clear */ + case 0x05: /* SDC with parity freeze */ + case 0x14: /* universal clear */ + if (DEBUG_PRI (da_dev, DEB_RWSC)) + fprintf (sim_deb, ">>DA rwsc: Unit %d device cleared\n", unit); + + sim_cancel (&da_unit [unit]); /* cancel any in-progress command */ + dl_idle_controller (&icd_cntlr [unit]); /* idle the controller */ + if_dsj [unit] = 0; /* clear DSJ */ + if_state [unit] = idle; /* idle the interface */ + di_poll_response (da, unit, SET); /* enable PPR */ + + if (DEBUG_PRI (da_dev, DEB_XFER)) + strcpy (action, "device clear"); + break; + + + default: /* unsupported universal command */ + break; /* universals are always accepted */ + } + + break; + + + case BUS_LAG: /* listen address group */ + my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */ + + if (message_address == my_address) { /* is it my listen address? */ + di [da].listeners |= (1 << unit); /* set my listener bit */ + di [da].talker &= ~(1 << unit); /* clear my talker bit */ + + addressed = TRUE; /* unit is now addressed */ + stopped_talking = TRUE; /* MLA stops the unit from talking */ + + if (DEBUG_PRI (da_dev, DEB_XFER)) + sprintf (action, "listen %d", message_address); + } + + else if (message_address == BUS_UNADDRESS) { /* is it an Unlisten? */ + di [da].listeners = 0; /* clear all of the listeners */ + + stopped_listening = TRUE; /* UNL stops the unit from listening */ + + if (DEBUG_PRI (da_dev, DEB_XFER)) + strcpy (action, "unlisten"); + } + + else /* other listen addresses */ + accepted = FALSE; /* are not accepted */ + + break; + + + case BUS_TAG: /* talk address group */ + my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */ + + if (message_address == my_address) { /* is it my talk address? */ + di [da].talker = (1 << unit); /* set my talker bit and clear the others */ + di [da].listeners &= ~(1 << unit); /* clear my listener bit */ + + addressed = TRUE; /* the unit is now addressed */ + stopped_listening = TRUE; /* MTA stops the unit from listening */ + + if (DEBUG_PRI (da_dev, DEB_XFER)) + sprintf (action, "talk %d", message_address); + } + + else { /* it is some other talker (or Untalk) */ + di [da].talker &= ~(1 << unit); /* clear my talker bit */ + + stopped_talking = TRUE; /* UNT or OTA stops the unit from talking */ + + if (message_address != BUS_UNADDRESS) /* other talk addresses */ + accepted = FALSE; /* are not accepted */ + + else /* it's an Untalk */ + if (DEBUG_PRI (da_dev, DEB_XFER)) + strcpy (action, "untalk"); + } + + break; + + + case BUS_SCG: /* secondary command group */ + icd_cntlr [unit].index = 0; /* reset the buffer index */ + + if (di [da].listeners & (1 << unit)) { /* is it a listen secondary? */ + if (if_state [unit] == write_wait /* if we're waiting for a write data secondary */ + && message_address != 0x00) /* but it's not there, */ + abort_command (unit, io_program_error, /* then abort the pending command */ + idle); /* and process the new command */ + + switch (message_address) { /* dispatch the listen secondary */ + + case 0x00: /* Receive Write Data */ + if (if_state [unit] != write_wait) /* if we're not expecting it */ + abort_command (unit, io_program_error, /* abort and sink any data */ + error_sink); + else { /* the sequence is correct */ + if_state [unit] = command_exec; /* the command is ready to execute */ + da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* schedule the unit */ + di_bus_control (da, unit, BUS_NRFD, 0); /* assert NRFD to hold off the card */ + } + + initiated = TRUE; /* log the command or abort initiation */ + break; + + case 0x08: /* disc commands */ + if_command [unit] = disc_command; /* set the command and wait */ + if_state [unit] = opcode_wait; /* for the opcode that must follow */ + break; + + case 0x09: /* CRC (Listen) */ + if_command [unit] = crc_listen; /* set up the command */ + if_state [unit] = error_sink; /* sink any data that will be coming */ + initiated = TRUE; /* log the command initiation */ + break; + + case 0x10: /* Amigo Clear */ + if_command [unit] = amigo_clear; /* set up the command */ + if_state [unit] = parameter_wait; /* a parameter must follow */ + icd_cntlr [unit].length = 1; /* set to expect one (unused) byte */ + break; + + case 0x1E: /* Write Loopback */ + if_command [unit] = write_loopback; /* set up the command */ + if_state [unit] = write_xfer; /* data will be coming */ + icd_cntlr [unit].length = 16; /* accept only the first 16 bytes */ + initiated = TRUE; /* log the command initiation */ + break; + + case 0x1F: /* Initiate Self-Test */ + if_command [unit] = initiate_self_test; /* set up the command */ + if_state [unit] = parameter_wait; /* a parameter must follow */ + icd_cntlr [unit].length = 1; /* set to expect the test ID byte */ + break; + + default: /* an unsupported listen secondary was received */ + abort_command (unit, io_program_error, /* abort and sink any data */ + error_sink); /* that might accompany the command */ + initiated = TRUE; /* log the abort initiation */ + break; + } + } + + + else if (di [da].talker & (1 << unit)) { /* is it a talk secondary? */ + da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* these are always scheduled and */ + initiated = TRUE; /* logged as initiated */ + + if (if_state [unit] == read_wait /* if we're waiting for a send data secondary */ + && message_address != 0x00 /* but it's not there */ + || if_state [unit] == status_wait /* or a send status secondary, */ + && message_address != 0x08) /* but it's not there */ + abort_command (unit, io_program_error, /* then abort the pending command */ + idle); /* and process the new command */ + + switch (message_address) { /* dispatch the talk secondary */ + + case 0x00: /* Send Read Data */ + if (if_state [unit] != read_wait) /* if we're not expecting it */ + abort_command (unit, io_program_error, /* abort and source a data byte */ + error_source); /* tagged with EOI */ + else + if_state [unit] = command_exec; /* the command is ready to execute */ + break; + + case 0x08: /* Read Status */ + if (if_state [unit] != status_wait) /* if we're not expecting it, */ + abort_command (unit, io_program_error, /* abort and source a data byte */ + error_source); /* tagged with EOI */ + else /* all status commands */ + if_state [unit] = read_xfer; /* are ready to transfer data */ + break; + + case 0x09: /* CRC (Talk) */ + if_command [unit] = crc_talk; /* set up the command */ + if_state [unit] = read_xfer; /* data will be going */ + break; + + case 0x10: /* Device-Specified Jump */ + if_command [unit] = device_specified_jump; /* set up the command */ + if_state [unit] = read_xfer; /* data will be going */ + break; + + case 0x1E: /* Read Loopback */ + if_command [unit] = read_loopback; /* set up the command */ + if_state [unit] = read_xfer; /* data will be going */ + break; + + case 0x1F: /* Return Self-Test Result */ + if_command [unit] = return_self_test_result; /* set up the command */ + if_state [unit] = read_xfer; /* data will be going */ + icd_cntlr [unit].length = 1; /* return one byte that indicates */ + buffer [0] = 0; /* that the self-test passed */ + break; + + default: /* an unsupported talk secondary was received */ + abort_command (unit, io_program_error, /* abort and source a data byte */ + error_source); /* tagged with EOI */ + break; + } + } + + + else { /* the unit is not addressed */ + my_address = GET_BUSADR (da_unit [unit].flags); /* get my bus address */ + + if (di [da].talker == 0 && di [da].listeners == 0 /* if there are no talkers or listeners */ + && message_address == my_address) { /* and this is my secondary address, */ + if_command [unit] = amigo_identify; /* then this is an Amigo ID sequence */ + if_state [unit] = command_exec; /* set up for execution */ + da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* schedule the unit */ + initiated = TRUE; /* log the command initiation */ + } + + else /* unaddressed secondaries */ + accepted = FALSE; /* are not accepted */ + } + + + if (accepted) { /* was the command accepted? */ + if (DEBUG_PRI (da_dev, DEB_XFER)) + sprintf (action, "secondary %02XH", message_address); + + if (if_command [unit] != amigo_identify) /* disable PPR for all commands */ + di_poll_response (da, unit, CLEAR); /* except Amigo ID */ + } + + break; /* end of secondary processing */ + } + + + if (addressed && sim_is_active (&da_unit [unit])) { /* is the unit being addressed while it is busy? */ + if_state [unit] = command_wait; /* change the interface state to wait */ + di_bus_control (da, unit, BUS_NRFD, 0); /* and assert NRFD to hold off the card */ + + if (DEBUG_PRI (da_dev, DEB_RWSC)) + fprintf (sim_deb, ">>DA rwsc: Unit %d addressed while controller is busy\n", unit); + } + + if (stopped_listening) { /* was the unit Unlistened? */ + if (icd_cntlr [unit].state == cntlr_busy) /* if the controller is busy, */ + complete_write (unit); /* then check for write completion */ + + else if (if_command [unit] == invalid) /* if a command was aborting, */ + complete_abort (unit); /* then complete it */ + + else if (if_state [unit] == opcode_wait /* if waiting for an opcode */ + || if_state [unit] == parameter_wait) /* or a parameter, */ + abort_command (unit, io_program_error, idle); /* then abort the pending command */ + } + + else if (stopped_talking) { /* was the unit Untalked? */ + if (icd_cntlr [unit].state == cntlr_busy) /* if the controller is busy, */ + complete_read (unit); /* then check for read completion */ + + else if (if_command [unit] == invalid) /* if a command was aborting, */ + complete_abort (unit); /* then complete it */ + } + } /* end of bus command processing */ + + +else { /* it is bus data (ATN is denied) */ + switch (if_state [unit]) { /* dispatch the interface state */ + + case opcode_wait: /* waiting for an opcode */ + if (DEBUG_PRI (da_dev, DEB_XFER)) + sprintf (action, "opcode %02XH", data & DL_OPCODE_MASK); + + buffer [0] = TO_WORD (data, 0); /* set the opcode into the buffer */ + + if (dl_prepare_command (&icd_cntlr [unit], /* is the command valid? */ + da_unit, unit)) { + if_state [unit] = parameter_wait; /* set up to get the pad byte */ + icd_cntlr [unit].index = 0; /* reset the word index for the next byte */ + icd_cntlr [unit].length = /* convert the parameter count to bytes */ + icd_cntlr [unit].length * 2 + 1; /* and include the pad byte */ + } + + else { /* the disc command is invalid */ + abort_command (unit, illegal_opcode, /* abort the command */ + error_sink); /* and sink any parameter bytes */ + initiated = TRUE; /* log the abort initiation */ + } /* (the unit cannot be busy) */ + break; + + + case parameter_wait: /* waiting for a parameter */ + if (DEBUG_PRI (da_dev, DEB_XFER)) + sprintf (action, "parameter %02XH", data); + + put_buffer_byte (&icd_cntlr [unit], data); /* add the byte to the buffer */ + + if (icd_cntlr [unit].length == 0) /* is this the last parameter? */ + if (di [da].bus_cntl & BUS_EOI) /* does the host agree? */ + initiated = start_command (unit); /* start the command and log the initiation */ + + else { /* the parameter count is wrong */ + abort_command (unit, io_program_error, /* abort the command and sink */ + error_sink); /* any additional parameter bytes */ + initiated = TRUE; /* log the abort initiation */ + } + break; + + + case write_xfer: /* transferring write data */ + if (icd_cntlr [unit].length > 0) /* if there is more to transfer */ + put_buffer_byte (&icd_cntlr [unit], data); /* then add the byte to the buffer */ + + /* fall into error_sink handler */ + + case error_sink: /* sinking data after an error */ + if (DEBUG_PRI (da_dev, DEB_XFER)) + sprintf (action, "data %03o", data); + + if (di [da].bus_cntl & BUS_EOI) /* is this the last byte from the bus? */ + icd_cntlr [unit].eod = SET; /* indicate EOD to the controller */ + + di_bus_control (da, unit, BUS_NRFD, 0); /* assert NRFD to hold off the card */ + + da_unit [unit].wait = icd_cntlr [unit].data_time; /* schedule the unit */ + break; + + + default: /* data was received in the wrong state */ + abort_command (unit, io_program_error, /* report the error */ + error_sink); /* and sink any data that follows */ + + if (DEBUG_PRI (da_dev, DEB_XFER)) + sprintf (action, "unhandled data %03o", data); + break; + } + } + + +if (accepted && DEBUG_PRI (da_dev, DEB_XFER)) + fprintf (sim_deb, ">>DA xfer: HP-IB address %d accepted %s\n", + GET_BUSADR (da_unit [unit].flags), action); + +if (da_unit [unit].wait > 0) /* was service requested? */ + activate_unit (&da_unit [unit]); /* schedule the unit */ + +if (initiated && DEBUG_PRI (da_dev, DEB_RWSC)) + if (if_command [unit] == disc_command) + fprintf (sim_deb, ">>DA rwsc: Unit %d position %" T_ADDR_FMT "d %s disc command initiated\n", + unit, da_unit [unit].pos, dl_opcode_name (ICD, icd_cntlr [unit].opcode)); + else + fprintf (sim_deb, ">>DA rwsc: Unit %d %s command initiated\n", + unit, if_command_name [if_command [unit]]); + +return accepted; /* indicate the acceptance condition */ +} + + +/* Respond to the bus control lines. + + The indicated unit is notified of the new control state on the bus. There + are two conditions to which we must respond: + + 1. An Interface Clear is initiated. IFC unaddresses all units, so any + in-progress disc command must be terminated as if an Untalk and Unlisten + were accepted from the data bus. + + 2. Attention and Not Ready for Data are denied. A device addressed to talk + must wait for ATN to deny before data may be sent. Also, a listener that + has asserted NRFD must deny it before a talker may send data. If the + interface is sending data and both ATN and NRFD are denied, then we + reschedule the service routine to send the next byte. +*/ + +void da_bus_respond (CARD_ID card, uint32 unit, uint8 new_cntl) +{ +if (new_cntl & BUS_IFC) { /* is interface clear asserted? */ + di [da].listeners = 0; /* perform an Unlisten */ + di [da].talker = 0; /* and an Untalk */ + + if (icd_cntlr [unit].state == cntlr_busy) { /* is the controller busy? */ + complete_write (unit); /* check for write completion */ + complete_read (unit); /* or read completion */ + + if (da_unit [unit].wait > 0) /* is service needed? */ + activate_unit (&da_unit [unit]); /* activate the unit */ + } + + else if (if_command [unit] == invalid) /* if a command was aborting, */ + complete_abort (unit); /* then complete it */ + + else if (if_state [unit] == opcode_wait /* if we're waiting for an opcode */ + || if_state [unit] == parameter_wait) /* or a parameter, */ + abort_command (unit, io_program_error, idle); /* then abort the pending command */ + } + +if (!(new_cntl & (BUS_ATN | BUS_NRFD)) /* is the card in data mode and ready for data? */ + && (if_state [unit] == read_xfer /* is the interface waiting to send data */ + || if_state [unit] == error_source)) /* or source error bytes? */ + da_service (&da_unit [unit]); /* start or resume the transfer */ +} + + + +/* Amigo disc local utility routines */ + + +/* Start a command with parameters. + + A command that has been waiting for all of its parameters to be received is + now ready to start. If this is a disc command, call the disc library to + validate the parameters and, if they are OK, to start the command. Status + commands return the status values in the sector buffer and the number of + words that were returned in the buffer length, which we convert to a byte + count. + + If the disc command was accepted, the library returns a pointer to the unit + to be scheduled. For an ICD controller, the unit is always the one currently + addressed, so we simply test if the return is not NULL. If it isn't, then we + set the next interface state as determined by the command that is executing. + For example, a Read command sets the interface to read_wait status in order + to wait until the accompanying Send Read Data secondary is received. + + If the return is NULL, then the command was rejected, so we set DSJ = 1 and + leave the interface state in parameter_wait; the controller status will have + been set to the reason for the rejection. + + If the next interface state is command_exec, then the disc command is ready + for execution, and we return TRUE to schedule the unit service. Otherwise, + we return FALSE, and the appropriate action will be taken by the caller. + + For all other commands, execution begins as soon as the correct parameters + are received, so we set command_exec state and return TRUE. (Only Amigo + Clear and Initiate Self Test require parameters, so they will be the only + other commands that must be started here.) + + + Implementation notes: + + 1. As the ICD implementation does not need to differentiate between unit and + controller commands, the return value from the dl_start_command routine + is not used other than as an indication of success or failure. +*/ + +static t_bool start_command (uint32 unit) +{ +if (if_command [unit] == disc_command) { /* are we starting a disc command? */ + if (dl_start_command (&icd_cntlr [unit], da_unit, unit)) { /* start the command; was it successful? */ + icd_cntlr [unit].length = icd_cntlr [unit].length * 2; /* convert the return length from words to bytes */ + if_state [unit] = next_state [icd_cntlr [unit].opcode]; /* set the next interface state */ + } + + else /* the command was rejected */ + if_dsj [unit] = 1; /* so indicate an error */ + + if (if_state [unit] == command_exec) /* if the command is executing */ + return TRUE; /* activate the unit */ + + else { /* if we must wait */ + da_unit [unit].wait = 0; /* for another secondary, */ + return FALSE; /* then skip the activation */ + } + } + +else { /* all other commands */ + if_state [unit] = command_exec; /* execute as soon */ + da_unit [unit].wait = icd_cntlr [unit].cmd_time; /* as they */ + return TRUE; /* are received */ + } +} + + +/* Abort an in-process command. + + A command sequence partially received via the bus must be aborted. The cause + might be an unknown secondary, an illegal disc command opcode, an improper + secondary sequence (e.g., a Read not followed by Send Read Data), an + incorrect number of parameters, or unaddressing before the sequence was + complete. In any event, the controller and interface are set to an abort + state, and the DSJ value is set to 1 to indicate an error. +*/ + +static void abort_command (uint32 unit, CNTLR_STATUS status, IF_STATE state) +{ +if_command [unit] = invalid; /* indicate an invalid command */ +if_state [unit] = state; /* set the interface state as directed */ +if_dsj [unit] = 1; /* set DSJ to indicate an error condition */ +dl_end_command (&icd_cntlr [unit], status); /* place the disc controller into the wait state */ +return; +} + + +/* Complete an in-process read command. + + An Untalk terminates a Read, Read Full Sector, Read Without Verify, Read With + Offset, or Cold Load Read command, which must be tied off cleanly by setting + the end-of-data condition and calling the service routine. This is required + only if the read has not already aborted (e.g., for an auto-seek error). + + If a read is in progress, the controller will be busy, and the interface + state will be either command_exec (if between sectors) or read_xfer (if + within a sector). We set up the end phase for the command and schedule the + disc service to tidy up. + + If a read has aborted, the controller will be waiting, and the interface + state will be error_source. In this latter case, we no nothing, as the + controller has already set the required error status. + + We must be careful NOT to trigger on an Untalk that may follow the opcode and + precede the Send Read Data sequence. In this case, the controller will be + busy, but the interface state will be either read_wait or status_wait. + + + Implementation notes: + + 1. The test for controller busy is made before calling this routine. This + saves the call overhead for the most common case, which is the card is + being unaddressed after command completion. + + 2. There is no need to test if we are processing a disc command, as the + controller would not be busy otherwise. + + 3. If an auto-seek will be needed to continue the read, but the seek will + fail, then an extra delay is inserted before the service call to start + the next sector. Once an Untalk is received, this delay is no longer + needed, so it is cancelled before rescheduling the service routine. +*/ + +static void complete_read (uint32 unit) +{ +if ((if_state [unit] == command_exec /* is a command executing */ + || if_state [unit] == read_xfer) /* or is data transferring */ + && (dl_classify (icd_cntlr [unit]) == class_read /* and the controller is executing */ + || dl_classify (icd_cntlr [unit]) == class_status)) { /* a read or status command? */ + icd_cntlr [unit].eod = SET; /* set the end of data flag */ + + if_state [unit] = command_exec; /* set to execute */ + da_unit [unit].PHASE = end_phase; /* the completion phase */ + + sim_cancel (&da_unit [unit]); /* cancel the EOT delay */ + da_unit [unit].wait = icd_cntlr [unit].data_time; /* reschedule for completion */ + } + +return; +} + + +/* Complete an in-process write command. + + Normally, the host sends a byte tagged with EOI to end a Write, Write Full + Sector, or Initialize command. However, an Unlisten may terminate a write, + which must be tied off cleanly by setting the end-of-data condition and + calling the service routine. This is required only if the write has not + already aborted (e.g., for a write-protected disc). + + If a write is in progress, the controller will be busy, and the interface + state will be either command_exec (if between sectors) or write_xfer (if + within a sector). We set up the end phase for the command and schedule the + disc service to tidy up. + + If a write has aborted, the controller will be waiting, and the interface + state will be error_sink. In this latter case, we do nothing, as the + controller has already set the required error status. + + We must be careful NOT to trigger on the Unlisten that may follow the opcode + and precede the Receive Write Data sequence. In this case, the controller + will be busy, but the interface state will be write_wait. + + + Implementation notes: + + 1. The test for controller busy is made before calling this routine. This + saves the call overhead for the most common case, which is the card is + being unaddressed after command completion. + + 2. There is no need to test if we are processing a disc command, as the + controller would not be busy otherwise. +*/ + +static void complete_write (uint32 unit) +{ +if ((if_state [unit] == command_exec /* is a command executing */ + || if_state [unit] == write_xfer) /* or is data transferring */ + && dl_classify (icd_cntlr [unit]) == class_write) { /* and the controller is executing a write? */ + icd_cntlr [unit].eod = SET; /* set the end of data flag */ + + if_state [unit] = command_exec; /* set to execute */ + da_unit [unit].PHASE = end_phase; /* the completion phase */ + da_unit [unit].wait = icd_cntlr [unit].data_time; /* ensure that the controller will finish */ + } + +return; +} + + +/* Complete an in-process command abort. + + Errors in the command protocol begin an abort sequence that may involve + sourcing or sinking bytes to allow the sequence to complete as expected by + the CPU. Unaddressing the unit terminates the aborted command. + + If an abort is in progress, and the interface is not idle, the end-of-data + indication is set, and the disc service routine is called directly to process + the completion of the abort. The service routine will terminate the + error_source or error_sink state cleanly and then idle the interface. + + + Implementation notes: + + 1. The test for an abort-in-progress is made before calling this routine. + This saves the call overhead for the most common case, which is the card + is being unaddressed after normal command completion. +*/ + +static void complete_abort (uint32 unit) +{ +if (if_state [unit] != idle) { /* is the interface busy? */ + icd_cntlr [unit].eod = SET; /* set the end of data flag */ + da_service (&da_unit [unit]); /* and process the abort completion */ + } + +return; +} + + +/* Get a byte from the sector buffer. + + The next available byte in the sector buffer is returned to the caller. The + determination of which byte of the 16-bit buffer word to return is made by + the polarity of the buffer byte count. The count always begins with an even + number, as it is set by doubling the word count returned from the disc + library. Therefore, because we decrement the count first, the upper byte is + indicated by an odd count, and the lower byte is indicated by an even count. + The buffer index is incremented only after the lower byte is returned. +*/ + +static uint8 get_buffer_byte (CVPTR cvptr) +{ +cvptr->length = cvptr->length - 1; /* count the byte */ + +if (cvptr->length & 1) /* is the upper byte next? */ + return UPPER_BYTE (buffer [cvptr->index]); /* return the byte */ +else /* the lower byte is next */ + return LOWER_BYTE (buffer [cvptr->index++]); /* return the byte and bump the word index */ +} + + +/* Put a byte into the sector buffer. + + The supplied byte is stored in the sector buffer. The determination of which + byte of the 16-bit buffer word to store is made by the polarity of the buffer + byte count. The count always begins with an even number, as it is set by + doubling the word count returned from the disc library. Therefore, because + we decrement the count first, the upper byte is indicated by an odd count, + and the lower byte is indicated by an even count. The buffer index is + incremented only after the lower byte is stored. +*/ + +static void put_buffer_byte (CVPTR cvptr, uint8 data) +{ +cvptr->length = cvptr->length - 1; /* count the byte */ + +if (cvptr->length & 1) /* is the upper byte next? */ + buffer [cvptr->index] = TO_WORD (data, 0); /* save the byte */ +else /* the lower byte is next */ + buffer [cvptr->index++] |= TO_WORD (0, data); /* merge the byte and bump the word index */ +return; +} + + +/* Activate the unit. + + The specified unit is activated using the unit's "wait" time. If debugging + is enabled, the activation is logged to the debug file. +*/ + +static t_stat activate_unit (UNIT *uptr) +{ +int32 unit; +t_stat result; + +if (DEBUG_PRI (da_dev, DEB_SERV)) { + unit = uptr - da_unit; + + fprintf (sim_deb, ">>DA serv: Unit %d state %s delay %d service scheduled\n", + unit, if_state_name [if_state [unit]], uptr->wait); + } + +result = sim_activate (uptr, uptr->wait); /* activate the unit */ +uptr->wait = 0; /* reset the activation time */ + +return result; /* return the activation status */ +} diff --git a/HP2100/hp2100_diag.txt b/HP2100/hp2100_diag.txt index 012a1304..538d75c0 100644 --- a/HP2100/hp2100_diag.txt +++ b/HP2100/hp2100_diag.txt @@ -1,6 +1,6 @@ SIMH/HP 21XX DIAGNOSTICS PERFORMANCE ==================================== - Last update: 2014-12-18 + Last update: 2016-12-18 The HP 24396 diagnostic suite has been run against the SIMH HP 21xx simulation. @@ -118,7 +118,7 @@ Part Number DSN Diagnostic Name Code Vers. Result 20433-60001 -- HP 3030 Magnetic Tape Subsystem -- - Not tested 22682-16017 177777 HP 2100 Fixed Head Disc/Drum (277x) 1612 3.3-0 Passed 24197-60001 -- 12875 Processor Interconnect Cable B 3.7-1 Passed -24203-60001 -- HP2100A Cartridge Disc Memory (2871) A 3.3-0 Partial +24203-60001 -- HP2100A Cartridge Disc Memory (2870) A 3.3-0 Partial The following online diagnostics were run for devices not supported by the @@ -3299,7 +3299,7 @@ TEST RESULT: Passed. ----------------------------------------------------------------- -DSN (none) - HP2100A Cartridge Disc Memory (2871) (multiple unit) +DSN (none) - HP2100A Cartridge Disc Memory (2870) (multiple unit) ----------------------------------------------------------------- TESTED DEVICE: DP (hp2100_dp.c) @@ -3307,10 +3307,10 @@ TESTED DEVICE: DP (hp2100_dp.c) BINARY TAPE: 24203-60001 Rev. A CONFIGURATION: sim> set DPC 12557A - sim> attach DPC0 scratch.U0.2871.disc - sim> attach DPC1 scratch.U1.2871.disc - sim> attach DPC2 scratch.U2.2871.disc - sim> attach DPC3 scratch.U3.2871.disc + sim> attach DPC0 scratch.U0.2870.disc + sim> attach DPC1 scratch.U1.2870.disc + sim> attach DPC2 scratch.U2.2870.disc + sim> attach DPC3 scratch.U3.2870.disc sim> deposit S 002211 sim> reset sim> go 2 @@ -3370,7 +3370,7 @@ TEST NOTES: Four passes are required to test all head/unit combinations. -------------------------------------------------------------------- -DSN (none) - HP2100A Cartridge Disc Memory (2871) (user interaction) +DSN (none) - HP2100A Cartridge Disc Memory (2870) (user interaction) -------------------------------------------------------------------- TESTED DEVICE: DP (hp2100_dp.c) @@ -3378,7 +3378,7 @@ TESTED DEVICE: DP (hp2100_dp.c) BINARY TAPE: 24203-60001 Rev. A CONFIGURATION: sim> set DPC 12557A - sim> attach DPC0 scratch.U0.2871.disc + sim> attach DPC0 scratch.U0.2870.disc sim> deposit S 002211 sim> reset sim> go 2 diff --git a/HP2100/hp2100_disclib.c b/HP2100/hp2100_disclib.c new file mode 100644 index 00000000..617b99e2 --- /dev/null +++ b/HP2100/hp2100_disclib.c @@ -0,0 +1,2408 @@ +/* hp2100_disclib.c: HP MAC/ICD disc controller simulator library + + Copyright (c) 2011-2017, J. David Bryan + Copyright (c) 2004-2011, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. + + 22-Apr-17 JDB A failed sim_fseek call now causes a drive fault + 09-Mar-17 JDB Added the simulator name to the "perror" message. + 17-Jan-17 JDB Moved "hp2100_defs.h" inclusion to "hp2100_disclib.c" + 13-May-16 JDB Modified for revised SCP API function parameter types + 04-Mar-16 JDB Name changed to "hp2100_disclib" until HP 3000 integration + 24-Dec-14 JDB Added casts for explicit downward conversions + 27-Oct-14 JDB Corrected the relative movement calculation in start_seek + 20-Dec-12 JDB sim_is_active() now returns t_bool + 24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash + 07-May-12 JDB Corrected end-of-track delay time logic + 02-May-12 JDB First release + 09-Nov-11 JDB Created disc controller common library from DS simulator + + References: + - 13037 Disc Controller Technical Information Package (13037-90902, Aug-1980) + - HP 13365 Integrated Controller Programming Guide (13365-90901, Feb-1980) + - HP 1000 ICD/MAC Disc Diagnostic Reference Manual (5955-4355, Jun-1984) + - RTE-IVB System Manager's Manual (92068-90006, Jan-1983) + - DVR32 RTE Moving Head Driver source (92084-18711, Revision 5000) + + + This library provides common functions required by HP disc controllers. It + implements the 13037 MAC and 13365 ICD controller command sets used with the + 7905/06/20/25 and 7906H/20H/25H disc drives. + + The library is an adaptation of the code originally written by Bob Supnik + for the DS simulator. DS simulates a 13037 controller connected via a 13175 + disc interface to an HP 1000 computer. To create the library, the functions + of the controller were separated from the functions of the interface. This + allows the library to work with other CPU interfaces, such as the 12821A + HP-IB disc interface, that use substantially different communication + protocols. The library functions implement the controller command set for + the drive units. The interface functions handle the transfer of commands and + data to and from the CPU. + + As a result of this separation, the library does not handle the data transfer + between the controller and the interface directly. Instead, data is moved + between the interface and a sector buffer by the interface simulator, and + then the buffer is passed to the disc library for reading or writing. This + buffer is also used to pass disc commands and parameters to the controller, + and to receive status information from the controller. Only one buffer is + needed per interface, regardless of the number of controllers or units + handled, as a single interface cannot perform data transfers concurrently + with controller commands. + + The library provides routines to prepare, start, and end commands, service + units, and poll drives for Attention status. In addition, routines are + provided to attach and detach disc images from drive units, load and unload + disc heads, classify commands, and provide opcode and phase name strings for + debugging. + + Autosizing is supported when attaching a disc image. If enabled, the model + of the drive is set to match the disc image size. For example, if a 50 MB + disc image is attached to a unit set for autosizing, the unit's model will be + set to a 7920(H). + + The interface simulator declares a structure that contains the state + variables for a controller. A MAC controller may handle multiple disc units. + An ICD controller handles only a single disc unit, but multiple controllers + may be employed to support several drives on a given interface. The type of + the controller (MAC or ICD) is contained in the structure, which is passed to + the disc library routines. The minor differences in controller action + between the two are handled internally. A macro (CNTLR_INIT) is provided to + initialize the structure. + + The interface simulator also declares the sector buffer. The buffer is an + array containing DL_BUFSIZE 16-bit elements. The address of the buffer is + stored in the controller state structure. The controller maintains the + current index into the buffer, as well as the length of valid data stored + there. Other than setting the length when the controller places data into + the buffer and resetting the index at the start of a sector read or write, + the interface simulator is free to manipulate these values as desired. + + In general, a user of the library is free to read any of the controller state + variable structure fields. Writing to the fields generally will interfere + with controller operations, with these exceptions: + + Field Name Description + =========== ============================ + status controller status + eod end of data flag + index data buffer index + length data buffer length + seek_time per-cylinder seek delay time + sector_time intersector delay time + cmd_time command response time + data_time data transfer response time + wait_time command wait time + + In hardware, the controller executes in three basic states: + + 1. In the Poll Loop, which looks for commands and drive attention requests. + + In each pass of the loop, the next CPU interface in turn is checked for a + command; if present, it is executed. If none are pending, all drives are + checked in turn until one is found with Attention status; if none are + found, the loop continues. If a drive is requesting attention, the + associated CPU interface is connected to check for a command; if present, + it is executed. If not, and the interface allows interrupts, an + interrupt request is made and the Command Wait Loop is entered. If + interrupts are not allowed, the Poll Loop continues. + + 2. In the Command Wait Loop, which looks for commands. + + In each pass of the loop, the current CPU interface is checked for a + command; if present, it is executed. If not, the Command Wait Loop + continues. While in the loop, a 1.8 second timer is running. If it + expires before a command is received, the file mask is reset, and the + Poll Loop is entered. + + 3. In command execution, which processes the current command. + + During command execution, the waits for input parameters, seek + completion, data transfers, and output status words are handled + internally. Each wait is governed by the 1.8 second timer; if it + expires, the command is aborted. + + In simulation, these states are represented by the values cntlr_idle, + cntlr_wait, and cntlr_busy, respectively. + + A MAC controller operates from one to eight drives, represented by an array + of one to eight units. When operating multiple units, a pointer to the first + unit of a contiguous array is passed, and the unit number present in the + command is used to index to the target unit. + + A MAC controller emulation also requires an array of two contiguous auxiliary + units containing a controller unit and a command wait timeout unit. Commands + that do not access the drive, such as Address Record, are scheduled on the + controller unit to allow controller commands to execute while drive units are + seeking. The command wait timer limits the amount of time the controller + will wait for the interface to supply a command or parameter. A pointer to + the auxiliary unit array is set up during controller state variable + initialization. The auxiliary array may be separate or an extension of the + drive unit array. + + An ICD controller manages a single unit corresponding to the drive in which + the controller is integrated. An interface declares a unit array + corresponding to the number of drives supported and passes the unit number to + use to the command preparation and start routines. Auxiliary units are not + used, and all commands are scheduled on the drive unit associated with a + given controller. + + The library provides a unit service routine to handle all of the disc + commands. The routine is called from the interface service routine to handle + the common disc actions, while the interface routine handles actions specific + to the operation of the interface (such as data transfer). + + The service routine schedules the unit to continue command execution under + these conditions: + + 1. A Seek or Recalibrate command is waiting for the seek completion. + + 2. A read or write command is waiting for the first data transfer of a + sector to start. + + 3. A read or write command is waiting for the next sector to start after + the final data transfer of the preceding sector. + + 4. A Verify command is waiting for the end of the current sector. + + The library also provides controller and timer service routines for MAC + emulations. All three (unit, controller, and timer) must be called from + their respective interface service routines before any interface-specific + actions, if any, are taken. + + On return from the library unit or controller service routines, the "wait" + field of the UNIT structure will be set to the activation time if the unit + is to be scheduled. The caller is responsible for activating the unit. If + the caller uses this feature, the field should be reset to zero before the + next service call. + + The MAC timer unit is activated by the library, and its "wait" field is not + used. The timer starts when a command other than End, Seek, or Recalibrate + completes, or when the controller is waiting for the interface to supply or + accept a parameter during command execution. It stops when an End, Seek, or + Recalibrate command completes, a command is prepared for execution, or the + final parameter has been supplied or accepted by the interface during command + execution. + + The controller maintains six variables in each drive's unit structure: + + wait -- the current service activation time + pos -- the current byte offset into the disc image file + u3 (CYL) -- the current drive cylinder + u4 (STAT) -- the drive status (Status-2) + u5 (OP) -- the drive operation in process + u6 (PHASE) -- the current operation phase + + These and other definitions are in the file hp_disclib.h, which must be + included in the interface simulator. + + The controller library supports up to eight drives per MAC controller and one + drive per ICD controller. Unit numbers 0-7 represent valid drive addresses + for a MAC controller. The unit number field is ignored for an ICD + controller, and unit 0 is always implied. In simulation, MAC unit numbers + correspond one-for-one with device units, whereas one ICD controller is + associated with each of the several device units that are independently + addressed as unit 0. + + The MAC controller firmware allows access to unit numbers 8-10 without + causing a Unit Unavailable error. Instead, the controller reports these + legal-but-invalid units as permanently offline. + + + Implementation notes: + + 1. The library does not simulate sector headers and trailers. Initialize + and Write Full Sector commands ignore the SPD bits and the supplied + header and trailer words. Read Full Sector fills in the header with the + current CHS address and sets the SPD bits to zero. The CRC and ECC words + in the trailer are returned as zeros. Programs that depend on drives + retaining the set values will fail. + + 2. The library does not simulate drive hold bits or support multiple CPU + interfaces connected to the same controller. CPU access to a valid drive + always succeeds. + + 3. The library does not simulate interface signals or function bus orders, + except for EOD (End of Data) and BUSY. The interface simulators must + decide for themselves what actions to take (e.g., interrupting the CPU) + on the basis of the controller state. + + 4. The command/sector buffer is an array of 16-bit elements. Byte-oriented + interface simulators, such as the 12821A HP-IB Disc Interface, must do + their own byte packing and unpacking. + + 5. The SAVE command does not save the "wait" and "pos" fields of the UNIT + structure automatically. To ensure that they are saved, they are + referenced by hidden, read-only registers. +*/ + + + +#include + +#include "hp2100_defs.h" /* this must reflect the machine used */ +#include "hp2100_disclib.h" + + + +/* Command accessors */ + +#define DL_V_OPCODE 8 /* bits 12- 8: general opcode */ +#define DL_V_HOLD 7 /* bits 7- 7: general hold flag */ +#define DL_V_UNIT 0 /* bits 3- 0: general unit number */ + +#define DL_V_SPD 13 /* bits 15-13: Initialize S/P/D flags */ +#define DL_V_CHEAD 6 /* bits 7- 6: Cold Load Read head number */ +#define DL_V_CSECT 0 /* bits 5- 0: Cold Load Read sector number */ +#define DL_V_FRETRY 4 /* bits 7- 4: Set File Mask retry count */ +#define DL_V_FDECR 3 /* bits 3- 3: Set File Mask seek decrement */ +#define DL_V_FSPEN 2 /* bits 2- 2: Set File Mask sparing enable */ +#define DL_V_FCYLM 1 /* bits 1- 1: Set File Mask cylinder mode */ +#define DL_V_FAUTSK 0 /* bits 0- 0: Set File Mask auto seek */ + +#define DL_V_FMASK 0 /* bits 3- 0: Set File Mask (flags combined) */ + + +#define DL_M_OPCODE 037 /* opcode mask */ +#define DL_M_UNIT 017 /* unit mask */ + +#define DL_M_SPD 007 /* S/P/D flags mask */ +#define DL_M_CHEAD 003 /* Cold Load Read head number mask */ +#define DL_M_CSECT 077 /* Cold Load Read sector number mask */ +#define DL_M_FRETRY 017 /* Set File Mask retry count mask */ +#define DL_M_FMASK 017 /* Set File Mask flags mask */ + + +#define GET_OPCODE(c) (CNTLR_OPCODE) (((c) >> DL_V_OPCODE) & DL_M_OPCODE) +#define GET_UNIT(c) (((c) >> DL_V_UNIT) & DL_M_UNIT) + +#define GET_SPD(c) (((c) >> DL_V_SPD) & DL_M_SPD) +#define GET_CHEAD(c) (((c) >> DL_V_CHEAD) & DL_M_CHEAD) +#define GET_CSECT(c) (((c) >> DL_V_CSECT) & DL_M_CSECT) +#define GET_FRETRY(c) (((c) >> DL_V_FRETRY) & DL_M_FRETRY) +#define GET_FMASK(c) (((c) >> DL_V_FMASK) & DL_M_FMASK) + +#define DL_FDECR (1 << DL_V_FDECR) +#define DL_FSPEN (1 << DL_V_FSPEN) +#define DL_FCYLM (1 << DL_V_FCYLM) +#define DL_FAUTSK (1 << DL_V_FAUTSK) + + +/* Parameter accessors */ + +#define DL_V_HEAD 8 /* bits 12- 8: head number */ +#define DL_V_SECTOR 0 /* bits 7- 0: sector number */ + +#define DL_M_HEAD 0017 /* head number mask */ +#define DL_M_SECTOR 0377 /* sector number mask */ + +#define GET_HEAD(p) (((p) >> DL_V_HEAD) & DL_M_HEAD) +#define GET_SECTOR(p) (((p) >> DL_V_SECTOR) & DL_M_SECTOR) + +#define SET_HEAD(c) (uint16) (((c)->head & DL_M_HEAD) << DL_V_HEAD) +#define SET_SECTOR(c) (uint16) (((c)->sector & DL_M_SECTOR) << DL_V_SECTOR) + + +/* Drive properties table. + + In hardware, drives report their Drive Type numbers to the controller upon + receipt of a Request Status tag bus command. The drive type is used to + determine the legal range of head and sector addresses (the drive itself will + validate the cylinder address during seeks). + + In simulation, we set up a table of drive properties and use the model ID as + an index into the table. The table is used to validate seek parameters and + to provide the mapping between CHS addresses and the linear byte addresses + required by the host file access routines. + + The 7905/06(H) drives consist of removable and fixed platters, whereas the + 7920(H)/25(H) drives have only removable multi-platter packs. As a result, + 7905/06 drives are almost always accessed in platter mode, i.e., a given + logical disc area is fully contained on either the removable or fixed + platter, whereas the 7920/25 drives are almost always accessed in cylinder + mode with logical disc areas spanning some or all of the platters. + + Disc image files are arranged as a linear set of tracks. To improve + locality of access, tracks in the 7905/06 images are grouped per-platter, + whereas tracks on the 7920 and 7925 are sequential by cylinder and head + number. + + The simulator maps the tracks on the 7905/06 removable platter (heads 0 and + 1) to the first half of the disc image, and the tracks on the fixed platter + (heads 2 and, for the 7906 only, 3) to the second half of the image. For the + 7906(H), the cylinder-head order of the tracks is 0-0, 0-1, 1-0, 1-1, ..., + 410-0, 410-1, 0-2, 0-3, 1-2, 1-3, ..., 410-2, 410-3. The 7905 order is the + same, except that head 3 tracks are omitted. + + For the 7920(H)/25(H), all tracks appear in cylinder-head order, e.g., 0-0, + 0-1, 0-2, 0-3, 0-4, 1-0, 1-1, ..., 822-2, 822-3, 822-4 for the 7920(H). + + This variable-access geometry is accomplished by defining additional "heads + per cylinder" values for the fixed and removable sections of each drive that + indicates the number of heads that should be grouped for locality. The + removable values are set to 2 on the 7905 and 7906, indicating that those + drives typically use cylinders consisting of two heads. They are set to the + number of heads per drive for the 7920 and 7925, as those typically use + cylinders encompassing the entire pack. +*/ + +#define D7905_RH 2 +#define D7905_FH (D7905_HEADS - D7905_RH) + +#define D7906_RH 2 +#define D7906_FH (D7906_HEADS - D7906_RH) + +#define D7920_RH D7920_HEADS +#define D7920_FH (D7920_HEADS - D7920_RH) + +#define D7925_RH D7925_HEADS +#define D7925_FH (D7925_HEADS - D7925_RH) + +typedef struct { + uint32 sectors; /* sectors per head */ + uint32 heads; /* heads per cylinder*/ + uint32 cylinders; /* cylinders per drive */ + uint32 words; /* words per drive */ + uint32 type; /* drive type */ + uint32 remov_heads; /* number of removable-platter heads */ + uint32 fixed_heads; /* number of fixed-platter heads */ + } DRIVE_PROPERTIES; + + +static const DRIVE_PROPERTIES drive_props [] = { + { D7905_SECTS, D7905_HEADS, D7905_CYLS, D7905_WORDS, D7905_TYPE, D7905_RH, D7905_FH }, + { D7906_SECTS, D7906_HEADS, D7906_CYLS, D7906_WORDS, D7906_TYPE, D7906_RH, D7906_FH }, + { D7920_SECTS, D7920_HEADS, D7920_CYLS, D7920_WORDS, D7920_TYPE, D7920_RH, D7920_FH }, + { D7925_SECTS, D7925_HEADS, D7925_CYLS, D7925_WORDS, D7925_TYPE, D7925_RH, D7925_FH } + }; + +#define PROPS_COUNT (sizeof (drive_props) / sizeof (drive_props [0])) + + +/* Convert a CHS address to a block offset. + + A cylinder/head/sector address is converted into a linear block address that + may be used to calculate a byte offset to pass to the file access routines. + The conversion logic is: + + if Head < removable_heads_per_cylinder then + tracks := Cylinder * removable_heads_per_cylinder + Head; + else + tracks := cylinders_per_drive * removable_heads_per_cylinder + + Cylinder * fixed_heads_per_cylinder + (Head - removable_heads_per_cylinder); + + block := tracks * sectors_per_track + Sector; + + byte_offset := block * words_per_sector * bytes_per_word; + + The byte offset is calculated in two steps to allow for future controller + enhancements to support the CS/80 command set and its associated linear block + addressing mode. +*/ + +#define TO_BLOCK(cylinder,head,sector,model) \ + (((head) < drive_props [model].remov_heads \ + ? (cylinder) * drive_props [model].remov_heads + (head) \ + : drive_props [model].cylinders * drive_props [model].remov_heads \ + + ((cylinder) * drive_props [model].fixed_heads + (head) - drive_props [model].remov_heads)) \ + * drive_props [model].sectors + (sector)) + +#define TO_OFFSET(block) ((block) * DL_WPSEC * sizeof (uint16)) + + +/* Estimate the current sector. + + The sector currently passing under the disc heads is estimated from the + current simulator time (i.e., the count of instructions since startup) and + the simulated disc rotation time. The computation logic is: + + per_sector_time := word_transfer_time * words_per_sector + intersector_time; + + current_sector := (current_time / per_sector_time) MOD sectors_per_track; +*/ + +#define GET_CURSEC(cvptr,uptr) \ + ((uint16) fmod (sim_gtime() / (double) ((cvptr->data_time * DL_WPSEC + cvptr->sector_time)), \ + (double) drive_props [GET_MODEL (uptr->flags)].sectors)) + + +/* Command properties table. + + The validity of each command for a specified controller type is checked + against the command properties table when it is prepared. The table also + includes the count of inbound and outbound properties, the class of the + command, and flags to indicate certain common actions that should be taken. +*/ + +typedef struct { + uint32 params_in; /* count of input parameters */ + uint32 params_out; /* count of output parameters */ + CNTLR_CLASS classification; /* command classification */ + t_bool valid [type_count]; /* per-type command validity */ + t_bool clear_status; /* command clears the controller status */ + t_bool unit_field; /* command has a unit field */ + t_bool unit_check; /* command checks the unit number validity */ + t_bool unit_access; /* command accesses the drive unit */ + t_bool seek_wait; /* command waits for seek completion */ + } DS_PROPS; + +typedef const DS_PROPS *PRPTR; + +#define T TRUE +#define F FALSE + +static const DS_PROPS cmd_props [] = { +/* par par opcode valid for clear unit unit unit seek */ +/* in out classification MAC ICD stat field check acces wait */ + { 0, 0, class_read, { T, T }, T, F, T, T, F }, /* 00 = cold load read */ + { 0, 0, class_control, { T, T }, T, T, T, T, T }, /* 01 = recalibrate */ + { 2, 0, class_control, { T, T }, T, T, T, T, F }, /* 02 = seek */ + { 0, 2, class_status, { T, T }, F, T, F, F, F }, /* 03 = request status */ + { 0, 1, class_status, { T, T }, T, T, T, F, F }, /* 04 = request sector address */ + { 0, 0, class_read, { T, T }, T, T, T, T, T }, /* 05 = read */ + { 0, 0, class_read, { T, T }, T, T, T, T, T }, /* 06 = read full sector */ + { 1, 0, class_read, { T, T }, T, T, T, T, T }, /* 07 = verify */ + { 0, 0, class_write, { T, T }, T, T, T, T, T }, /* 10 = write */ + { 0, 0, class_write, { T, T }, T, T, T, T, T }, /* 11 = write full sector */ + { 0, 0, class_control, { T, T }, T, F, F, F, F }, /* 12 = clear */ + { 0, 0, class_write, { T, T }, T, T, T, T, T }, /* 13 = initialize */ + { 2, 0, class_control, { T, T }, T, F, F, F, F }, /* 14 = address record */ + { 0, 7, class_status, { T, F }, F, F, F, F, F }, /* 15 = request syndrome */ + { 1, 0, class_read, { T, T }, T, T, T, T, T }, /* 16 = read with offset */ + { 0, 0, class_control, { T, T }, T, F, F, F, F }, /* 17 = set file mask */ + { 0, 0, class_invalid, { F, F }, T, F, F, F, F }, /* 20 = invalid */ + { 0, 0, class_invalid, { F, F }, T, F, F, F, F }, /* 21 = invalid */ + { 0, 0, class_read, { T, T }, T, T, T, T, T }, /* 22 = read without verify */ + { 1, 0, class_status, { T, F }, T, F, F, F, F }, /* 23 = load TIO register */ + { 0, 2, class_status, { T, T }, F, F, F, F, F }, /* 24 = request disc address */ + { 0, 0, class_control, { T, T }, T, F, F, F, F }, /* 25 = end */ + { 0, 0, class_control, { T, F }, T, T, T, F, F } /* 26 = wakeup */ + }; + + +/* Auxiliary unit indices */ + +typedef enum { + controller = 0, /* controller unit index */ + timer /* command wait timer index */ + } AUX_INDEX; + + +/* Controller opcode names */ + +static const char invalid_name [] = "invalid"; + +static const char *opcode_name [] = { + "cold load read", /* 00 */ + "recalibrate", /* 01 */ + "seek", /* 02 */ + "request status", /* 03 */ + "request sector address", /* 04 */ + "read", /* 05 */ + "read full sector", /* 06 */ + "verify", /* 07 */ + "write", /* 10 */ + "write full sector", /* 11 */ + "clear", /* 12 */ + "initialize", /* 13 */ + "address record", /* 14 */ + "request syndrome", /* 15 */ + "read with offset", /* 16 */ + "set file mask", /* 17 */ + invalid_name, /* 20 = invalid */ + invalid_name, /* 21 = invalid */ + "read without verify", /* 22 */ + "load TIO register", /* 23 */ + "request disc address", /* 24 */ + "end", /* 25 */ + "wakeup" /* 26 */ + }; + +/* Controller phase names */ + +static const char *phase_name [] = { + "start", + "data", + "end" + }; + + + +/* Disc library local controller routines */ + +static t_bool start_seek (CVPTR cvptr, UNIT *uptr, CNTLR_OPCODE next_opcode, CNTLR_PHASE next_phase); +static t_stat start_read (CVPTR cvptr, UNIT *uptr); +static void end_read (CVPTR cvptr, UNIT *uptr); +static void start_write (CVPTR cvptr, UNIT *uptr); +static t_stat end_write (CVPTR cvptr, UNIT *uptr); +static t_bool position_sector (CVPTR cvptr, UNIT *uptr, t_bool verify); +static void next_sector (CVPTR cvptr, UNIT *uptr); +static t_stat io_error (CVPTR cvptr, UNIT *uptr, CNTLR_STATUS status); + +/* Disc library local utility routines */ + +static void set_address (CVPTR cvptr, uint32 index); +static void set_timer (CVPTR cvptr, FLIP_FLOP action); +static uint16 drive_status (UNIT *uptr); + + + +/* Disc library global controller routines */ + + +/* Prepare a command for execution. + + On entry, the first word of the controller buffer contains the command to + prepare, the "cvptr" parameter points at the controller state variable + structure, and the "units" parameter points at the first unit of the unit + array. For a MAC controller, the "unit limit" parameter indicates the last + valid unit number, and the unit to use is taken from the unit field of the + command word. For an ICD controller, the parameter indicates the number + of the unit to use directly. + + If a valid command was prepared for execution, the routine returns TRUE and + sets the controller state to "busy." If the command is illegal, the routine + returns FALSE and sets the controller state to "waiting." In the latter + case, the controller status will indicate the reason for the rejection. + + The opcode and unit number (for MAC controllers) are obtained from the buffer + and checked for legality. If either is illegal, the controller status is set + appropriately, and the routine returns FALSE. + + For a valid command and an available unit, the controller's opcode field is + set from the buffer, the length field is set to the number of inbound + parameter words expected, and the index field is set to 1 to point at the + first parameter entry in the buffer. +*/ + +t_bool dl_prepare_command (CVPTR cvptr, UNIT *units, uint32 unit_limit) +{ +uint32 unit; +PRPTR props; +CNTLR_OPCODE opcode; + +set_timer (cvptr, CLEAR); /* stop the command wait timer */ + +opcode = GET_OPCODE (cvptr->buffer [0]); /* get the opcode from the command */ + +if (opcode > Last_Opcode) /* is the opcode invalid? */ + props = &cmd_props [Invalid_Opcode]; /* undefined commands clear prior status */ +else /* the opcode is potentially valid */ + props = &cmd_props [opcode]; /* get the command properties */ + +if (cvptr->type == MAC) /* is this a MAC controller? */ + if (props->unit_field) /* is the unit field defined for this command? */ + unit = GET_UNIT (cvptr->buffer [0]); /* get the unit from the command */ + else /* no unit specified in the command */ + unit = 0; /* so the unit is always unit 0 */ + +else /* an ICD controller */ + unit = unit_limit; /* uses the supplied unit number */ + +if (props->clear_status) { /* clear the prior controller status */ + cvptr->status = normal_completion; /* if indicated for this command */ + cvptr->spd_unit = SET_S1UNIT (unit); /* save the unit number for status requests */ + } + +if (cvptr->type <= last_type /* is the controller type legal, */ + && props->valid [cvptr->type]) /* and the opcode defined for this controller? */ + if (props->unit_check && unit > DL_MAXUNIT) /* if the unit number is checked and is illegal, */ + dl_end_command (cvptr, unit_unavailable); /* end with a unit unavailable error */ + + else { + cvptr->state = cntlr_busy; /* legal unit, so controller is now busy */ + cvptr->opcode = opcode; /* save the controller opcode */ + cvptr->length = props->params_in; /* set the inbound parameter count */ + cvptr->index = 1; /* point at the first parameter element (if any) */ + + if (cvptr->type == MAC && cvptr->length) { /* is this a MAC controller with inbound parameters? */ + cvptr->aux [controller].OP = opcode; /* save the opcode */ + cvptr->aux [controller].PHASE = data_phase; /* and set the phase for parameter pickup */ + set_timer (cvptr, SET); /* start the timer to wait for the first parameter */ + } + + return TRUE; /* the command is now prepared for execution */ + } + +else /* the opcode is undefined */ + dl_end_command (cvptr, illegal_opcode); /* so set bad opcode status */ + +return FALSE; /* the preparation has failed */ +} + + +/* Start a command. + + On entry, the controller's opcode field contains the command to start, and + the buffer contains the command word in element 0 and the parameters required + by the command, if any, beginning in element 1. The call parameters are the + same as those supplied to the "prepare command" routine. + + If the command was started successfully, the routine returns a pointer to the + unit to be activated and sets that unit's "wait" field to the activation + time. The caller should activate the unit upon return to complete or + continue command processing. If the command did not start, the routine + returns NULL. + + If a seek is in progress on a drive when a command accessing that drive is + started, the unit pointer is returned but the unit's "wait" field is set to + zero. In this case, the unit must not be activated (as it already is). + Instead, the unit's opcode and phase fields will have been set to start the + command automatically when the seek completes. + + For commands that return status from the controller, the buffer will contain + the returned value(s), the buffer index will be zero, and the buffer length + will be set to the number of words returned in the buffer. These words must + be returned to the CPU via the interface. + + + Implementation notes: + + 1. A command must have been prepared by calling dl_prepare_command first. + After preparation, the controller's opcode will be valid, and the unit + number field will be legal (but not necessarily valid) for those commands + that check the unit. + + Unit numbers 0-7 represent valid drive addresses. However, the MAC + controller firmware allows access to unit numbers 8-10 without causing a + Unit Unavailable error. Instead, the controller reports these units as + permanently offline. + + 2. Commands that check for a valid unit do some processing before failing + with a Status-2 (not ready) error if the unit is invalid. For example, + the Seek command accepts its parameters from the CPU and sets the CHS + values into the controller before failing. + + 3. In hardware, read, write, and recalibrate commands wait in an internal + loop for a pending seek completion and clear the resulting Attention + status before executing. In simulation, we change a seeking drive unit's + opcode and phase fields from seek completion to the start of the next + command. This eliminates the setting of the Attention status and begins + command execution automatically when the seek completes. + + If the seek completed between the command preparation and start, + Attention will have been set. If the unit is idle on entry, we clear the + Attention status unilaterally (it doesn't matter whether or not it was + set; Attention always is clear when commands start). + + 4. The Seek and Cold Load Read commands do not check for a seek or + recalibrate in progress. If the heads are moving, the drive will reject + a seek command with a Seek Check error. The firmware does not test + explicitly for Access Not Ready before executing the command, so the + parameters (e.g., controller CHS addresses) are still set as though the + command had succeeded. + + A Seek command will return to the Poll Loop with Seek Check status set. + When the seek in progress completes, the controller will interrupt with + Drive Attention status. The controller address will differ from the + drive address, so it's incumbent upon the caller to issue a Request + Status command after the seek, which will return Status-2 Error status. + + A Cold Load Read command issues a seek to cylinder 0 and then begins a + read, which first waits for seek completion. The Seek Check error will + abort the command at this point with Status-2 Error status. + + In simulation, a Seek command allows the seek in progress to complete + normally, whereas a Cold Load Read command modifies the unit command + and phase from the end phase of Seek or Recalibrate to the start + phase of Read, which will catch the Seek Check error as in hardware. + + 5. The Cold Load Read command checks if the drive is ready before setting + the file mask. Therefore, we normally defer setting the file mask until + the unit service is called. However, if a seek is in progress, then the + drive must be ready, so we set the file mask here. + + 6. ECC is not simulated, so the Request Syndrome command always returns zero + values for the displacement and patterns. + + 7. The Request Status, Request Sector Address, and Wakeup commands reference + drive units but are scheduled on the controller unit because they may be + issued while a drive is processing a seek. + + 8. The activation time is set to the intersector time (latency) for read and + write commands, and to the controller processing time for all others. + The read/write start time cannot be shorter than 20 instructions, or + DVR32 will be unable to start DCPC in time to avoid an over/underrun. +*/ + +UNIT *dl_start_command (CVPTR cvptr, UNIT *units, uint32 unit_limit) +{ +UNIT *uptr, *rptr; +uint32 unit; +PRPTR props; +t_bool is_seeking = FALSE; + +props = &cmd_props [cvptr->opcode]; /* get the command properties */ + +if (cvptr->type == MAC) { /* is this a MAC controller? */ + if (props->unit_field) /* is the unit field defined for this command? */ + unit = GET_UNIT (cvptr->buffer [0]); /* get the unit number from the command */ + else /* no unit is specified in the command */ + unit = 0; /* so the unit number defaults to 0 */ + + if (unit > unit_limit) /* if the unit number is invalid, */ + uptr = NULL; /* it does not correspond to a unit */ + else if (props->unit_access) /* if the command accesses a drive, */ + uptr = units + unit; /* get the address of the unit */ + else /* the command accesses the controller only */ + uptr = cvptr->aux + controller; /* so use the controller unit */ + } + +else { /* for an ICD controller, */ + unit = 0; /* the unit value is ignored */ + uptr = units + unit_limit; /* and we use the indicated unit */ + } + +if (props->unit_check && !uptr /* if the unit number is checked and is invalid */ + || props->seek_wait && (drive_status (uptr) & DL_S2STOPS)) { /* or if we're waiting for an offline drive */ + dl_end_command (cvptr, status_2_error); /* then the command ends with a Status-2 error */ + uptr = NULL; /* prevent the command from starting */ + } + +else if (uptr) { /* otherwise, we have a valid unit */ + uptr->wait = cvptr->cmd_time; /* most commands use the command delay */ + + if (props->unit_access) { /* does the command access the unit? */ + is_seeking = sim_is_active (uptr); /* see if the unit is busy */ + + if (is_seeking) /* if a seek is in progress, */ + uptr->wait = 0; /* set for no unit activation */ + + else { /* otherwise, the unit is idle */ + uptr->STAT &= ~DL_S2ATN; /* clear the drive Attention status */ + + if (props->classification == class_read /* if a read command */ + || props->classification == class_write) /* or a write command */ + uptr->wait = cvptr->sector_time; /* schedule the sector start latency */ + } + } + } + +cvptr->index = 0; /* reset the buffer index */ +cvptr->length = props->params_out; /* set the count of outbound parameters */ +cvptr->eod = CLEAR; /* clear the end of data flag */ + + +switch (cvptr->opcode) { /* dispatch the command */ + + case Cold_Load_Read: + cvptr->cylinder = 0; /* set the cylinder address to 0 */ + cvptr->head = GET_CHEAD (cvptr->buffer [0]); /* set the head */ + cvptr->sector = GET_CSECT (cvptr->buffer [0]); /* and sector from the command */ + + if (is_seeking) { /* if a seek is in progress, */ + uptr->STAT |= DL_S2SC; /* a Seek Check occurs */ + cvptr->file_mask = DL_FSPEN; /* enable sparing */ + uptr->OP = Read; /* start the read on the seek completion */ + uptr->PHASE = start_phase; /* and reset the command phase */ + return uptr; /* to allow the seek to complete normally */ + } + + else /* the drive is not seeking */ + uptr->wait = cvptr->cmd_time; /* the command starts with a seek, not a read */ + + break; + + + case Seek: + cvptr->cylinder = cvptr->buffer [1]; /* get the supplied cylinder */ + cvptr->head = GET_HEAD (cvptr->buffer [2]); /* and head */ + cvptr->sector = GET_SECTOR (cvptr->buffer [2]); /* and sector addresses */ + + if (is_seeking) { /* if a seek is in progress, */ + uptr->STAT |= DL_S2SC; /* a Seek Check occurs */ + dl_idle_controller (cvptr); /* return the controller to the idle condition */ + return uptr; /* to allow the seek to complete normally */ + } + + break; + + + case Request_Status: + cvptr->buffer [0] = (uint16) (cvptr->spd_unit /* set the Status-1 value */ + | SET_S1STAT (cvptr->status)); /* into the buffer */ + + if (cvptr->type == MAC) /* is this a MAC controller? */ + if (unit > unit_limit) /* if the unit number is invalid */ + rptr = NULL; /* it does not correspond to a unit */ + else /* otherwise, the unit is valid */ + rptr = &units [unit]; /* so get the address of the referenced unit */ + else /* if not a MAC controller */ + rptr = uptr; /* then the referenced unit is the current unit */ + + cvptr->buffer [1] = drive_status (rptr); /* set the Status-2 value */ + + if (rptr) /* if the unit is valid */ + rptr->STAT &= ~DL_S2FS; /* clear the First Status bit */ + + cvptr->spd_unit = SET_S1UNIT (unit); /* save the unit number */ + + if (unit > DL_MAXUNIT) /* if the unit number is illegal, */ + cvptr->status = unit_unavailable; /* the next status will be Unit Unavailable */ + else /* a legal unit */ + cvptr->status = normal_completion; /* clears the controller status */ + + break; + + + case Request_Disc_Address: + set_address (cvptr, 0); /* return the CHS values in buffer 0-1 */ + break; + + + case Request_Sector_Address: + if (unit > unit_limit) /* if the unit number is invalid */ + rptr = NULL; /* it does not correspond to a unit */ + else /* otherwise, the unit is valid */ + rptr = &units [unit]; /* so get the address of the referenced unit */ + + if (drive_status (rptr) & DL_S2NR) /* if the drive is not ready, */ + dl_end_command (cvptr, status_2_error); /* terminate with not ready status */ + else /* otherwise, the drive is ready */ + cvptr->buffer [0] = GET_CURSEC (cvptr, rptr); /* so calculate the current sector address */ + break; + + + case Request_Syndrome: + cvptr->buffer [0] = (uint16) (cvptr->spd_unit /* return the Status-1 value */ + | SET_S1STAT (cvptr->status)); /* in buffer 0 */ + + set_address (cvptr, 1); /* return the CHS values in buffer 1-2 */ + + cvptr->buffer [3] = 0; /* the displacement is always zero */ + cvptr->buffer [4] = 0; /* the syndrome is always zero */ + cvptr->buffer [5] = 0; + cvptr->buffer [6] = 0; + break; + + + case Address_Record: + cvptr->cylinder = cvptr->buffer [1]; /* get the supplied cylinder */ + cvptr->head = GET_HEAD (cvptr->buffer [2]); /* and head */ + cvptr->sector = GET_SECTOR (cvptr->buffer [2]); /* and sector addresses */ + cvptr->eoc = CLEAR; /* clear the end-of-cylinder flag */ + break; + + + case Set_File_Mask: + cvptr->file_mask = GET_FMASK (cvptr->buffer [0]); /* get the supplied file mask */ + + if (cvptr->type == MAC) /* if this is a MAC controller, */ + cvptr->retry = GET_FRETRY (cvptr->buffer [0]); /* the retry count is supplied too */ + break; + + + case Initialize: + if (uptr) /* if the unit is valid, */ + cvptr->spd_unit |= /* merge the SPD flags */ + SET_S1SPD (GET_SPD (cvptr->buffer [0])); /* from the command word */ + break; + + + case Verify: + cvptr->verify_count = cvptr->buffer [1]; /* get the supplied sector count */ + break; + + + default: /* the remaining commands */ + break; /* are handled by the service routines */ + } + + +if (uptr) { /* if the command accesses a valid unit */ + uptr->OP = cvptr->opcode; /* save the opcode in the unit */ + + if (cvptr->length) /* if the command has outbound parameters, */ + uptr->PHASE = data_phase; /* set up the data phase for the transfer */ + else /* if there are no parameters, */ + uptr->PHASE = start_phase; /* set up the command phase for execution */ + + return uptr; /* return a pointer to the scheduled unit */ + } + +else + return NULL; /* the command did not start */ +} + + +/* Complete a command. + + The current command is completed with the indicated status. The command + result status is set, the controller enters the command wait state, and the + CPU timer is restarted. +*/ + +void dl_end_command (CVPTR cvptr, CNTLR_STATUS status) +{ +cvptr->status = status; /* set the command result status */ +cvptr->state = cntlr_wait; /* set the controller state to waiting */ +set_timer (cvptr, SET); /* start the command wait timer */ +return; +} + + +/* Poll the drives for Attention status. + + If interrupts are enabled on the interface, this routine is called to check + if any drive is requesting attention. The routine returns TRUE if a drive is + requesting attention and FALSE if not. + + Starting with the last unit requesting attention, each drive is checked in + sequence. If a drive has its Attention status set, the controller saves its + unit number, sets the result status to Drive Attention, and enters the + command wait state. The routine returns TRUE to indicate that an interrupt + should be generated. The next time the routine is called, the poll begins + with the last unit that requested attention, so that each unit is given an + equal chance to respond. + + If no unit is requesting attention, the routine returns FALSE to indicate + that no interrupt should be generated. +*/ + +t_bool dl_poll_drives (CVPTR cvptr, UNIT *units, uint32 unit_limit) +{ +uint32 unit; + +for (unit = 0; unit <= unit_limit; unit++) { /* check each unit in turn */ + cvptr->poll_unit = /* start with the last unit checked */ + (cvptr->poll_unit + 1) % (unit_limit + 1); /* and cycle back to unit 0 */ + + if (units [cvptr->poll_unit].STAT & DL_S2ATN) { /* if the unit is requesting attention, */ + units [cvptr->poll_unit].STAT &= ~DL_S2ATN; /* clear the Attention status */ + cvptr->spd_unit = SET_S1UNIT (cvptr->poll_unit); /* set the controller's unit number */ + cvptr->status = drive_attention; /* and status */ + cvptr->state = cntlr_wait; /* and wait for a command */ + return TRUE; /* tell the caller to interrupt */ + } + } + +return FALSE; /* no requests, so do not generate an interrupt */ +} + + +/* Service the disc drive unit. + + The unit service routine is called to execute scheduled controller commands + for the specified unit. The actions to be taken depend on the current state + of the controller and the unit. + + In addition to the controller state variables supplied in the call, the + service routine accesses these six variables in the UNIT structure: + + wait -- the current service activation time + pos -- the current byte offset into the disc image file + u3 (CYL) -- the current drive cylinder + u4 (STAT) -- the drive status (Status-2) + u5 (OP) -- the drive operation in process + u6 (PHASE) -- the current operation phase + + The activation time is set non-zero if the service should be rescheduled. + The caller is responsible upon return for activating the unit. The file + offset indicates the byte position in the disc image file for the next read + or write operation. + + The drive cylinder gives the current location of the head positioner. This + may differ from the cylinder value in the controller if the Address Record + command has been used. The drive status maintains various per-drive + conditions (e.g., the state of the read-only and format switches, drive + ready, first status). The operation in process and operation phase define + the action to be taken by this service routine. + + Initially, the operation in process is set to the opcode field of the command + when it is started. However, the operation in process may change during + execution (the controller opcode never does). This is to aid code reuse in + the service routine. For example, a Cold Load Read command is changed to a + Read command once the seek portion is complete, and a Read Without Verify + command is changed to a normal Read command after a track boundary is + crossed. + + The operation phase provides different substates for those commands that + transfer data or that have different starting and ending actions. Three + phases are defined: start, data, and end. Commands that do not transfer data + to or from the CPU interface do not have data phases, and commands that + complete upon first service do not have end phases. The service routine + validates phase assignments and returns SCPE_IERR (Internal Error) if entry + is made with an illegal operation phase or a phase that is not valid for a + given operation. + + An operation in the data phase is in the process of transferring data between + the CPU and sector buffer. Because this process is interface-specific, the + service routine does nothing (other than validate) in this phase. It is up + to the caller to transition from the data phase to the end phase when the + transfer is complete. + + If an operation is completed, or an error has occurred, the controller state + on return will be either idle or waiting, instead of busy. The caller should + check the controller status to determine if normal completion or error + recovery is appropriate. + + If the command is continuing, the service activation time will be set + appropriately. The caller should then call sim_activate to schedule the next + service and clear the "wait" field in preparation for the next service call. + + + Implementation notes: + + 1. The Cold Load Read and Seek commands check only the drive's Not Ready + status because seeking clears a Seek Check. The other commands that + access the unit (e.g., Read and Write) have already checked in the + command start routine for Not Ready, Seek Check, or Fault status and + terminated with a Status-2 error. + + 2. Several commands (e.g., Set File Mask, Address Record) are executed + completely within the dl_start_command routine, so all we do here is + finish the command with the expected status. The service routine is + called only to provide the proper command execution delay. + + 3. If a host file system error occurs, the service routine returns SCPE_IERR + to stop simulation. If simulation is resumed, the controller will behave + as though an uncorrectable data error had occurred. +*/ + +t_stat dl_service_drive (CVPTR cvptr, UNIT *uptr) +{ +t_stat result = SCPE_OK; +const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP; + +switch ((CNTLR_PHASE) uptr->PHASE) { /* dispatch the phase */ + + case start_phase: + switch (opcode) { /* dispatch the current operation */ + + case Recalibrate: + case Seek: + if (start_seek (cvptr, uptr, opcode, end_phase) /* start the seek; if it succeeded, */ + && (cvptr->type == MAC)) /* and this a MAC controller, */ + dl_idle_controller (cvptr); /* then go idle until it completes */ + break; + + + case Cold_Load_Read: + if (start_seek (cvptr, uptr, Read, start_phase)) /* start the seek; did it succeed? */ + cvptr->file_mask = DL_FSPEN; /* set sparing enabled now */ + break; + + + case Read: + case Read_With_Offset: + case Read_Without_Verify: + cvptr->length = DL_WPSEC; /* transfer just the data */ + result = start_read (cvptr, uptr); /* start the sector read */ + break; + + + case Read_Full_Sector: + cvptr->length = DL_WPFSEC; /* transfer the header/data/trailer */ + result = start_read (cvptr, uptr); /* start the sector read */ + break; + + + case Verify: + cvptr->length = 0; /* no data transfer needed */ + result = start_read (cvptr, uptr); /* start the sector read */ + + if (uptr->PHASE == data_phase) { /* did the read start successfully? */ + uptr->PHASE = end_phase; /* skip the data phase */ + uptr->wait = cvptr->sector_time /* reschedule for the intersector time */ + + cvptr->data_time * DL_WPSEC; /* plus the data read time */ + } + break; + + + case Write: + case Initialize: + cvptr->length = DL_WPSEC; /* transfer just the data */ + start_write (cvptr, uptr); /* start the sector write */ + break; + + + case Write_Full_Sector: + cvptr->length = DL_WPFSEC; /* transfer the header/data/trailer */ + start_write (cvptr, uptr); /* start the sector write */ + break; + + + case Request_Status: + case Request_Sector_Address: + case Clear: + case Address_Record: + case Request_Syndrome: + case Set_File_Mask: + case Load_TIO_Register: + case Request_Disc_Address: + case End: + case Wakeup: + dl_service_controller (cvptr, uptr); /* the controller service handles these */ + break; + + + default: /* we were entered with an invalid state */ + result = SCPE_IERR; /* return an internal (programming) error */ + break; + } /* end of operation dispatch */ + break; /* end of start phase handlers */ + + + case data_phase: + switch (opcode) { /* dispatch the current operation */ + case Read: + case Read_Full_Sector: + case Read_With_Offset: + case Read_Without_Verify: + case Write: + case Write_Full_Sector: + case Initialize: + break; /* data transfers are handled by the caller */ + + + default: /* entered with an invalid state */ + result = SCPE_IERR; /* return an internal (programming) error */ + break; + } /* end of operation dispatch */ + break; /* end of data phase handlers */ + + + case end_phase: + switch (opcode) { /* dispatch the operation command */ + + case Recalibrate: + case Seek: + if (cvptr->type == ICD) /* is this an ICD controller? */ + dl_end_command (cvptr, drive_attention); /* seeks end with Drive Attention status */ + else /* if not an ICD controller, */ + uptr->STAT |= DL_S2ATN; /* set Attention in the unit status */ + break; + + + case Read: + case Read_Full_Sector: + case Read_With_Offset: + end_read (cvptr, uptr); /* end the sector read */ + break; + + + case Read_Without_Verify: + if (cvptr->sector == 0) /* have we reached the end of the track? */ + uptr->OP = Read; /* begin verifying the next time */ + + end_read (cvptr, uptr); /* end the sector read */ + break; + + + case Verify: + cvptr->verify_count = /* decrement the count */ + (cvptr->verify_count - 1) & DMASK; /* modulo 65536 */ + + if (cvptr->verify_count == 0) /* are there more sectors to verify? */ + cvptr->eod = SET; /* no, so terminate the command cleanly */ + + end_read (cvptr, uptr); /* end the sector read */ + break; + + + case Write: + case Write_Full_Sector: + case Initialize: + result = end_write (cvptr, uptr); /* end the sector write */ + break; + + + case Request_Status: + case Request_Sector_Address: + case Request_Disc_Address: + dl_service_controller (cvptr, uptr); /* the controller service handles these */ + break; + + + default: /* we were entered with an invalid state */ + result = SCPE_IERR; /* return an internal (programming) error */ + break; + } /* end of operation dispatch */ + break; /* end of end phase handlers */ + } /* end of phase dispatch */ + +return result; /* return the result of the service */ +} + + +/* Service the controller unit. + + The controller service routine is called to execute scheduled controller + commands that do not access drive units. It is also called to obtain command + parameters from the interface and to return command result values to the + interface. The actions to be taken depend on the current state of the + controller. + + Controller commands are scheduled on a separate unit to allow concurrent + processing while seeks are in progress. For example, a seek may be started + on unit 0. While the seek is in progress, the CPU may request status from + the controller. In between returning the first and second status words to + the CPU, the seek may complete. Separating the controller unit allows seek + completion to be handled while the controller is "busy" waiting for the CPU + to indicate that it is ready for the second word. + + For ICD controllers, the controller unit is not used, and all commands are + scheduled on the drive unit. This is possible because ICD controllers always + wait for seeks to complete before executing additional commands. To reduce + code duplication, however, the drive unit service calls the controller + service directly to handle controller commands. + + The service routine validates phase assignments and returns SCPE_IERR + (Internal Error) if entry is made with an illegal operation phase or a phase + that is not valid for a given operation. + + Implementation notes: + + 1. While the interface simulator is responsible for data phase transfers, + the controller service routine is responsible for (re)starting and + stopping the command wait timer for each parameter sent to and received + from the interface. +*/ + +t_stat dl_service_controller (CVPTR cvptr, UNIT *uptr) +{ +t_stat result = SCPE_OK; +const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP; + +switch ((CNTLR_PHASE) uptr->PHASE) { /* dispatch the phase */ + + case start_phase: + case end_phase: + switch (opcode) { /* dispatch the current operation */ + case Request_Status: + dl_end_command (cvptr, cvptr->status); /* the command completes with no status change */ + break; + + + case Clear: + dl_clear_controller (cvptr, uptr, soft_clear); /* clear the controller */ + dl_end_command (cvptr, normal_completion); /* the command is complete */ + break; + + + case Request_Sector_Address: + case Address_Record: + case Request_Syndrome: + case Set_File_Mask: + case Load_TIO_Register: + case Request_Disc_Address: + dl_end_command (cvptr, normal_completion); /* the command is complete */ + break; + + + case End: + dl_idle_controller (cvptr); /* the command completes with the controller idle */ + break; + + + case Wakeup: + dl_end_command (cvptr, unit_available); /* the command completes with Unit Available status */ + break; + + + default: /* we were entered with an invalid state */ + result = SCPE_IERR; /* return an internal (programming) error */ + break; + } /* end of operation dispatch */ + break; /* end of start and end phase handlers */ + + + case data_phase: + switch (opcode) { /* dispatch the current operation */ + + case Seek: + case Verify: + case Address_Record: + case Read_With_Offset: + case Load_TIO_Register: + if (cvptr->length > 1) /* at least one more parameter to input? */ + set_timer (cvptr, SET); /* restart the timer for the next parameter */ + else /* this is the last one */ + set_timer (cvptr, CLEAR); /* so stop the command wait timer */ + break; + + + case Request_Status: + case Request_Sector_Address: + case Request_Syndrome: + case Request_Disc_Address: + if (cvptr->length > 0) /* at least one more to parameter output? */ + set_timer (cvptr, SET); /* restart the timer for the next parameter */ + else /* this is the last one */ + set_timer (cvptr, CLEAR); /* so stop the command wait timer */ + break; + + + default: /* we were entered with an invalid state */ + result = SCPE_IERR; /* return an internal (programming) error */ + break; + } /* end of operation dispatch */ + break; /* end of data phase handlers */ + } /* end of phase dispatch */ + +return result; /* return the result of the service */ +} + + +/* Service the command wait timer unit. + + The command wait timer service routine is called if the command wait timer + expires. This indicates that the CPU did not respond to a parameter transfer + or did not issue a new command within the ~1.8 second timeout period. The + timer is used with the MAC controller to ensure that a hung CPU does not tie + up the controller, preventing it from servicing other CPUs or drives. ICD + controllers do not use the command wait timer; they will wait forever, as + each controller is dedicated to a single interface. + + When a timeout occurs, the controller unit is cancelled in case the cause was + a parameter timeout. Then the file mask is reset, and the controller is + idled. + + The interface is responsible for polling for a new command and for drive + attention when a timeout occurs. + + Implementation notes: + + 1. Only the controller unit may be active when the command wait timer + expires. A unit is never active because the timer is cancelled when + commands are executing and is restarted after the command completes. +*/ + +t_stat dl_service_timer (CVPTR cvptr, UNIT *uptr) +{ +sim_cancel (cvptr->aux); /* cancel any controller activation */ + +dl_idle_controller (cvptr); /* idle the controller */ +cvptr->file_mask = 0; /* clear the file mask */ + +return SCPE_OK; +} + + +/* Clear the controller. + + The controller connected to the specified unit is cleared as directed. A MAC + controller is connected to several units, so the unit is used to find the + associated device and thereby the unit array. An ICD controller is connected + only to the specified unit. + + In hardware, four conditions clear the 13037 controller: + + - an initial application of power + - an assertion of the CLEAR signal by the CPU interface + - a timeout of the command wait timer + - a programmed Clear command + + The first two conditions, called "hard clears," are equivalent and cause a + firmware restart with the PWRON flag set. The 13175 interface for the HP + 1000 asserts the CLEAR signal in response to the backplane CRS signal if the + PRESET ENABLE jumper is not installed (which is the usual case). The third + condition also causes a firmware restart but with the PWRON flag clear. The + last condition is executed in the command handler and therefore returns to + the Command Wait Loop instead of the Poll Loop. + + For a hard clear, the 13037 controller will: + + - disconnect the CPU interface + - zero the controller RAM (no drives held, last polled unit number reset) + - issue a Controller Preset to clear all connected drives + - clear the clock offset + - clear the file mask + - enter the Poll Loop (which clears the controller status) + + For a timeout clear, the 13037 controller will: + + - disconnect the CPU interface + - clear the hold bits of any drives held by the interface that timed out + - clear the clock offset + - clear the file mask + - enter the Poll Loop (which clears the controller status) + + For a programmed "soft" clear, the 13037 controller will: + + - clear the controller status + - issue a Controller Preset to clear all connected drives + - enter the Command Wait Loop + + Controller Preset is a tag bus command that is sent to all drives connected + to the controller. Each drive will: + + - disconnect from the controller + - clear its internal drive faults + - clear its head and sector registers + - clear its illegal head and sector flip-flops + - reset its seek check, first status, drive fault, and attention status + + In simulation, a hard clear occurs when a RESET -P or RESET command is + issued, or a programmed CLC 0 instruction is executed. A soft clear occurs + when a programmed Clear command is started. A timeout clear occurs when the + command wait timer unit is serviced, but this action is handled in the timer + unit service. + + Because the controller execution state is implemented by scheduling command + phases for the target or controller unit, a simulated firmware restart must + abort any in-process activation. However, a firmware restart does not affect + seeks in progress, so these must be allowed to continue to completion so that + their Attention requests will be honored. + + + Implementation notes: + + 1. The specific 13365 controller actions on hard or soft clears are not + documented. Therefore, an ICD controller clear is handled as a MAC + controller clear, except that only the current drive is preset (as an ICD + controller manages only a single drive). + + 2. Neither hard nor soft clears affect the controller flags (e.g., EOC) or + registers (e.g., cylinder address). + + 3. In simulation, an internal seek, such as an auto-seek during a Read + command or the initial seek during a Cold Load Read command, will be + aborted for a hard or timeout clear, whereas in hardware it would + complete normally. This is OK, however, because an internal seek always + clears the drive's Attention status on completion, so aborting the + simulated seek is equivalent to an immediate seek completion. + + 4. In simulation, a Controller Preset only resets the specified status bits, + as the remainder of the hardware actions are not implemented. +*/ + +t_stat dl_clear_controller (CVPTR cvptr, UNIT *uptr, CNTLR_CLEAR clear_type) +{ +uint32 unit, unit_count; +DEVICE *dptr = NULL; + +if (clear_type == hard_clear) { /* is this a hard clear? */ + dl_idle_controller (cvptr); /* idle the controller */ + cvptr->file_mask = 0; /* clear the file mask */ + cvptr->poll_unit = 0; /* clear the last unit polled */ + } + +if (cvptr->type == ICD) /* is this an ICD controller? */ + unit_count = 1; /* there is only one unit per controller */ + +else { /* a MAC controller clears all units */ + dptr = find_dev_from_unit (uptr); /* find the associated device */ + + if (dptr == NULL) /* the device doesn't exist?!? */ + return SCPE_IERR; /* this is an impossible condition! */ + else /* the device was found */ + unit_count = dptr->numunits; /* so get the number of units */ + } + +for (unit = 0; unit < unit_count; unit++) { /* loop through the unit(s) */ + if (dptr) /* pick up the unit from the device? */ + uptr = dptr->units + unit; /* yes, so get the next unit */ + + if (!(uptr->flags & UNIT_DIS)) { /* is the unit enabled? */ + if (clear_type == hard_clear /* a hard clear cancels */ + && uptr->OP != Seek /* only if not seeking */ + && uptr->OP != Recalibrate) /* or recalibrating */ + sim_cancel (uptr); /* cancel the service */ + + uptr->STAT &= ~DL_S2CPS; /* do "Controller Preset" for the unit */ + } + } + +return SCPE_OK; +} + + +/* Idle the controller. + + The command wait timer is turned off, the status is reset, and the controller + is returned to the idle state (Poll Loop). +*/ + +void dl_idle_controller (CVPTR cvptr) +{ +cvptr->state = cntlr_idle; /* idle the controller */ +cvptr->status = normal_completion; /* the Poll Loop clears the status */ + +set_timer (cvptr, CLEAR); /* stop the command wait timer */ +return; +} + + + +/* Load or unload the drive heads. + + In hardware, a drive's heads are loaded when a disc pack is installed and the + RUN/STOP switch is set to RUN. The drive reports First Status when the heads + load to indicate that the pack has potentially changed. Setting the switch + to STOP unloads the heads. When the heads are unloaded, the drive reports + Not Ready and Drive Busy status. + + In simulation, the unit must be attached before the heads may be unloaded or + loaded. As the heads should be automatically loaded when a unit is attached + and unloaded when a unit is detached, this routine must be called after + attaching and before detaching. + + + Implementation notes: + + 1. The drive sets its Attention status bit when the heads load or unload. + However, the ICD controller reports Attention only for head unloading. + + 2. Loading or unloading the heads clears Fault and Seek Check status. + + 3. If we are called during a RESTORE command, the unit's flags are not + changed to avoid upsetting the state that was SAVEd. +*/ + +t_stat dl_load_unload (CVPTR cvptr, UNIT *uptr, t_bool load) +{ +if ((uptr->flags & UNIT_ATT) == 0) /* the unit must be attached to [un]load */ + return SCPE_UNATT; /* return "Unit not attached" if not */ + +else if (!(sim_switches & SIM_SW_REST)) /* modify the flags only if not restoring */ + if (load) { /* are we loading the heads? */ + uptr->flags = uptr->flags & ~UNIT_UNLOAD; /* clear the unload flag */ + uptr->STAT = DL_S2FS; /* and set First Status */ + + if (cvptr->type != ICD) /* if this is not an ICD controller */ + uptr->STAT |= DL_S2ATN; /* set Attention status also */ + } + + else { /* we are unloading the heads */ + uptr->flags = uptr->flags | UNIT_UNLOAD; /* set the unload flag */ + uptr->STAT = DL_S2ATN; /* and Attention status */ + } + +return SCPE_OK; +} + + + +/* Disc library global utility routines */ + + +/* Classify the current controller opcode. + + The controller opcode is classified as a read, write, control, or status + command, and the classification is returned to the caller. If the opcode is + illegal or undefined for the indicated controller, the classification is + marked as invalid. +*/ + +CNTLR_CLASS dl_classify (CNTLR_VARS cntlr) +{ +if (cntlr.type <= last_type /* if the controller type is legal */ + && cntlr.opcode <= Last_Opcode /* and the opcode is legal */ + && cmd_props [cntlr.opcode].valid [cntlr.type]) /* and is defined for this controller, */ + return cmd_props [cntlr.opcode].classification; /* then return the command classification */ +else /* the type or opcode is illegal */ + return class_invalid; /* so return an invalid classification */ +} + + +/* Return the name of an opcode. + + A string representing the supplied controller opcode is returned to the + caller. If the opcode is illegal or undefined for the indicated controller, + the string "invalid" is returned. +*/ + +const char *dl_opcode_name (CNTLR_TYPE controller, CNTLR_OPCODE opcode) +{ +if (controller <= last_type /* if the controller type is legal */ + && opcode <= Last_Opcode /* and the opcode is legal */ + && cmd_props [opcode].valid [controller]) /* and is defined for this controller, */ + return opcode_name [opcode]; /* then return the opcode name */ +else /* the type or opcode is illegal, */ + return invalid_name; /* so return an error indication */ +} + + +/* Return the name of a command phase. + + A string representing the supplied phase is returned to the caller. If the + phase is illegal, the string "invalid" is returned. +*/ + +const char *dl_phase_name (CNTLR_PHASE phase) +{ +if (phase <= last_phase) /* if the phase is legal, */ + return phase_name [phase]; /* return the phase name */ +else /* the phase is illegal, */ + return invalid_name; /* so return an error indication */ +} + + + +/* Disc library global VM routines */ + + +/* Attach a disc image file to a unit. + + The file specified by the supplied filename is attached to the indicated + unit. If the attach was successful, the heads are loaded on the drive. + + If the drive is set to autosize, the size of the image file is compared to + the table of drive capacities to determine which model of drive was used to + create it. If the image file is new, then the previous drive model is + retained. +*/ + +t_stat dl_attach (CVPTR cvptr, UNIT *uptr, CONST char *cptr) +{ +uint32 id, size; +t_stat result; + +result = attach_unit (uptr, cptr); /* attach the unit */ + +if (result != SCPE_OK) /* did the attach fail? */ + return result; /* yes, so return the error status */ + +dl_load_unload (cvptr, uptr, TRUE); /* if the attach succeeded, load the heads */ + +if (uptr->flags & UNIT_AUTO) { /* is autosizing enabled? */ + size = sim_fsize (uptr->fileref) / sizeof (uint16); /* get the file size in words */ + + if (size > 0) /* a new file retains the current drive model */ + for (id = 0; id < PROPS_COUNT; id++) /* find the best fit to the drive models */ + if (size <= drive_props [id].words /* if the file size fits the drive capacity */ + || id == PROPS_COUNT - 1) { /* or this is the largest available drive */ + uptr->capac = drive_props [id].words; /* then set the capacity */ + uptr->flags = (uptr->flags & ~UNIT_MODEL) /* and the model */ + | SET_MODEL (id); + break; + } + } + +return SCPE_OK; /* the unit was successfully attached */ +} + + +/* Detach a disc image file from a unit. + + The heads are unloaded on the drive, and the attached file, if any, is + detached. +*/ + +t_stat dl_detach (CVPTR cvptr, UNIT *uptr) +{ +dl_load_unload (cvptr, uptr, FALSE); /* unload the heads if attached */ +return detach_unit (uptr); /* and detach the unit */ +} + + +/* Set the drive model. + + This validation routine is called to set the model of disc drive associated + with the specified unit. The "value" parameter indicates the model ID, and + the unit capacity is set to the size indicated. +*/ + +t_stat dl_set_model (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +if (uptr->flags & UNIT_ATT) /* we cannot alter the disc model */ + return SCPE_ALATT; /* if the unit is attached */ + +if (value != UNIT_AUTO) /* if we are not autosizing */ + uptr->capac = drive_props [GET_MODEL (value)].words; /* set the capacity to the new value */ + +return SCPE_OK; +} + + + +/* Disc library local controller routines */ + + +/* Start a read operation on the current sector. + + The current sector indicated by the controller address is read from the disc + image file into the sector buffer in preparation for data transfer to the + CPU. If the end of the track had been reached, and the file mask permits, + an auto-seek is scheduled instead to allow the read to continue. + + On entry, the end-of-data flag is checked. If it is set, the current read is + completed. Otherwise, the buffer data offset and verify options are set up. + For a Read Full Sector, the sync word is set from the controller type, and + dummy cylinder and head-sector words are generated from the current location + (as would be the case in the absence of track sparing). + + The image file is positioned to the correct sector in preparation for + reading. If the positioning requires a permitted seek, it is scheduled, and + the routine returns with the operation phase unchanged to wait for seek + completion before resuming the read (when the seek completes, the service + routine will be entered, and we will be called again; this time, the + end-of-cylinder flag will be clear and positioning will succeed). If + positioning resulted in an error, the current read is terminated with the + error status set. + + If positioning succeeded within the same cylinder, the sector image is read + into the buffer at an offset determined by the operation (Read Full Sector + leaves room at the start of the buffer for the sector header). If the image + file read did not return a full sector, the remainder of the buffer is padded + with zeros. If the image read failed with a file system error, SCPE_IOERR is + returned from the service routine to cause a simulation stop; resumption is + handled as an Uncorrectable Data Error. + + If the image was read correctly, the next sector address is updated, the + operation phase is set for the data transfer, and the index of the first word + to transfer is set. + + + Implementation notes: + + 1. The length of the transfer required (cvptr->length) must be set before + entry. + + 2. Entry while executing a Read Without Verify or Read Full Sector command + inhibits address verification. The unit opcode is tested instead of the + controller opcode because a Read Without Verify is changed to a Read to + begin verifying after a track switch occurs. +*/ + +static t_stat start_read (CVPTR cvptr, UNIT *uptr) +{ +uint32 count, offset; +t_bool verify; +const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP; + +if (cvptr->eod == SET) { /* is the end of data indicated? */ + dl_end_command (cvptr, normal_completion); /* complete the command */ + return SCPE_OK; + } + +if (opcode == Read_Full_Sector) { /* are we starting a Read Full Sector command? */ + if (cvptr->type == ICD) /* is this an ICD controller? */ + cvptr->buffer [0] = 0100377; /* ICD does not support ECC */ + else + cvptr->buffer [0] = 0100376; /* MAC does support ECC */ + + set_address (cvptr, 1); /* set the current address into buffer 1-2 */ + offset = 3; /* start the data after the header */ + verify = FALSE; /* set for no address verification */ + } + +else { /* it's another read command */ + offset = 0; /* data starts at the beginning */ + verify = (opcode != Read_Without_Verify); /* set for address verification unless it's a RWV */ + } + +if (! position_sector (cvptr, uptr, verify)) /* position the sector */ + return SCPE_OK; /* a seek is in progress or an error occurred */ + +count = sim_fread (cvptr->buffer + offset, /* read the sector from the image */ + sizeof (uint16), DL_WPSEC, /* into the sector buffer */ + uptr->fileref); + +for (count = count + offset; count < cvptr->length; count++) /* pad the sector as needed */ + cvptr->buffer [count] = 0; /* e.g., if reading from a new file */ + +if (ferror (uptr->fileref)) /* did a host file system error occur? */ + return io_error (cvptr, uptr, uncorrectable_data_error); /* set up the data error status and stop the simulation */ + +next_sector (cvptr, uptr); /* address the next sector */ + +uptr->PHASE = data_phase; /* set up the data transfer phase */ +cvptr->index = 0; /* reset the data index */ + +return SCPE_OK; /* the read was successfully started */ +} + + +/* Finish a read operation on the current sector. + + On entry, the end-of-data flag is checked. If it is set, the current read is + completed. Otherwise, the command phase is reset to start the next sector, + and the disc service is set to allow for the intersector delay. + + + Implementation notes: + + 1. The CPU indicates the end of a read data transfer to an ICD controller by + untalking the drive. The untalk is done by the driver as soon as the + DCPC completion interrupt is processed. However, the time from the final + DCPC transfer through driver entry to the point where the untalk is + asserted on the bus varies from 80 instructions (RTE-6/VM with OS + microcode and the buffer in the system map) to 152 instructions (RTE-IVB + with the buffer in the user map). The untalk must occur before the start + of the next sector, or the drive will begin the data transfer. + + Normally, this is not a problem, as the driver clears the FIFO of any + received data after DCPC completion. However, if the read terminates + after the last sector of a track, and accessing the next sector would + require an intervening seek, and the file mask disables auto-seeking or + an enabled seek would move the positioner beyond the drive limits, then + the controller will indicate an End of Cylinder error if the untalk does + not arrive before the seek is initiated. + + The RTE driver (DVA32) and various utilities that manage the disc + directly (e.g., SWTCH) do not appear to account for these bogus errors, + so the ICD controller hardware must avoid them in some unknown manner. + We work around the issue by extending the intersector delay to allow time + for a potential untalk whenever the next access would otherwise fail. + + Note that this issue does not occur with writes because DCPC completion + asserts EOI concurrently with the final data byte to terminate the + command. +*/ + +static void end_read (CVPTR cvptr, UNIT *uptr) +{ +uint32 limit; + +if (cvptr->eod == SET) /* is the end of data indicated? */ + dl_end_command (cvptr, normal_completion); /* complete the command */ + +else { /* reading continues */ + uptr->PHASE = start_phase; /* reset to the start phase */ + uptr->wait = cvptr->sector_time; /* delay for the intersector time */ + + if (cvptr->eoc == SET && cvptr->type == ICD) { /* seek will be required and controller is ICD? */ + if (!(cvptr->file_mask & DL_FAUTSK)) /* if auto-seek is disabled */ + limit = cvptr->cylinder; /* then the limit is the current cylinder */ + else if (cvptr->file_mask & DL_FDECR) /* else if enabled and decremental seek */ + limit = 0; /* then the limit is cylinder 0 */ + else /* else the enabled limit is the last cylinder */ + limit = drive_props [GET_MODEL (uptr->flags)].cylinders; + + if (cvptr->cylinder == limit) /* is positioner at the limit? */ + uptr->wait = cvptr->eot_time; /* seek will fail; delay to allow CPU to untalk */ + } + } + +return; +} + + +/* Start a write operation on the current sector. + + The current sector indicated by the controller address is positioned for + writing from the sector buffer to the disc image file after data transfer + from the CPU. If the end of the track had been reached, and the file mask + permits, an auto-seek is scheduled instead to allow the write to continue. + + On entry, if writing is not permitted, or formatting is required but not + enabled, the command is terminated with an error. Otherwise, the disc image + file is positioned to the correct sector in preparation for writing. + + If the positioning requires a permitted seek, it is scheduled, and the + routine returns with the operation phase unchanged to wait for seek + completion before resuming the write (when the seek completes, the service + routine will be entered, and we will be called again; this time, the + end-of-cylinder flag will be clear and positioning will succeed). If + positioning resulted in an error, the current write is terminated with the + error status set. + + If positioning succeeded within the same cylinder, the operation phase is set + for the data transfer, and the index of the first word to transfer is set. + + + Implementation notes: + + 1. Entry while executing a Write Full Sector or Initialize command inhibits + address verification. In addition, the drive's FORMAT switch must be set + to the enabled position for these commands to succeed. +*/ + +static void start_write (CVPTR cvptr, UNIT *uptr) +{ +const t_bool verify = (CNTLR_OPCODE) uptr->OP == Write; /* only Write verifies the sector address */ + +if ((uptr->flags & UNIT_WPROT) /* is the unit write protected, */ + || !verify && !(uptr->flags & UNIT_FMT)) /* or is formatting required but not enabled? */ + dl_end_command (cvptr, status_2_error); /* terminate the write with an error */ + +else if (position_sector (cvptr, uptr, verify)) { /* writing is permitted; position the sector */ + uptr->PHASE = data_phase; /* positioning succeeded; set up data transfer phase */ + cvptr->index = 0; /* reset the data index */ + } + +return; +} + + +/* Finish a write operation on the current sector. + + The current sector is written from the sector buffer to the disc image file + at the current file position. The next sector address is then updated to + allow writing to continue. + + On entry, the drive is checked to ensure that it is ready for the write. + Then the sector buffer is padded appropriately if a full sector of data was + not transferred. The buffer is written to the disc image file at the + position corresponding to the controller address as set when the sector was + started. The write begins at a buffer offset determined by the command (a + Write Full Sector has header words at the start of the buffer that are not + written to the disc image). + + If the image write failed with a file system error, SCPE_IOERR is returned + from the service routine to cause a simulation stop; resumption is handled as + an Uncorrectable Data Error. If the image was written correctly, the next + sector address is updated. If the end-of-data flag is set, the current write + is completed. Otherwise, the command phase is reset to start the next + sector, and the disc service is scheduled to allow for the intersector delay. + + + Implementation notes: + + 1. A partial sector is filled with 177777B words (ICD) or copies of the last + word (MAC) per page 7-10 of the ICD/MAC Disc Diagnostic manual. +*/ + +static t_stat end_write (CVPTR cvptr, UNIT *uptr) +{ +uint32 count; +uint16 pad; +const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP; +const uint32 offset = (opcode == Write_Full_Sector ? 3 : 0); + +if (uptr->flags & UNIT_UNLOAD) { /* if the drive is not ready, */ + dl_end_command (cvptr, access_not_ready); /* terminate the command with an error */ + return SCPE_OK; + } + +if (cvptr->index < DL_WPSEC + offset) { /* was a partial sector transferred? */ + if (cvptr->type == ICD) /* an ICD controller */ + pad = DMASK; /* pads the sector with -1 */ + else /* a MAC controller */ + pad = cvptr->buffer [cvptr->index - 1]; /* pads with the last word written */ + + for (count = cvptr->index; count < DL_WPSEC + offset; count++) + cvptr->buffer [count] = pad; /* pad the sector buffer as needed */ + } + +sim_fwrite (cvptr->buffer + offset, sizeof (uint16), /* write the sector to the file */ + DL_WPSEC, uptr->fileref); + +if (ferror (uptr->fileref)) /* did a host file system error occur? */ + return io_error (cvptr, uptr, uncorrectable_data_error); /* set up the data error status and stop the simulation */ + +next_sector (cvptr, uptr); /* address the next sector */ + +if (cvptr->eod == SET) /* is the end of data indicated? */ + dl_end_command (cvptr, normal_completion); /* complete the command */ + +else { /* writing continues */ + uptr->PHASE = start_phase; /* reset to the start phase */ + uptr->wait = cvptr->sector_time; /* delay for the intersector time */ + } + +return SCPE_OK; +} + + +/* Position the disc image file at the current sector. + + The image file is positioned at the byte address corresponding to the drive's + current cylinder and the controller's current head and sector addresses. + Positioning may involve an auto-seek if a prior read or write addressed the + final sector of a cylinder. If a seek is initiated or an error is detected, + the routine returns FALSE to indicate that the positioning was not performed. + If the file was positioned, the routine returns TRUE. + + On entry, if the controller's end-of-cylinder flag is set, a prior read or + write addressed the final sector in the current cylinder. If the file mask + does not permit auto-seeking, the current command is terminated with an End + of Cylinder error. Otherwise, the cylinder is incremented or decremented as + directed by the file mask, and a seek to the new cylinder is started. + + If the increment or decrement resulted in an out-of-bounds value, the seek + will return Seek Check status, and the command is terminated with an error. + If the seek is legal, the routine returns with the disc service scheduled for + seek completion and the command state unchanged. When the service is + reentered, the read or write will continue on the new cylinder. + + If the EOC flag was not set, the drive's position is checked against the + controller's position if address verification is requested. If they are + different (as may occur with an Address Record command that specified a + different location than the last Seek command), a seek is started to the + correct cylinder, and the routine returns with the disc service scheduled for + seek completion as above. + + If the drive and controller positions agree or verification is not requested, + the CHS addresses are validated against the drive limits. If they are + invalid, Seek Check status is set, and the command is terminated with an + error. + + If the addresses are valid, the drive is checked to ensure that it is ready + for positioning. If it is, the file is positioned to a byte offset in the + image file that is calculated from the CHS address. If positioning succeeds, + the disc service is scheduled to begin the data transfer, and the routine + returns TRUE to indicate that the file position is set. If positioning fails + with a host file system error, it is reported to the simulation console, and + the routine returns FALSE to indicate that an AGC (drive positioner) fault + occurred. + + + Implementation notes: + + 1. The ICD controller returns an End of Cylinder error if an auto-seek + results in a position beyond the drive limits. The MAC controller + returns a Status-2 error. Both controllers set the Seek Check bit in the + drive status word. +*/ + +static t_bool position_sector (CVPTR cvptr, UNIT *uptr, t_bool verify) +{ +uint32 block; +uint32 model = GET_MODEL (uptr->flags); + +if (cvptr->eoc == SET) /* are we at the end of a cylinder? */ + if (cvptr->file_mask & DL_FAUTSK) { /* is an auto-seek allowed? */ + if (cvptr->file_mask & DL_FDECR) /* is a decremental seek requested? */ + cvptr->cylinder = (cvptr->cylinder - 1) & DMASK; /* decrease the cylinder address with wraparound */ + else /* an incremental seek is requested */ + cvptr->cylinder = (cvptr->cylinder + 1) & DMASK; /* increase the cylinder address with wraparound */ + + start_seek (cvptr, uptr, /* start the auto-seek */ + (CNTLR_OPCODE) uptr->OP, /* with the current operation */ + (CNTLR_PHASE) uptr->PHASE); /* and phase unchanged */ + + if (uptr->STAT & DL_S2SC) /* did a seek check occur? */ + if (cvptr->type == ICD) /* is this ICD controller? */ + dl_end_command (cvptr, end_of_cylinder); /* report it as an End of Cylinder error */ + else /* it is a MAC controller */ + dl_end_command (cvptr, status_2_error); /* report it as a Status-2 error */ + } + + else /* the file mask does not permit an auto-seek */ + dl_end_command (cvptr, end_of_cylinder); /* so terminate with an EOC error */ + +else if (verify && (uint32) uptr->CYL != cvptr->cylinder) { /* is the positioner on the wrong cylinder? */ + start_seek (cvptr, uptr, /* start a seek to the correct cylinder */ + (CNTLR_OPCODE) uptr->OP, /* with the current operation */ + (CNTLR_PHASE) uptr->PHASE); /* and phase unchanged */ + + if (uptr->STAT & DL_S2SC) /* did a seek check occur? */ + dl_end_command (cvptr, status_2_error); /* report a Status-2 error */ + } + +else if (((uint32) uptr->CYL >= drive_props [model].cylinders) /* is the cylinder out of bounds? */ + || (cvptr->head >= drive_props [model].heads) /* or the head? */ + || (cvptr->sector >= drive_props [model].sectors)) { /* or the sector? */ + uptr->STAT = uptr->STAT | DL_S2SC; /* set Seek Check status */ + dl_end_command (cvptr, status_2_error); /* and terminate with an error */ + } + +else if (uptr->flags & UNIT_UNLOAD) /* is the drive ready for positioning? */ + dl_end_command (cvptr, access_not_ready); /* terminate the command with an access error */ + +else { /* we are ready to position the image file */ + block = TO_BLOCK (uptr->CYL, cvptr->head, /* calculate the new block position */ + cvptr->sector, model); /* (for inspection only) */ + uptr->pos = TO_OFFSET (block); /* and then convert to a byte offset */ + + if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the image file position; if it failed */ + io_error (cvptr, uptr, status_2_error); /* then report it to the simulation console */ + + dl_load_unload (cvptr, uptr, FALSE); /* unload the heads */ + uptr->STAT |= DL_S2FAULT; /* and set Fault status */ + } + + else { /* otherwise the seek succeeded */ + uptr->wait = cvptr->data_time; /* so delay for the data access time */ + + return TRUE; /* report that positioning was accomplished */ + } + } + +return FALSE; /* report that positioning failed or was deferred */ +} + + +/* Address the next sector. + + The controller's CHS address is incremented to point at the next sector. If + the next sector number is valid, the routine returns. Otherwise, the sector + number is reset to sector 0. If the file mask is set for cylinder mode, the + head is incremented, and if the new head number is valid, the routine + returns. If the head number is invalid, it is reset to head 0, and the + end-of-cylinder flag is set. The EOC flag is also set if the file mask is + set for surface mode. + + The new cylinder address is not set here, because cylinder validation must + only occur when the next sector is actually accessed. Otherwise, reading or + writing the last sector on a track or cylinder with auto-seek disabled would + cause an End of Cylinder error, even if the transfer ended with that sector. + Instead, we set the EOC flag to indicate that a cylinder update is pending. + + As a result of this deferred update method, the state of the EOC flag must be + considered when returning the disc address to the CPU. +*/ + +static void next_sector (CVPTR cvptr, UNIT *uptr) +{ +const uint32 model = GET_MODEL (uptr->flags); /* get the disc model */ + +cvptr->sector = cvptr->sector + 1; /* increment the sector number */ + +if (cvptr->sector < drive_props [model].sectors) /* are we at the end of the track? */ + return; /* no, so the next sector value is OK */ + +cvptr->sector = 0; /* wrap the sector number */ + +if (cvptr->file_mask & DL_FCYLM) { /* are we in cylinder mode? */ + cvptr->head = cvptr->head + 1; /* yes, so increment the head */ + + if (cvptr->head < drive_props [model].heads) /* are we at the end of the cylinder? */ + return; /* no, so the next head value is OK */ + + cvptr->head = 0; /* wrap the head number */ + } + +cvptr->eoc = SET; /* set the end-of-cylinder flag to */ +return; /* indicate that an update is required */ +} + + +/* Start a seek. + + A seek is initiated on the indicated unit if the drive is ready and the + cylinder, head, and sector values in the controller are valid for the current + drive model. If the current operation is a recalibrate, a seek is initiated + to cylinder 0 instead of the cylinder value stored in the controller. The + routine returns TRUE if the drive was ready for the seek and FALSE if it was + not. + + If the controller cylinder is beyond the drive's limit, Seek Check status is + set in the unit, and the heads are not moved. Otherwise, the relative + cylinder position change is calculated, and the heads are moved to the new + position. + + If the controller head or sector is beyond the drive's limit, Seek Check + status is set in the unit. Otherwise, Seek Check status is cleared, and the + new file offset is calculated. + + A seek check terminates the current command for an ICD controller. For a MAC + controller, the seek check is noted in the drive status, but processing will + continue until the drive sets Attention status. + + Finally, the drive operation and phase are set to the supplied values before + returning. + + + Implementation notes: + + 1. EOC is not reset for recalibrate so that a reseek will return to the same + location as was current when the recalibrate was done. + + 2. Calculation of the file offset is performed here simply to keep the unit + position register available for inspection. The actual file positioning + is done in position_sector. + + 3. In hardware, a seek to the current location will set Drive Busy status + for 1.3 milliseconds (the head settling time). In simulation, disc + service is scheduled as though a one-cylinder seek was requested. +*/ + +static t_bool start_seek (CVPTR cvptr, UNIT *uptr, CNTLR_OPCODE next_opcode, CNTLR_PHASE next_phase) +{ +int32 delta; +uint32 block, target_cylinder; +const uint32 model = GET_MODEL (uptr->flags); /* get the drive model */ + +if (uptr->flags & UNIT_UNLOAD) { /* are the heads unloaded? */ + dl_end_command (cvptr, status_2_error); /* the seek ends with Status-2 error */ + return FALSE; /* as the drive was not ready */ + } + +if ((CNTLR_OPCODE) uptr->OP == Recalibrate) /* is the unit recalibrating? */ + target_cylinder = 0; /* seek to cylinder 0 and don't reset the EOC flag */ + +else { /* it's a Seek command or an auto-seek request */ + target_cylinder = cvptr->cylinder; /* seek to the controller cylinder */ + cvptr->eoc = CLEAR; /* clear the end-of-cylinder flag */ + } + +if (target_cylinder >= drive_props [model].cylinders) { /* is the cylinder out of bounds? */ + delta = 0; /* don't change the positioner */ + uptr->STAT = uptr->STAT | DL_S2SC; /* and set Seek Check status */ + } + +else { /* the cylinder value is OK */ + delta = abs (uptr->CYL - (int32) target_cylinder); /* calculate the relative movement */ + uptr->CYL = target_cylinder; /* and move the positioner */ + + if ((cvptr->head >= drive_props [model].heads) /* if the head */ + || (cvptr->sector >= drive_props [model].sectors)) /* or the sector is out of bounds, */ + uptr->STAT = uptr->STAT | DL_S2SC; /* set Seek Check status */ + + else { /* the head and sector are OK */ + uptr->STAT = uptr->STAT & ~DL_S2SC; /* clear Seek Check status */ + + block = TO_BLOCK (uptr->CYL, cvptr->head, /* set up the new block position */ + cvptr->sector, model); /* (for inspection only) */ + uptr->pos = TO_OFFSET (block); /* and then convert to a byte offset */ + } + } + +if ((uptr->STAT & DL_S2SC) && cvptr->type == ICD) /* did a Seek Check occur for an ICD controller? */ + dl_end_command (cvptr, status_2_error); /* the command ends with a Status-2 error */ + +else { /* the seek was OK or this is a MAC controller */ + if (delta == 0) /* if the seek is to the same cylinder, */ + delta = 1; /* then schedule as a one-cylinder seek */ + + uptr->wait = cvptr->seek_time * delta; /* the seek delay is based on the relative movement */ + } + +uptr->OP = next_opcode; /* set the next operation */ +uptr->PHASE = next_phase; /* and command phase */ +return TRUE; /* and report that the drive was ready */ +} + + +/* Report a stream I/O error. + + Errors indicated by the host file system are reported to the console, and + simulation is stopped with an "I/O error" message. If the simulation is + continued, the CPU will receive the supplied status indication from the + controller. +*/ + +static t_stat io_error (CVPTR cvptr, UNIT *uptr, CNTLR_STATUS status) +{ +dl_end_command (cvptr, status); /* terminate the command with an error */ + +perror ("HP 2100 simulator disc library I/O error"); /* report the error to the console */ +clearerr (uptr->fileref); /* and clear the error in case we resume */ + +return SCPE_IOERR; /* return an I/O error to stop the simulator */ +} + + + +/* Disc library local utility routines */ + + +/* Set the current controller address into the buffer. + + The controller's current cylinder, head, and sector are packed into two words + and stored in the sector buffer, starting at the index specified. If the + end-of-cylinder flag is set, the cylinder is incremented to reflect the + auto-seek that will be attempted when the next sequential access is made. + + + Implementation notes: + + 1. The 13037 firmware always increments the cylinder number if the EOC flag + is set, rather than checking cylinder increment/decrement bit in the file + mask. +*/ + +static void set_address (CVPTR cvptr, uint32 index) +{ +cvptr->buffer [index] = (uint16) cvptr->cylinder /* update the cylinder if EOC is set */ + + (cvptr->eoc == SET ? 1 : 0); + +cvptr->buffer [index + 1] = SET_HEAD (cvptr) /* merge the head and sector */ + | SET_SECTOR (cvptr); + +return; +} + + +/* Start or stop the command wait timer. + + A MAC controller uses a 1.8 second timer to ensure that it does not wait + forever for a non-responding disc drive or CPU interface. In simulation, MAC + interfaces supply an auxiliary timer unit that is activated when the command + wait timer is started and cancelled when the timer is stopped. + + ICD interfaces do not use the command wait timer or supply an auxiliary unit. + + + Implementation notes: + + 1. Absolute activation is used because the timer is restarted between + parameter word transfers. +*/ + +static void set_timer (CVPTR cvptr, FLIP_FLOP action) +{ +if (cvptr->type == MAC) /* is this a MAC controller? */ + if (action == SET) /* should we start the timer? */ + sim_activate_abs (cvptr->aux + timer, /* activate the auxiliary unit */ + cvptr->wait_time); + else /* we stop the timer */ + sim_cancel (cvptr->aux + timer); /* by canceling the unit */ +return; +} + + +/* Return the drive status (status word 2). + + In hardware, the controller outputs the Address Unit command on the drive tag + bus and the unit number on the drive control bus. The addressed drive then + responds by setting its internal "selected" flag. The controller then + outputs the Request Status command on the tag bug, and the selected drive + returns its status on the control bus. If a drive is selected but the heads + are unloaded, the drive returns Not Ready and Busy status. If no drive is + selected, the control bus floats inactive. This is interpreted by the + controller as Not Ready status (because the drive returns an inactive Ready + status). + + In simulation, an enabled but detached unit corresponds to "selected but + heads unloaded," and a disabled unit corresponds to a non-existent unit. + + + Implementation notes: + + 1. The Attention, Drive Fault, First Status, and Seek Check bits are stored + in the unit status word. The other status bits are determined + dynamically. + + 2. The Drive Busy bit is set if the unit service is scheduled. In hardware, + this bit indicates that the heads are not positioned over a track, i.e., + that a seek is in progress. In simulation, the only time a Request + Status command is allowed is either when the controller is waiting for + seek completion or for a new command. In the latter case, unit service + will not be scheduled, so activation can only be for seek completion. +*/ + +static uint16 drive_status (UNIT *uptr) +{ +uint16 status; +uint32 model; + +if (uptr == NULL) /* if the unit is invalid */ + return DL_S2ERR | DL_S2NR; /* then it does not respond */ + +model = GET_MODEL (uptr->flags); /* get the drive model */ +status = (uint16) (drive_props [model].type | uptr->STAT); /* start with the drive type and unit status */ + +if (uptr->flags & UNIT_WPROT) /* is the write protect switch set? */ + status |= DL_S2RO; /* set the Protected status bit */ + +if (uptr->flags & UNIT_FMT) /* is the format switch enabled? */ + status |= DL_S2FMT; /* set the Format status bit */ + +if (uptr->flags & UNIT_DIS) /* is the unit non-existent? */ + status |= DL_S2NR; /* set the Not Ready bit */ + +else if (uptr->flags & UNIT_UNLOAD) /* are the heads unloaded? */ + status |= DL_S2NR | DL_S2BUSY; /* set the Not Ready and Drive Busy bits */ + +if (sim_is_active (uptr)) /* is the drive positioner moving? */ + status |= DL_S2BUSY; /* set the Drive Busy bit */ + +if (status & DL_S2ERRORS) /* are there any Status-2 errors? */ + status |= DL_S2ERR; /* set the Error bit */ + +return status; /* return the unit status */ +} diff --git a/HP2100/hp2100_disclib.h b/HP2100/hp2100_disclib.h new file mode 100644 index 00000000..ef765ed8 --- /dev/null +++ b/HP2100/hp2100_disclib.h @@ -0,0 +1,387 @@ +/* hp_disclib.h: HP MAC/ICD disc controller simulator library declarations + + Copyright (c) 2011-2017, J. David Bryan + Copyright (c) 2004-2011, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. + + 17-Jan-17 JDB Moved "hp2100_defs.h" inclusion to "hp2100_disclib.c" + 13-May-16 JDB Modified for revised SCP API function parameter types + 24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash + 07-May-12 JDB Added end-of-track delay time as a controller variable + 02-May-12 JDB First release + 09-Nov-11 JDB Created disc controller common library from DS simulator + + + This file defines the interface between interface simulators and the + simulation library for the HP 13037 and 13365 disc controllers. It must be + included by the interface-specific modules (DA, DS, etc.). +*/ + + + +/* Program limits */ + +#define DL_MAXDRIVE 7 /* last valid drive number */ +#define DL_MAXUNIT 10 /* last legal unit number */ + +#define DL_AUXUNITS 2 /* number of MAC auxiliary units required */ + +#define DL_WPSEC 128 /* words per normal sector */ +#define DL_WPFSEC 138 /* words per full sector */ +#define DL_BUFSIZE DL_WPFSEC /* required buffer size in words */ + + +/* Default controller times */ + +#define DL_EOT_TIME 160 /* end-of-track delay time */ +#define DL_SEEK_TIME 100 /* seek delay time (per cylinder) */ +#define DL_SECTOR_TIME 27 /* intersector delay time */ +#define DL_CMD_TIME 3 /* command start delay time */ +#define DL_DATA_TIME 1 /* data transfer delay time */ + +#define DL_WAIT_TIME 2749200 /* command wait timeout (1.74 seconds) */ + + +/* Common per-unit disc drive state variables */ + +#define CYL u3 /* current drive cylinder */ +#define STAT u4 /* current drive status (Status 2) */ +#define OP u5 /* current drive operation in process */ +#define PHASE u6 /* current drive operation phase */ + + +/* Unit flags and accessors */ + +#define UNIT_V_MODEL (UNIT_V_UF + 0) /* bits 1-0: model ID */ +#define UNIT_V_WLK (UNIT_V_UF + 2) /* bits 2-2: write locked (protect switch) */ +#define UNIT_V_UNLOAD (UNIT_V_UF + 3) /* bits 3-3: heads unloaded */ +#define UNIT_V_FMT (UNIT_V_UF + 4) /* bits 4-4: format enabled */ +#define UNIT_V_AUTO (UNIT_V_UF + 5) /* bits 5-5: autosize */ +#define DL_V_UF (UNIT_V_UF + 6) /* first free unit flag bit */ + +#define UNIT_M_MODEL 03 /* model ID mask */ + +#define UNIT_MODEL (UNIT_M_MODEL << UNIT_V_MODEL) +#define UNIT_WLK (1 << UNIT_V_WLK) +#define UNIT_UNLOAD (1 << UNIT_V_UNLOAD) +#define UNIT_FMT (1 << UNIT_V_FMT) +#define UNIT_AUTO (1 << UNIT_V_AUTO) + +#define UNIT_WPROT (UNIT_WLK | UNIT_RO) /* write protected if locked or read-only */ + +#define GET_MODEL(t) (((t) >> UNIT_V_MODEL) & UNIT_M_MODEL) +#define SET_MODEL(t) (((t) & UNIT_M_MODEL) << UNIT_V_MODEL) + + +/* Status-1 accessors */ + +#define DL_V_S1SPD 13 /* bits 15-13: S/P/D flags */ +#define DL_V_S1STAT 8 /* bits 12- 8: controller status */ +#define DL_V_S1UNIT 0 /* bits 3- 0: last unit number */ + +#define DL_M_S1UNIT 017 /* unit number mask */ + +#define GET_S1UNIT(v) (((v) >> DL_V_S1UNIT) & DL_M_S1UNIT) + +#define SET_S1SPD(v) ((v) << DL_V_S1SPD) +#define SET_S1STAT(v) ((v) << DL_V_S1STAT) +#define SET_S1UNIT(v) ((v) << DL_V_S1UNIT) + + +/* Status-2 accessors (+ = kept in unit status, - = determined dynamically) */ + +#define DL_V_S2ERR 15 /* bits 15-15: (-) any error flag */ +#define DL_V_S2DTYP 9 /* bits 12- 9: (-) drive type */ +#define DL_V_S2ATN 7 /* bits 7- 7: (+) attention flag */ +#define DL_V_S2RO 6 /* bits 6- 6: (-) read only flag */ +#define DL_V_S2FMT 5 /* bits 5- 5: (-) format enabled flag */ +#define DL_V_S2FAULT 4 /* bits 4- 4: (+) drive fault flag */ +#define DL_V_S2FS 3 /* bits 3- 3: (+) first status flag */ +#define DL_V_S2SC 2 /* bits 2- 2: (+) seek check flag */ +#define DL_V_S2NR 1 /* bits 1- 1: (-) not ready flag */ +#define DL_V_S2BUSY 0 /* bits 0- 1: (-) drive busy flag */ + +#define DL_S2ERR (1 << DL_V_S2ERR) +#define DL_S2DTYP (1 << DL_V_S2DTYP) +#define DL_S2ATN (1 << DL_V_S2ATN) +#define DL_S2RO (1 << DL_V_S2RO) +#define DL_S2FMT (1 << DL_V_S2FMT) +#define DL_S2FAULT (1 << DL_V_S2FAULT) +#define DL_S2FS (1 << DL_V_S2FS) +#define DL_S2SC (1 << DL_V_S2SC) +#define DL_S2NR (1 << DL_V_S2NR) +#define DL_S2BUSY (1 << DL_V_S2BUSY) + +#define DL_S2STOPS (DL_S2FAULT | DL_S2SC | DL_S2NR) /* bits that stop drive access */ +#define DL_S2ERRORS (DL_S2FAULT | DL_S2SC | DL_S2NR | DL_S2BUSY) /* bits that set S2ERR */ +#define DL_S2CPS (DL_S2ATN | DL_S2FAULT | DL_S2FS | DL_S2SC) /* bits cleared by Controller Preset */ + + +/* Drive properties. + + The controller library supports four different disc drive models with these + properties: + + Drive Model Drive Sectors Heads per Cylinders Megabytes + Model ID Type per Head Cylinder per Drive per Drive + ----- ----- ----- -------- --------- --------- --------- + 7905 0 2 48 3 411 15 + 7906 1 0 48 4 411 20 + 7920 2 1 48 5 823 50 + 7925 3 3 64 9 823 120 + + The Drive Type is reported by the controller in the second status word + (Status-2) returned by the Request Status command. + + Model IDs are used in the unit flags to identify the unit's model. For the + autosizing feature to work, models must be assigned ascending IDs in order of + ascending drive sizes. +*/ + +#define D7905_MODEL 0 +#define D7905_SECTS 48 +#define D7905_HEADS 3 +#define D7905_CYLS 411 +#define D7905_TYPE (2 << DL_V_S2DTYP) +#define D7905_WORDS (D7905_SECTS * D7905_HEADS * D7905_CYLS * DL_WPSEC) + +#define D7906_MODEL 1 +#define D7906_SECTS 48 +#define D7906_HEADS 4 +#define D7906_CYLS 411 +#define D7906_TYPE (0 << DL_V_S2DTYP) +#define D7906_WORDS (D7906_SECTS * D7906_HEADS * D7906_CYLS * DL_WPSEC) + +#define D7920_MODEL 2 +#define D7920_SECTS 48 +#define D7920_HEADS 5 +#define D7920_CYLS 823 +#define D7920_TYPE (1 << DL_V_S2DTYP) +#define D7920_WORDS (D7920_SECTS * D7920_HEADS * D7920_CYLS * DL_WPSEC) + +#define D7925_MODEL 3 +#define D7925_SECTS 64 +#define D7925_HEADS 9 +#define D7925_CYLS 823 +#define D7925_TYPE (3 << DL_V_S2DTYP) +#define D7925_WORDS (D7925_SECTS * D7925_HEADS * D7925_CYLS * DL_WPSEC) + +#define MODEL_7905 SET_MODEL (D7905_MODEL) +#define MODEL_7906 SET_MODEL (D7906_MODEL) +#define MODEL_7920 SET_MODEL (D7920_MODEL) +#define MODEL_7925 SET_MODEL (D7925_MODEL) + + +/* Controller types */ + +typedef enum { + MAC = 0, + ICD, + last_type = ICD, /* last valid type */ + type_count /* count of controller types */ + } CNTLR_TYPE; + + +/* Controller opcodes */ + +typedef enum { + Cold_Load_Read = 000, + Recalibrate = 001, + Seek = 002, + Request_Status = 003, + Request_Sector_Address = 004, + Read = 005, + Read_Full_Sector = 006, + Verify = 007, + Write = 010, + Write_Full_Sector = 011, + Clear = 012, + Initialize = 013, + Address_Record = 014, + Request_Syndrome = 015, + Read_With_Offset = 016, + Set_File_Mask = 017, + Invalid_Opcode = 020, + Read_Without_Verify = 022, + Load_TIO_Register = 023, + Request_Disc_Address = 024, + End = 025, + Wakeup = 026, + Last_Opcode = Wakeup /* last valid opcode */ + } CNTLR_OPCODE; + +#define DL_OPCODE_MASK 037 + + +/* Controller command phases */ + +typedef enum { + start_phase = 0, + data_phase, + end_phase, + last_phase = end_phase /* last valid phase */ + } CNTLR_PHASE; + + +/* Controller status. + + Not all status values are returned by the library. The values not currently + returned are: + + - illegal_drive_type + - cylinder_miscompare + - head_sector_miscompare + - io_program_error + - sync_timeout + - correctable_data_error + - illegal_spare_access + - defective_track + - protected_track +*/ + +typedef enum { + normal_completion = 000, + illegal_opcode = 001, + unit_available = 002, + illegal_drive_type = 003, + cylinder_miscompare = 007, + uncorrectable_data_error = 010, + head_sector_miscompare = 011, + io_program_error = 012, + sync_timeout = 013, + end_of_cylinder = 014, + data_overrun = 016, + correctable_data_error = 017, + illegal_spare_access = 020, + defective_track = 021, + access_not_ready = 022, + status_2_error = 023, + protected_track = 026, + unit_unavailable = 027, + drive_attention = 037 + } CNTLR_STATUS; + + +/* Controller execution states */ + +typedef enum { + cntlr_idle, /* idle */ + cntlr_wait, /* command wait */ + cntlr_busy /* busy */ + } CNTLR_STATE; + + +/* Controller command classifications */ + +typedef enum { + class_invalid, /* invalid classification */ + class_read, /* read classification */ + class_write, /* write classification */ + class_control, /* control classification */ + class_status /* status classification */ + } CNTLR_CLASS; + + +/* Controller clear types */ + +typedef enum { + hard_clear, /* power-on/preset hard clear */ + soft_clear /* programmed soft clear */ + } CNTLR_CLEAR; + + +/* Controller state variables */ + +typedef struct { + CNTLR_TYPE type; /* controller type */ + CNTLR_STATE state; /* controller state */ + CNTLR_OPCODE opcode; /* controller opcode */ + CNTLR_STATUS status; /* controller status */ + FLIP_FLOP eoc; /* end-of-cylinder flag */ + FLIP_FLOP eod; /* end-of-data flag */ + uint32 spd_unit; /* S/P/D flags and unit number */ + uint32 file_mask; /* file mask */ + uint32 retry; /* retry counter */ + uint32 cylinder; /* cylinder address */ + uint32 head; /* head address */ + uint32 sector; /* sector address */ + uint32 verify_count; /* count of sectors to verify */ + uint32 poll_unit; /* last unit polled for attention */ + uint16 *buffer; /* data buffer pointer */ + uint32 index; /* data buffer current index */ + uint32 length; /* data buffer valid length */ + UNIT *aux; /* MAC auxiliary units (controller and timer) */ + int32 eot_time; /* end-of-track read delay time */ + int32 seek_time; /* per-cylinder seek delay time */ + int32 sector_time; /* intersector delay time */ + int32 cmd_time; /* command response time */ + int32 data_time; /* data transfer response time */ + int32 wait_time; /* command wait time */ + } CNTLR_VARS; + + +typedef CNTLR_VARS *CVPTR; /* pointer to controller state variables */ + +/* Controller state variables initialization. + + The parameters are: + + ctype - type of the controller (CNTLR_TYPE) + bufptr - pointer to the data buffer + auxptr - pointer to the auxiliary units (MAC only; NULL for ICD) +*/ + +#define CNTLR_INIT(ctype,bufptr,auxptr) \ + (ctype), cntlr_idle, End, normal_completion, \ + CLEAR, CLEAR, \ + 0, 0, 0, 0, 0, 0, 0, 0, \ + (bufptr), 0, 0, (auxptr), \ + DL_EOT_TIME, DL_SEEK_TIME, DL_SECTOR_TIME, \ + DL_CMD_TIME, DL_DATA_TIME, DL_WAIT_TIME + + + +/* Disc library global controller routines */ + +extern t_bool dl_prepare_command (CVPTR cvptr, UNIT *units, uint32 unit_limit); +extern UNIT *dl_start_command (CVPTR cvptr, UNIT *units, uint32 unit_limit); +extern void dl_end_command (CVPTR cvptr, CNTLR_STATUS status); +extern t_bool dl_poll_drives (CVPTR cvptr, UNIT *units, uint32 unit_limit); +extern t_stat dl_service_drive (CVPTR cvptr, UNIT *uptr); +extern t_stat dl_service_controller (CVPTR cvptr, UNIT *uptr); +extern t_stat dl_service_timer (CVPTR cvptr, UNIT *uptr); +extern void dl_idle_controller (CVPTR cvptr); +extern t_stat dl_clear_controller (CVPTR cvptr, UNIT *uptr, CNTLR_CLEAR clear_type); +extern t_stat dl_load_unload (CVPTR cvptr, UNIT *uptr, t_bool load); + +/* Disc library global utility routines */ + +extern CNTLR_CLASS dl_classify (CNTLR_VARS cntlr); +extern const char *dl_opcode_name (CNTLR_TYPE controller, CNTLR_OPCODE opcode); +extern const char *dl_phase_name (CNTLR_PHASE phase); + +/* Disc library global VM routines */ + +extern t_stat dl_attach (CVPTR cvptr, UNIT *uptr, CONST char *cptr); +extern t_stat dl_detach (CVPTR cvptr, UNIT *uptr); +extern t_stat dl_set_model (UNIT *uptr, int32 value, CONST char *cptr, void *desc); diff --git a/HP2100/hp2100_dp.c b/HP2100/hp2100_dp.c index fcd0723c..0c3e9e99 100644 --- a/HP2100/hp2100_dp.c +++ b/HP2100/hp2100_dp.c @@ -1,1198 +1,1204 @@ -/* hp2100_dp.c: HP 2100 12557A/13210A disk simulator - - Copyright (c) 1993-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - DP 12557A 2871 disk subsystem - 13210A 7900 disk subsystem - - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 24-Dec-14 JDB Added casts for explicit downward conversions - 18-Dec-12 MP Now calls sim_activate_time to get remaining seek time - 09-May-12 JDB Separated assignments from conditional expressions - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - Added CNTLR_TYPE cast to dp_settype - 28-Mar-11 JDB Tidied up signal handling - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 10-Aug-08 JDB Added REG_FIT to register variables < 32-bit size - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 01-Mar-05 JDB Added SET UNLOAD/LOAD - 07-Oct-04 JDB Fixed enable/disable from either device - Fixed ANY ERROR status for 12557A interface - Fixed unattached drive status for 12557A interface - Status cmd without prior STC DC now completes (12557A) - OTA/OTB CC on 13210A interface also does CLC CC - Fixed RAR model - Fixed seek check on 13210 if sector out of range - 20-Aug-04 JDB Fixes from Dave Bryan - - Check status on unattached drive set busy and not ready - - Check status tests wrong unit for write protect status - - Drive on line sets ATN, will set FLG if polling - 15-Aug-04 RMS Controller resumes polling for ATN interrupts after - read status (found by Dave Bryan) - 22-Jul-04 RMS Controller sets ATN for all commands except - read status (found by Dave Bryan) - 21-Apr-04 RMS Fixed typo in boot loader (found by Dave Bryan) - 26-Apr-04 RMS Fixed SFS x,C and SFC x,C - Fixed SR setting in IBL - Fixed interpretation of SR<0> - Revised IBL loader - Implemented DMA SRQ (follows FLG) - 25-Apr-03 RMS Revised for extended file support - Fixed bug(s) in boot (found by Terry Newton) - 10-Nov-02 RMS Added BOOT command, fixed numerous bugs - 15-Jan-02 RMS Fixed INIT handling (found by Bill McDermith) - 10-Jan-02 RMS Fixed f(x)write call (found by Bill McDermith) - 03-Dec-01 RMS Changed DEVNO to use extended SET/SHOW - 24-Nov-01 RMS Changed STA to be an array - 07-Sep-01 RMS Moved function prototypes - 29-Nov-00 RMS Made variable names unique - 21-Nov-00 RMS Fixed flag, buffer power up state - - References: - - 7900A Disc Drive Operating and Service Manual (07900-90002, Feb-1975) - - 13210A Disc Drive Interface Kit Operating and Service Manual - (13210-90003, Nov-1974) - - 12557A Cartridge Disc Interface Kit Operating and Service Manual - (12557-90001, Sep-1970) - - - The simulator uses a number of state variables: - - dpc_busy set to drive number + 1 when the controller is busy - of the unit in use - dpd_xfer set to 1 if the data channel is executing a data transfer - dpd_wval set to 1 by OTx if either !dpc_busy or dpd_xfer - dpc_poll set to 1 if attention polling is enabled - - dpc_busy and dpd_xfer are set together at the start of a read, write, refine, - or init. When data transfers are complete (CLC DC), dpd_xfer is cleared, but the - operation is not necessarily over. When the operation is complete, dpc_busy - is cleared and the command channel flag is set. - - dpc_busy && !dpd_xfer && STC DC (controller is busy, data channel transfer has - been terminated by CLC DC, but a word has been placed in the data channel buffer) - indicates data overrun. - - dpd_wval is used in write operations to fill out the sector buffer with 0's - if only a partial sector has been transferred. - - dpc_poll indicates whether seek completion polling can occur. It is cleared - by reset and CLC CC and set by issuance of a seek or completion of check status. - - The controller's "Record Address Register" (RAR) contains the CHS address of - the last Seek or Address Record command executed. The RAR is shared among - all drives on the controller. In addition, each drive has an internal - position register that contains the last cylinder position transferred to the - drive during Seek command execution (data operations always start with the - RAR head and sector position). - - In a real drive, the address field of the sector under the head is read and - compared to the RAR. When they match, the target sector is under the head - and is ready for reading or writing. If a match doesn't occur, an Address - Error is indicated. In the simulator, the address field is obtained from the - drive's current position register during a read, i.e., the "on-disc" address - field is assumed to match the current position. - - The following implemented behaviors have been inferred from secondary sources - (diagnostics, operating system drivers, etc.), due to absent or contradictory - authoritative information; future correction may be needed: - - 1. Status bit 15 (ATTENTION) does not set bit 0 (ANY ERROR) on the 12557A. - 2. Omitting STC DC before Status Check does not set DC flag but does poll. -*/ - -#include "hp2100_defs.h" - -#define UNIT_V_WLK (UNIT_V_UF + 0) /* write locked */ -#define UNIT_V_UNLOAD (UNIT_V_UF + 1) /* heads unloaded */ -#define UNIT_WLK (1 << UNIT_V_WLK) -#define UNIT_UNLOAD (1 << UNIT_V_UNLOAD) -#define FNC u3 /* saved function */ -#define DRV u4 /* drive number (DC) */ -#define UNIT_WPRT (UNIT_WLK | UNIT_RO) /* write prot */ - -#define DP_N_NUMWD 7 -#define DP_NUMWD (1 << DP_N_NUMWD) /* words/sector */ -#define DP_NUMSC2 12 /* sectors/srf 12557 */ -#define DP_NUMSC3 24 /* sectors/srf 13210 */ -#define DP_NUMSC (dp_ctype ? DP_NUMSC3 : DP_NUMSC2) -#define DP_NUMSF 4 /* surfaces/cylinder */ -#define DP_NUMCY 203 /* cylinders/disk */ -#define DP_SIZE2 (DP_NUMSF * DP_NUMCY * DP_NUMSC2 * DP_NUMWD) -#define DP_SIZE3 (DP_NUMSF * DP_NUMCY * DP_NUMSC3 * DP_NUMWD) -#define DP_NUMDRV 4 /* # drives */ - -/* Command word */ - -#define CW_V_FNC 12 /* function */ -#define CW_M_FNC 017 -#define CW_GETFNC(x) (((x) >> CW_V_FNC) & CW_M_FNC) -#define FNC_STA 000 /* status check */ -#define FNC_WD 001 /* write */ -#define FNC_RD 002 /* read */ -#define FNC_SEEK 003 /* seek */ -#define FNC_REF 005 /* refine */ -#define FNC_CHK 006 /* check */ -#define FNC_INIT 011 /* init */ -#define FNC_AR 013 /* address */ -#define FNC_SEEK1 020 /* fake - seek1 */ -#define FNC_SEEK2 021 /* fake - seek2 */ -#define FNC_SEEK3 022 /* fake - seek3 */ -#define FNC_CHK1 023 /* fake - check1 */ -#define FNC_AR1 024 /* fake - arec1 */ -#define CW_V_DRV 0 /* drive */ -#define CW_M_DRV 03 -#define CW_GETDRV(x) (((x) >> CW_V_DRV) & CW_M_DRV) - -/* Disk address words */ - -#define DA_V_CYL 0 /* cylinder */ -#define DA_M_CYL 0377 -#define DA_GETCYL(x) (((x) >> DA_V_CYL) & DA_M_CYL) -#define DA_V_HD 8 /* head */ -#define DA_M_HD 03 -#define DA_GETHD(x) (((x) >> DA_V_HD) & DA_M_HD) -#define DA_V_SC 0 /* sector */ -#define DA_M_SC2 017 -#define DA_M_SC3 037 -#define DA_M_SC (dp_ctype ? DA_M_SC3 : DA_M_SC2) -#define DA_GETSC(x) (((x) >> DA_V_SC) & DA_M_SC) -#define DA_CKMASK2 037 /* check mask */ -#define DA_CKMASK3 077 -#define DA_CKMASK (dp_ctype ? DA_CKMASK3 : DA_CKMASK2) - -/* Status in dpc_sta[drv], (u) = unused in 13210, (d) = dynamic */ - -#define STA_ATN 0100000 /* attention (u) */ -#define STA_1ST 0040000 /* first status */ -#define STA_OVR 0020000 /* overrun */ -#define STA_RWU 0010000 /* rw unsafe NI (u) */ -#define STA_ACU 0004000 /* access unsafe NI */ -#define STA_HUNT 0002000 /* hunting NI (12557) */ -#define STA_PROT 0002000 /* protected (13210) */ -#define STA_SKI 0001000 /* incomplete NI (u) */ -#define STA_SKE 0000400 /* seek error */ -/* 0000200 (unused) */ -#define STA_NRDY 0000100 /* not ready (d) */ -#define STA_EOC 0000040 /* end of cylinder */ -#define STA_AER 0000020 /* addr error */ -#define STA_FLG 0000010 /* flagged */ -#define STA_BSY 0000004 /* seeking */ -#define STA_DTE 0000002 /* data error */ -#define STA_ERR 0000001 /* any error (d) */ - -#define STA_ERSET2 (STA_1ST | STA_OVR | STA_RWU | STA_ACU | \ - STA_SKI | STA_SKE | STA_NRDY | \ - STA_EOC | STA_AER | STA_DTE) /* 12557A error set */ -#define STA_ERSET3 (STA_ATN | STA_1ST | STA_OVR | STA_RWU | STA_ACU | \ - STA_SKI | STA_SKE | STA_NRDY | STA_EOC | STA_AER | \ - STA_FLG | STA_BSY | STA_DTE) /* 13210A error set */ -#define STA_ANYERR (dp_ctype ? STA_ERSET3 : STA_ERSET2) - -#define STA_UNLOADED (dp_ctype ? (STA_NRDY | STA_BSY) : STA_NRDY) -#define STA_MBZ13 (STA_ATN | STA_RWU | STA_SKI) /* zero in 13210 */ - -struct { - FLIP_FLOP command; /* cch command flip-flop */ - FLIP_FLOP control; /* cch control flip-flop */ - FLIP_FLOP flag; /* cch flag flip-flop */ - FLIP_FLOP flagbuf; /* cch flag buffer flip-flop */ - } dpc = { CLEAR, CLEAR, CLEAR, CLEAR }; - -/* Controller types */ - -typedef enum { - A12557, - A13210 - } CNTLR_TYPE; - -CNTLR_TYPE dp_ctype = A13210; /* ctrl type */ -int32 dpc_busy = 0; /* cch unit */ -int32 dpc_poll = 0; /* cch poll enable */ -int32 dpc_cnt = 0; /* check count */ -int32 dpc_eoc = 0; /* end of cyl */ -int32 dpc_stime = 100; /* seek time */ -int32 dpc_ctime = 100; /* command time */ -int32 dpc_xtime = 5; /* xfer time */ -int32 dpc_dtime = 2; /* dch time */ -int32 dpd_obuf = 0, dpd_ibuf = 0; /* dch buffers */ -int32 dpc_obuf = 0; /* cch buffers */ - -struct { - FLIP_FLOP command; /* dch command flip-flop */ - FLIP_FLOP control; /* dch control flip-flop */ - FLIP_FLOP flag; /* dch flag flip-flop */ - FLIP_FLOP flagbuf; /* dch flag buffer flip-flop */ - } dpd = { CLEAR, CLEAR, CLEAR, CLEAR }; - -int32 dpd_xfer = 0; /* xfer in prog */ -int32 dpd_wval = 0; /* write data valid */ -int32 dp_ptr = 0; /* buffer ptr */ -uint8 dpc_rarc = 0; /* RAR cylinder */ -uint8 dpc_rarh = 0; /* RAR head */ -uint8 dpc_rars = 0; /* RAR sector */ -uint8 dpc_ucyl[DP_NUMDRV] = { 0 }; /* unit cylinder */ -uint16 dpc_sta[DP_NUMDRV] = { 0 }; /* status regs */ -uint16 dpxb[DP_NUMWD]; /* sector buffer */ - -DEVICE dpd_dev, dpc_dev; - -IOHANDLER dpdio; -IOHANDLER dpcio; - -t_stat dpc_svc (UNIT *uptr); -t_stat dpd_svc (UNIT *uptr); -t_stat dpc_reset (DEVICE *dptr); -t_stat dpc_attach (UNIT *uptr, char *cptr); -t_stat dpc_detach (UNIT* uptr); -t_stat dpc_boot (int32 unitno, DEVICE *dptr); -void dp_god (int32 fnc, int32 drv, int32 time); -void dp_goc (int32 fnc, int32 drv, int32 time); -t_stat dpc_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc); -t_stat dp_settype (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat dp_showtype (FILE *st, UNIT *uptr, int32 val, void *desc); - -/* DPD data structures - - dpd_dev DPD device descriptor - dpd_unit DPD unit list - dpd_reg DPD register list -*/ - -DIB dp_dib[] = { - { &dpdio, DPD }, - { &dpcio, DPC } - }; - -#define dpd_dib dp_dib[0] -#define dpc_dib dp_dib[1] - -UNIT dpd_unit = { UDATA (&dpd_svc, 0, 0) }; - -REG dpd_reg[] = { - { ORDATA (IBUF, dpd_ibuf, 16) }, - { ORDATA (OBUF, dpd_obuf, 16) }, - { BRDATA (DBUF, dpxb, 8, 16, DP_NUMWD) }, - { DRDATA (BPTR, dp_ptr, DP_N_NUMWD) }, - { FLDATA (CMD, dpd.command, 0) }, - { FLDATA (CTL, dpd.control, 0) }, - { FLDATA (FLG, dpd.flag, 0) }, - { FLDATA (FBF, dpd.flagbuf, 0) }, - { FLDATA (XFER, dpd_xfer, 0) }, - { FLDATA (WVAL, dpd_wval, 0) }, - { ORDATA (SC, dpd_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, dpd_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB dpd_mod[] = { - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &dpd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dpd_dev }, - { 0 } - }; - -DEVICE dpd_dev = { - "DPD", &dpd_unit, dpd_reg, dpd_mod, - 1, 10, DP_N_NUMWD, 1, 8, 16, - NULL, NULL, &dpc_reset, - NULL, NULL, NULL, - &dpd_dib, DEV_DISABLE - }; - -/* DPC data structures - - dpc_dev DPC device descriptor - dpc_unit DPC unit list - dpc_reg DPC register list - dpc_mod DPC modifier list -*/ - -UNIT dpc_unit[] = { - { UDATA (&dpc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_UNLOAD, DP_SIZE3) }, - { UDATA (&dpc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_UNLOAD, DP_SIZE3) }, - { UDATA (&dpc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_UNLOAD, DP_SIZE3) }, - { UDATA (&dpc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_UNLOAD, DP_SIZE3) } - }; - -REG dpc_reg[] = { - { ORDATA (OBUF, dpc_obuf, 16) }, - { ORDATA (BUSY, dpc_busy, 4), REG_RO }, - { ORDATA (CNT, dpc_cnt, 5) }, - { FLDATA (CMD, dpc.command, 0) }, - { FLDATA (CTL, dpc.control, 0) }, - { FLDATA (FLG, dpc.flag, 0) }, - { FLDATA (FBF, dpc.flagbuf, 0) }, - { FLDATA (EOC, dpc_eoc, 0) }, - { FLDATA (POLL, dpc_poll, 0) }, - { DRDATA (RARC, dpc_rarc, 8), PV_RZRO | REG_FIT }, - { DRDATA (RARH, dpc_rarh, 2), PV_RZRO | REG_FIT }, - { DRDATA (RARS, dpc_rars, 5), PV_RZRO | REG_FIT }, - { BRDATA (CYL, dpc_ucyl, 10, 8, DP_NUMDRV), PV_RZRO }, - { BRDATA (STA, dpc_sta, 8, 16, DP_NUMDRV) }, - { DRDATA (CTIME, dpc_ctime, 24), PV_LEFT }, - { DRDATA (DTIME, dpc_dtime, 24), PV_LEFT }, - { DRDATA (STIME, dpc_stime, 24), PV_LEFT }, - { DRDATA (XTIME, dpc_xtime, 24), REG_NZ | PV_LEFT }, - { FLDATA (CTYPE, dp_ctype, 0), REG_HRO }, - { URDATA (UFNC, dpc_unit[0].FNC, 8, 8, 0, - DP_NUMDRV, REG_HRO) }, - { URDATA (CAPAC, dpc_unit[0].capac, 10, T_ADDR_W, 0, - DP_NUMDRV, PV_LEFT | REG_HRO) }, - { ORDATA (SC, dpc_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, dpc_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB dpc_mod[] = { - { UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", dpc_load_unload }, - { UNIT_UNLOAD, 0, "heads loaded", "LOADED", dpc_load_unload }, - { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL }, - { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL }, - { MTAB_XTD | MTAB_VDV, 1, NULL, "13210A", - &dp_settype, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 0, NULL, "12557A", - &dp_settype, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 0, "TYPE", NULL, - NULL, &dp_showtype, NULL }, - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &dpd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dpd_dev }, - { 0 } - }; - -DEVICE dpc_dev = { - "DPC", dpc_unit, dpc_reg, dpc_mod, - DP_NUMDRV, 8, 24, 1, 8, 16, - NULL, NULL, &dpc_reset, - &dpc_boot, &dpc_attach, &dpc_detach, - &dpc_dib, DEV_DISABLE - }; - - -/* Data channel I/O signal handler. - - For the 12557A, the card contains the usual control, flag, and flag buffer - flip-flops. PRL, IRQ, and SRQ are standard. A command flip-flop indicates - that data is available. - - For the 13210A, the card has a flag and a flag buffer flip-flop, but no - control or interrupt flip-flop. SRQ is standard. IRQ and PRL are not - driven, and the card does not respond to IAK. STC sets the command flip-flop - to initiate a data transfer. CLC has no effect. - - Implementation notes: - - 1. The CRS signal clears the drive attention register. Under simulation, - drive attention status is generated dynamically, so there is no attention - register. -*/ - -uint32 dpdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - dpd.flag = dpd.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - dpd.flag = dpd.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (dpd); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (dpd); - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, dpd_ibuf); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - dpd_obuf = IODATA (stat_data); /* clear supplied status */ - - if (!dpc_busy || dpd_xfer) /* if !overrun */ - dpd_wval = 1; /* valid */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - dpd.flag = dpd.flagbuf = SET; /* set flag buffer and flag */ - - if (dp_ctype == A12557) /* 12557? */ - dpd_obuf = 0; /* clear output buffer */ - break; - - - case ioCRS: /* control reset */ - dpd.command = CLEAR; /* clear command */ - - if (dp_ctype == A12557) /* 12557? */ - dpd.control = CLEAR; /* clear control */ - - else { /* 13210 */ - dpc_rarc = 0; /* clear controller cylinder address */ - dpc_ucyl [CW_GETDRV (dpc_obuf)] = 0; /* clear last drive addressed cylinder */ - } - break; - - - case ioCLC: /* clear control flip-flop */ - if (dp_ctype == A12557) /* 12557? */ - dpd.control = CLEAR; /* clear control */ - - dpd_xfer = 0; /* clr xfer in progress */ - break; - - - case ioSTC: /* set control flip-flop */ - if (dp_ctype == A12557) /* 12557? */ - dpd.control = SET; /* set control */ - - dpd.command = SET; /* set cmd */ - - if (dpc_busy && !dpd_xfer) /* overrun? */ - dpc_sta[dpc_busy - 1] |= STA_OVR; - break; - - - case ioSIR: /* set interrupt request */ - if (dp_ctype == A12557) { /* 12557? */ - setstdPRL (dpd); /* set standard PRL signal */ - setstdIRQ (dpd); /* set standard IRQ signal */ - } - - setstdSRQ (dpd); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - if (dp_ctype == A12557) /* 12557? */ - dpd.flagbuf = CLEAR; /* clear flag buffer */ - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Command channel I/O signal handler. - - The 12557A and 13210A have the usual control, flag, and flag buffer - flip-flops. Only the 12557A has a command flip-flop. IRQ, PRL, and SRQ are - standard. - - Implementation notes: - - 1. In hardware, the command channel card passes PRH to PRL. The data card - actually drives PRL with the command channel's control and flag states, - even though the command channel's control, flag, and flag buffer drive - IRQH. That is, the priority chain is broken at the data card, although - the command card is interrupting. This works in hardware, but we must - break PRL at the command card under simulation to allow the command card - to interrupt. -*/ - -uint32 dpcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -uint16 data; -int32 i, fnc, drv; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - dpc.flag = dpc.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - dpc.flag = dpc.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (dpc); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (dpc); - break; - - - case ioIOI: /* I/O data input */ - data = 0; - - for (i = 0; i < DP_NUMDRV; i++) /* form attention register value */ - if (dpc_sta[i] & STA_ATN) - data = data | (uint16) (1 << i); - - stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - dpc_obuf = IODATA (stat_data); /* clear supplied status */ - - if (dp_ctype == A13210) /* 13210? */ - dpcio (dibptr, ioCLC, 0); /* OTx causes CLC */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - dpc.flag = dpc.flagbuf = SET; /* set flag buffer and flag */ - - if (dp_ctype == A12557) /* 12557? */ - dpd_obuf = 0; /* clear output buffer */ - break; - - - case ioCRS: /* control reset */ - dpc.control = CLEAR; /* clear control */ - - if (dp_ctype == A12557) /* 12557? */ - dpc.command = CLEAR; /* clear command */ - break; - - - case ioCLC: /* clear control flip-flop */ - dpc.control = CLEAR; /* clr ctl */ - - if (dp_ctype == A12557) /* 12557? */ - dpc.command = CLEAR; /* cancel non-seek */ - - if (dpc_busy) - sim_cancel (&dpc_unit[dpc_busy - 1]); - - sim_cancel (&dpd_unit); /* cancel dch */ - dpd_xfer = 0; /* clr dch xfer */ - dpc_busy = 0; /* clr cch busy */ - dpc_poll = 0; /* clr cch poll */ - break; - - - case ioSTC: /* set control flip-flop */ - dpc.control = SET; /* set ctl */ - - if ((dp_ctype == A13210) || !dpc.command) { /* 13210 or command is clear? */ - if (dp_ctype == A12557) /* 12557? */ - dpc.command = SET; /* set command */ - - drv = CW_GETDRV (dpc_obuf); /* get fnc, drv */ - fnc = CW_GETFNC (dpc_obuf); /* from cmd word */ - - switch (fnc) { /* case on fnc */ - - case FNC_SEEK: /* seek */ - dpc_poll = 1; /* enable polling */ - dp_god (fnc, drv, dpc_dtime); /* sched dch xfr */ - break; - - case FNC_STA: /* rd sta */ - if (dp_ctype == A13210) /* 13210? clr dch flag */ - dpdio (&dpd_dib, ioCLF, 0); - - case FNC_CHK: /* check */ - case FNC_AR: /* addr rec */ - dp_god (fnc, drv, dpc_dtime); /* sched dch xfr */ - break; - - case FNC_RD: case FNC_WD: /* read, write */ - case FNC_REF: case FNC_INIT: /* refine, init */ - dp_goc (fnc, drv, dpc_ctime); /* sched drive */ - break; - } /* end case */ - } /* end if */ - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (dpc); /* set standard PRL signal */ - setstdIRQ (dpc); /* set standard IRQ signal */ - setstdSRQ (dpc); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - dpc.flagbuf = CLEAR; /* clear flag buffer */ - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Start data channel operation */ - -void dp_god (int32 fnc, int32 drv, int32 time) -{ -dpd_unit.DRV = drv; /* save unit */ -dpd_unit.FNC = fnc; /* save function */ -sim_activate (&dpd_unit, time); -return; -} - - -/* Start controller operation */ - -void dp_goc (int32 fnc, int32 drv, int32 time) -{ -int32 t; - -t = sim_activate_time (&dpc_unit[drv]); -if (t) { /* still seeking? */ - sim_cancel (&dpc_unit[drv]); /* stop seek */ - dpc_sta[drv] = dpc_sta[drv] & ~STA_BSY; /* clear busy */ - time = time + t; /* include seek time */ - } -dp_ptr = 0; /* init buf ptr */ -dpc_eoc = 0; /* clear end cyl */ -dpc_busy = drv + 1; /* set busy */ -dpd_xfer = 1; /* xfer in prog */ -dpc_unit[drv].FNC = fnc; /* save function */ -dpc_sta[drv] = dpc_sta[drv] & ~STA_ATN; /* clear ATN */ -sim_activate (&dpc_unit[drv], time); /* activate unit */ -return; -} - - -/* Data channel unit service - - This routine handles the data channel transfers. It also handles - data transfers that are blocked by seek in progress. - - uptr->DRV = target drive - uptr->FNC = target function - - Seek substates - seek - transfer cylinder - seek1 - transfer head/surface - Address record - ar - transfer cylinder - ar1 - transfer head/surface, finish operation - Status check - transfer status, finish operation - Check data - chk - transfer sector count -*/ - -t_stat dpd_svc (UNIT *uptr) -{ -int32 i, drv, st; - -drv = uptr->DRV; /* get drive no */ -switch (uptr->FNC) { /* case function */ - - case FNC_AR: /* arec, need cyl */ - case FNC_SEEK: /* seek, need cyl */ - if (dpd.command) { /* dch active? */ - dpc_rarc = DA_GETCYL (dpd_obuf); /* set RAR from cyl word */ - dpd_wval = 0; /* clr data valid */ - - dpd.command = CLEAR; /* clr dch cmd */ - dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ - - if (uptr->FNC == FNC_AR) uptr->FNC = FNC_AR1; - else uptr->FNC = FNC_SEEK1; /* advance state */ - } - sim_activate (uptr, dpc_xtime); /* no, wait more */ - break; - - case FNC_AR1: /* arec, need hd/sec */ - case FNC_SEEK1: /* seek, need hd/sec */ - if (dpd.command) { /* dch active? */ - dpc_rarh = DA_GETHD (dpd_obuf); /* set RAR from head */ - dpc_rars = DA_GETSC (dpd_obuf); /* set RAR from sector */ - dpd_wval = 0; /* clr data valid */ - - dpd.command = CLEAR; /* clr dch cmd */ - dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ - - if (uptr->FNC == FNC_AR1) { - dpc.command = CLEAR; /* clr cch cmd */ - dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ - - dpc_sta[drv] = dpc_sta[drv] | STA_ATN; /* set drv attn */ - break; /* done if Address Record */ - } - if (sim_is_active (&dpc_unit[drv])) { /* if busy, */ - dpc_sta[drv] = dpc_sta[drv] | STA_SKE; /* seek check */ - break; /* allow prev seek to cmpl */ - } - if ((dpc_rarc >= DP_NUMCY) || /* invalid cyl? */ - ((dp_ctype == A13210) && /* or 13210A */ - (dpc_rars >= DP_NUMSC3))) { /* and invalid sector? */ - dpc_sta[drv] = dpc_sta[drv] | STA_SKE; /* seek check */ - sim_activate (&dpc_unit[drv], 1); /* schedule drive no-wait */ - dpc_unit[drv].FNC = FNC_SEEK3; /* do immed compl w/poll */ - break; - } - st = abs (dpc_rarc - dpc_ucyl[drv]) * dpc_stime; - if (st == 0) st = dpc_stime; /* min time */ - dpc_ucyl[drv] = dpc_rarc; /* transfer RAR */ - sim_activate (&dpc_unit[drv], st); /* schedule drive */ - dpc_sta[drv] = (dpc_sta[drv] | STA_BSY) & - ~(STA_SKE | STA_SKI | STA_HUNT); - dpc_unit[drv].FNC = FNC_SEEK2; /* set operation */ - } - else sim_activate (uptr, dpc_xtime); /* no, wait more */ - break; - - case FNC_STA: /* read status */ - if (dpd.command || (dp_ctype == A13210)) { /* dch act or 13210? */ - if ((dpc_unit[drv].flags & UNIT_UNLOAD) == 0) { /* drive up? */ - dpd_ibuf = dpc_sta[drv] & ~STA_ERR; /* clear err */ - if (dp_ctype == A13210) dpd_ibuf = /* 13210? */ - (dpd_ibuf & ~(STA_MBZ13 | STA_PROT)) | - (dpc_unit[drv].flags & UNIT_WPRT? STA_PROT: 0); - } - else dpd_ibuf = STA_UNLOADED; /* not ready */ - if (dpd_ibuf & STA_ANYERR) /* errors? set flg */ - dpd_ibuf = dpd_ibuf | STA_ERR; - - dpc.command = CLEAR; /* clr cch cmd */ - dpd.command = CLEAR; /* clr dch cmd */ - dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ - } - - dpc_sta[drv] = dpc_sta[drv] & /* clr sta flags */ - ~(STA_ATN | STA_1ST | STA_OVR | - STA_RWU | STA_ACU | STA_EOC | - STA_AER | STA_FLG | STA_DTE); - dpc_poll = 1; /* enable polling */ - for (i = 0; i < DP_NUMDRV; i++) { /* loop thru drives */ - if (dpc_sta[i] & STA_ATN) { /* any ATN set? */ - dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ - break; - } - } - break; - - case FNC_CHK: /* check, need cnt */ - if (dpd.command) { /* dch active? */ - dpc_cnt = dpd_obuf & DA_CKMASK; /* get count */ - dpd_wval = 0; /* clr data valid */ - dp_goc (FNC_CHK1, drv, dpc_xtime); /* sched drv */ - } - else sim_activate (uptr, dpc_xtime); /* wait more */ - break; - - default: - return SCPE_IERR; - } - -return SCPE_OK; -} - - -/* Drive unit service - - This routine handles the data transfers. - - Seek substates - seek2 - done - Refine sector - erase sector, finish operation - Check data - chk1 - finish operation - Read - Write -*/ - -#define GETDA(x,y,z) \ - (((((x) * DP_NUMSF) + (y)) * DP_NUMSC) + (z)) * DP_NUMWD - -t_stat dpc_svc (UNIT *uptr) -{ -int32 da, drv, err; - -err = 0; /* assume no err */ -drv = uptr - dpc_unit; /* get drive no */ -if (uptr->flags & UNIT_UNLOAD) { /* drive down? */ - - dpc.command = CLEAR; /* clr cch cmd */ - dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ - - dpc_sta[drv] = 0; /* clr status */ - dpc_busy = 0; /* ctlr is free */ - dpc_poll = 0; /* polling disabled */ - dpd_xfer = 0; - dpd_wval = 0; - return SCPE_OK; - } -switch (uptr->FNC) { /* case function */ - - case FNC_SEEK2: /* positioning done */ - dpc_sta[drv] = (dpc_sta[drv] | STA_ATN) & ~STA_BSY; /* fall into cmpl */ - case FNC_SEEK3: /* seek complete */ - if (dpc_poll) { /* polling enabled? */ - dpc.command = CLEAR; /* clr cch cmd */ - dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ - } - return SCPE_OK; - - case FNC_REF: /* refine sector */ - break; /* just a NOP */ - - case FNC_RD: /* read */ - case FNC_CHK1: /* check */ - if (dp_ptr == 0) { /* new sector? */ - if (!dpd.command && (uptr->FNC != FNC_CHK1)) break; - if (dpc_rarc != dpc_ucyl[drv]) /* RAR cyl miscompare? */ - dpc_sta[drv] = dpc_sta[drv] | STA_AER; /* set flag, read */ - if (dpc_rars >= DP_NUMSC) { /* bad sector? */ - dpc_sta[drv] = dpc_sta[drv] | STA_AER; /* set flag, stop */ - break; - } - if (dpc_eoc) { /* end of cyl? */ - dpc_sta[drv] = dpc_sta[drv] | STA_EOC; - break; - } - da = GETDA (dpc_rarc, dpc_rarh, dpc_rars); /* calc disk addr */ - dpc_rars = (dpc_rars + 1) % DP_NUMSC; /* incr sector */ - if (dpc_rars == 0) { /* wrap? */ - dpc_rarh = dpc_rarh ^ 1; /* incr head */ - dpc_eoc = ((dpc_rarh & 1) == 0); /* calc eoc */ - } - err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET); - if (err) /* error? */ - break; - fxread (dpxb, sizeof (int16), DP_NUMWD, uptr->fileref); - err = ferror (uptr->fileref); - if (err) /* error? */ - break; - } - dpd_ibuf = dpxb[dp_ptr++]; /* get word */ - if (dp_ptr >= DP_NUMWD) { /* end of sector? */ - if (uptr->FNC == FNC_CHK1) { /* check? */ - dpc_cnt = (dpc_cnt - 1) & DA_CKMASK; /* decr count */ - if (dpc_cnt == 0) break; /* stop at zero */ - } - dp_ptr = 0; /* wrap buf ptr */ - } - if (dpd.command && dpd_xfer) /* dch on, xfer? */ - dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ - - dpd.command = CLEAR; /* clr dch cmd */ - sim_activate (uptr, dpc_xtime); /* sched next word */ - return SCPE_OK; - - case FNC_INIT: /* init */ - case FNC_WD: /* write */ - if (dp_ptr == 0) { /* start sector? */ - if (!dpd.command && !dpd_wval) break; /* xfer done? */ - if (uptr->flags & UNIT_WPRT) { /* wr prot? */ - dpc_sta[drv] = dpc_sta[drv] | STA_FLG; /* set status */ - break; /* done */ - } - if ((dpc_rarc != dpc_ucyl[drv]) || /* RAR cyl miscompare? */ - (dpc_rars >= DP_NUMSC)) { /* bad sector? */ - dpc_sta[drv] = dpc_sta[drv] | STA_AER; /* address error */ - break; - } - if (dpc_eoc) { /* end of cyl? */ - dpc_sta[drv] = dpc_sta[drv] | STA_EOC; /* set status */ - break; /* done */ - } - } - dpxb[dp_ptr++] = dpd_wval ? (uint16) dpd_obuf : 0; /* store word/fill */ - dpd_wval = 0; /* clr data valid */ - if (dp_ptr >= DP_NUMWD) { /* buffer full? */ - da = GETDA (dpc_rarc, dpc_rarh, dpc_rars); /* calc disk addr */ - dpc_rars = (dpc_rars + 1) % DP_NUMSC; /* incr sector */ - if (dpc_rars == 0) { /* wrap? */ - dpc_rarh = dpc_rarh ^ 1; /* incr head */ - dpc_eoc = ((dpc_rarh & 1) == 0); /* calc eoc */ - } - err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET); - if (err) /* error? */ - break; - fxwrite (dpxb, sizeof (int16), DP_NUMWD, uptr->fileref); - err = ferror (uptr->fileref); - if (err) /* error? */ - break; - dp_ptr = 0; /* next sector */ - } - if (dpd.command && dpd_xfer) /* dch on, xfer? */ - dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ - - dpd.command = CLEAR; /* clr dch cmd */ - sim_activate (uptr, dpc_xtime); /* sched next word */ - return SCPE_OK; - - default: - return SCPE_IERR; - } /* end case fnc */ - -dpc_sta[drv] = dpc_sta[drv] | STA_ATN; /* set ATN */ - -dpc.command = CLEAR; /* clr cch cmd */ -dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ - -dpc_busy = 0; /* ctlr is free */ -dpd_xfer = dpd_wval = 0; - -if (err != 0) { /* error? */ - perror ("DP I/O error"); - clearerr (uptr->fileref); - return SCPE_IOERR; - } -return SCPE_OK; -} - - -/* Reset routine */ - -t_stat dpc_reset (DEVICE *dptr) -{ -int32 drv; -DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ - -hp_enbdis_pair (dptr, /* make pair cons */ - (dptr == &dpd_dev) ? &dpc_dev : &dpd_dev); - -if (sim_switches & SWMASK ('P')) { /* initialization reset? */ - dpd_ibuf = dpd_obuf = 0; /* clear buffers */ - dpc_obuf = 0; /* clear buffer */ - dpc_rarc = dpc_rarh = dpc_rars = 0; /* clear RAR */ - } - -IOPRESET (dibptr); /* PRESET device (does not use PON) */ - -dpc_busy = 0; /* reset controller state */ -dpc_poll = 0; -dpd_xfer = 0; -dpd_wval = 0; -dpc_eoc = 0; -dp_ptr = 0; - -sim_cancel (&dpd_unit); /* cancel dch */ - -for (drv = 0; drv < DP_NUMDRV; drv++) { /* loop thru drives */ - sim_cancel (&dpc_unit[drv]); /* cancel activity */ - dpc_unit[drv].FNC = 0; /* clear function */ - dpc_ucyl[drv] = 0; /* clear drive pos */ - if (dpc_unit[drv].flags & UNIT_ATT) - dpc_sta[drv] = dpc_sta[drv] & STA_1ST; /* first seek status */ - else dpc_sta[drv] = 0; /* clear status */ - } - -return SCPE_OK; -} - - -/* Attach routine */ - -t_stat dpc_attach (UNIT *uptr, char *cptr) -{ -t_stat r; - -r = attach_unit (uptr, cptr); /* attach unit */ -if (r == SCPE_OK) dpc_load_unload (uptr, 0, NULL, NULL);/* if OK, load heads */ -return r; -} - - -/* Detach routine */ - -t_stat dpc_detach (UNIT* uptr) -{ -dpc_load_unload (uptr, UNIT_UNLOAD, NULL, NULL); /* unload heads */ -return detach_unit (uptr); /* detach unit */ -} - - -/* Load and unload heads */ - -t_stat dpc_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc) -{ -int32 drv; - -if ((uptr->flags & UNIT_ATT) == 0) return SCPE_UNATT; /* must be attached to load */ - -if (value == UNIT_UNLOAD) /* unload heads? */ - uptr->flags = uptr->flags | UNIT_UNLOAD; /* indicate unload */ -else { /* load heads */ - uptr->flags = uptr->flags & ~UNIT_UNLOAD; /* indicate load */ - drv = uptr - dpc_unit; /* get drive no */ - dpc_sta[drv] = dpc_sta[drv] | STA_ATN | STA_1ST; /* update status */ - if (dpc_poll) /* polling enabled? */ - dpcio (&dpc_dib, ioENF, 0); /* set flag */ - } -return SCPE_OK; -} - - -/* Set controller type */ - -t_stat dp_settype (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -int32 i; - -if ((val < 0) || (val > 1) || (cptr != NULL)) - return SCPE_ARG; - -for (i = 0; i < DP_NUMDRV; i++) { - if (dpc_unit[i].flags & UNIT_ATT) - return SCPE_ALATT; - } - -for (i = 0; i < DP_NUMDRV; i++) - dpc_unit[i].capac = (val? DP_SIZE3: DP_SIZE2); - -dp_ctype = (CNTLR_TYPE) val; -return SCPE_OK; -} - - -/* Show controller type */ - -t_stat dp_showtype (FILE *st, UNIT *uptr, int32 val, void *desc) -{ -if (dp_ctype == A13210) - fprintf (st, "13210A"); -else - fprintf (st, "12557A"); - -return SCPE_OK; -} - - -/* 7900/7901 bootstrap routine (HP 12992F ROM) */ - -const BOOT_ROM dp_rom = { - 0106710, /*ST CLC DC ; clr dch */ - 0106711, /* CLC CC ; clr cch */ - 0017757, /* JSB STAT ; get status */ - 0067746, /*SK LDB SKCMD ; seek cmd */ - 0106610, /* OTB DC ; cyl # */ - 0103710, /* STC DC,C ; to dch */ - 0106611, /* OTB CC ; seek cmd */ - 0103711, /* STC CC,C ; to cch */ - 0102310, /* SFS DC ; addr wd ok? */ - 0027710, /* JMP *-1 ; no, wait */ - 0006400, /* CLB */ - 0102501, /* LIA 1 ; read switches */ - 0002011, /* SLA,RSS ; <0> set? */ - 0047747, /* ADB BIT9 ; head 2 = removable */ - 0106610, /* OTB DC ; head/sector */ - 0103710, /* STC DC,C ; to dch */ - 0102311, /* SFS CC ; seek done? */ - 0027720, /* JMP *-1 ; no, wait */ - 0017757, /* JSB STAT ; get status */ - 0067776, /* LDB DMACW ; DMA control */ - 0106606, /* OTB 6 */ - 0067750, /* LDB ADDR1 ; memory addr */ - 0106602, /* OTB 2 */ - 0102702, /* STC 2 ; flip DMA ctrl */ - 0067752, /* LDB CNT ; word count */ - 0106602, /* OTB 2 */ - 0063745, /* LDB RDCMD ; read cmd */ - 0102611, /* OTA CC ; to cch */ - 0103710, /* STC DC,C ; start dch */ - 0103706, /* STC 6,C ; start DMA */ - 0103711, /* STC CC,C ; start cch */ - 0102311, /* SFS CC ; done? */ - 0027737, /* JMP *-1 ; no, wait */ - 0017757, /* JSB STAT ; get status */ - 0027775, /* JMP XT ; done */ - 0037766, /*FSMSK 037766 ; status mask */ - 0004000, /*STMSK 004000 ; unsafe mask */ - 0020000, /*RDCMD 020000 ; read cmd */ - 0030000, /*SKCMD 030000 ; seek cmd */ - 0001000, /*BIT9 001000 ; head 2 select */ - 0102011, /*ADDR1 102011 */ - 0102055, /*ADDR2 102055 */ - 0164000, /*CNT -6144. */ - 0, 0, 0, 0, /* unused */ - 0000000, /*STAT 0 */ - 0002400, /* CLA ; status request */ - 0102611, /* OTC CC ; to cch */ - 0103711, /* STC CC,C ; start cch */ - 0102310, /* SFS DC ; done? */ - 0027763, /* JMP *-1 */ - 0102510, /* LIA DC ; get status */ - 0013743, /* AND FSMSK ; mask 15,14,3,0 */ - 0002003, /* SZA,RSS ; drive ready? */ - 0127757, /* JMP STAT,I ; yes */ - 0013744, /* AND STMSK ; fault? */ - 0002002, /* SZA */ - 0102030, /* HLT 30 ; yes */ - 0027700, /* JMP ST ; no, retry */ - 0117751, /*XT JSB ADDR2,I ; start program */ - 0120010, /*DMACW 120000+DC */ - 0000000 /* -ST */ - }; - -t_stat dpc_boot (int32 unitno, DEVICE *dptr) -{ -const int32 dev = dpd_dib.select_code; /* data chan select code */ - -if (unitno != 0) /* boot supported on drive unit 0 only */ - return SCPE_NOFNC; /* report "Command not allowed" if attempted */ - -if (ibl_copy (dp_rom, dev, IBL_OPT, /* copy the boot ROM to memory and configure */ - IBL_DP | IBL_SET_SC (dev) /* the S register accordingly */ - | (sim_switches & SWMASK ('R') ? IBL_DP_REM : 0))) - return SCPE_IERR; /* return an internal error if the copy failed */ -else - return SCPE_OK; -} +/* hp2100_dp.c: HP 2100 12557A/13210A disk simulator + + Copyright (c) 1993-2016, Robert M. Supnik + Copyright (c) 2017 J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + DP 12557A 2870 disk subsystem + 13210A 7900 disk subsystem + + 22-Apr-17 JDB Added fall-through comment for FNC_STA case in dpcio + 09-Nov-16 JDB Corrected disk subsystem model number from 2871 to 2870 + 13-May-16 JDB Modified for revised SCP API function parameter types + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 24-Dec-14 JDB Added casts for explicit downward conversions + 18-Dec-12 MP Now calls sim_activate_time to get remaining seek time + 09-May-12 JDB Separated assignments from conditional expressions + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + Added CNTLR_TYPE cast to dp_settype + 28-Mar-11 JDB Tidied up signal handling + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 10-Aug-08 JDB Added REG_FIT to register variables < 32-bit size + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 01-Mar-05 JDB Added SET UNLOAD/LOAD + 07-Oct-04 JDB Fixed enable/disable from either device + Fixed ANY ERROR status for 12557A interface + Fixed unattached drive status for 12557A interface + Status cmd without prior STC DC now completes (12557A) + OTA/OTB CC on 13210A interface also does CLC CC + Fixed RAR model + Fixed seek check on 13210 if sector out of range + 20-Aug-04 JDB Fixes from Dave Bryan + - Check status on unattached drive set busy and not ready + - Check status tests wrong unit for write protect status + - Drive on line sets ATN, will set FLG if polling + 15-Aug-04 RMS Controller resumes polling for ATN interrupts after + read status (found by Dave Bryan) + 22-Jul-04 RMS Controller sets ATN for all commands except + read status (found by Dave Bryan) + 21-Apr-04 RMS Fixed typo in boot loader (found by Dave Bryan) + 26-Apr-04 RMS Fixed SFS x,C and SFC x,C + Fixed SR setting in IBL + Fixed interpretation of SR<0> + Revised IBL loader + Implemented DMA SRQ (follows FLG) + 25-Apr-03 RMS Revised for extended file support + Fixed bug(s) in boot (found by Terry Newton) + 10-Nov-02 RMS Added BOOT command, fixed numerous bugs + 15-Jan-02 RMS Fixed INIT handling (found by Bill McDermith) + 10-Jan-02 RMS Fixed f(x)write call (found by Bill McDermith) + 03-Dec-01 RMS Changed DEVNO to use extended SET/SHOW + 24-Nov-01 RMS Changed STA to be an array + 07-Sep-01 RMS Moved function prototypes + 29-Nov-00 RMS Made variable names unique + 21-Nov-00 RMS Fixed flag, buffer power up state + + References: + - 7900A Disc Drive Operating and Service Manual (07900-90002, Feb-1975) + - 13210A Disc Drive Interface Kit Operating and Service Manual + (13210-90003, Nov-1974) + - 12557A Cartridge Disc Interface Kit Operating and Service Manual + (12557-90001, Sep-1970) + + + The simulator uses a number of state variables: + + dpc_busy set to drive number + 1 when the controller is busy + of the unit in use + dpd_xfer set to 1 if the data channel is executing a data transfer + dpd_wval set to 1 by OTx if either !dpc_busy or dpd_xfer + dpc_poll set to 1 if attention polling is enabled + + dpc_busy and dpd_xfer are set together at the start of a read, write, refine, + or init. When data transfers are complete (CLC DC), dpd_xfer is cleared, but the + operation is not necessarily over. When the operation is complete, dpc_busy + is cleared and the command channel flag is set. + + dpc_busy && !dpd_xfer && STC DC (controller is busy, data channel transfer has + been terminated by CLC DC, but a word has been placed in the data channel buffer) + indicates data overrun. + + dpd_wval is used in write operations to fill out the sector buffer with 0's + if only a partial sector has been transferred. + + dpc_poll indicates whether seek completion polling can occur. It is cleared + by reset and CLC CC and set by issuance of a seek or completion of check status. + + The controller's "Record Address Register" (RAR) contains the CHS address of + the last Seek or Address Record command executed. The RAR is shared among + all drives on the controller. In addition, each drive has an internal + position register that contains the last cylinder position transferred to the + drive during Seek command execution (data operations always start with the + RAR head and sector position). + + In a real drive, the address field of the sector under the head is read and + compared to the RAR. When they match, the target sector is under the head + and is ready for reading or writing. If a match doesn't occur, an Address + Error is indicated. In the simulator, the address field is obtained from the + drive's current position register during a read, i.e., the "on-disc" address + field is assumed to match the current position. + + The following implemented behaviors have been inferred from secondary sources + (diagnostics, operating system drivers, etc.), due to absent or contradictory + authoritative information; future correction may be needed: + + 1. Status bit 15 (ATTENTION) does not set bit 0 (ANY ERROR) on the 12557A. + 2. Omitting STC DC before Status Check does not set DC flag but does poll. +*/ + +#include "hp2100_defs.h" + +#define UNIT_V_WLK (UNIT_V_UF + 0) /* write locked */ +#define UNIT_V_UNLOAD (UNIT_V_UF + 1) /* heads unloaded */ +#define UNIT_WLK (1 << UNIT_V_WLK) +#define UNIT_UNLOAD (1 << UNIT_V_UNLOAD) +#define FNC u3 /* saved function */ +#define DRV u4 /* drive number (DC) */ +#define UNIT_WPRT (UNIT_WLK | UNIT_RO) /* write prot */ + +#define DP_N_NUMWD 7 +#define DP_NUMWD (1 << DP_N_NUMWD) /* words/sector */ +#define DP_NUMSC2 12 /* sectors/srf 12557 */ +#define DP_NUMSC3 24 /* sectors/srf 13210 */ +#define DP_NUMSC (dp_ctype ? DP_NUMSC3 : DP_NUMSC2) +#define DP_NUMSF 4 /* surfaces/cylinder */ +#define DP_NUMCY 203 /* cylinders/disk */ +#define DP_SIZE2 (DP_NUMSF * DP_NUMCY * DP_NUMSC2 * DP_NUMWD) +#define DP_SIZE3 (DP_NUMSF * DP_NUMCY * DP_NUMSC3 * DP_NUMWD) +#define DP_NUMDRV 4 /* # drives */ + +/* Command word */ + +#define CW_V_FNC 12 /* function */ +#define CW_M_FNC 017 +#define CW_GETFNC(x) (((x) >> CW_V_FNC) & CW_M_FNC) +#define FNC_STA 000 /* status check */ +#define FNC_WD 001 /* write */ +#define FNC_RD 002 /* read */ +#define FNC_SEEK 003 /* seek */ +#define FNC_REF 005 /* refine */ +#define FNC_CHK 006 /* check */ +#define FNC_INIT 011 /* init */ +#define FNC_AR 013 /* address */ +#define FNC_SEEK1 020 /* fake - seek1 */ +#define FNC_SEEK2 021 /* fake - seek2 */ +#define FNC_SEEK3 022 /* fake - seek3 */ +#define FNC_CHK1 023 /* fake - check1 */ +#define FNC_AR1 024 /* fake - arec1 */ +#define CW_V_DRV 0 /* drive */ +#define CW_M_DRV 03 +#define CW_GETDRV(x) (((x) >> CW_V_DRV) & CW_M_DRV) + +/* Disk address words */ + +#define DA_V_CYL 0 /* cylinder */ +#define DA_M_CYL 0377 +#define DA_GETCYL(x) (((x) >> DA_V_CYL) & DA_M_CYL) +#define DA_V_HD 8 /* head */ +#define DA_M_HD 03 +#define DA_GETHD(x) (((x) >> DA_V_HD) & DA_M_HD) +#define DA_V_SC 0 /* sector */ +#define DA_M_SC2 017 +#define DA_M_SC3 037 +#define DA_M_SC (dp_ctype ? DA_M_SC3 : DA_M_SC2) +#define DA_GETSC(x) (((x) >> DA_V_SC) & DA_M_SC) +#define DA_CKMASK2 037 /* check mask */ +#define DA_CKMASK3 077 +#define DA_CKMASK (dp_ctype ? DA_CKMASK3 : DA_CKMASK2) + +/* Status in dpc_sta[drv], (u) = unused in 13210, (d) = dynamic */ + +#define STA_ATN 0100000 /* attention (u) */ +#define STA_1ST 0040000 /* first status */ +#define STA_OVR 0020000 /* overrun */ +#define STA_RWU 0010000 /* rw unsafe NI (u) */ +#define STA_ACU 0004000 /* access unsafe NI */ +#define STA_HUNT 0002000 /* hunting NI (12557) */ +#define STA_PROT 0002000 /* protected (13210) */ +#define STA_SKI 0001000 /* incomplete NI (u) */ +#define STA_SKE 0000400 /* seek error */ +/* 0000200 (unused) */ +#define STA_NRDY 0000100 /* not ready (d) */ +#define STA_EOC 0000040 /* end of cylinder */ +#define STA_AER 0000020 /* addr error */ +#define STA_FLG 0000010 /* flagged */ +#define STA_BSY 0000004 /* seeking */ +#define STA_DTE 0000002 /* data error */ +#define STA_ERR 0000001 /* any error (d) */ + +#define STA_ERSET2 (STA_1ST | STA_OVR | STA_RWU | STA_ACU | \ + STA_SKI | STA_SKE | STA_NRDY | \ + STA_EOC | STA_AER | STA_DTE) /* 12557A error set */ +#define STA_ERSET3 (STA_ATN | STA_1ST | STA_OVR | STA_RWU | STA_ACU | \ + STA_SKI | STA_SKE | STA_NRDY | STA_EOC | STA_AER | \ + STA_FLG | STA_BSY | STA_DTE) /* 13210A error set */ +#define STA_ANYERR (dp_ctype ? STA_ERSET3 : STA_ERSET2) + +#define STA_UNLOADED (dp_ctype ? (STA_NRDY | STA_BSY) : STA_NRDY) +#define STA_MBZ13 (STA_ATN | STA_RWU | STA_SKI) /* zero in 13210 */ + +struct { + FLIP_FLOP command; /* cch command flip-flop */ + FLIP_FLOP control; /* cch control flip-flop */ + FLIP_FLOP flag; /* cch flag flip-flop */ + FLIP_FLOP flagbuf; /* cch flag buffer flip-flop */ + } dpc = { CLEAR, CLEAR, CLEAR, CLEAR }; + +/* Controller types */ + +typedef enum { + A12557, + A13210 + } CNTLR_TYPE; + +CNTLR_TYPE dp_ctype = A13210; /* ctrl type */ +int32 dpc_busy = 0; /* cch unit */ +int32 dpc_poll = 0; /* cch poll enable */ +int32 dpc_cnt = 0; /* check count */ +int32 dpc_eoc = 0; /* end of cyl */ +int32 dpc_stime = 100; /* seek time */ +int32 dpc_ctime = 100; /* command time */ +int32 dpc_xtime = 5; /* xfer time */ +int32 dpc_dtime = 2; /* dch time */ +int32 dpd_obuf = 0, dpd_ibuf = 0; /* dch buffers */ +int32 dpc_obuf = 0; /* cch buffers */ + +struct { + FLIP_FLOP command; /* dch command flip-flop */ + FLIP_FLOP control; /* dch control flip-flop */ + FLIP_FLOP flag; /* dch flag flip-flop */ + FLIP_FLOP flagbuf; /* dch flag buffer flip-flop */ + } dpd = { CLEAR, CLEAR, CLEAR, CLEAR }; + +int32 dpd_xfer = 0; /* xfer in prog */ +int32 dpd_wval = 0; /* write data valid */ +int32 dp_ptr = 0; /* buffer ptr */ +uint8 dpc_rarc = 0; /* RAR cylinder */ +uint8 dpc_rarh = 0; /* RAR head */ +uint8 dpc_rars = 0; /* RAR sector */ +uint8 dpc_ucyl[DP_NUMDRV] = { 0 }; /* unit cylinder */ +uint16 dpc_sta[DP_NUMDRV] = { 0 }; /* status regs */ +uint16 dpxb[DP_NUMWD]; /* sector buffer */ + +DEVICE dpd_dev, dpc_dev; + +IOHANDLER dpdio; +IOHANDLER dpcio; + +t_stat dpc_svc (UNIT *uptr); +t_stat dpd_svc (UNIT *uptr); +t_stat dpc_reset (DEVICE *dptr); +t_stat dpc_attach (UNIT *uptr, CONST char *cptr); +t_stat dpc_detach (UNIT* uptr); +t_stat dpc_boot (int32 unitno, DEVICE *dptr); +void dp_god (int32 fnc, int32 drv, int32 time); +void dp_goc (int32 fnc, int32 drv, int32 time); +t_stat dpc_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +t_stat dp_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat dp_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc); + +/* DPD data structures + + dpd_dev DPD device descriptor + dpd_unit DPD unit list + dpd_reg DPD register list +*/ + +DIB dp_dib[] = { + { &dpdio, DPD }, + { &dpcio, DPC } + }; + +#define dpd_dib dp_dib[0] +#define dpc_dib dp_dib[1] + +UNIT dpd_unit = { UDATA (&dpd_svc, 0, 0) }; + +REG dpd_reg[] = { + { ORDATA (IBUF, dpd_ibuf, 16) }, + { ORDATA (OBUF, dpd_obuf, 16) }, + { BRDATA (DBUF, dpxb, 8, 16, DP_NUMWD) }, + { DRDATA (BPTR, dp_ptr, DP_N_NUMWD) }, + { FLDATA (CMD, dpd.command, 0) }, + { FLDATA (CTL, dpd.control, 0) }, + { FLDATA (FLG, dpd.flag, 0) }, + { FLDATA (FBF, dpd.flagbuf, 0) }, + { FLDATA (XFER, dpd_xfer, 0) }, + { FLDATA (WVAL, dpd_wval, 0) }, + { ORDATA (SC, dpd_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, dpd_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB dpd_mod[] = { + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &dpd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dpd_dev }, + { 0 } + }; + +DEVICE dpd_dev = { + "DPD", &dpd_unit, dpd_reg, dpd_mod, + 1, 10, DP_N_NUMWD, 1, 8, 16, + NULL, NULL, &dpc_reset, + NULL, NULL, NULL, + &dpd_dib, DEV_DISABLE + }; + +/* DPC data structures + + dpc_dev DPC device descriptor + dpc_unit DPC unit list + dpc_reg DPC register list + dpc_mod DPC modifier list +*/ + +UNIT dpc_unit[] = { + { UDATA (&dpc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_UNLOAD, DP_SIZE3) }, + { UDATA (&dpc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_UNLOAD, DP_SIZE3) }, + { UDATA (&dpc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_UNLOAD, DP_SIZE3) }, + { UDATA (&dpc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_UNLOAD, DP_SIZE3) } + }; + +REG dpc_reg[] = { + { ORDATA (OBUF, dpc_obuf, 16) }, + { ORDATA (BUSY, dpc_busy, 4), REG_RO }, + { ORDATA (CNT, dpc_cnt, 5) }, + { FLDATA (CMD, dpc.command, 0) }, + { FLDATA (CTL, dpc.control, 0) }, + { FLDATA (FLG, dpc.flag, 0) }, + { FLDATA (FBF, dpc.flagbuf, 0) }, + { FLDATA (EOC, dpc_eoc, 0) }, + { FLDATA (POLL, dpc_poll, 0) }, + { DRDATA (RARC, dpc_rarc, 8), PV_RZRO | REG_FIT }, + { DRDATA (RARH, dpc_rarh, 2), PV_RZRO | REG_FIT }, + { DRDATA (RARS, dpc_rars, 5), PV_RZRO | REG_FIT }, + { BRDATA (CYL, dpc_ucyl, 10, 8, DP_NUMDRV), PV_RZRO }, + { BRDATA (STA, dpc_sta, 8, 16, DP_NUMDRV) }, + { DRDATA (CTIME, dpc_ctime, 24), PV_LEFT }, + { DRDATA (DTIME, dpc_dtime, 24), PV_LEFT }, + { DRDATA (STIME, dpc_stime, 24), PV_LEFT }, + { DRDATA (XTIME, dpc_xtime, 24), REG_NZ | PV_LEFT }, + { FLDATA (CTYPE, dp_ctype, 0), REG_HRO }, + { URDATA (UFNC, dpc_unit[0].FNC, 8, 8, 0, + DP_NUMDRV, REG_HRO) }, + { URDATA (CAPAC, dpc_unit[0].capac, 10, T_ADDR_W, 0, + DP_NUMDRV, PV_LEFT | REG_HRO) }, + { ORDATA (SC, dpc_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, dpc_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB dpc_mod[] = { + { UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", dpc_load_unload }, + { UNIT_UNLOAD, 0, "heads loaded", "LOADED", dpc_load_unload }, + { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL }, + { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL }, + { MTAB_XTD | MTAB_VDV, 1, NULL, "13210A", + &dp_settype, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 0, NULL, "12557A", + &dp_settype, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 0, "TYPE", NULL, + NULL, &dp_showtype, NULL }, + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &dpd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dpd_dev }, + { 0 } + }; + +DEVICE dpc_dev = { + "DPC", dpc_unit, dpc_reg, dpc_mod, + DP_NUMDRV, 8, 24, 1, 8, 16, + NULL, NULL, &dpc_reset, + &dpc_boot, &dpc_attach, &dpc_detach, + &dpc_dib, DEV_DISABLE + }; + + +/* Data channel I/O signal handler. + + For the 12557A, the card contains the usual control, flag, and flag buffer + flip-flops. PRL, IRQ, and SRQ are standard. A command flip-flop indicates + that data is available. + + For the 13210A, the card has a flag and a flag buffer flip-flop, but no + control or interrupt flip-flop. SRQ is standard. IRQ and PRL are not + driven, and the card does not respond to IAK. STC sets the command flip-flop + to initiate a data transfer. CLC has no effect. + + Implementation notes: + + 1. The CRS signal clears the drive attention register. Under simulation, + drive attention status is generated dynamically, so there is no attention + register. +*/ + +uint32 dpdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + dpd.flag = dpd.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + dpd.flag = dpd.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (dpd); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (dpd); + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, dpd_ibuf); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + dpd_obuf = IODATA (stat_data); /* clear supplied status */ + + if (!dpc_busy || dpd_xfer) /* if !overrun */ + dpd_wval = 1; /* valid */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + dpd.flag = dpd.flagbuf = SET; /* set flag buffer and flag */ + + if (dp_ctype == A12557) /* 12557? */ + dpd_obuf = 0; /* clear output buffer */ + break; + + + case ioCRS: /* control reset */ + dpd.command = CLEAR; /* clear command */ + + if (dp_ctype == A12557) /* 12557? */ + dpd.control = CLEAR; /* clear control */ + + else { /* 13210 */ + dpc_rarc = 0; /* clear controller cylinder address */ + dpc_ucyl [CW_GETDRV (dpc_obuf)] = 0; /* clear last drive addressed cylinder */ + } + break; + + + case ioCLC: /* clear control flip-flop */ + if (dp_ctype == A12557) /* 12557? */ + dpd.control = CLEAR; /* clear control */ + + dpd_xfer = 0; /* clr xfer in progress */ + break; + + + case ioSTC: /* set control flip-flop */ + if (dp_ctype == A12557) /* 12557? */ + dpd.control = SET; /* set control */ + + dpd.command = SET; /* set cmd */ + + if (dpc_busy && !dpd_xfer) /* overrun? */ + dpc_sta[dpc_busy - 1] |= STA_OVR; + break; + + + case ioSIR: /* set interrupt request */ + if (dp_ctype == A12557) { /* 12557? */ + setstdPRL (dpd); /* set standard PRL signal */ + setstdIRQ (dpd); /* set standard IRQ signal */ + } + + setstdSRQ (dpd); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + if (dp_ctype == A12557) /* 12557? */ + dpd.flagbuf = CLEAR; /* clear flag buffer */ + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Command channel I/O signal handler. + + The 12557A and 13210A have the usual control, flag, and flag buffer + flip-flops. Only the 12557A has a command flip-flop. IRQ, PRL, and SRQ are + standard. + + Implementation notes: + + 1. In hardware, the command channel card passes PRH to PRL. The data card + actually drives PRL with the command channel's control and flag states, + even though the command channel's control, flag, and flag buffer drive + IRQH. That is, the priority chain is broken at the data card, although + the command card is interrupting. This works in hardware, but we must + break PRL at the command card under simulation to allow the command card + to interrupt. +*/ + +uint32 dpcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +uint16 data; +int32 i, fnc, drv; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + dpc.flag = dpc.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + dpc.flag = dpc.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (dpc); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (dpc); + break; + + + case ioIOI: /* I/O data input */ + data = 0; + + for (i = 0; i < DP_NUMDRV; i++) /* form attention register value */ + if (dpc_sta[i] & STA_ATN) + data = data | (uint16) (1 << i); + + stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + dpc_obuf = IODATA (stat_data); /* clear supplied status */ + + if (dp_ctype == A13210) /* 13210? */ + dpcio (dibptr, ioCLC, 0); /* OTx causes CLC */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + dpc.flag = dpc.flagbuf = SET; /* set flag buffer and flag */ + + if (dp_ctype == A12557) /* 12557? */ + dpd_obuf = 0; /* clear output buffer */ + break; + + + case ioCRS: /* control reset */ + dpc.control = CLEAR; /* clear control */ + + if (dp_ctype == A12557) /* 12557? */ + dpc.command = CLEAR; /* clear command */ + break; + + + case ioCLC: /* clear control flip-flop */ + dpc.control = CLEAR; /* clr ctl */ + + if (dp_ctype == A12557) /* 12557? */ + dpc.command = CLEAR; /* cancel non-seek */ + + if (dpc_busy) + sim_cancel (&dpc_unit[dpc_busy - 1]); + + sim_cancel (&dpd_unit); /* cancel dch */ + dpd_xfer = 0; /* clr dch xfer */ + dpc_busy = 0; /* clr cch busy */ + dpc_poll = 0; /* clr cch poll */ + break; + + + case ioSTC: /* set control flip-flop */ + dpc.control = SET; /* set ctl */ + + if ((dp_ctype == A13210) || !dpc.command) { /* 13210 or command is clear? */ + if (dp_ctype == A12557) /* 12557? */ + dpc.command = SET; /* set command */ + + drv = CW_GETDRV (dpc_obuf); /* get fnc, drv */ + fnc = CW_GETFNC (dpc_obuf); /* from cmd word */ + + switch (fnc) { /* case on fnc */ + + case FNC_SEEK: /* seek */ + dpc_poll = 1; /* enable polling */ + dp_god (fnc, drv, dpc_dtime); /* sched dch xfr */ + break; + + case FNC_STA: /* rd sta */ + if (dp_ctype == A13210) /* 13210? clr dch flag */ + dpdio (&dpd_dib, ioCLF, 0); + + /* fall into FNC_CHK and FNC_AR cases */ + + case FNC_CHK: /* check */ + case FNC_AR: /* addr rec */ + dp_god (fnc, drv, dpc_dtime); /* sched dch xfr */ + break; + + case FNC_RD: case FNC_WD: /* read, write */ + case FNC_REF: case FNC_INIT: /* refine, init */ + dp_goc (fnc, drv, dpc_ctime); /* sched drive */ + break; + } /* end case */ + } /* end if */ + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (dpc); /* set standard PRL signal */ + setstdIRQ (dpc); /* set standard IRQ signal */ + setstdSRQ (dpc); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + dpc.flagbuf = CLEAR; /* clear flag buffer */ + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Start data channel operation */ + +void dp_god (int32 fnc, int32 drv, int32 time) +{ +dpd_unit.DRV = drv; /* save unit */ +dpd_unit.FNC = fnc; /* save function */ +sim_activate (&dpd_unit, time); +return; +} + + +/* Start controller operation */ + +void dp_goc (int32 fnc, int32 drv, int32 time) +{ +int32 t; + +t = sim_activate_time (&dpc_unit[drv]); +if (t) { /* still seeking? */ + sim_cancel (&dpc_unit[drv]); /* stop seek */ + dpc_sta[drv] = dpc_sta[drv] & ~STA_BSY; /* clear busy */ + time = time + t; /* include seek time */ + } +dp_ptr = 0; /* init buf ptr */ +dpc_eoc = 0; /* clear end cyl */ +dpc_busy = drv + 1; /* set busy */ +dpd_xfer = 1; /* xfer in prog */ +dpc_unit[drv].FNC = fnc; /* save function */ +dpc_sta[drv] = dpc_sta[drv] & ~STA_ATN; /* clear ATN */ +sim_activate (&dpc_unit[drv], time); /* activate unit */ +return; +} + + +/* Data channel unit service + + This routine handles the data channel transfers. It also handles + data transfers that are blocked by seek in progress. + + uptr->DRV = target drive + uptr->FNC = target function + + Seek substates + seek - transfer cylinder + seek1 - transfer head/surface + Address record + ar - transfer cylinder + ar1 - transfer head/surface, finish operation + Status check - transfer status, finish operation + Check data + chk - transfer sector count +*/ + +t_stat dpd_svc (UNIT *uptr) +{ +int32 i, drv, st; + +drv = uptr->DRV; /* get drive no */ +switch (uptr->FNC) { /* case function */ + + case FNC_AR: /* arec, need cyl */ + case FNC_SEEK: /* seek, need cyl */ + if (dpd.command) { /* dch active? */ + dpc_rarc = DA_GETCYL (dpd_obuf); /* set RAR from cyl word */ + dpd_wval = 0; /* clr data valid */ + + dpd.command = CLEAR; /* clr dch cmd */ + dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ + + if (uptr->FNC == FNC_AR) uptr->FNC = FNC_AR1; + else uptr->FNC = FNC_SEEK1; /* advance state */ + } + sim_activate (uptr, dpc_xtime); /* no, wait more */ + break; + + case FNC_AR1: /* arec, need hd/sec */ + case FNC_SEEK1: /* seek, need hd/sec */ + if (dpd.command) { /* dch active? */ + dpc_rarh = DA_GETHD (dpd_obuf); /* set RAR from head */ + dpc_rars = DA_GETSC (dpd_obuf); /* set RAR from sector */ + dpd_wval = 0; /* clr data valid */ + + dpd.command = CLEAR; /* clr dch cmd */ + dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ + + if (uptr->FNC == FNC_AR1) { + dpc.command = CLEAR; /* clr cch cmd */ + dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ + + dpc_sta[drv] = dpc_sta[drv] | STA_ATN; /* set drv attn */ + break; /* done if Address Record */ + } + if (sim_is_active (&dpc_unit[drv])) { /* if busy, */ + dpc_sta[drv] = dpc_sta[drv] | STA_SKE; /* seek check */ + break; /* allow prev seek to cmpl */ + } + if ((dpc_rarc >= DP_NUMCY) || /* invalid cyl? */ + ((dp_ctype == A13210) && /* or 13210A */ + (dpc_rars >= DP_NUMSC3))) { /* and invalid sector? */ + dpc_sta[drv] = dpc_sta[drv] | STA_SKE; /* seek check */ + sim_activate (&dpc_unit[drv], 1); /* schedule drive no-wait */ + dpc_unit[drv].FNC = FNC_SEEK3; /* do immed compl w/poll */ + break; + } + st = abs (dpc_rarc - dpc_ucyl[drv]) * dpc_stime; + if (st == 0) st = dpc_stime; /* min time */ + dpc_ucyl[drv] = dpc_rarc; /* transfer RAR */ + sim_activate (&dpc_unit[drv], st); /* schedule drive */ + dpc_sta[drv] = (dpc_sta[drv] | STA_BSY) & + ~(STA_SKE | STA_SKI | STA_HUNT); + dpc_unit[drv].FNC = FNC_SEEK2; /* set operation */ + } + else sim_activate (uptr, dpc_xtime); /* no, wait more */ + break; + + case FNC_STA: /* read status */ + if (dpd.command || (dp_ctype == A13210)) { /* dch act or 13210? */ + if ((dpc_unit[drv].flags & UNIT_UNLOAD) == 0) { /* drive up? */ + dpd_ibuf = dpc_sta[drv] & ~STA_ERR; /* clear err */ + if (dp_ctype == A13210) dpd_ibuf = /* 13210? */ + (dpd_ibuf & ~(STA_MBZ13 | STA_PROT)) | + (dpc_unit[drv].flags & UNIT_WPRT? STA_PROT: 0); + } + else dpd_ibuf = STA_UNLOADED; /* not ready */ + if (dpd_ibuf & STA_ANYERR) /* errors? set flg */ + dpd_ibuf = dpd_ibuf | STA_ERR; + + dpc.command = CLEAR; /* clr cch cmd */ + dpd.command = CLEAR; /* clr dch cmd */ + dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ + } + + dpc_sta[drv] = dpc_sta[drv] & /* clr sta flags */ + ~(STA_ATN | STA_1ST | STA_OVR | + STA_RWU | STA_ACU | STA_EOC | + STA_AER | STA_FLG | STA_DTE); + dpc_poll = 1; /* enable polling */ + for (i = 0; i < DP_NUMDRV; i++) { /* loop thru drives */ + if (dpc_sta[i] & STA_ATN) { /* any ATN set? */ + dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ + break; + } + } + break; + + case FNC_CHK: /* check, need cnt */ + if (dpd.command) { /* dch active? */ + dpc_cnt = dpd_obuf & DA_CKMASK; /* get count */ + dpd_wval = 0; /* clr data valid */ + dp_goc (FNC_CHK1, drv, dpc_xtime); /* sched drv */ + } + else sim_activate (uptr, dpc_xtime); /* wait more */ + break; + + default: + return SCPE_IERR; + } + +return SCPE_OK; +} + + +/* Drive unit service + + This routine handles the data transfers. + + Seek substates + seek2 - done + Refine sector - erase sector, finish operation + Check data + chk1 - finish operation + Read + Write +*/ + +#define GETDA(x,y,z) \ + (((((x) * DP_NUMSF) + (y)) * DP_NUMSC) + (z)) * DP_NUMWD + +t_stat dpc_svc (UNIT *uptr) +{ +int32 da, drv, err; + +err = 0; /* assume no err */ +drv = uptr - dpc_unit; /* get drive no */ +if (uptr->flags & UNIT_UNLOAD) { /* drive down? */ + + dpc.command = CLEAR; /* clr cch cmd */ + dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ + + dpc_sta[drv] = 0; /* clr status */ + dpc_busy = 0; /* ctlr is free */ + dpc_poll = 0; /* polling disabled */ + dpd_xfer = 0; + dpd_wval = 0; + return SCPE_OK; + } +switch (uptr->FNC) { /* case function */ + + case FNC_SEEK2: /* positioning done */ + dpc_sta[drv] = (dpc_sta[drv] | STA_ATN) & ~STA_BSY; /* fall into cmpl */ + case FNC_SEEK3: /* seek complete */ + if (dpc_poll) { /* polling enabled? */ + dpc.command = CLEAR; /* clr cch cmd */ + dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ + } + return SCPE_OK; + + case FNC_REF: /* refine sector */ + break; /* just a NOP */ + + case FNC_RD: /* read */ + case FNC_CHK1: /* check */ + if (dp_ptr == 0) { /* new sector? */ + if (!dpd.command && (uptr->FNC != FNC_CHK1)) break; + if (dpc_rarc != dpc_ucyl[drv]) /* RAR cyl miscompare? */ + dpc_sta[drv] = dpc_sta[drv] | STA_AER; /* set flag, read */ + if (dpc_rars >= DP_NUMSC) { /* bad sector? */ + dpc_sta[drv] = dpc_sta[drv] | STA_AER; /* set flag, stop */ + break; + } + if (dpc_eoc) { /* end of cyl? */ + dpc_sta[drv] = dpc_sta[drv] | STA_EOC; + break; + } + da = GETDA (dpc_rarc, dpc_rarh, dpc_rars); /* calc disk addr */ + dpc_rars = (dpc_rars + 1) % DP_NUMSC; /* incr sector */ + if (dpc_rars == 0) { /* wrap? */ + dpc_rarh = dpc_rarh ^ 1; /* incr head */ + dpc_eoc = ((dpc_rarh & 1) == 0); /* calc eoc */ + } + err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET); + if (err) /* error? */ + break; + fxread (dpxb, sizeof (int16), DP_NUMWD, uptr->fileref); + err = ferror (uptr->fileref); + if (err) /* error? */ + break; + } + dpd_ibuf = dpxb[dp_ptr++]; /* get word */ + if (dp_ptr >= DP_NUMWD) { /* end of sector? */ + if (uptr->FNC == FNC_CHK1) { /* check? */ + dpc_cnt = (dpc_cnt - 1) & DA_CKMASK; /* decr count */ + if (dpc_cnt == 0) break; /* stop at zero */ + } + dp_ptr = 0; /* wrap buf ptr */ + } + if (dpd.command && dpd_xfer) /* dch on, xfer? */ + dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ + + dpd.command = CLEAR; /* clr dch cmd */ + sim_activate (uptr, dpc_xtime); /* sched next word */ + return SCPE_OK; + + case FNC_INIT: /* init */ + case FNC_WD: /* write */ + if (dp_ptr == 0) { /* start sector? */ + if (!dpd.command && !dpd_wval) break; /* xfer done? */ + if (uptr->flags & UNIT_WPRT) { /* wr prot? */ + dpc_sta[drv] = dpc_sta[drv] | STA_FLG; /* set status */ + break; /* done */ + } + if ((dpc_rarc != dpc_ucyl[drv]) || /* RAR cyl miscompare? */ + (dpc_rars >= DP_NUMSC)) { /* bad sector? */ + dpc_sta[drv] = dpc_sta[drv] | STA_AER; /* address error */ + break; + } + if (dpc_eoc) { /* end of cyl? */ + dpc_sta[drv] = dpc_sta[drv] | STA_EOC; /* set status */ + break; /* done */ + } + } + dpxb[dp_ptr++] = dpd_wval ? (uint16) dpd_obuf : 0; /* store word/fill */ + dpd_wval = 0; /* clr data valid */ + if (dp_ptr >= DP_NUMWD) { /* buffer full? */ + da = GETDA (dpc_rarc, dpc_rarh, dpc_rars); /* calc disk addr */ + dpc_rars = (dpc_rars + 1) % DP_NUMSC; /* incr sector */ + if (dpc_rars == 0) { /* wrap? */ + dpc_rarh = dpc_rarh ^ 1; /* incr head */ + dpc_eoc = ((dpc_rarh & 1) == 0); /* calc eoc */ + } + err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET); + if (err) /* error? */ + break; + fxwrite (dpxb, sizeof (int16), DP_NUMWD, uptr->fileref); + err = ferror (uptr->fileref); + if (err) /* error? */ + break; + dp_ptr = 0; /* next sector */ + } + if (dpd.command && dpd_xfer) /* dch on, xfer? */ + dpdio (&dpd_dib, ioENF, 0); /* set dch flg */ + + dpd.command = CLEAR; /* clr dch cmd */ + sim_activate (uptr, dpc_xtime); /* sched next word */ + return SCPE_OK; + + default: + return SCPE_IERR; + } /* end case fnc */ + +dpc_sta[drv] = dpc_sta[drv] | STA_ATN; /* set ATN */ + +dpc.command = CLEAR; /* clr cch cmd */ +dpcio (&dpc_dib, ioENF, 0); /* set cch flg */ + +dpc_busy = 0; /* ctlr is free */ +dpd_xfer = dpd_wval = 0; + +if (err != 0) { /* error? */ + perror ("DP I/O error"); + clearerr (uptr->fileref); + return SCPE_IOERR; + } +return SCPE_OK; +} + + +/* Reset routine */ + +t_stat dpc_reset (DEVICE *dptr) +{ +int32 drv; +DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ + +hp_enbdis_pair (dptr, /* make pair cons */ + (dptr == &dpd_dev) ? &dpc_dev : &dpd_dev); + +if (sim_switches & SWMASK ('P')) { /* initialization reset? */ + dpd_ibuf = dpd_obuf = 0; /* clear buffers */ + dpc_obuf = 0; /* clear buffer */ + dpc_rarc = dpc_rarh = dpc_rars = 0; /* clear RAR */ + } + +IOPRESET (dibptr); /* PRESET device (does not use PON) */ + +dpc_busy = 0; /* reset controller state */ +dpc_poll = 0; +dpd_xfer = 0; +dpd_wval = 0; +dpc_eoc = 0; +dp_ptr = 0; + +sim_cancel (&dpd_unit); /* cancel dch */ + +for (drv = 0; drv < DP_NUMDRV; drv++) { /* loop thru drives */ + sim_cancel (&dpc_unit[drv]); /* cancel activity */ + dpc_unit[drv].FNC = 0; /* clear function */ + dpc_ucyl[drv] = 0; /* clear drive pos */ + if (dpc_unit[drv].flags & UNIT_ATT) + dpc_sta[drv] = dpc_sta[drv] & STA_1ST; /* first seek status */ + else dpc_sta[drv] = 0; /* clear status */ + } + +return SCPE_OK; +} + + +/* Attach routine */ + +t_stat dpc_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat r; + +r = attach_unit (uptr, cptr); /* attach unit */ +if (r == SCPE_OK) dpc_load_unload (uptr, 0, NULL, NULL);/* if OK, load heads */ +return r; +} + + +/* Detach routine */ + +t_stat dpc_detach (UNIT* uptr) +{ +dpc_load_unload (uptr, UNIT_UNLOAD, NULL, NULL); /* unload heads */ +return detach_unit (uptr); /* detach unit */ +} + + +/* Load and unload heads */ + +t_stat dpc_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +int32 drv; + +if ((uptr->flags & UNIT_ATT) == 0) return SCPE_UNATT; /* must be attached to load */ + +if (value == UNIT_UNLOAD) /* unload heads? */ + uptr->flags = uptr->flags | UNIT_UNLOAD; /* indicate unload */ +else { /* load heads */ + uptr->flags = uptr->flags & ~UNIT_UNLOAD; /* indicate load */ + drv = uptr - dpc_unit; /* get drive no */ + dpc_sta[drv] = dpc_sta[drv] | STA_ATN | STA_1ST; /* update status */ + if (dpc_poll) /* polling enabled? */ + dpcio (&dpc_dib, ioENF, 0); /* set flag */ + } +return SCPE_OK; +} + + +/* Set controller type */ + +t_stat dp_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +int32 i; + +if ((val < 0) || (val > 1) || (cptr != NULL)) + return SCPE_ARG; + +for (i = 0; i < DP_NUMDRV; i++) { + if (dpc_unit[i].flags & UNIT_ATT) + return SCPE_ALATT; + } + +for (i = 0; i < DP_NUMDRV; i++) + dpc_unit[i].capac = (val? DP_SIZE3: DP_SIZE2); + +dp_ctype = (CNTLR_TYPE) val; +return SCPE_OK; +} + + +/* Show controller type */ + +t_stat dp_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +if (dp_ctype == A13210) + fprintf (st, "13210A"); +else + fprintf (st, "12557A"); + +return SCPE_OK; +} + + +/* 7900/7901 bootstrap routine (HP 12992F ROM) */ + +const BOOT_ROM dp_rom = { + 0106710, /*ST CLC DC ; clr dch */ + 0106711, /* CLC CC ; clr cch */ + 0017757, /* JSB STAT ; get status */ + 0067746, /*SK LDB SKCMD ; seek cmd */ + 0106610, /* OTB DC ; cyl # */ + 0103710, /* STC DC,C ; to dch */ + 0106611, /* OTB CC ; seek cmd */ + 0103711, /* STC CC,C ; to cch */ + 0102310, /* SFS DC ; addr wd ok? */ + 0027710, /* JMP *-1 ; no, wait */ + 0006400, /* CLB */ + 0102501, /* LIA 1 ; read switches */ + 0002011, /* SLA,RSS ; <0> set? */ + 0047747, /* ADB BIT9 ; head 2 = removable */ + 0106610, /* OTB DC ; head/sector */ + 0103710, /* STC DC,C ; to dch */ + 0102311, /* SFS CC ; seek done? */ + 0027720, /* JMP *-1 ; no, wait */ + 0017757, /* JSB STAT ; get status */ + 0067776, /* LDB DMACW ; DMA control */ + 0106606, /* OTB 6 */ + 0067750, /* LDB ADDR1 ; memory addr */ + 0106602, /* OTB 2 */ + 0102702, /* STC 2 ; flip DMA ctrl */ + 0067752, /* LDB CNT ; word count */ + 0106602, /* OTB 2 */ + 0063745, /* LDB RDCMD ; read cmd */ + 0102611, /* OTA CC ; to cch */ + 0103710, /* STC DC,C ; start dch */ + 0103706, /* STC 6,C ; start DMA */ + 0103711, /* STC CC,C ; start cch */ + 0102311, /* SFS CC ; done? */ + 0027737, /* JMP *-1 ; no, wait */ + 0017757, /* JSB STAT ; get status */ + 0027775, /* JMP XT ; done */ + 0037766, /*FSMSK 037766 ; status mask */ + 0004000, /*STMSK 004000 ; unsafe mask */ + 0020000, /*RDCMD 020000 ; read cmd */ + 0030000, /*SKCMD 030000 ; seek cmd */ + 0001000, /*BIT9 001000 ; head 2 select */ + 0102011, /*ADDR1 102011 */ + 0102055, /*ADDR2 102055 */ + 0164000, /*CNT -6144. */ + 0, 0, 0, 0, /* unused */ + 0000000, /*STAT 0 */ + 0002400, /* CLA ; status request */ + 0102611, /* OTC CC ; to cch */ + 0103711, /* STC CC,C ; start cch */ + 0102310, /* SFS DC ; done? */ + 0027763, /* JMP *-1 */ + 0102510, /* LIA DC ; get status */ + 0013743, /* AND FSMSK ; mask 15,14,3,0 */ + 0002003, /* SZA,RSS ; drive ready? */ + 0127757, /* JMP STAT,I ; yes */ + 0013744, /* AND STMSK ; fault? */ + 0002002, /* SZA */ + 0102030, /* HLT 30 ; yes */ + 0027700, /* JMP ST ; no, retry */ + 0117751, /*XT JSB ADDR2,I ; start program */ + 0120010, /*DMACW 120000+DC */ + 0000000 /* -ST */ + }; + +t_stat dpc_boot (int32 unitno, DEVICE *dptr) +{ +const int32 dev = dpd_dib.select_code; /* data chan select code */ + +if (unitno != 0) /* boot supported on drive unit 0 only */ + return SCPE_NOFNC; /* report "Command not allowed" if attempted */ + +if (ibl_copy (dp_rom, dev, IBL_OPT, /* copy the boot ROM to memory and configure */ + IBL_DP | IBL_SET_SC (dev) /* the S register accordingly */ + | (sim_switches & SWMASK ('R') ? IBL_DP_REM : 0))) + return SCPE_IERR; /* return an internal error if the copy failed */ +else + return SCPE_OK; +} diff --git a/HP2100/hp2100_dq.c b/HP2100/hp2100_dq.c index fe1cc44a..970ebab2 100644 --- a/HP2100/hp2100_dq.c +++ b/HP2100/hp2100_dq.c @@ -1,977 +1,978 @@ -/* hp2100_dq.c: HP 2100 12565A disk simulator - - Copyright (c) 1993-2006, Bill McDermith - Copyright (c) 2004-2014 J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the names of the authors shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the authors. - - DQ 12565A 2883 disk system - - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 24-Dec-14 JDB Added casts for explicit downward conversions - 18-Dec-12 MP Now calls sim_activate_time to get remaining seek time - 09-May-12 JDB Separated assignments from conditional expressions - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - 28-Mar-11 JDB Tidied up signal handling - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 10-Aug-08 JDB Added REG_FIT to register variables < 32-bit size - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 01-Mar-05 JDB Added SET UNLOAD/LOAD - 07-Oct-04 JDB Fixed enable/disable from either device - Shortened xtime from 5 to 3 (drive avg 156KW/second) - Fixed not ready/any error status - Fixed RAR model - 21-Apr-04 RMS Fixed typo in boot loader (found by Dave Bryan) - 26-Apr-04 RMS Fixed SFS x,C and SFC x,C - Fixed SR setting in IBL - Revised IBL loader - Implemented DMA SRQ (follows FLG) - 25-Apr-03 RMS Fixed bug in status check - 10-Nov-02 RMS Added boot command, rebuilt like 12559/13210 - 09-Jan-02 WOM Copied dp driver and mods for 2883 - - Reference: - - 12565A Disc Interface Kit Operating and Service Manual (12565-90003, Aug-1973) - - - Differences between 12559/13210 and 12565 controllers - - 12565 stops transfers on address miscompares; 12559/13210 only stops writes - - 12565 does not set error on positioner busy - - 12565 does not set positioner busy if already on cylinder - - 12565 does not need eoc logic, it will hit an invalid head number - - The controller's "Record Address Register" (RAR) contains the CHS address of - the last Position or Load Address command executed. The RAR is shared among - all drives on the controller. In addition, each drive has an internal - position register that contains the last cylinder and head position - transferred to the drive during Position command execution (sector operations - always start with the RAR sector position). - - In a real drive, the address field of the sector under the head is read and - compared to the RAR. When they match, the target sector is under the head - and is ready for reading or writing. If a match doesn't occur, an Address - Error is indicated. In the simulator, the address field is obtained from the - drive's current position register during a read, i.e., the "on-disc" address - field is assumed to match the current position. - - The following implemented behaviors have been inferred from secondary sources - (diagnostics, operating system drivers, etc.), due to absent or contradictory - authoritative information; future correction may be needed: - - 1. Read Address command starts at the sector number in the RAR. -*/ - -#include "hp2100_defs.h" - -#define UNIT_V_WLK (UNIT_V_UF + 0) /* write locked */ -#define UNIT_V_UNLOAD (UNIT_V_UF + 1) /* heads unloaded */ -#define UNIT_WLK (1 << UNIT_V_WLK) -#define UNIT_UNLOAD (1 << UNIT_V_UNLOAD) -#define FNC u3 /* saved function */ -#define DRV u4 /* drive number (DC) */ -#define UNIT_WPRT (UNIT_WLK | UNIT_RO) /* write prot */ - -#define DQ_N_NUMWD 7 -#define DQ_NUMWD (1 << DQ_N_NUMWD) /* words/sector */ -#define DQ_NUMSC 23 /* sectors/track */ -#define DQ_NUMSF 20 /* tracks/cylinder */ -#define DQ_NUMCY 203 /* cylinders/disk */ -#define DQ_SIZE (DQ_NUMSF * DQ_NUMCY * DQ_NUMSC * DQ_NUMWD) -#define DQ_NUMDRV 2 /* # drives */ - -/* Command word */ - -#define CW_V_FNC 12 /* function */ -#define CW_M_FNC 017 -#define CW_GETFNC(x) (((x) >> CW_V_FNC) & CW_M_FNC) -/* 000 (unused) */ -#define FNC_STA 001 /* status check */ -#define FNC_RCL 002 /* recalibrate */ -#define FNC_SEEK 003 /* seek */ -#define FNC_RD 004 /* read */ -#define FNC_WD 005 /* write */ -#define FNC_RA 006 /* read address */ -#define FNC_WA 007 /* write address */ -#define FNC_CHK 010 /* check */ -#define FNC_LA 013 /* load address */ -#define FNC_AS 014 /* address skip */ - -#define FNC_SEEK1 020 /* fake - seek1 */ -#define FNC_SEEK2 021 /* fake - seek2 */ -#define FNC_SEEK3 022 /* fake - seek3 */ -#define FNC_CHK1 023 /* fake - check1 */ -#define FNC_LA1 024 /* fake - ldaddr1 */ - -#define CW_V_DRV 0 /* drive */ -#define CW_M_DRV 01 -#define CW_GETDRV(x) (((x) >> CW_V_DRV) & CW_M_DRV) - -/* Disk address words */ - -#define DA_V_CYL 0 /* cylinder */ -#define DA_M_CYL 0377 -#define DA_GETCYL(x) (((x) >> DA_V_CYL) & DA_M_CYL) -#define DA_V_HD 8 /* head */ -#define DA_M_HD 037 -#define DA_GETHD(x) (((x) >> DA_V_HD) & DA_M_HD) -#define DA_V_SC 0 /* sector */ -#define DA_M_SC 037 -#define DA_GETSC(x) (((x) >> DA_V_SC) & DA_M_SC) -#define DA_CKMASK 0777 /* check mask */ - -/* Status in dqc_sta[drv] - (d) = dynamic */ - -#define STA_DID 0000200 /* drive ID (d) */ -#define STA_NRDY 0000100 /* not ready (d) */ -#define STA_EOC 0000040 /* end of cylinder */ -#define STA_AER 0000020 /* addr error */ -#define STA_FLG 0000010 /* flagged */ -#define STA_BSY 0000004 /* seeking */ -#define STA_DTE 0000002 /* data error */ -#define STA_ERR 0000001 /* any error */ -#define STA_ANYERR (STA_NRDY | STA_EOC | STA_AER | STA_FLG | STA_DTE) - -struct { - FLIP_FLOP command; /* cch command flip-flop */ - FLIP_FLOP control; /* cch control flip-flop */ - FLIP_FLOP flag; /* cch flag flip-flop */ - FLIP_FLOP flagbuf; /* cch flag buffer flip-flop */ - } dqc = { CLEAR, CLEAR, CLEAR, CLEAR }; - -int32 dqc_busy = 0; /* cch xfer */ -int32 dqc_cnt = 0; /* check count */ -int32 dqc_stime = 100; /* seek time */ -int32 dqc_ctime = 100; /* command time */ -int32 dqc_xtime = 3; /* xfer time */ -int32 dqc_dtime = 2; /* dch time */ - -struct { - FLIP_FLOP command; /* dch command flip-flop */ - FLIP_FLOP control; /* dch control flip-flop */ - FLIP_FLOP flag; /* dch flag flip-flop */ - FLIP_FLOP flagbuf; /* dch flag buffer flip-flop */ - } dqd = { CLEAR, CLEAR, CLEAR, CLEAR }; - -int32 dqd_obuf = 0, dqd_ibuf = 0; /* dch buffers */ -int32 dqc_obuf = 0; /* cch buffers */ -int32 dqd_xfer = 0; /* xfer in prog */ -int32 dqd_wval = 0; /* write data valid */ -int32 dq_ptr = 0; /* buffer ptr */ -uint8 dqc_rarc = 0; /* RAR cylinder */ -uint8 dqc_rarh = 0; /* RAR head */ -uint8 dqc_rars = 0; /* RAR sector */ -uint8 dqc_ucyl[DQ_NUMDRV] = { 0 }; /* unit cylinder */ -uint8 dqc_uhed[DQ_NUMDRV] = { 0 }; /* unit head */ -uint16 dqc_sta[DQ_NUMDRV] = { 0 }; /* unit status */ -uint16 dqxb[DQ_NUMWD]; /* sector buffer */ - -DEVICE dqd_dev, dqc_dev; - -IOHANDLER dqdio; -IOHANDLER dqcio; - -t_stat dqc_svc (UNIT *uptr); -t_stat dqd_svc (UNIT *uptr); -t_stat dqc_reset (DEVICE *dptr); -t_stat dqc_attach (UNIT *uptr, char *cptr); -t_stat dqc_detach (UNIT* uptr); -t_stat dqc_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc); -t_stat dqc_boot (int32 unitno, DEVICE *dptr); -void dq_god (int32 fnc, int32 drv, int32 time); -void dq_goc (int32 fnc, int32 drv, int32 time); - -/* DQD data structures - - dqd_dev DQD device descriptor - dqd_unit DQD unit list - dqd_reg DQD register list -*/ - -DIB dq_dib[] = { - { &dqdio, DQD }, - { &dqcio, DQC } - }; - -#define dqd_dib dq_dib[0] -#define dqc_dib dq_dib[1] - -UNIT dqd_unit = { UDATA (&dqd_svc, 0, 0) }; - -REG dqd_reg[] = { - { ORDATA (IBUF, dqd_ibuf, 16) }, - { ORDATA (OBUF, dqd_obuf, 16) }, - { BRDATA (DBUF, dqxb, 8, 16, DQ_NUMWD) }, - { DRDATA (BPTR, dq_ptr, DQ_N_NUMWD) }, - { FLDATA (CMD, dqd.command, 0) }, - { FLDATA (CTL, dqd.control, 0) }, - { FLDATA (FLG, dqd.flag, 0) }, - { FLDATA (FBF, dqd.flagbuf, 0) }, - { FLDATA (XFER, dqd_xfer, 0) }, - { FLDATA (WVAL, dqd_wval, 0) }, - { ORDATA (SC, dqd_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, dqd_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB dqd_mod[] = { - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &dqd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dqd_dev }, - { 0 } - }; - -DEVICE dqd_dev = { - "DQD", &dqd_unit, dqd_reg, dqd_mod, - 1, 10, DQ_N_NUMWD, 1, 8, 16, - NULL, NULL, &dqc_reset, - NULL, NULL, NULL, - &dqd_dib, DEV_DISABLE - }; - -/* DQC data structures - - dqc_dev DQC device descriptor - dqc_unit DQC unit list - dqc_reg DQC register list - dqc_mod DQC modifier list -*/ - -UNIT dqc_unit[] = { - { UDATA (&dqc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_UNLOAD, DQ_SIZE) }, - { UDATA (&dqc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_UNLOAD, DQ_SIZE) } - }; - -REG dqc_reg[] = { - { ORDATA (OBUF, dqc_obuf, 16) }, - { ORDATA (BUSY, dqc_busy, 2), REG_RO }, - { ORDATA (CNT, dqc_cnt, 9) }, - { FLDATA (CMD, dqc.command, 0) }, - { FLDATA (CTL, dqc.control, 0) }, - { FLDATA (FLG, dqc.flag, 0) }, - { FLDATA (FBF, dqc.flagbuf, 0) }, - { DRDATA (RARC, dqc_rarc, 8), PV_RZRO | REG_FIT }, - { DRDATA (RARH, dqc_rarh, 5), PV_RZRO | REG_FIT }, - { DRDATA (RARS, dqc_rars, 5), PV_RZRO | REG_FIT }, - { BRDATA (CYL, dqc_ucyl, 10, 8, DQ_NUMDRV), PV_RZRO }, - { BRDATA (HED, dqc_uhed, 10, 5, DQ_NUMDRV), PV_RZRO }, - { BRDATA (STA, dqc_sta, 8, 16, DQ_NUMDRV) }, - { DRDATA (CTIME, dqc_ctime, 24), PV_LEFT }, - { DRDATA (DTIME, dqc_dtime, 24), PV_LEFT }, - { DRDATA (STIME, dqc_stime, 24), PV_LEFT }, - { DRDATA (XTIME, dqc_xtime, 24), REG_NZ + PV_LEFT }, - { URDATA (UFNC, dqc_unit[0].FNC, 8, 8, 0, - DQ_NUMDRV, REG_HRO) }, - { ORDATA (SC, dqc_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, dqc_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB dqc_mod[] = { - { UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", dqc_load_unload }, - { UNIT_UNLOAD, 0, "heads loaded", "LOADED", dqc_load_unload }, - { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL }, - { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL }, - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &dqd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dqd_dev }, - { 0 } - }; - -DEVICE dqc_dev = { - "DQC", dqc_unit, dqc_reg, dqc_mod, - DQ_NUMDRV, 8, 24, 1, 8, 16, - NULL, NULL, &dqc_reset, - &dqc_boot, &dqc_attach, &dqc_detach, - &dqc_dib, DEV_DISABLE - }; - - -/* Data channel I/O signal handler */ - -uint32 dqdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - dqd.flag = dqd.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - dqd.flag = dqd.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (dqd); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (dqd); - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, dqd_ibuf); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - dqd_obuf = IODATA (stat_data); /* clear supplied status */ - - if (!dqc_busy || dqd_xfer) - dqd_wval = 1; /* if !overrun, valid */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - dqd.flag = dqd.flagbuf = SET; /* set flag and flag buffer */ - dqd_obuf = 0; /* clear output buffer */ - break; - - - case ioCRS: /* control reset */ - dqd.command = CLEAR; /* clear command */ - /* fall into CLC handler */ - - case ioCLC: /* clear control flip-flop */ - dqd.control = CLEAR; /* clear control */ - dqd_xfer = 0; /* clr xfer */ - break; - - - case ioSTC: /* set control flip-flop */ - dqd.command = SET; /* set ctl, cmd */ - dqd.control = SET; - - if (dqc_busy && !dqd_xfer) /* overrun? */ - dqc_sta[dqc_busy - 1] |= STA_DTE; - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (dqd); /* set standard PRL signal */ - setstdIRQ (dqd); /* set standard IRQ signal */ - setstdSRQ (dqd); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - dqd.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Command channel I/O signal handler. - - Implementation notes: - - 1. The input buffer register is not connected to the disc controller. - Pullups on the card and an inversion result in reading zeros when IOI is - signalled. -*/ - -uint32 dqcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -int32 fnc, drv; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - dqc.flag = dqc.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - dqc.flag = dqc.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (dqc); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (dqc); - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, 0); /* no data */ - break; - - - case ioIOO: /* I/O data output */ - dqc_obuf = IODATA (stat_data); /* clear supplied status */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - dqc.flag = dqc.flagbuf = SET; /* set flag and flag buffer */ - dqc_obuf = 0; /* clear output buffer */ - break; - - - case ioCRS: /* control reset */ - case ioCLC: /* clear control flip-flop */ - dqc.command = CLEAR; /* clear command */ - dqc.control = CLEAR; /* clear control */ - - if (dqc_busy) - sim_cancel (&dqc_unit[dqc_busy - 1]); - - sim_cancel (&dqd_unit); /* cancel dch */ - dqd_xfer = 0; /* clr dch xfer */ - dqc_busy = 0; /* clr busy */ - break; - - - case ioSTC: /* set control flip-flop */ - dqc.control = SET; /* set ctl */ - - if (!dqc.command) { /* cmd clr? */ - dqc.command = SET; /* set cmd */ - drv = CW_GETDRV (dqc_obuf); /* get fnc, drv */ - fnc = CW_GETFNC (dqc_obuf); /* from cmd word */ - - switch (fnc) { /* case on fnc */ - case FNC_SEEK: case FNC_RCL: /* seek, recal */ - case FNC_CHK: /* check */ - dqc_sta[drv] = 0; /* clear status */ - case FNC_STA: case FNC_LA: /* rd sta, load addr */ - dq_god (fnc, drv, dqc_dtime); /* sched dch xfer */ - break; - case FNC_RD: case FNC_WD: /* read, write */ - case FNC_RA: case FNC_WA: /* rd addr, wr addr */ - case FNC_AS: /* address skip */ - dq_goc (fnc, drv, dqc_ctime); /* sched drive */ - break; - } /* end case */ - } /* end if !CMD */ - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (dqc); /* set standard PRL signal */ - setstdIRQ (dqc); /* set standard IRQ signal */ - setstdSRQ (dqc); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - dqc.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } -return stat_data; -} - - -/* Start data channel operation */ - -void dq_god (int32 fnc, int32 drv, int32 time) -{ -dqd_unit.DRV = drv; /* save unit */ -dqd_unit.FNC = fnc; /* save function */ -sim_activate (&dqd_unit, time); -return; -} - -/* Start controller operation */ - -void dq_goc (int32 fnc, int32 drv, int32 time) -{ -int32 t; - -t = sim_activate_time (&dqc_unit[drv]); - -if (t) { /* still seeking? */ - sim_cancel (&dqc_unit[drv]); /* cancel */ - time = time + t; /* include seek time */ - } -dqc_sta[drv] = 0; /* clear status */ -dq_ptr = 0; /* init buf ptr */ -dqc_busy = drv + 1; /* set busy */ -dqd_xfer = 1; /* xfer in prog */ -dqc_unit[drv].FNC = fnc; /* save function */ -sim_activate (&dqc_unit[drv], time); /* activate unit */ -return; -} - -/* Data channel unit service - - This routine handles the data channel transfers. It also handles - data transfers that are blocked by seek in progress. - - uptr->DRV = target drive - uptr->FNC = target function - - Seek substates - seek - transfer cylinder - seek1 - transfer head/surface, sched drive - Recalibrate substates - rcl - clear cyl/head/surface, sched drive - Load address - la - transfer cylinder - la1 - transfer head/surface, finish operation - Status check - transfer status, finish operation - Check data - chk - transfer sector count, sched drive -*/ - -t_stat dqd_svc (UNIT *uptr) -{ -int32 drv, st; - -drv = uptr->DRV; /* get drive no */ - -switch (uptr->FNC) { /* case function */ - - case FNC_LA: /* arec, need cyl */ - case FNC_SEEK: /* seek, need cyl */ - if (dqd.command) { /* dch active? */ - dqc_rarc = DA_GETCYL (dqd_obuf); /* set RAR from cyl word */ - dqd_wval = 0; /* clr data valid */ - dqd.command = CLEAR; /* clr dch cmd */ - dqdio (&dqd_dib, ioENF, 0); /* set dch flg */ - if (uptr->FNC == FNC_LA) uptr->FNC = FNC_LA1; - else uptr->FNC = FNC_SEEK1; /* advance state */ - } - sim_activate (uptr, dqc_xtime); /* no, wait more */ - break; - - case FNC_LA1: /* arec, need hd/sec */ - case FNC_SEEK1: /* seek, need hd/sec */ - if (dqd.command) { /* dch active? */ - dqc_rarh = DA_GETHD (dqd_obuf); /* set RAR from head */ - dqc_rars = DA_GETSC (dqd_obuf); /* set RAR from sector */ - dqd_wval = 0; /* clr data valid */ - dqd.command = CLEAR; /* clr dch cmd */ - dqdio (&dqd_dib, ioENF, 0); /* set dch flg */ - if (uptr->FNC == FNC_LA1) { - dqc.command = CLEAR; /* clr cch cmd */ - dqcio (&dqc_dib, ioENF, 0); /* set cch flg */ - break; /* done if Load Address */ - } - if (sim_is_active (&dqc_unit[drv])) break; /* if busy, seek check */ - st = abs (dqc_rarc - dqc_ucyl[drv]) * dqc_stime; - if (st == 0) st = dqc_xtime; /* if on cyl, min time */ - else dqc_sta[drv] = dqc_sta[drv] | STA_BSY; /* set busy */ - dqc_ucyl[drv] = dqc_rarc; /* transfer RAR */ - dqc_uhed[drv] = dqc_rarh; - sim_activate (&dqc_unit[drv], st); /* schedule op */ - dqc_unit[drv].FNC = FNC_SEEK2; /* advance state */ - } - else sim_activate (uptr, dqc_xtime); /* no, wait more */ - break; - - case FNC_RCL: /* recalibrate */ - dqc_rarc = dqc_rarh = dqc_rars = 0; /* clear RAR */ - if (sim_is_active (&dqc_unit[drv])) break; /* ignore if busy */ - st = dqc_ucyl[drv] * dqc_stime; /* calc diff */ - if (st == 0) st = dqc_xtime; /* if on cyl, min time */ - else dqc_sta[drv] = dqc_sta[drv] | STA_BSY; /* set busy */ - sim_activate (&dqc_unit[drv], st); /* schedule drive */ - dqc_ucyl[drv] = dqc_uhed[drv] = 0; /* clear drive pos */ - dqc_unit[drv].FNC = FNC_SEEK2; /* advance state */ - break; - - case FNC_STA: /* read status */ - if (dqd.command) { /* dch active? */ - if ((dqc_unit[drv].flags & UNIT_UNLOAD) == 0) /* drive up? */ - dqd_ibuf = dqc_sta[drv] & ~STA_DID; - else dqd_ibuf = STA_NRDY; - if (dqd_ibuf & STA_ANYERR) /* errors? set flg */ - dqd_ibuf = dqd_ibuf | STA_ERR; - if (drv) dqd_ibuf = dqd_ibuf | STA_DID; - dqc.command = CLEAR; /* clr cch cmd */ - dqd.command = CLEAR; /* clr dch cmd */ - dqdio (&dqd_dib, ioENF, 0); /* set dch flg */ - dqc_sta[drv] = dqc_sta[drv] & ~STA_ANYERR; /* clr sta flags */ - } - else sim_activate (uptr, dqc_xtime); /* wait more */ - break; - - case FNC_CHK: /* check, need cnt */ - if (dqd.command) { /* dch active? */ - dqc_cnt = dqd_obuf & DA_CKMASK; /* get count */ - dqd_wval = 0; /* clr data valid */ - dq_goc (FNC_CHK1, drv, dqc_ctime); /* sched drv */ - } - else sim_activate (uptr, dqc_xtime); /* wait more */ - break; - - default: - return SCPE_IERR; - } - -return SCPE_OK; -} - -/* Drive unit service - - This routine handles the data transfers. - - Seek substates - seek2 - done - Recalibrate substate - rcl1 - done - Check data substates - chk1 - finish operation - Read - Read address - Address skip (read without header check) - Write - Write address -*/ - -#define GETDA(x,y,z) \ - (((((x) * DQ_NUMSF) + (y)) * DQ_NUMSC) + (z)) * DQ_NUMWD - -t_stat dqc_svc (UNIT *uptr) -{ -int32 da, drv, err; - -err = 0; /* assume no err */ -drv = uptr - dqc_unit; /* get drive no */ -if (uptr->flags & UNIT_UNLOAD) { /* drive down? */ - dqc.command = CLEAR; /* clr cch cmd */ - dqcio (&dqc_dib, ioENF, 0); /* set cch flg */ - dqc_sta[drv] = 0; /* clr status */ - dqc_busy = 0; /* ctlr is free */ - dqd_xfer = dqd_wval = 0; - return SCPE_OK; - } -switch (uptr->FNC) { /* case function */ - - case FNC_SEEK2: /* seek done */ - if (dqc_ucyl[drv] >= DQ_NUMCY) { /* out of range? */ - dqc_sta[drv] = dqc_sta[drv] | STA_BSY | STA_ERR; /* seek check */ - dqc_ucyl[drv] = 0; /* seek to cyl 0 */ - } - else dqc_sta[drv] = dqc_sta[drv] & ~STA_BSY; /* drive not busy */ - case FNC_SEEK3: - if (dqc_busy || dqc.flag) { /* ctrl busy? */ - uptr->FNC = FNC_SEEK3; /* next state */ - sim_activate (uptr, dqc_xtime); /* ctrl busy? wait */ - } - else { - dqc.command = CLEAR; /* clr cch cmd */ - dqcio (&dqc_dib, ioENF, 0); /* set cch flg */ - } - return SCPE_OK; - - case FNC_RA: /* read addr */ - if (!dqd.command) break; /* dch clr? done */ - if (dq_ptr == 0) dqd_ibuf = dqc_ucyl[drv]; /* 1st word? */ - else if (dq_ptr == 1) { /* second word? */ - dqd_ibuf = (dqc_uhed[drv] << DA_V_HD) | /* use drive head */ - (dqc_rars << DA_V_SC); /* and RAR sector */ - dqc_rars = (dqc_rars + 1) % DQ_NUMSC; /* incr sector */ - } - else break; - dq_ptr = dq_ptr + 1; - dqd.command = CLEAR; /* clr dch cmd */ - dqdio (&dqd_dib, ioENF, 0); /* set dch flg */ - sim_activate (uptr, dqc_xtime); /* sched next word */ - return SCPE_OK; - - case FNC_AS: /* address skip */ - case FNC_RD: /* read */ - case FNC_CHK1: /* check */ - if (dq_ptr == 0) { /* new sector? */ - if (!dqd.command && (uptr->FNC != FNC_CHK1)) break; - if ((dqc_rarc != dqc_ucyl[drv]) || /* RAR cyl miscompare? */ - (dqc_rarh != dqc_uhed[drv]) || /* RAR head miscompare? */ - (dqc_rars >= DQ_NUMSC)) { /* bad sector? */ - dqc_sta[drv] = dqc_sta[drv] | STA_AER; /* no record found err */ - break; - } - if (dqc_rarh >= DQ_NUMSF) { /* bad head? */ - dqc_sta[drv] = dqc_sta[drv] | STA_EOC; /* end of cyl err */ - break; - } - da = GETDA (dqc_rarc, dqc_rarh, dqc_rars); /* calc disk addr */ - dqc_rars = (dqc_rars + 1) % DQ_NUMSC; /* incr sector */ - if (dqc_rars == 0) /* wrap? incr head */ - dqc_uhed[drv] = dqc_rarh = dqc_rarh + 1; - err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET); - if (err) - break; - fxread (dqxb, sizeof (int16), DQ_NUMWD, uptr->fileref); - err = ferror (uptr->fileref); - if (err) - break; - } - dqd_ibuf = dqxb[dq_ptr++]; /* get word */ - if (dq_ptr >= DQ_NUMWD) { /* end of sector? */ - if (uptr->FNC == FNC_CHK1) { /* check? */ - dqc_cnt = (dqc_cnt - 1) & DA_CKMASK; /* decr count */ - if (dqc_cnt == 0) break; /* if zero, done */ - } - dq_ptr = 0; /* wrap buf ptr */ - } - if (dqd.command && dqd_xfer) { /* dch on, xfer? */ - dqdio (&dqd_dib, ioENF, 0); /* set flag */ - } - dqd.command = CLEAR; /* clr dch cmd */ - sim_activate (uptr, dqc_xtime); /* sched next word */ - return SCPE_OK; - - case FNC_WA: /* write address */ - case FNC_WD: /* write */ - if (dq_ptr == 0) { /* sector start? */ - if (!dqd.command && !dqd_wval) break; /* xfer done? */ - if (uptr->flags & UNIT_WPRT) { /* write protect? */ - dqc_sta[drv] = dqc_sta[drv] | STA_FLG; - break; /* done */ - } - if ((dqc_rarc != dqc_ucyl[drv]) || /* RAR cyl miscompare? */ - (dqc_rarh != dqc_uhed[drv]) || /* RAR head miscompare? */ - (dqc_rars >= DQ_NUMSC)) { /* bad sector? */ - dqc_sta[drv] = dqc_sta[drv] | STA_AER; /* no record found err */ - break; - } - if (dqc_rarh >= DQ_NUMSF) { /* bad head? */ - dqc_sta[drv] = dqc_sta[drv] | STA_EOC; /* end of cyl err */ - break; - } - } - dqxb[dq_ptr++] = dqd_wval ? (uint16) dqd_obuf : 0; /* store word/fill */ - dqd_wval = 0; /* clr data valid */ - if (dq_ptr >= DQ_NUMWD) { /* buffer full? */ - da = GETDA (dqc_rarc, dqc_rarh, dqc_rars); /* calc disk addr */ - dqc_rars = (dqc_rars + 1) % DQ_NUMSC; /* incr sector */ - if (dqc_rars == 0) /* wrap? incr head */ - dqc_uhed[drv] = dqc_rarh = dqc_rarh + 1; - err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET); - if (err) - break; - fxwrite (dqxb, sizeof (int16), DQ_NUMWD, uptr->fileref); - err = ferror (uptr->fileref); - if (err) - break; - dq_ptr = 0; - } - if (dqd.command && dqd_xfer) { /* dch on, xfer? */ - dqdio (&dqd_dib, ioENF, 0); /* set flag */ - } - dqd.command = CLEAR; /* clr dch cmd */ - sim_activate (uptr, dqc_xtime); /* sched next word */ - return SCPE_OK; - - default: - return SCPE_IERR; - } /* end case fnc */ - -dqc.command = CLEAR; /* clr cch cmd */ -dqcio (&dqc_dib, ioENF, 0); /* set cch flg */ -dqc_busy = 0; /* ctlr is free */ -dqd_xfer = dqd_wval = 0; -if (err != 0) { /* error? */ - perror ("DQ I/O error"); - clearerr (uptr->fileref); - return SCPE_IOERR; - } -return SCPE_OK; -} - -/* Reset routine */ - -t_stat dqc_reset (DEVICE *dptr) -{ -int32 drv; -DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ - -hp_enbdis_pair (dptr, /* make pair cons */ - (dptr == &dqd_dev)? &dqc_dev: &dqd_dev); - -if (sim_switches & SWMASK ('P')) { /* initialization reset? */ - dqd_ibuf = 0; /* clear buffers */ - dqd_obuf = 0; - dqc_obuf = 0; /* clear buffer */ - dqc_rarc = dqc_rarh = dqc_rars = 0; /* clear RAR */ - } - -IOPRESET (dibptr); /* PRESET device (does not use PON) */ - -dqc_busy = 0; /* reset controller state */ -dqd_xfer = 0; -dqd_wval = 0; -dq_ptr = 0; - -sim_cancel (&dqd_unit); /* cancel dch */ - -for (drv = 0; drv < DQ_NUMDRV; drv++) { /* loop thru drives */ - sim_cancel (&dqc_unit[drv]); /* cancel activity */ - dqc_unit[drv].FNC = 0; /* clear function */ - dqc_ucyl[drv] = dqc_uhed[drv] = 0; /* clear drive pos */ - dqc_sta[drv] = 0; /* clear status */ - } - -return SCPE_OK; -} - -/* Attach routine */ - -t_stat dqc_attach (UNIT *uptr, char *cptr) -{ -t_stat r; - -r = attach_unit (uptr, cptr); /* attach unit */ -if (r == SCPE_OK) dqc_load_unload (uptr, 0, NULL, NULL);/* if OK, load heads */ -return r; -} - -/* Detach routine */ - -t_stat dqc_detach (UNIT* uptr) -{ -dqc_load_unload (uptr, UNIT_UNLOAD, NULL, NULL); /* unload heads */ -return detach_unit (uptr); /* detach unit */ -} - -/* Load and unload heads */ - -t_stat dqc_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc) -{ -if ((uptr->flags & UNIT_ATT) == 0) return SCPE_UNATT; /* must be attached to load */ -if (value == UNIT_UNLOAD) /* unload heads? */ - uptr->flags = uptr->flags | UNIT_UNLOAD; /* indicate unload */ -else uptr->flags = uptr->flags & ~UNIT_UNLOAD; /* indicate load */ -return SCPE_OK; -} - -/* 7900/7901/2883/2884 bootstrap routine (HP 12992A ROM) */ - -const BOOT_ROM dq_rom = { - 0102501, /*ST LIA 1 ; get switches */ - 0106501, /* LIB 1 */ - 0013765, /* AND D7 ; isolate hd */ - 0005750, /* BLF,CLE,SLB */ - 0027741, /* JMP RD */ - 0005335, /* RBR,SLB,ERB ; <13>->E, set = 2883 */ - 0027717, /* JMP IS */ - 0102611, /*LP OTA CC ; do 7900 status to */ - 0103711, /* STC CC,C ; clear first seek */ - 0102310, /* SFS DC */ - 0027711, /* JMP *-1 */ - 0002004, /* INA ; get next drive */ - 0053765, /* CPA D7 ; all cleared? */ - 0002001, /* RSS */ - 0027707, /* JMP LP */ - 0067761, /*IS LDB SEEKC ; get seek comnd */ - 0106610, /* OTB DC ; issue cyl addr (0) */ - 0103710, /* STC DC,C ; to dch */ - 0106611, /* OTB CC ; seek cmd */ - 0103711, /* STC CC,C ; to cch */ - 0102310, /* SFS DC ; addr wd ok? */ - 0027724, /* JMP *-1 ; no, wait */ - 0006400, /* CLB */ - 0102501, /* LIA 1 ; get switches */ - 0002051, /* SEZ,SLA,RSS ; subchan = 1 or ISS */ - 0047770, /* ADB BIT9 ; head 2 */ - 0106610, /* OTB DC ; head/sector */ - 0103710, /* STC DC,C ; to dch */ - 0102311, /* SFS CC ; seek done? */ - 0027734, /* JMP *-1 ; no, wait */ - 0063731, /* LDA ISSRD ; get read read */ - 0002341, /* SEZ,CCE,RSS ; iss disc? */ - 0001100, /* ARS ; no, make 7900 read */ - 0067776, /*RD LDB DMACW ; DMA control */ - 0106606, /* OTB 6 */ - 0067762, /* LDB ADDR1 ; memory addr */ - 0077741, /* STB RD ; make non re-executable */ - 0106602, /* OTB 2 */ - 0102702, /* STC 2 ; flip DMA ctrl */ - 0067764, /* LDB COUNT ; word count */ - 0106602, /* OTB 2 */ - 0002041, /* SEZ,RSS */ - 0027766, /* JMP NW */ - 0102611, /* OTA CC ; to cch */ - 0103710, /* STC DC,C ; start dch */ - 0103706, /* STC 6,C ; start DMA */ - 0103711, /* STC CC,C ; start cch */ - 0037773, /* ISZ SK */ - 0027773, /* JMP SK */ - 0030000, /*SEEKC 030000 */ - 0102011, /*ADDR1 102011 */ - 0102055, /*ADDR2 102055 */ - 0164000, /*COUNT -6144. */ - 0000007, /*D7 7 */ - 0106710, /*NW CLC DC ; set 'next wd is cmd' flag */ - 0001720, /* ALF,ALF ; move to head number loc */ - 0001000, /*BIT9 ALS */ - 0103610, /* OTA DC,C ; output cold load cmd */ - 0103706, /* STC 6,C ; start DMA */ - 0102310, /* SFS DC ; done? */ - 0027773, /* JMP *-1 ; no, wait */ - 0117763, /*XT JSB ADDR2,I ; start program */ - 0120010, /*DMACW 120000+DC */ - 0000000 /* -ST */ - }; - -t_stat dqc_boot (int32 unitno, DEVICE *dptr) -{ -const int32 dev = dqd_dib.select_code; /* data chan select code */ - -if (unitno != 0) /* boot supported on drive unit 0 only */ - return SCPE_NOFNC; /* report "Command not allowed" if attempted */ - -if (ibl_copy (dq_rom, dev, IBL_OPT, /* copy the boot ROM to memory and configure */ - IBL_DQ | IBL_SET_SC (dev))) /* the S register accordingly */ - return SCPE_IERR; /* return an internal error if the copy failed */ -else - return SCPE_OK; -} +/* hp2100_dq.c: HP 2100 12565A disk simulator + + Copyright (c) 1993-2006, Bill McDermith + Copyright (c) 2004-2016 J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. + + DQ 12565A 2883 disk system + + 13-May-16 JDB Modified for revised SCP API function parameter types + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 24-Dec-14 JDB Added casts for explicit downward conversions + 18-Dec-12 MP Now calls sim_activate_time to get remaining seek time + 09-May-12 JDB Separated assignments from conditional expressions + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + 28-Mar-11 JDB Tidied up signal handling + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 10-Aug-08 JDB Added REG_FIT to register variables < 32-bit size + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 01-Mar-05 JDB Added SET UNLOAD/LOAD + 07-Oct-04 JDB Fixed enable/disable from either device + Shortened xtime from 5 to 3 (drive avg 156KW/second) + Fixed not ready/any error status + Fixed RAR model + 21-Apr-04 RMS Fixed typo in boot loader (found by Dave Bryan) + 26-Apr-04 RMS Fixed SFS x,C and SFC x,C + Fixed SR setting in IBL + Revised IBL loader + Implemented DMA SRQ (follows FLG) + 25-Apr-03 RMS Fixed bug in status check + 10-Nov-02 RMS Added boot command, rebuilt like 12559/13210 + 09-Jan-02 WOM Copied dp driver and mods for 2883 + + Reference: + - 12565A Disc Interface Kit Operating and Service Manual (12565-90003, Aug-1973) + + + Differences between 12559/13210 and 12565 controllers + - 12565 stops transfers on address miscompares; 12559/13210 only stops writes + - 12565 does not set error on positioner busy + - 12565 does not set positioner busy if already on cylinder + - 12565 does not need eoc logic, it will hit an invalid head number + + The controller's "Record Address Register" (RAR) contains the CHS address of + the last Position or Load Address command executed. The RAR is shared among + all drives on the controller. In addition, each drive has an internal + position register that contains the last cylinder and head position + transferred to the drive during Position command execution (sector operations + always start with the RAR sector position). + + In a real drive, the address field of the sector under the head is read and + compared to the RAR. When they match, the target sector is under the head + and is ready for reading or writing. If a match doesn't occur, an Address + Error is indicated. In the simulator, the address field is obtained from the + drive's current position register during a read, i.e., the "on-disc" address + field is assumed to match the current position. + + The following implemented behaviors have been inferred from secondary sources + (diagnostics, operating system drivers, etc.), due to absent or contradictory + authoritative information; future correction may be needed: + + 1. Read Address command starts at the sector number in the RAR. +*/ + +#include "hp2100_defs.h" + +#define UNIT_V_WLK (UNIT_V_UF + 0) /* write locked */ +#define UNIT_V_UNLOAD (UNIT_V_UF + 1) /* heads unloaded */ +#define UNIT_WLK (1 << UNIT_V_WLK) +#define UNIT_UNLOAD (1 << UNIT_V_UNLOAD) +#define FNC u3 /* saved function */ +#define DRV u4 /* drive number (DC) */ +#define UNIT_WPRT (UNIT_WLK | UNIT_RO) /* write prot */ + +#define DQ_N_NUMWD 7 +#define DQ_NUMWD (1 << DQ_N_NUMWD) /* words/sector */ +#define DQ_NUMSC 23 /* sectors/track */ +#define DQ_NUMSF 20 /* tracks/cylinder */ +#define DQ_NUMCY 203 /* cylinders/disk */ +#define DQ_SIZE (DQ_NUMSF * DQ_NUMCY * DQ_NUMSC * DQ_NUMWD) +#define DQ_NUMDRV 2 /* # drives */ + +/* Command word */ + +#define CW_V_FNC 12 /* function */ +#define CW_M_FNC 017 +#define CW_GETFNC(x) (((x) >> CW_V_FNC) & CW_M_FNC) +/* 000 (unused) */ +#define FNC_STA 001 /* status check */ +#define FNC_RCL 002 /* recalibrate */ +#define FNC_SEEK 003 /* seek */ +#define FNC_RD 004 /* read */ +#define FNC_WD 005 /* write */ +#define FNC_RA 006 /* read address */ +#define FNC_WA 007 /* write address */ +#define FNC_CHK 010 /* check */ +#define FNC_LA 013 /* load address */ +#define FNC_AS 014 /* address skip */ + +#define FNC_SEEK1 020 /* fake - seek1 */ +#define FNC_SEEK2 021 /* fake - seek2 */ +#define FNC_SEEK3 022 /* fake - seek3 */ +#define FNC_CHK1 023 /* fake - check1 */ +#define FNC_LA1 024 /* fake - ldaddr1 */ + +#define CW_V_DRV 0 /* drive */ +#define CW_M_DRV 01 +#define CW_GETDRV(x) (((x) >> CW_V_DRV) & CW_M_DRV) + +/* Disk address words */ + +#define DA_V_CYL 0 /* cylinder */ +#define DA_M_CYL 0377 +#define DA_GETCYL(x) (((x) >> DA_V_CYL) & DA_M_CYL) +#define DA_V_HD 8 /* head */ +#define DA_M_HD 037 +#define DA_GETHD(x) (((x) >> DA_V_HD) & DA_M_HD) +#define DA_V_SC 0 /* sector */ +#define DA_M_SC 037 +#define DA_GETSC(x) (((x) >> DA_V_SC) & DA_M_SC) +#define DA_CKMASK 0777 /* check mask */ + +/* Status in dqc_sta[drv] - (d) = dynamic */ + +#define STA_DID 0000200 /* drive ID (d) */ +#define STA_NRDY 0000100 /* not ready (d) */ +#define STA_EOC 0000040 /* end of cylinder */ +#define STA_AER 0000020 /* addr error */ +#define STA_FLG 0000010 /* flagged */ +#define STA_BSY 0000004 /* seeking */ +#define STA_DTE 0000002 /* data error */ +#define STA_ERR 0000001 /* any error */ +#define STA_ANYERR (STA_NRDY | STA_EOC | STA_AER | STA_FLG | STA_DTE) + +struct { + FLIP_FLOP command; /* cch command flip-flop */ + FLIP_FLOP control; /* cch control flip-flop */ + FLIP_FLOP flag; /* cch flag flip-flop */ + FLIP_FLOP flagbuf; /* cch flag buffer flip-flop */ + } dqc = { CLEAR, CLEAR, CLEAR, CLEAR }; + +int32 dqc_busy = 0; /* cch xfer */ +int32 dqc_cnt = 0; /* check count */ +int32 dqc_stime = 100; /* seek time */ +int32 dqc_ctime = 100; /* command time */ +int32 dqc_xtime = 3; /* xfer time */ +int32 dqc_dtime = 2; /* dch time */ + +struct { + FLIP_FLOP command; /* dch command flip-flop */ + FLIP_FLOP control; /* dch control flip-flop */ + FLIP_FLOP flag; /* dch flag flip-flop */ + FLIP_FLOP flagbuf; /* dch flag buffer flip-flop */ + } dqd = { CLEAR, CLEAR, CLEAR, CLEAR }; + +int32 dqd_obuf = 0, dqd_ibuf = 0; /* dch buffers */ +int32 dqc_obuf = 0; /* cch buffers */ +int32 dqd_xfer = 0; /* xfer in prog */ +int32 dqd_wval = 0; /* write data valid */ +int32 dq_ptr = 0; /* buffer ptr */ +uint8 dqc_rarc = 0; /* RAR cylinder */ +uint8 dqc_rarh = 0; /* RAR head */ +uint8 dqc_rars = 0; /* RAR sector */ +uint8 dqc_ucyl[DQ_NUMDRV] = { 0 }; /* unit cylinder */ +uint8 dqc_uhed[DQ_NUMDRV] = { 0 }; /* unit head */ +uint16 dqc_sta[DQ_NUMDRV] = { 0 }; /* unit status */ +uint16 dqxb[DQ_NUMWD]; /* sector buffer */ + +DEVICE dqd_dev, dqc_dev; + +IOHANDLER dqdio; +IOHANDLER dqcio; + +t_stat dqc_svc (UNIT *uptr); +t_stat dqd_svc (UNIT *uptr); +t_stat dqc_reset (DEVICE *dptr); +t_stat dqc_attach (UNIT *uptr, CONST char *cptr); +t_stat dqc_detach (UNIT* uptr); +t_stat dqc_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +t_stat dqc_boot (int32 unitno, DEVICE *dptr); +void dq_god (int32 fnc, int32 drv, int32 time); +void dq_goc (int32 fnc, int32 drv, int32 time); + +/* DQD data structures + + dqd_dev DQD device descriptor + dqd_unit DQD unit list + dqd_reg DQD register list +*/ + +DIB dq_dib[] = { + { &dqdio, DQD }, + { &dqcio, DQC } + }; + +#define dqd_dib dq_dib[0] +#define dqc_dib dq_dib[1] + +UNIT dqd_unit = { UDATA (&dqd_svc, 0, 0) }; + +REG dqd_reg[] = { + { ORDATA (IBUF, dqd_ibuf, 16) }, + { ORDATA (OBUF, dqd_obuf, 16) }, + { BRDATA (DBUF, dqxb, 8, 16, DQ_NUMWD) }, + { DRDATA (BPTR, dq_ptr, DQ_N_NUMWD) }, + { FLDATA (CMD, dqd.command, 0) }, + { FLDATA (CTL, dqd.control, 0) }, + { FLDATA (FLG, dqd.flag, 0) }, + { FLDATA (FBF, dqd.flagbuf, 0) }, + { FLDATA (XFER, dqd_xfer, 0) }, + { FLDATA (WVAL, dqd_wval, 0) }, + { ORDATA (SC, dqd_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, dqd_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB dqd_mod[] = { + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &dqd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dqd_dev }, + { 0 } + }; + +DEVICE dqd_dev = { + "DQD", &dqd_unit, dqd_reg, dqd_mod, + 1, 10, DQ_N_NUMWD, 1, 8, 16, + NULL, NULL, &dqc_reset, + NULL, NULL, NULL, + &dqd_dib, DEV_DISABLE + }; + +/* DQC data structures + + dqc_dev DQC device descriptor + dqc_unit DQC unit list + dqc_reg DQC register list + dqc_mod DQC modifier list +*/ + +UNIT dqc_unit[] = { + { UDATA (&dqc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_UNLOAD, DQ_SIZE) }, + { UDATA (&dqc_svc, UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_UNLOAD, DQ_SIZE) } + }; + +REG dqc_reg[] = { + { ORDATA (OBUF, dqc_obuf, 16) }, + { ORDATA (BUSY, dqc_busy, 2), REG_RO }, + { ORDATA (CNT, dqc_cnt, 9) }, + { FLDATA (CMD, dqc.command, 0) }, + { FLDATA (CTL, dqc.control, 0) }, + { FLDATA (FLG, dqc.flag, 0) }, + { FLDATA (FBF, dqc.flagbuf, 0) }, + { DRDATA (RARC, dqc_rarc, 8), PV_RZRO | REG_FIT }, + { DRDATA (RARH, dqc_rarh, 5), PV_RZRO | REG_FIT }, + { DRDATA (RARS, dqc_rars, 5), PV_RZRO | REG_FIT }, + { BRDATA (CYL, dqc_ucyl, 10, 8, DQ_NUMDRV), PV_RZRO }, + { BRDATA (HED, dqc_uhed, 10, 5, DQ_NUMDRV), PV_RZRO }, + { BRDATA (STA, dqc_sta, 8, 16, DQ_NUMDRV) }, + { DRDATA (CTIME, dqc_ctime, 24), PV_LEFT }, + { DRDATA (DTIME, dqc_dtime, 24), PV_LEFT }, + { DRDATA (STIME, dqc_stime, 24), PV_LEFT }, + { DRDATA (XTIME, dqc_xtime, 24), REG_NZ + PV_LEFT }, + { URDATA (UFNC, dqc_unit[0].FNC, 8, 8, 0, + DQ_NUMDRV, REG_HRO) }, + { ORDATA (SC, dqc_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, dqc_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB dqc_mod[] = { + { UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", dqc_load_unload }, + { UNIT_UNLOAD, 0, "heads loaded", "LOADED", dqc_load_unload }, + { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL }, + { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL }, + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &dqd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &dqd_dev }, + { 0 } + }; + +DEVICE dqc_dev = { + "DQC", dqc_unit, dqc_reg, dqc_mod, + DQ_NUMDRV, 8, 24, 1, 8, 16, + NULL, NULL, &dqc_reset, + &dqc_boot, &dqc_attach, &dqc_detach, + &dqc_dib, DEV_DISABLE + }; + + +/* Data channel I/O signal handler */ + +uint32 dqdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + dqd.flag = dqd.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + dqd.flag = dqd.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (dqd); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (dqd); + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, dqd_ibuf); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + dqd_obuf = IODATA (stat_data); /* clear supplied status */ + + if (!dqc_busy || dqd_xfer) + dqd_wval = 1; /* if !overrun, valid */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + dqd.flag = dqd.flagbuf = SET; /* set flag and flag buffer */ + dqd_obuf = 0; /* clear output buffer */ + break; + + + case ioCRS: /* control reset */ + dqd.command = CLEAR; /* clear command */ + /* fall into CLC handler */ + + case ioCLC: /* clear control flip-flop */ + dqd.control = CLEAR; /* clear control */ + dqd_xfer = 0; /* clr xfer */ + break; + + + case ioSTC: /* set control flip-flop */ + dqd.command = SET; /* set ctl, cmd */ + dqd.control = SET; + + if (dqc_busy && !dqd_xfer) /* overrun? */ + dqc_sta[dqc_busy - 1] |= STA_DTE; + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (dqd); /* set standard PRL signal */ + setstdIRQ (dqd); /* set standard IRQ signal */ + setstdSRQ (dqd); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + dqd.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Command channel I/O signal handler. + + Implementation notes: + + 1. The input buffer register is not connected to the disc controller. + Pullups on the card and an inversion result in reading zeros when IOI is + signalled. +*/ + +uint32 dqcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +int32 fnc, drv; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + dqc.flag = dqc.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + dqc.flag = dqc.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (dqc); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (dqc); + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, 0); /* no data */ + break; + + + case ioIOO: /* I/O data output */ + dqc_obuf = IODATA (stat_data); /* clear supplied status */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + dqc.flag = dqc.flagbuf = SET; /* set flag and flag buffer */ + dqc_obuf = 0; /* clear output buffer */ + break; + + + case ioCRS: /* control reset */ + case ioCLC: /* clear control flip-flop */ + dqc.command = CLEAR; /* clear command */ + dqc.control = CLEAR; /* clear control */ + + if (dqc_busy) + sim_cancel (&dqc_unit[dqc_busy - 1]); + + sim_cancel (&dqd_unit); /* cancel dch */ + dqd_xfer = 0; /* clr dch xfer */ + dqc_busy = 0; /* clr busy */ + break; + + + case ioSTC: /* set control flip-flop */ + dqc.control = SET; /* set ctl */ + + if (!dqc.command) { /* cmd clr? */ + dqc.command = SET; /* set cmd */ + drv = CW_GETDRV (dqc_obuf); /* get fnc, drv */ + fnc = CW_GETFNC (dqc_obuf); /* from cmd word */ + + switch (fnc) { /* case on fnc */ + case FNC_SEEK: case FNC_RCL: /* seek, recal */ + case FNC_CHK: /* check */ + dqc_sta[drv] = 0; /* clear status */ + case FNC_STA: case FNC_LA: /* rd sta, load addr */ + dq_god (fnc, drv, dqc_dtime); /* sched dch xfer */ + break; + case FNC_RD: case FNC_WD: /* read, write */ + case FNC_RA: case FNC_WA: /* rd addr, wr addr */ + case FNC_AS: /* address skip */ + dq_goc (fnc, drv, dqc_ctime); /* sched drive */ + break; + } /* end case */ + } /* end if !CMD */ + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (dqc); /* set standard PRL signal */ + setstdIRQ (dqc); /* set standard IRQ signal */ + setstdSRQ (dqc); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + dqc.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } +return stat_data; +} + + +/* Start data channel operation */ + +void dq_god (int32 fnc, int32 drv, int32 time) +{ +dqd_unit.DRV = drv; /* save unit */ +dqd_unit.FNC = fnc; /* save function */ +sim_activate (&dqd_unit, time); +return; +} + +/* Start controller operation */ + +void dq_goc (int32 fnc, int32 drv, int32 time) +{ +int32 t; + +t = sim_activate_time (&dqc_unit[drv]); + +if (t) { /* still seeking? */ + sim_cancel (&dqc_unit[drv]); /* cancel */ + time = time + t; /* include seek time */ + } +dqc_sta[drv] = 0; /* clear status */ +dq_ptr = 0; /* init buf ptr */ +dqc_busy = drv + 1; /* set busy */ +dqd_xfer = 1; /* xfer in prog */ +dqc_unit[drv].FNC = fnc; /* save function */ +sim_activate (&dqc_unit[drv], time); /* activate unit */ +return; +} + +/* Data channel unit service + + This routine handles the data channel transfers. It also handles + data transfers that are blocked by seek in progress. + + uptr->DRV = target drive + uptr->FNC = target function + + Seek substates + seek - transfer cylinder + seek1 - transfer head/surface, sched drive + Recalibrate substates + rcl - clear cyl/head/surface, sched drive + Load address + la - transfer cylinder + la1 - transfer head/surface, finish operation + Status check - transfer status, finish operation + Check data + chk - transfer sector count, sched drive +*/ + +t_stat dqd_svc (UNIT *uptr) +{ +int32 drv, st; + +drv = uptr->DRV; /* get drive no */ + +switch (uptr->FNC) { /* case function */ + + case FNC_LA: /* arec, need cyl */ + case FNC_SEEK: /* seek, need cyl */ + if (dqd.command) { /* dch active? */ + dqc_rarc = DA_GETCYL (dqd_obuf); /* set RAR from cyl word */ + dqd_wval = 0; /* clr data valid */ + dqd.command = CLEAR; /* clr dch cmd */ + dqdio (&dqd_dib, ioENF, 0); /* set dch flg */ + if (uptr->FNC == FNC_LA) uptr->FNC = FNC_LA1; + else uptr->FNC = FNC_SEEK1; /* advance state */ + } + sim_activate (uptr, dqc_xtime); /* no, wait more */ + break; + + case FNC_LA1: /* arec, need hd/sec */ + case FNC_SEEK1: /* seek, need hd/sec */ + if (dqd.command) { /* dch active? */ + dqc_rarh = DA_GETHD (dqd_obuf); /* set RAR from head */ + dqc_rars = DA_GETSC (dqd_obuf); /* set RAR from sector */ + dqd_wval = 0; /* clr data valid */ + dqd.command = CLEAR; /* clr dch cmd */ + dqdio (&dqd_dib, ioENF, 0); /* set dch flg */ + if (uptr->FNC == FNC_LA1) { + dqc.command = CLEAR; /* clr cch cmd */ + dqcio (&dqc_dib, ioENF, 0); /* set cch flg */ + break; /* done if Load Address */ + } + if (sim_is_active (&dqc_unit[drv])) break; /* if busy, seek check */ + st = abs (dqc_rarc - dqc_ucyl[drv]) * dqc_stime; + if (st == 0) st = dqc_xtime; /* if on cyl, min time */ + else dqc_sta[drv] = dqc_sta[drv] | STA_BSY; /* set busy */ + dqc_ucyl[drv] = dqc_rarc; /* transfer RAR */ + dqc_uhed[drv] = dqc_rarh; + sim_activate (&dqc_unit[drv], st); /* schedule op */ + dqc_unit[drv].FNC = FNC_SEEK2; /* advance state */ + } + else sim_activate (uptr, dqc_xtime); /* no, wait more */ + break; + + case FNC_RCL: /* recalibrate */ + dqc_rarc = dqc_rarh = dqc_rars = 0; /* clear RAR */ + if (sim_is_active (&dqc_unit[drv])) break; /* ignore if busy */ + st = dqc_ucyl[drv] * dqc_stime; /* calc diff */ + if (st == 0) st = dqc_xtime; /* if on cyl, min time */ + else dqc_sta[drv] = dqc_sta[drv] | STA_BSY; /* set busy */ + sim_activate (&dqc_unit[drv], st); /* schedule drive */ + dqc_ucyl[drv] = dqc_uhed[drv] = 0; /* clear drive pos */ + dqc_unit[drv].FNC = FNC_SEEK2; /* advance state */ + break; + + case FNC_STA: /* read status */ + if (dqd.command) { /* dch active? */ + if ((dqc_unit[drv].flags & UNIT_UNLOAD) == 0) /* drive up? */ + dqd_ibuf = dqc_sta[drv] & ~STA_DID; + else dqd_ibuf = STA_NRDY; + if (dqd_ibuf & STA_ANYERR) /* errors? set flg */ + dqd_ibuf = dqd_ibuf | STA_ERR; + if (drv) dqd_ibuf = dqd_ibuf | STA_DID; + dqc.command = CLEAR; /* clr cch cmd */ + dqd.command = CLEAR; /* clr dch cmd */ + dqdio (&dqd_dib, ioENF, 0); /* set dch flg */ + dqc_sta[drv] = dqc_sta[drv] & ~STA_ANYERR; /* clr sta flags */ + } + else sim_activate (uptr, dqc_xtime); /* wait more */ + break; + + case FNC_CHK: /* check, need cnt */ + if (dqd.command) { /* dch active? */ + dqc_cnt = dqd_obuf & DA_CKMASK; /* get count */ + dqd_wval = 0; /* clr data valid */ + dq_goc (FNC_CHK1, drv, dqc_ctime); /* sched drv */ + } + else sim_activate (uptr, dqc_xtime); /* wait more */ + break; + + default: + return SCPE_IERR; + } + +return SCPE_OK; +} + +/* Drive unit service + + This routine handles the data transfers. + + Seek substates + seek2 - done + Recalibrate substate + rcl1 - done + Check data substates + chk1 - finish operation + Read + Read address + Address skip (read without header check) + Write + Write address +*/ + +#define GETDA(x,y,z) \ + (((((x) * DQ_NUMSF) + (y)) * DQ_NUMSC) + (z)) * DQ_NUMWD + +t_stat dqc_svc (UNIT *uptr) +{ +int32 da, drv, err; + +err = 0; /* assume no err */ +drv = uptr - dqc_unit; /* get drive no */ +if (uptr->flags & UNIT_UNLOAD) { /* drive down? */ + dqc.command = CLEAR; /* clr cch cmd */ + dqcio (&dqc_dib, ioENF, 0); /* set cch flg */ + dqc_sta[drv] = 0; /* clr status */ + dqc_busy = 0; /* ctlr is free */ + dqd_xfer = dqd_wval = 0; + return SCPE_OK; + } +switch (uptr->FNC) { /* case function */ + + case FNC_SEEK2: /* seek done */ + if (dqc_ucyl[drv] >= DQ_NUMCY) { /* out of range? */ + dqc_sta[drv] = dqc_sta[drv] | STA_BSY | STA_ERR; /* seek check */ + dqc_ucyl[drv] = 0; /* seek to cyl 0 */ + } + else dqc_sta[drv] = dqc_sta[drv] & ~STA_BSY; /* drive not busy */ + case FNC_SEEK3: + if (dqc_busy || dqc.flag) { /* ctrl busy? */ + uptr->FNC = FNC_SEEK3; /* next state */ + sim_activate (uptr, dqc_xtime); /* ctrl busy? wait */ + } + else { + dqc.command = CLEAR; /* clr cch cmd */ + dqcio (&dqc_dib, ioENF, 0); /* set cch flg */ + } + return SCPE_OK; + + case FNC_RA: /* read addr */ + if (!dqd.command) break; /* dch clr? done */ + if (dq_ptr == 0) dqd_ibuf = dqc_ucyl[drv]; /* 1st word? */ + else if (dq_ptr == 1) { /* second word? */ + dqd_ibuf = (dqc_uhed[drv] << DA_V_HD) | /* use drive head */ + (dqc_rars << DA_V_SC); /* and RAR sector */ + dqc_rars = (dqc_rars + 1) % DQ_NUMSC; /* incr sector */ + } + else break; + dq_ptr = dq_ptr + 1; + dqd.command = CLEAR; /* clr dch cmd */ + dqdio (&dqd_dib, ioENF, 0); /* set dch flg */ + sim_activate (uptr, dqc_xtime); /* sched next word */ + return SCPE_OK; + + case FNC_AS: /* address skip */ + case FNC_RD: /* read */ + case FNC_CHK1: /* check */ + if (dq_ptr == 0) { /* new sector? */ + if (!dqd.command && (uptr->FNC != FNC_CHK1)) break; + if ((dqc_rarc != dqc_ucyl[drv]) || /* RAR cyl miscompare? */ + (dqc_rarh != dqc_uhed[drv]) || /* RAR head miscompare? */ + (dqc_rars >= DQ_NUMSC)) { /* bad sector? */ + dqc_sta[drv] = dqc_sta[drv] | STA_AER; /* no record found err */ + break; + } + if (dqc_rarh >= DQ_NUMSF) { /* bad head? */ + dqc_sta[drv] = dqc_sta[drv] | STA_EOC; /* end of cyl err */ + break; + } + da = GETDA (dqc_rarc, dqc_rarh, dqc_rars); /* calc disk addr */ + dqc_rars = (dqc_rars + 1) % DQ_NUMSC; /* incr sector */ + if (dqc_rars == 0) /* wrap? incr head */ + dqc_uhed[drv] = dqc_rarh = dqc_rarh + 1; + err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET); + if (err) + break; + fxread (dqxb, sizeof (int16), DQ_NUMWD, uptr->fileref); + err = ferror (uptr->fileref); + if (err) + break; + } + dqd_ibuf = dqxb[dq_ptr++]; /* get word */ + if (dq_ptr >= DQ_NUMWD) { /* end of sector? */ + if (uptr->FNC == FNC_CHK1) { /* check? */ + dqc_cnt = (dqc_cnt - 1) & DA_CKMASK; /* decr count */ + if (dqc_cnt == 0) break; /* if zero, done */ + } + dq_ptr = 0; /* wrap buf ptr */ + } + if (dqd.command && dqd_xfer) { /* dch on, xfer? */ + dqdio (&dqd_dib, ioENF, 0); /* set flag */ + } + dqd.command = CLEAR; /* clr dch cmd */ + sim_activate (uptr, dqc_xtime); /* sched next word */ + return SCPE_OK; + + case FNC_WA: /* write address */ + case FNC_WD: /* write */ + if (dq_ptr == 0) { /* sector start? */ + if (!dqd.command && !dqd_wval) break; /* xfer done? */ + if (uptr->flags & UNIT_WPRT) { /* write protect? */ + dqc_sta[drv] = dqc_sta[drv] | STA_FLG; + break; /* done */ + } + if ((dqc_rarc != dqc_ucyl[drv]) || /* RAR cyl miscompare? */ + (dqc_rarh != dqc_uhed[drv]) || /* RAR head miscompare? */ + (dqc_rars >= DQ_NUMSC)) { /* bad sector? */ + dqc_sta[drv] = dqc_sta[drv] | STA_AER; /* no record found err */ + break; + } + if (dqc_rarh >= DQ_NUMSF) { /* bad head? */ + dqc_sta[drv] = dqc_sta[drv] | STA_EOC; /* end of cyl err */ + break; + } + } + dqxb[dq_ptr++] = dqd_wval ? (uint16) dqd_obuf : 0; /* store word/fill */ + dqd_wval = 0; /* clr data valid */ + if (dq_ptr >= DQ_NUMWD) { /* buffer full? */ + da = GETDA (dqc_rarc, dqc_rarh, dqc_rars); /* calc disk addr */ + dqc_rars = (dqc_rars + 1) % DQ_NUMSC; /* incr sector */ + if (dqc_rars == 0) /* wrap? incr head */ + dqc_uhed[drv] = dqc_rarh = dqc_rarh + 1; + err = fseek (uptr->fileref, da * sizeof (int16), SEEK_SET); + if (err) + break; + fxwrite (dqxb, sizeof (int16), DQ_NUMWD, uptr->fileref); + err = ferror (uptr->fileref); + if (err) + break; + dq_ptr = 0; + } + if (dqd.command && dqd_xfer) { /* dch on, xfer? */ + dqdio (&dqd_dib, ioENF, 0); /* set flag */ + } + dqd.command = CLEAR; /* clr dch cmd */ + sim_activate (uptr, dqc_xtime); /* sched next word */ + return SCPE_OK; + + default: + return SCPE_IERR; + } /* end case fnc */ + +dqc.command = CLEAR; /* clr cch cmd */ +dqcio (&dqc_dib, ioENF, 0); /* set cch flg */ +dqc_busy = 0; /* ctlr is free */ +dqd_xfer = dqd_wval = 0; +if (err != 0) { /* error? */ + perror ("DQ I/O error"); + clearerr (uptr->fileref); + return SCPE_IOERR; + } +return SCPE_OK; +} + +/* Reset routine */ + +t_stat dqc_reset (DEVICE *dptr) +{ +int32 drv; +DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ + +hp_enbdis_pair (dptr, /* make pair cons */ + (dptr == &dqd_dev)? &dqc_dev: &dqd_dev); + +if (sim_switches & SWMASK ('P')) { /* initialization reset? */ + dqd_ibuf = 0; /* clear buffers */ + dqd_obuf = 0; + dqc_obuf = 0; /* clear buffer */ + dqc_rarc = dqc_rarh = dqc_rars = 0; /* clear RAR */ + } + +IOPRESET (dibptr); /* PRESET device (does not use PON) */ + +dqc_busy = 0; /* reset controller state */ +dqd_xfer = 0; +dqd_wval = 0; +dq_ptr = 0; + +sim_cancel (&dqd_unit); /* cancel dch */ + +for (drv = 0; drv < DQ_NUMDRV; drv++) { /* loop thru drives */ + sim_cancel (&dqc_unit[drv]); /* cancel activity */ + dqc_unit[drv].FNC = 0; /* clear function */ + dqc_ucyl[drv] = dqc_uhed[drv] = 0; /* clear drive pos */ + dqc_sta[drv] = 0; /* clear status */ + } + +return SCPE_OK; +} + +/* Attach routine */ + +t_stat dqc_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat r; + +r = attach_unit (uptr, cptr); /* attach unit */ +if (r == SCPE_OK) dqc_load_unload (uptr, 0, NULL, NULL);/* if OK, load heads */ +return r; +} + +/* Detach routine */ + +t_stat dqc_detach (UNIT* uptr) +{ +dqc_load_unload (uptr, UNIT_UNLOAD, NULL, NULL); /* unload heads */ +return detach_unit (uptr); /* detach unit */ +} + +/* Load and unload heads */ + +t_stat dqc_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +if ((uptr->flags & UNIT_ATT) == 0) return SCPE_UNATT; /* must be attached to load */ +if (value == UNIT_UNLOAD) /* unload heads? */ + uptr->flags = uptr->flags | UNIT_UNLOAD; /* indicate unload */ +else uptr->flags = uptr->flags & ~UNIT_UNLOAD; /* indicate load */ +return SCPE_OK; +} + +/* 7900/7901/2883/2884 bootstrap routine (HP 12992A ROM) */ + +const BOOT_ROM dq_rom = { + 0102501, /*ST LIA 1 ; get switches */ + 0106501, /* LIB 1 */ + 0013765, /* AND D7 ; isolate hd */ + 0005750, /* BLF,CLE,SLB */ + 0027741, /* JMP RD */ + 0005335, /* RBR,SLB,ERB ; <13>->E, set = 2883 */ + 0027717, /* JMP IS */ + 0102611, /*LP OTA CC ; do 7900 status to */ + 0103711, /* STC CC,C ; clear first seek */ + 0102310, /* SFS DC */ + 0027711, /* JMP *-1 */ + 0002004, /* INA ; get next drive */ + 0053765, /* CPA D7 ; all cleared? */ + 0002001, /* RSS */ + 0027707, /* JMP LP */ + 0067761, /*IS LDB SEEKC ; get seek comnd */ + 0106610, /* OTB DC ; issue cyl addr (0) */ + 0103710, /* STC DC,C ; to dch */ + 0106611, /* OTB CC ; seek cmd */ + 0103711, /* STC CC,C ; to cch */ + 0102310, /* SFS DC ; addr wd ok? */ + 0027724, /* JMP *-1 ; no, wait */ + 0006400, /* CLB */ + 0102501, /* LIA 1 ; get switches */ + 0002051, /* SEZ,SLA,RSS ; subchan = 1 or ISS */ + 0047770, /* ADB BIT9 ; head 2 */ + 0106610, /* OTB DC ; head/sector */ + 0103710, /* STC DC,C ; to dch */ + 0102311, /* SFS CC ; seek done? */ + 0027734, /* JMP *-1 ; no, wait */ + 0063731, /* LDA ISSRD ; get read read */ + 0002341, /* SEZ,CCE,RSS ; iss disc? */ + 0001100, /* ARS ; no, make 7900 read */ + 0067776, /*RD LDB DMACW ; DMA control */ + 0106606, /* OTB 6 */ + 0067762, /* LDB ADDR1 ; memory addr */ + 0077741, /* STB RD ; make non re-executable */ + 0106602, /* OTB 2 */ + 0102702, /* STC 2 ; flip DMA ctrl */ + 0067764, /* LDB COUNT ; word count */ + 0106602, /* OTB 2 */ + 0002041, /* SEZ,RSS */ + 0027766, /* JMP NW */ + 0102611, /* OTA CC ; to cch */ + 0103710, /* STC DC,C ; start dch */ + 0103706, /* STC 6,C ; start DMA */ + 0103711, /* STC CC,C ; start cch */ + 0037773, /* ISZ SK */ + 0027773, /* JMP SK */ + 0030000, /*SEEKC 030000 */ + 0102011, /*ADDR1 102011 */ + 0102055, /*ADDR2 102055 */ + 0164000, /*COUNT -6144. */ + 0000007, /*D7 7 */ + 0106710, /*NW CLC DC ; set 'next wd is cmd' flag */ + 0001720, /* ALF,ALF ; move to head number loc */ + 0001000, /*BIT9 ALS */ + 0103610, /* OTA DC,C ; output cold load cmd */ + 0103706, /* STC 6,C ; start DMA */ + 0102310, /* SFS DC ; done? */ + 0027773, /* JMP *-1 ; no, wait */ + 0117763, /*XT JSB ADDR2,I ; start program */ + 0120010, /*DMACW 120000+DC */ + 0000000 /* -ST */ + }; + +t_stat dqc_boot (int32 unitno, DEVICE *dptr) +{ +const int32 dev = dqd_dib.select_code; /* data chan select code */ + +if (unitno != 0) /* boot supported on drive unit 0 only */ + return SCPE_NOFNC; /* report "Command not allowed" if attempted */ + +if (ibl_copy (dq_rom, dev, IBL_OPT, /* copy the boot ROM to memory and configure */ + IBL_DQ | IBL_SET_SC (dev))) /* the S register accordingly */ + return SCPE_IERR; /* return an internal error if the copy failed */ +else + return SCPE_OK; +} diff --git a/HP2100/hp2100_dr.c b/HP2100/hp2100_dr.c index e98ea460..9ac83407 100644 --- a/HP2100/hp2100_dr.c +++ b/HP2100/hp2100_dr.c @@ -1,751 +1,831 @@ -/* hp2100_dr.c: HP 2100 12606B/12610B fixed head disk/drum simulator - - Copyright (c) 1993-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - DR 12606B 2770/2771 fixed head disk - 12610B 2773/2774/2775 drum - - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 24-Dec-14 JDB Added casts for explicit downward conversions - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - 28-Mar-11 JDB Tidied up signal handling - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 09-Jul-08 JDB Revised drc_boot to use ibl_copy - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 07-Oct-04 JDB Fixed enable/disable from either device - Fixed sector return in status word - Provided protected tracks and "Writing Enabled" status bit - Fixed DMA last word write, incomplete sector fill value - Added "parity error" status return on writes for 12606 - Added track origin test for 12606 - Added SCP test for 12606 - Fixed 12610 SFC operation - Added "Sector Flag" status bit - Added "Read Inhibit" status bit for 12606 - Fixed current-sector determination - Added PROTECTED, UNPROTECTED, TRACKPROT modifiers - 26-Aug-04 RMS Fixed CLC to stop operation (from Dave Bryan) - 26-Apr-04 RMS Fixed SFS x,C and SFC x,C - Revised boot rom to use IBL algorithm - Implemented DMA SRQ (follows FLG) - 27-Jul-03 RMS Fixed drum sizes - Fixed variable capacity interaction with SAVE/RESTORE - 10-Nov-02 RMS Added BOOT command - - References: - - 12606B Disc Memory Interface Kit Operating and Service Manual - (12606-90012, Mar-1970) - - 12610B Drum Memory Interface Kit Operating and Service Manual - (12610-9001, Feb-1970) - - - These head-per-track devices are buffered in memory, to minimize overhead. - - The drum data channel does not have a command flip-flop. Its control - flip-flop is not wired into the interrupt chain; accordingly, the - simulator uses command rather than control for the data channel. Its - flag does not respond to SFS, SFC, or STF. - - The drum control channel does not have any of the traditional flip-flops. - - The 12606 interface implements two diagnostic tests. An SFS CC instruction - will skip if the disk has passed the track origin (sector 0) since the last - CLF CC instruction, and an SFC CC instruction will skip if the Sector Clock - Phase (SCP) flip-flop is clear, indicating that the current sector is - accessible. The 12610 interface does not support these tests; the SKF signal - is not driven, so neither SFC CC nor SFS CC will skip. - - The interface implements a track protect mechanism via a switch and a set of - on-card diodes. The switch sets the protected/unprotected status, and the - particular diodes installed indicate the range of tracks (a power of 2) that - are read-only in the protected mode. - - Somewhat unusually, writing to a protected track completes normally, but the - data isn't actually written, as the write current is inhibited. There is no - "failure" status indication. Instead, a program must note the lack of - "Writing Enabled" status before the write is attempted. - - Specifications (2770/2771): - - 90 sectors per logical track - - 45 sectors per revolution - - 64 words per sector - - 2880 words per revolution - - 3450 RPM = 17.4 ms/revolution - - data timing = 6.0 us/word, 375 us/sector - - inst timing = 4 inst/word, 11520 inst/revolution - - Specifications 2773/2774/2775: - - 32 sectors per logical track - - 32 sectors per revolution - - 64 words per sector - - 2048 words per revolution - - 3450 RPM = 17.4 ms/revolution - - data timing = 8.5 us/word, 550 us/sector - - inst timing = 6 inst/word, 12288 inst/revolution -*/ - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include - -/* Constants */ - -#define DR_NUMWD 64 /* words/sector */ -#define DR_FNUMSC 90 /* fhd sec/track */ -#define DR_DNUMSC 32 /* drum sec/track */ -#define DR_NUMSC ((drc_unit.flags & UNIT_DRUM)? DR_DNUMSC: DR_FNUMSC) -#define DR_SIZE (512 * DR_DNUMSC * DR_NUMWD) /* initial size */ -#define DR_FTIME 4 /* fhd per-word time */ -#define DR_DTIME 6 /* drum per-word time */ -#define DR_OVRHEAD 5 /* overhead words at track start */ -#define UNIT_V_PROT (UNIT_V_UF + 0) /* track protect */ -#define UNIT_V_SZ (UNIT_V_UF + 1) /* disk vs drum */ -#define UNIT_M_SZ 017 /* size */ -#define UNIT_PROT (1 << UNIT_V_PROT) -#define UNIT_SZ (UNIT_M_SZ << UNIT_V_SZ) -#define UNIT_DRUM (1 << UNIT_V_SZ) /* low order bit */ -#define SZ_180K 000 /* disks */ -#define SZ_360K 002 -#define SZ_720K 004 -#define SZ_1024K 001 /* drums: default size */ -#define SZ_1536K 003 -#define SZ_384K 005 -#define SZ_512K 007 -#define SZ_640K 011 -#define SZ_768K 013 -#define SZ_896K 015 -#define DR_GETSZ(x) (((x) >> UNIT_V_SZ) & UNIT_M_SZ) - -/* Command word */ - -#define CW_WR 0100000 /* write vs read */ -#define CW_V_FTRK 7 /* fhd track */ -#define CW_M_FTRK 0177 -#define CW_V_DTRK 5 /* drum track */ -#define CW_M_DTRK 01777 -#define MAX_TRK (((drc_unit.flags & UNIT_DRUM)? CW_M_DTRK: CW_M_FTRK) + 1) -#define CW_GETTRK(x) ((drc_unit.flags & UNIT_DRUM)? \ - (((x) >> CW_V_DTRK) & CW_M_DTRK): \ - (((x) >> CW_V_FTRK) & CW_M_FTRK)) -#define CW_PUTTRK(x) ((drc_unit.flags & UNIT_DRUM)? \ - (((x) & CW_M_DTRK) << CW_V_DTRK): \ - (((x) & CW_M_FTRK) << CW_V_FTRK)) -#define CW_V_FSEC 0 /* fhd sector */ -#define CW_M_FSEC 0177 -#define CW_V_DSEC 0 /* drum sector */ -#define CW_M_DSEC 037 -#define CW_GETSEC(x) ((drc_unit.flags & UNIT_DRUM)? \ - (((x) >> CW_V_DSEC) & CW_M_DSEC): \ - (((x) >> CW_V_FSEC) & CW_M_FSEC)) -#define CW_PUTSEC(x) ((drc_unit.flags & UNIT_DRUM)? \ - (((x) & CW_M_DSEC) << CW_V_DSEC): \ - (((x) & CW_M_FSEC) << CW_V_FSEC)) - -/* Status register, ^ = dynamic */ - -#define DRS_V_NS 8 /* ^next sector */ -#define DRS_M_NS 0177 -#define DRS_SEC 0100000 /* ^sector flag */ -#define DRS_RDY 0000200 /* ^ready */ -#define DRS_RIF 0000100 /* ^read inhibit */ -#define DRS_SAC 0000040 /* sector coincidence */ -#define DRS_ABO 0000010 /* abort */ -#define DRS_WEN 0000004 /* ^write enabled */ -#define DRS_PER 0000002 /* parity error */ -#define DRS_BSY 0000001 /* ^busy */ - -#define CALC_SCP(x) (((int32) fmod ((x) / (double) dr_time, \ - (double) (DR_NUMWD))) >= (DR_NUMWD - 3)) - -int32 drc_cw = 0; /* fnc, addr */ -int32 drc_sta = 0; /* status */ -int32 drc_run = 0; /* run flip-flop */ - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - } drd = { CLEAR, CLEAR }; - -int32 drd_ibuf = 0; /* input buffer */ -int32 drd_obuf = 0; /* output buffer */ -int32 drd_ptr = 0; /* sector pointer */ -int32 drc_pcount = 1; /* number of prot tracks */ -int32 dr_stopioe = 1; /* stop on error */ -int32 dr_time = DR_DTIME; /* time per word */ - -static int32 sz_tab[16] = { - 184320, 1048576, 368640, 1572864, 737280, 393216, 0, 524288, - 0, 655360, 0, 786432, 0, 917504, 0, 0 }; - -IOHANDLER drdio; -IOHANDLER drcio; - -t_stat drc_svc (UNIT *uptr); -t_stat drc_reset (DEVICE *dptr); -t_stat drc_attach (UNIT *uptr, char *cptr); -t_stat drc_boot (int32 unitno, DEVICE *dptr); -int32 dr_incda (int32 trk, int32 sec, int32 ptr); -int32 dr_seccntr (double simtime); -t_stat dr_set_prot (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat dr_show_prot (FILE *st, UNIT *uptr, int32 val, void *desc); -t_stat dr_set_size (UNIT *uptr, int32 val, char *cptr, void *desc); - -DEVICE drd_dev, drc_dev; - -/* DRD data structures - - drd_dev device descriptor - drd_unit unit descriptor - drd_reg register list -*/ - -DIB dr_dib[] = { - { &drdio, DRD }, - { &drcio, DRC } - }; - -#define drd_dib dr_dib[0] -#define drc_dib dr_dib[1] - -UNIT drd_unit[] = { - { UDATA (NULL, 0, 0) }, - { UDATA (NULL, UNIT_DIS, 0) } - }; - -#define TMR_ORG 0 /* origin timer */ -#define TMR_INH 1 /* inhibit timer */ - -REG drd_reg[] = { - { ORDATA (IBUF, drd_ibuf, 16) }, - { ORDATA (OBUF, drd_obuf, 16) }, - { FLDATA (CTL, drd.control, 0) }, - { FLDATA (FLG, drd.flag, 0) }, - { ORDATA (BPTR, drd_ptr, 6) }, - { ORDATA (SC, drd_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, drd_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB drd_mod[] = { - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &drd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &drd_dev }, - { 0 } - }; - -DEVICE drd_dev = { - "DRD", drd_unit, drd_reg, drd_mod, - 2, 0, 0, 0, 0, 0, - NULL, NULL, &drc_reset, - NULL, NULL, NULL, - &drd_dib, DEV_DISABLE - }; - -/* DRC data structures - - drc_dev device descriptor - drc_unit unit descriptor - drc_mod unit modifiers - drc_reg register list -*/ - -UNIT drc_unit = { - UDATA (&drc_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+ - UNIT_MUSTBUF+UNIT_DRUM+UNIT_BINK, DR_SIZE) - }; - -REG drc_reg[] = { - { DRDATA (PCNT, drc_pcount, 10), REG_HIDDEN | PV_LEFT }, - { ORDATA (CW, drc_cw, 16) }, - { ORDATA (STA, drc_sta, 16) }, - { FLDATA (RUN, drc_run, 0) }, - { DRDATA (TIME, dr_time, 24), REG_NZ + PV_LEFT }, - { FLDATA (STOP_IOE, dr_stopioe, 0) }, - { ORDATA (SC, drc_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, drc_dib.select_code, 6), REG_HRO }, - { DRDATA (CAPAC, drc_unit.capac, 24), REG_HRO }, - { NULL } - }; - -MTAB drc_mod[] = { - { UNIT_DRUM, 0, "disk", NULL, NULL }, - { UNIT_DRUM, UNIT_DRUM, "drum", NULL, NULL }, - { UNIT_SZ, (SZ_180K << UNIT_V_SZ), NULL, "180K", &dr_set_size }, - { UNIT_SZ, (SZ_360K << UNIT_V_SZ), NULL, "360K", &dr_set_size }, - { UNIT_SZ, (SZ_720K << UNIT_V_SZ), NULL, "720K", &dr_set_size }, - { UNIT_SZ, (SZ_384K << UNIT_V_SZ), NULL, "384K", &dr_set_size }, - { UNIT_SZ, (SZ_512K << UNIT_V_SZ), NULL, "512K", &dr_set_size }, - { UNIT_SZ, (SZ_640K << UNIT_V_SZ), NULL, "640K", &dr_set_size }, - { UNIT_SZ, (SZ_768K << UNIT_V_SZ), NULL, "768K", &dr_set_size }, - { UNIT_SZ, (SZ_896K << UNIT_V_SZ), NULL, "896K", &dr_set_size }, - { UNIT_SZ, (SZ_1024K << UNIT_V_SZ), NULL, "1024K", &dr_set_size }, - { UNIT_SZ, (SZ_1536K << UNIT_V_SZ), NULL, "1536K", &dr_set_size }, - { UNIT_PROT, UNIT_PROT, "protected", "PROTECTED", NULL }, - { UNIT_PROT, 0, "unprotected", "UNPROTECTED", NULL }, - { MTAB_XTD | MTAB_VDV, 0, "TRACKPROT", "TRACKPROT", - &dr_set_prot, &dr_show_prot, NULL }, - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &drd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &drd_dev }, - { 0 } - }; - -DEVICE drc_dev = { - "DRC", &drc_unit, drc_reg, drc_mod, - 1, 8, 21, 1, 8, 16, - NULL, NULL, &drc_reset, - &drc_boot, &drc_attach, NULL, - &drc_dib, DEV_DISABLE - }; - - -/* Data channel I/O signal handler. - - The data channel card does not follow the usual interface I/O configuration. - PRL is always asserted, the card does not drive IRQ, FLG, or SKF and does not - respond to IAK. SRQ is driven by the output of the flag flip-flop, which - obeys CLF only. There is no flag buffer. The control flip-flop obeys STC - and CLC. Clearing control clears the flag flip-flop, and setting control - sets the flag flip-flop if the interface is configured for writing. On the - 12606B, POPIO and CRS clear the track address register. - - Implementation notes: - - 1. In response to CRS, the 12606B data channel clears only the track address - register; the command channel clears the sector address register and the - direction flip-flop. Under simulation, all three form the control word, - and as CRS is sent to all devices, we simply clear the control word here. -*/ - -uint32 drdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -int32 t; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - drd.flag = CLEAR; - break; - - - case ioENF: /* enable flag */ - drd.flag = SET; - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, drd_ibuf); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - drd_obuf = IODATA (stat_data); /* clear supplied status */ - break; - - - case ioCRS: /* control reset */ - if (!(drc_unit.flags & UNIT_DRUM)) /* 12606B? */ - drc_cw = 0; /* clear control word */ - /* fall into CLC handler */ - - case ioCLC: /* clear control flip-flop */ - drd.flag = drd.control = CLEAR; /* clear control and flag */ - - if (!drc_run) /* cancel curr op */ - sim_cancel (&drc_unit); - - drc_sta = drc_sta & ~DRS_SAC; /* clear SAC flag */ - break; - - - case ioSTC: /* set control flip-flop */ - drd.control = SET; /* set ctl */ - - if (drc_cw & CW_WR) /* writing? */ - drd.flag = SET; /* prime DMA */ - - drc_sta = 0; /* clr status */ - drd_ptr = 0; /* clear sec ptr */ - sim_cancel (&drc_unit); /* cancel curr op */ - t = CW_GETSEC (drc_cw) - dr_seccntr (sim_gtime()); - if (t <= 0) t = t + DR_NUMSC; - sim_activate (&drc_unit, t * DR_NUMWD * dr_time); - break; - - - case ioSIR: /* set interrupt request */ - setstdSRQ (drd); /* set SRQ signal */ - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Command channel I/O signal dispatcher. - - The command channel card does not follow the usual interface I/O - configuration. PRL is always asserted, the card does not drive IRQ, FLG, or - SRQ and does not respond to IAK. There are no control, flag, or flag buffer - flip-flops. CLF clears the track origin flip-flop; STF is ignored. The - 12606B drives SKF, whereas the 12610B does not. On the 12610B, SFS tests the - Track Origin flip-flop, and SFC tests the Sector Clock Phase (SCP) flip-flop. - - Implementation notes: - - 1. CRS clears the Run Flip-Flop, stopping the current operation. Under - simulation, we allow the data channel signal handler to do this, as the - same operation is invoked by CLC DC, and as CRS is sent to all devices. - - 2. The command channel cannot interrupt, so there is no SIR handler. -*/ - -uint32 drcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ - -uint16 data; -int32 sec; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - if (!(drc_unit.flags & UNIT_DRUM)) { /* disk? */ - sec = dr_seccntr (sim_gtime ()); /* current sector */ - sim_cancel (&drd_unit[TMR_ORG]); /* sched origin tmr */ - sim_activate (&drd_unit[TMR_ORG], - (DR_FNUMSC - sec) * DR_NUMWD * dr_time); - } - break; - - - case ioSFC: /* skip if flag is clear */ - if (!(drc_unit.flags & UNIT_DRUM)) /* 12606? */ - setSKF (!(CALC_SCP (sim_gtime()))); /* skip if nearing end of sector */ - break; - - - case ioSFS: /* skip if flag is set */ - if (!(drc_unit.flags & UNIT_DRUM)) /* 12606? */ - setSKF (!sim_is_active (&drd_unit[TMR_ORG])); /* skip if origin seen */ - break; - - - case ioIOI: /* I/O data input */ - data = (uint16) drc_sta; /* static bits */ - - if (!(drc_unit.flags & UNIT_PROT) || /* not protected? */ - (CW_GETTRK(drc_cw) >= drc_pcount)) /* or not in range? */ - data = data | DRS_WEN; /* set wrt enb status */ - - if (drc_unit.flags & UNIT_ATT) { /* attached? */ - data = data | (uint16) (dr_seccntr (sim_gtime()) << DRS_V_NS) | DRS_RDY; - if (sim_is_active (&drc_unit)) /* op in progress? */ - data = data | DRS_BSY; - if (CALC_SCP (sim_gtime())) /* SCP ff set? */ - data = data | DRS_SEC; /* set sector flag */ - if (sim_is_active (&drd_unit[TMR_INH]) && /* inhibit timer on? */ - !(drc_cw & CW_WR)) - data = data | DRS_RIF; /* set read inh flag */ - } - - stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - if (!(drc_unit.flags & UNIT_DRUM)) { /* disk? */ - sim_cancel (&drd_unit[TMR_INH]); /* schedule inhibit timer */ - sim_activate (&drd_unit[TMR_INH], DR_FTIME * DR_NUMWD); - } - drc_cw = IODATA (stat_data); /* get control word */ - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Unit service */ - -t_stat drc_svc (UNIT *uptr) -{ -int32 trk, sec; -uint32 da; -uint16 *bptr = (uint16 *) uptr->filebuf; - -if ((uptr->flags & UNIT_ATT) == 0) { - drc_sta = DRS_ABO; - return IOERROR (dr_stopioe, SCPE_UNATT); - } - -trk = CW_GETTRK (drc_cw); -sec = CW_GETSEC (drc_cw); -da = ((trk * DR_NUMSC) + sec) * DR_NUMWD; -drc_sta = drc_sta | DRS_SAC; -drc_run = 1; /* set run ff */ - -if (drc_cw & CW_WR) { /* write? */ - if ((da < uptr->capac) && (sec < DR_NUMSC)) { - bptr[da + drd_ptr] = (uint16) drd_obuf; - if (((uint32) (da + drd_ptr)) >= uptr->hwmark) - uptr->hwmark = da + drd_ptr + 1; - } - drd_ptr = dr_incda (trk, sec, drd_ptr); /* inc disk addr */ - if (drd.control) { /* dch active? */ - drdio (&drd_dib, ioENF, 0); /* set SRQ */ - sim_activate (uptr, dr_time); /* sched next word */ - } - else { /* done */ - if (drd_ptr) /* need to fill? */ - for ( ; drd_ptr < DR_NUMWD; drd_ptr++) - bptr[da + drd_ptr] = (uint16) drd_obuf; /* fill with last word */ - if (!(drc_unit.flags & UNIT_DRUM)) /* disk? */ - drc_sta = drc_sta | DRS_PER; /* parity bit sets on write */ - drc_run = 0; /* clear run ff */ - } - } /* end write */ -else { /* read */ - if (drd.control) { /* dch active? */ - if ((da >= uptr->capac) || (sec >= DR_NUMSC)) drd_ibuf = 0; - else drd_ibuf = bptr[da + drd_ptr]; - drd_ptr = dr_incda (trk, sec, drd_ptr); - drdio (&drd_dib, ioENF, 0); /* set SRQ */ - sim_activate (uptr, dr_time); /* sched next word */ - } - else drc_run = 0; /* clear run ff */ - } -return SCPE_OK; -} - -/* Increment current disk address */ - -int32 dr_incda (int32 trk, int32 sec, int32 ptr) -{ -ptr = ptr + 1; /* inc pointer */ -if (ptr >= DR_NUMWD) { /* end sector? */ - ptr = 0; /* new sector */ - sec = sec + 1; /* adv sector */ - if (sec >= DR_NUMSC) { /* end track? */ - sec = 0; /* new track */ - trk = trk + 1; /* adv track */ - if (trk >= MAX_TRK) trk = 0; /* wraps at max */ - } - drc_cw = (drc_cw & CW_WR) | CW_PUTTRK (trk) | CW_PUTSEC (sec); - } -return ptr; -} - -/* Read the sector counter - - The hardware sector counter contains the number of the next sector that will - pass under the heads (so it is one ahead of the current sector). For the - duration of the last sector of the track, the sector counter contains 90 for - the 12606 and 0 for the 12610. The sector counter resets to 0 at track - origin and increments at the start of the first sector. Therefore, the - counter value ranges from 0-90 for the 12606 and 0-31 for the 12610. The 0 - state is quite short in the 12606 and long in the 12610, relative to the - other sector counter states. - - The simulated sector counter is calculated from the simulation time, based on - the time per word and the number of words per track. -*/ - -int32 dr_seccntr (double simtime) -{ -int32 curword; - -curword = (int32) fmod (simtime / (double) dr_time, - (double) (DR_NUMWD * DR_NUMSC + DR_OVRHEAD)); -if (curword <= DR_OVRHEAD) return 0; -else return ((curword - DR_OVRHEAD) / DR_NUMWD + - ((drc_unit.flags & UNIT_DRUM)? 0: 1)); -} - -/* Reset routine */ - -t_stat drc_reset (DEVICE *dptr) -{ -DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ - -hp_enbdis_pair (dptr, /* make pair cons */ - (dptr == &drd_dev)? &drc_dev: &drd_dev); - -if (sim_switches & SWMASK ('P')) { /* power-on reset? */ - drd_ptr = 0; /* clear sector pointer */ - drc_sta = drc_cw = 0; /* clear controller state variables */ - } - -IOPRESET (dibptr); /* PRESET device (does not use PON) */ - -sim_cancel (&drc_unit); -sim_cancel (&drd_unit[TMR_ORG]); -sim_cancel (&drd_unit[TMR_INH]); - -return SCPE_OK; -} - -/* Attach routine */ - -t_stat drc_attach (UNIT *uptr, char *cptr) -{ -int32 sz = sz_tab[DR_GETSZ (uptr->flags)]; - -if (sz == 0) return SCPE_IERR; -uptr->capac = sz; -return attach_unit (uptr, cptr); -} - -/* Set protected track count */ - -t_stat dr_set_prot (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -int32 count; -t_stat status; - -if (cptr == NULL) - return SCPE_ARG; -count = (int32) get_uint (cptr, 10, 768, &status); -if (status != SCPE_OK) - return status; -else switch (count) { - case 1: - case 2: - case 4: - case 8: - case 16: - case 32: - case 64: - case 128: - drc_pcount = count; - break; - case 256: - case 512: - case 768: - if (drc_unit.flags & UNIT_DRUM) - drc_pcount = count; - else return SCPE_ARG; - break; - default: - return SCPE_ARG; - } -return SCPE_OK; -} - -/* Show protected track count */ - -t_stat dr_show_prot (FILE *st, UNIT *uptr, int32 val, void *desc) -{ -fprintf (st, "protected tracks=%d", drc_pcount); -return SCPE_OK; -} - -/* Set size routine */ - -t_stat dr_set_size (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -int32 sz; -int32 szindex; - -if (val < 0) return SCPE_IERR; -if ((sz = sz_tab[szindex = DR_GETSZ (val)]) == 0) return SCPE_IERR; -if (uptr->flags & UNIT_ATT) return SCPE_ALATT; -uptr->capac = sz; -if (szindex & UNIT_DRUM) dr_time = DR_DTIME; /* drum */ -else { - dr_time = DR_FTIME; /* disk */ - if (drc_pcount > 128) drc_pcount = 128; /* max prot track count */ - } -return SCPE_OK; -} - -/* Fixed head disk/drum bootstrap routine (disc subset of disc/paper tape loader) */ - -#define BOOT_START 060 - -static const BOOT_ROM dr_rom = { /* padded to start at x7760 */ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, - 0020010, /*DMA 20000+DC */ - 0000000, /* 0 */ - 0107700, /* CLC 0,C */ - 0063756, /* LDA DMA ; DMA ctrl */ - 0102606, /* OTA 6 */ - 0002700, /* CLA,CCE */ - 0102611, /* OTA CC ; trk = sec = 0 */ - 0001500, /* ERA ; A = 100000 */ - 0102602, /* OTA 2 ; DMA in, addr */ - 0063777, /* LDA M64 */ - 0102702, /* STC 2 */ - 0102602, /* OTA 2 ; DMA wc = -64 */ - 0103706, /* STC 6,C ; start DMA */ - 0067776, /* LDB JSF ; get JMP . */ - 0074077, /* STB 77 ; in base page */ - 0102710, /* STC DC ; start disc */ - 0024077, /*JSF JMP 77 ; go wait */ - 0177700 /*M64 -100 */ - }; - -t_stat drc_boot (int32 unitno, DEVICE *dptr) -{ -const int32 dev = drd_dib.select_code; /* data chan select code */ - -if (unitno != 0) /* boot supported on drive unit 0 only */ - return SCPE_NOFNC; /* report "Command not allowed" if attempted */ - -if (ibl_copy (dr_rom, dev, IBL_S_NOCLR, IBL_S_NOSET)) /* copy the boot ROM to memory and configure */ - return SCPE_IERR; /* return an internal error if the copy failed */ - -WritePW (PC + IBL_DPC, dr_rom [IBL_DPC]); /* restore overwritten word */ -WritePW (PC + IBL_END, dr_rom [IBL_END]); /* restore overwritten word */ -PC = PC + BOOT_START; /* correct starting address */ - -return SCPE_OK; -} +/* hp2100_dr.c: HP 2100 12606B/12610B fixed head disk/drum simulator + + Copyright (c) 1993-2016, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + DR 12606B 2770/2771 fixed head disk + 12610B 2773/2774/2775 drum + + 10-Nov-16 JDB Modified the drc_boot routine to use the BBDL + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 13-May-16 JDB Modified for revised SCP API function parameter types + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 24-Dec-14 JDB Added casts for explicit downward conversions + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + 28-Mar-11 JDB Tidied up signal handling + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 09-Jul-08 JDB Revised drc_boot to use ibl_copy + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 07-Oct-04 JDB Fixed enable/disable from either device + Fixed sector return in status word + Provided protected tracks and "Writing Enabled" status bit + Fixed DMA last word write, incomplete sector fill value + Added "parity error" status return on writes for 12606 + Added track origin test for 12606 + Added SCP test for 12606 + Fixed 12610 SFC operation + Added "Sector Flag" status bit + Added "Read Inhibit" status bit for 12606 + Fixed current-sector determination + Added PROTECTED, UNPROTECTED, TRACKPROT modifiers + 26-Aug-04 RMS Fixed CLC to stop operation (from Dave Bryan) + 26-Apr-04 RMS Fixed SFS x,C and SFC x,C + Revised boot rom to use IBL algorithm + Implemented DMA SRQ (follows FLG) + 27-Jul-03 RMS Fixed drum sizes + Fixed variable capacity interaction with SAVE/RESTORE + 10-Nov-02 RMS Added BOOT command + + References: + - 12606B Disc Memory Interface Kit Operating and Service Manual + (12606-90012, Mar-1970) + - 12610B Drum Memory Interface Kit Operating and Service Manual + (12610-9001, Feb-1970) + + + These head-per-track devices are buffered in memory, to minimize overhead. + + The drum data channel does not have a command flip-flop. Its control + flip-flop is not wired into the interrupt chain; accordingly, the + simulator uses command rather than control for the data channel. Its + flag does not respond to SFS, SFC, or STF. + + The drum control channel does not have any of the traditional flip-flops. + + The 12606 interface implements two diagnostic tests. An SFS CC instruction + will skip if the disk has passed the track origin (sector 0) since the last + CLF CC instruction, and an SFC CC instruction will skip if the Sector Clock + Phase (SCP) flip-flop is clear, indicating that the current sector is + accessible. The 12610 interface does not support these tests; the SKF signal + is not driven, so neither SFC CC nor SFS CC will skip. + + The interface implements a track protect mechanism via a switch and a set of + on-card diodes. The switch sets the protected/unprotected status, and the + particular diodes installed indicate the range of tracks (a power of 2) that + are read-only in the protected mode. + + Somewhat unusually, writing to a protected track completes normally, but the + data isn't actually written, as the write current is inhibited. There is no + "failure" status indication. Instead, a program must note the lack of + "Writing Enabled" status before the write is attempted. + + Specifications (2770/2771): + - 90 sectors per logical track + - 45 sectors per revolution + - 64 words per sector + - 2880 words per revolution + - 3450 RPM = 17.4 ms/revolution + - data timing = 6.0 us/word, 375 us/sector + - inst timing = 4 inst/word, 11520 inst/revolution + + Specifications 2773/2774/2775: + - 32 sectors per logical track + - 32 sectors per revolution + - 64 words per sector + - 2048 words per revolution + - 3450 RPM = 17.4 ms/revolution + - data timing = 8.5 us/word, 550 us/sector + - inst timing = 6 inst/word, 12288 inst/revolution +*/ + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include + +/* Constants */ + +#define DR_NUMWD 64 /* words/sector */ +#define DR_FNUMSC 90 /* fhd sec/track */ +#define DR_DNUMSC 32 /* drum sec/track */ +#define DR_NUMSC ((drc_unit.flags & UNIT_DRUM)? DR_DNUMSC: DR_FNUMSC) +#define DR_SIZE (512 * DR_DNUMSC * DR_NUMWD) /* initial size */ +#define DR_FTIME 4 /* fhd per-word time */ +#define DR_DTIME 6 /* drum per-word time */ +#define DR_OVRHEAD 5 /* overhead words at track start */ +#define UNIT_V_PROT (UNIT_V_UF + 0) /* track protect */ +#define UNIT_V_SZ (UNIT_V_UF + 1) /* disk vs drum */ +#define UNIT_M_SZ 017 /* size */ +#define UNIT_PROT (1 << UNIT_V_PROT) +#define UNIT_SZ (UNIT_M_SZ << UNIT_V_SZ) +#define UNIT_DRUM (1 << UNIT_V_SZ) /* low order bit */ +#define SZ_180K 000 /* disks */ +#define SZ_360K 002 +#define SZ_720K 004 +#define SZ_1024K 001 /* drums: default size */ +#define SZ_1536K 003 +#define SZ_384K 005 +#define SZ_512K 007 +#define SZ_640K 011 +#define SZ_768K 013 +#define SZ_896K 015 +#define DR_GETSZ(x) (((x) >> UNIT_V_SZ) & UNIT_M_SZ) + +/* Command word */ + +#define CW_WR 0100000 /* write vs read */ +#define CW_V_FTRK 7 /* fhd track */ +#define CW_M_FTRK 0177 +#define CW_V_DTRK 5 /* drum track */ +#define CW_M_DTRK 01777 +#define MAX_TRK (((drc_unit.flags & UNIT_DRUM)? CW_M_DTRK: CW_M_FTRK) + 1) +#define CW_GETTRK(x) ((drc_unit.flags & UNIT_DRUM)? \ + (((x) >> CW_V_DTRK) & CW_M_DTRK): \ + (((x) >> CW_V_FTRK) & CW_M_FTRK)) +#define CW_PUTTRK(x) ((drc_unit.flags & UNIT_DRUM)? \ + (((x) & CW_M_DTRK) << CW_V_DTRK): \ + (((x) & CW_M_FTRK) << CW_V_FTRK)) +#define CW_V_FSEC 0 /* fhd sector */ +#define CW_M_FSEC 0177 +#define CW_V_DSEC 0 /* drum sector */ +#define CW_M_DSEC 037 +#define CW_GETSEC(x) ((drc_unit.flags & UNIT_DRUM)? \ + (((x) >> CW_V_DSEC) & CW_M_DSEC): \ + (((x) >> CW_V_FSEC) & CW_M_FSEC)) +#define CW_PUTSEC(x) ((drc_unit.flags & UNIT_DRUM)? \ + (((x) & CW_M_DSEC) << CW_V_DSEC): \ + (((x) & CW_M_FSEC) << CW_V_FSEC)) + +/* Status register, ^ = dynamic */ + +#define DRS_V_NS 8 /* ^next sector */ +#define DRS_M_NS 0177 +#define DRS_SEC 0100000 /* ^sector flag */ +#define DRS_RDY 0000200 /* ^ready */ +#define DRS_RIF 0000100 /* ^read inhibit */ +#define DRS_SAC 0000040 /* sector coincidence */ +#define DRS_ABO 0000010 /* abort */ +#define DRS_WEN 0000004 /* ^write enabled */ +#define DRS_PER 0000002 /* parity error */ +#define DRS_BSY 0000001 /* ^busy */ + +#define CALC_SCP(x) (((int32) fmod ((x) / (double) dr_time, \ + (double) (DR_NUMWD))) >= (DR_NUMWD - 3)) + +int32 drc_cw = 0; /* fnc, addr */ +int32 drc_sta = 0; /* status */ +int32 drc_run = 0; /* run flip-flop */ + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + } drd = { CLEAR, CLEAR }; + +int32 drd_ibuf = 0; /* input buffer */ +int32 drd_obuf = 0; /* output buffer */ +int32 drd_ptr = 0; /* sector pointer */ +int32 drc_pcount = 1; /* number of prot tracks */ +int32 dr_stopioe = 1; /* stop on error */ +int32 dr_time = DR_DTIME; /* time per word */ + +static int32 sz_tab[16] = { + 184320, 1048576, 368640, 1572864, 737280, 393216, 0, 524288, + 0, 655360, 0, 786432, 0, 917504, 0, 0 }; + +IOHANDLER drdio; +IOHANDLER drcio; + +t_stat drc_svc (UNIT *uptr); +t_stat drc_reset (DEVICE *dptr); +t_stat drc_attach (UNIT *uptr, CONST char *cptr); +t_stat drc_boot (int32 unitno, DEVICE *dptr); +int32 dr_incda (int32 trk, int32 sec, int32 ptr); +int32 dr_seccntr (double simtime); +t_stat dr_set_prot (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat dr_show_prot (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat dr_set_size (UNIT *uptr, int32 val, CONST char *cptr, void *desc); + +DEVICE drd_dev, drc_dev; + +/* DRD data structures + + drd_dev device descriptor + drd_unit unit descriptor + drd_reg register list +*/ + +DIB dr_dib[] = { + { &drdio, DRD }, + { &drcio, DRC } + }; + +#define drd_dib dr_dib[0] +#define drc_dib dr_dib[1] + +UNIT drd_unit[] = { + { UDATA (NULL, 0, 0) }, + { UDATA (NULL, UNIT_DIS, 0) } + }; + +#define TMR_ORG 0 /* origin timer */ +#define TMR_INH 1 /* inhibit timer */ + +REG drd_reg[] = { + { ORDATA (IBUF, drd_ibuf, 16) }, + { ORDATA (OBUF, drd_obuf, 16) }, + { FLDATA (CTL, drd.control, 0) }, + { FLDATA (FLG, drd.flag, 0) }, + { ORDATA (BPTR, drd_ptr, 6) }, + { ORDATA (SC, drd_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, drd_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB drd_mod[] = { + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &drd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &drd_dev }, + { 0 } + }; + +DEVICE drd_dev = { + "DRD", drd_unit, drd_reg, drd_mod, + 2, 0, 0, 0, 0, 0, + NULL, NULL, &drc_reset, + NULL, NULL, NULL, + &drd_dib, DEV_DISABLE + }; + +/* DRC data structures + + drc_dev device descriptor + drc_unit unit descriptor + drc_mod unit modifiers + drc_reg register list +*/ + +UNIT drc_unit = { + UDATA (&drc_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BUFABLE+ + UNIT_MUSTBUF+UNIT_DRUM+UNIT_BINK, DR_SIZE) + }; + +REG drc_reg[] = { + { DRDATA (PCNT, drc_pcount, 10), REG_HIDDEN | PV_LEFT }, + { ORDATA (CW, drc_cw, 16) }, + { ORDATA (STA, drc_sta, 16) }, + { FLDATA (RUN, drc_run, 0) }, + { DRDATA (TIME, dr_time, 24), REG_NZ + PV_LEFT }, + { FLDATA (STOP_IOE, dr_stopioe, 0) }, + { ORDATA (SC, drc_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, drc_dib.select_code, 6), REG_HRO }, + { DRDATA (CAPAC, drc_unit.capac, 24), REG_HRO }, + { NULL } + }; + +MTAB drc_mod[] = { + { UNIT_DRUM, 0, "disk", NULL, NULL }, + { UNIT_DRUM, UNIT_DRUM, "drum", NULL, NULL }, + { UNIT_SZ, (SZ_180K << UNIT_V_SZ), NULL, "180K", &dr_set_size }, + { UNIT_SZ, (SZ_360K << UNIT_V_SZ), NULL, "360K", &dr_set_size }, + { UNIT_SZ, (SZ_720K << UNIT_V_SZ), NULL, "720K", &dr_set_size }, + { UNIT_SZ, (SZ_384K << UNIT_V_SZ), NULL, "384K", &dr_set_size }, + { UNIT_SZ, (SZ_512K << UNIT_V_SZ), NULL, "512K", &dr_set_size }, + { UNIT_SZ, (SZ_640K << UNIT_V_SZ), NULL, "640K", &dr_set_size }, + { UNIT_SZ, (SZ_768K << UNIT_V_SZ), NULL, "768K", &dr_set_size }, + { UNIT_SZ, (SZ_896K << UNIT_V_SZ), NULL, "896K", &dr_set_size }, + { UNIT_SZ, (SZ_1024K << UNIT_V_SZ), NULL, "1024K", &dr_set_size }, + { UNIT_SZ, (SZ_1536K << UNIT_V_SZ), NULL, "1536K", &dr_set_size }, + { UNIT_PROT, UNIT_PROT, "protected", "PROTECTED", NULL }, + { UNIT_PROT, 0, "unprotected", "UNPROTECTED", NULL }, + { MTAB_XTD | MTAB_VDV, 0, "TRACKPROT", "TRACKPROT", + &dr_set_prot, &dr_show_prot, NULL }, + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &drd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &drd_dev }, + { 0 } + }; + +DEVICE drc_dev = { + "DRC", &drc_unit, drc_reg, drc_mod, + 1, 8, 21, 1, 8, 16, + NULL, NULL, &drc_reset, + &drc_boot, &drc_attach, NULL, + &drc_dib, DEV_DISABLE + }; + + +/* Data channel I/O signal handler. + + The data channel card does not follow the usual interface I/O configuration. + PRL is always asserted, the card does not drive IRQ, FLG, or SKF and does not + respond to IAK. SRQ is driven by the output of the flag flip-flop, which + obeys CLF only. There is no flag buffer. The control flip-flop obeys STC + and CLC. Clearing control clears the flag flip-flop, and setting control + sets the flag flip-flop if the interface is configured for writing. On the + 12606B, POPIO and CRS clear the track address register. + + Implementation notes: + + 1. In response to CRS, the 12606B data channel clears only the track address + register; the command channel clears the sector address register and the + direction flip-flop. Under simulation, all three form the control word, + and as CRS is sent to all devices, we simply clear the control word here. +*/ + +uint32 drdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +int32 t; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + drd.flag = CLEAR; + break; + + + case ioENF: /* enable flag */ + drd.flag = SET; + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, drd_ibuf); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + drd_obuf = IODATA (stat_data); /* clear supplied status */ + break; + + + case ioCRS: /* control reset */ + if (!(drc_unit.flags & UNIT_DRUM)) /* 12606B? */ + drc_cw = 0; /* clear control word */ + /* fall into CLC handler */ + + case ioCLC: /* clear control flip-flop */ + drd.flag = drd.control = CLEAR; /* clear control and flag */ + + if (!drc_run) /* cancel curr op */ + sim_cancel (&drc_unit); + + drc_sta = drc_sta & ~DRS_SAC; /* clear SAC flag */ + break; + + + case ioSTC: /* set control flip-flop */ + drd.control = SET; /* set ctl */ + + if (drc_cw & CW_WR) /* writing? */ + drd.flag = SET; /* prime DMA */ + + drc_sta = 0; /* clr status */ + drd_ptr = 0; /* clear sec ptr */ + sim_cancel (&drc_unit); /* cancel curr op */ + t = CW_GETSEC (drc_cw) - dr_seccntr (sim_gtime()); + if (t <= 0) t = t + DR_NUMSC; + sim_activate (&drc_unit, t * DR_NUMWD * dr_time); + break; + + + case ioSIR: /* set interrupt request */ + setstdSRQ (drd); /* set SRQ signal */ + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Command channel I/O signal dispatcher. + + The command channel card does not follow the usual interface I/O + configuration. PRL is always asserted, the card does not drive IRQ, FLG, or + SRQ and does not respond to IAK. There are no control, flag, or flag buffer + flip-flops. CLF clears the track origin flip-flop; STF is ignored. The + 12606B drives SKF, whereas the 12610B does not. On the 12610B, SFS tests the + Track Origin flip-flop, and SFC tests the Sector Clock Phase (SCP) flip-flop. + + Implementation notes: + + 1. CRS clears the Run Flip-Flop, stopping the current operation. Under + simulation, we allow the data channel signal handler to do this, as the + same operation is invoked by CLC DC, and as CRS is sent to all devices. + + 2. The command channel cannot interrupt, so there is no SIR handler. +*/ + +uint32 drcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ + +uint16 data; +int32 sec; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + if (!(drc_unit.flags & UNIT_DRUM)) { /* disk? */ + sec = dr_seccntr (sim_gtime ()); /* current sector */ + sim_cancel (&drd_unit[TMR_ORG]); /* sched origin tmr */ + sim_activate (&drd_unit[TMR_ORG], + (DR_FNUMSC - sec) * DR_NUMWD * dr_time); + } + break; + + + case ioSFC: /* skip if flag is clear */ + if (!(drc_unit.flags & UNIT_DRUM)) /* 12606? */ + setSKF (!(CALC_SCP (sim_gtime()))); /* skip if nearing end of sector */ + break; + + + case ioSFS: /* skip if flag is set */ + if (!(drc_unit.flags & UNIT_DRUM)) /* 12606? */ + setSKF (!sim_is_active (&drd_unit[TMR_ORG])); /* skip if origin seen */ + break; + + + case ioIOI: /* I/O data input */ + data = (uint16) drc_sta; /* static bits */ + + if (!(drc_unit.flags & UNIT_PROT) || /* not protected? */ + (CW_GETTRK(drc_cw) >= drc_pcount)) /* or not in range? */ + data = data | DRS_WEN; /* set wrt enb status */ + + if (drc_unit.flags & UNIT_ATT) { /* attached? */ + data = data | (uint16) (dr_seccntr (sim_gtime()) << DRS_V_NS) | DRS_RDY; + if (sim_is_active (&drc_unit)) /* op in progress? */ + data = data | DRS_BSY; + if (CALC_SCP (sim_gtime())) /* SCP ff set? */ + data = data | DRS_SEC; /* set sector flag */ + if (sim_is_active (&drd_unit[TMR_INH]) && /* inhibit timer on? */ + !(drc_cw & CW_WR)) + data = data | DRS_RIF; /* set read inh flag */ + } + + stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + if (!(drc_unit.flags & UNIT_DRUM)) { /* disk? */ + sim_cancel (&drd_unit[TMR_INH]); /* schedule inhibit timer */ + sim_activate (&drd_unit[TMR_INH], DR_FTIME * DR_NUMWD); + } + drc_cw = IODATA (stat_data); /* get control word */ + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Unit service */ + +t_stat drc_svc (UNIT *uptr) +{ +int32 trk, sec; +uint32 da; +uint16 *bptr = (uint16 *) uptr->filebuf; + +if ((uptr->flags & UNIT_ATT) == 0) { + drc_sta = DRS_ABO; + return IOERROR (dr_stopioe, SCPE_UNATT); + } + +trk = CW_GETTRK (drc_cw); +sec = CW_GETSEC (drc_cw); +da = ((trk * DR_NUMSC) + sec) * DR_NUMWD; +drc_sta = drc_sta | DRS_SAC; +drc_run = 1; /* set run ff */ + +if (drc_cw & CW_WR) { /* write? */ + if ((da < uptr->capac) && (sec < DR_NUMSC)) { + bptr[da + drd_ptr] = (uint16) drd_obuf; + if (((uint32) (da + drd_ptr)) >= uptr->hwmark) + uptr->hwmark = da + drd_ptr + 1; + } + drd_ptr = dr_incda (trk, sec, drd_ptr); /* inc disk addr */ + if (drd.control) { /* dch active? */ + drdio (&drd_dib, ioENF, 0); /* set SRQ */ + sim_activate (uptr, dr_time); /* sched next word */ + } + else { /* done */ + if (drd_ptr) /* need to fill? */ + for ( ; drd_ptr < DR_NUMWD; drd_ptr++) + bptr[da + drd_ptr] = (uint16) drd_obuf; /* fill with last word */ + if (!(drc_unit.flags & UNIT_DRUM)) /* disk? */ + drc_sta = drc_sta | DRS_PER; /* parity bit sets on write */ + drc_run = 0; /* clear run ff */ + } + } /* end write */ +else { /* read */ + if (drd.control) { /* dch active? */ + if ((da >= uptr->capac) || (sec >= DR_NUMSC)) drd_ibuf = 0; + else drd_ibuf = bptr[da + drd_ptr]; + drd_ptr = dr_incda (trk, sec, drd_ptr); + drdio (&drd_dib, ioENF, 0); /* set SRQ */ + sim_activate (uptr, dr_time); /* sched next word */ + } + else drc_run = 0; /* clear run ff */ + } +return SCPE_OK; +} + +/* Increment current disk address */ + +int32 dr_incda (int32 trk, int32 sec, int32 ptr) +{ +ptr = ptr + 1; /* inc pointer */ +if (ptr >= DR_NUMWD) { /* end sector? */ + ptr = 0; /* new sector */ + sec = sec + 1; /* adv sector */ + if (sec >= DR_NUMSC) { /* end track? */ + sec = 0; /* new track */ + trk = trk + 1; /* adv track */ + if (trk >= MAX_TRK) trk = 0; /* wraps at max */ + } + drc_cw = (drc_cw & CW_WR) | CW_PUTTRK (trk) | CW_PUTSEC (sec); + } +return ptr; +} + +/* Read the sector counter + + The hardware sector counter contains the number of the next sector that will + pass under the heads (so it is one ahead of the current sector). For the + duration of the last sector of the track, the sector counter contains 90 for + the 12606 and 0 for the 12610. The sector counter resets to 0 at track + origin and increments at the start of the first sector. Therefore, the + counter value ranges from 0-90 for the 12606 and 0-31 for the 12610. The 0 + state is quite short in the 12606 and long in the 12610, relative to the + other sector counter states. + + The simulated sector counter is calculated from the simulation time, based on + the time per word and the number of words per track. +*/ + +int32 dr_seccntr (double simtime) +{ +int32 curword; + +curword = (int32) fmod (simtime / (double) dr_time, + (double) (DR_NUMWD * DR_NUMSC + DR_OVRHEAD)); +if (curword <= DR_OVRHEAD) return 0; +else return ((curword - DR_OVRHEAD) / DR_NUMWD + + ((drc_unit.flags & UNIT_DRUM)? 0: 1)); +} + +/* Reset routine */ + +t_stat drc_reset (DEVICE *dptr) +{ +DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ + +hp_enbdis_pair (dptr, /* make pair cons */ + (dptr == &drd_dev)? &drc_dev: &drd_dev); + +if (sim_switches & SWMASK ('P')) { /* power-on reset? */ + drd_ptr = 0; /* clear sector pointer */ + drc_sta = drc_cw = 0; /* clear controller state variables */ + } + +IOPRESET (dibptr); /* PRESET device (does not use PON) */ + +sim_cancel (&drc_unit); +sim_cancel (&drd_unit[TMR_ORG]); +sim_cancel (&drd_unit[TMR_INH]); + +return SCPE_OK; +} + +/* Attach routine */ + +t_stat drc_attach (UNIT *uptr, CONST char *cptr) +{ +int32 sz = sz_tab[DR_GETSZ (uptr->flags)]; + +if (sz == 0) return SCPE_IERR; +uptr->capac = sz; +return attach_unit (uptr, cptr); +} + +/* Set protected track count */ + +t_stat dr_set_prot (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +int32 count; +t_stat status; + +if (cptr == NULL) + return SCPE_ARG; +count = (int32) get_uint (cptr, 10, 768, &status); +if (status != SCPE_OK) + return status; +else switch (count) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + case 128: + drc_pcount = count; + break; + case 256: + case 512: + case 768: + if (drc_unit.flags & UNIT_DRUM) + drc_pcount = count; + else return SCPE_ARG; + break; + default: + return SCPE_ARG; + } +return SCPE_OK; +} + +/* Show protected track count */ + +t_stat dr_show_prot (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +fprintf (st, "protected tracks=%d", drc_pcount); +return SCPE_OK; +} + +/* Set size routine */ + +t_stat dr_set_size (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +int32 sz; +int32 szindex; + +if (val < 0) return SCPE_IERR; +if ((sz = sz_tab[szindex = DR_GETSZ (val)]) == 0) return SCPE_IERR; +if (uptr->flags & UNIT_ATT) return SCPE_ALATT; +uptr->capac = sz; +if (szindex & UNIT_DRUM) dr_time = DR_DTIME; /* drum */ +else { + dr_time = DR_FTIME; /* disk */ + if (drc_pcount > 128) drc_pcount = 128; /* max prot track count */ + } +return SCPE_OK; +} + + +/* Basic Binary Disc Loader. + + The Basic Binary Disc Loader (BBDL) contains two programs. The program + starting at address x7700 loads absolute paper tapes into memory. The + program starting at address x7760 loads a disc-resident bootstrap from the + 277x fixed-head disc/drum. Entering a BOOT DRC command loads the BBDL into + memory and executes the disc portion starting at x7760. The bootstrap issues + a CLC 0,C to clear the disc track and sector address registers and then sets + up a 64-word read from track 0 sector 0 to memory locations 0-77 octal. It + then stores a JMP * instruction in location 77, starts the read, and jumps to + location 77. The JMP * causes the CPU to loop until the last word read from + the disc overlays location 77 which, typically, would be a JMP instruction to + the start of the disc-resident bootstrap. + + In hardware, the BBDL was hand-configured for the disc and paper tape reader + select codes when it was installed on a given system. Under simulation, we + treat it as a standard HP 1000 loader, even though it is not structured that + way, and so the ibl_copy mechanism used to load and configure it must be + augmented to account for the differences. + + + Implementaion notes: + + 1. The full BBDL is loaded into memory, even though only the disc portion + will be used. + + 2. For compatibility with the ibl_copy routine, the loader has been changed + from the standard HP version. The device I/O instructions are modified + to address locations 10 and 11. +*/ + +static const BOOT_ROM dr_rom = { + 0107700, /* ST2 CLC 0,C START OF PAPER TAPE LOADER */ + 0002401, /* CLA,RSS */ + 0063726, /* CONT2 LDA CM21 */ + 0006700, /* CLB,CCE */ + 0017742, /* JSB READ2 */ + 0007306, /* LEDR2 CMB,CCE,INB,SZB */ + 0027713, /* JMP RECL2 */ + 0002006, /* EOTC2 INA,SZA */ + 0027703, /* JMP CONT2+1 */ + 0102077, /* HLT 77B */ + 0027700, /* JMP ST2 */ + 0077754, /* RECL2 STB CNT2 */ + 0017742, /* JSB READ2 */ + 0017742, /* JSB READ2 */ + 0074000, /* STB A */ + 0077757, /* STB ADR11 */ + 0067757, /* SUCID LDB ADR11 */ + 0047755, /* ADB MAXAD */ + 0002040, /* SEZ */ + 0027740, /* JMP RESCU */ + 0017742, /* LOAD2 JSB READ2 */ + 0040001, /* ADA B */ + 0177757, /* CM21 STB ADR11,I */ + 0037757, /* ISZ ADR11 */ + 0000040, /* CLE */ + 0037754, /* ISZ CNT2 */ + 0027720, /* JMP SUCID */ + 0017742, /* JSB READ2 */ + 0054000, /* CPB A */ + 0027702, /* JMP CONT2 */ + 0102011, /* HLT 11B */ + 0027700, /* JMP ST2 */ + 0102055, /* RESCU HLT 55B */ + 0027700, /* JMP ST2 */ + 0000000, /* READ2 NOP */ + 0006600, /* CLB,CME */ + 0103710, /* RED2 STC PR,C */ + 0102310, /* SFS PR */ + 0027745, /* JMP *-1 */ + 0107410, /* MIB PR,C */ + 0002041, /* SEZ,RSS */ + 0127742, /* JMP READ2,I */ + 0005767, /* BLF,CLE,BLF */ + 0027744, /* JMP RED2 */ + 0000000, /* CNT2 NOP */ + 0000000, /* MAXAD NOP */ + 0020000, /* CWORD ABS 20000B+DC */ + 0000000, /* ADR11 NOP */ + + 0107700, /* DLDR CLC 0,C START OF FIXED DISC LOADER */ + 0063756, /* LDA CWORD */ + 0102606, /* OTA 6 */ + 0002700, /* CLA,CCE */ + 0102611, /* OTA CC */ + 0001500, /* ERA */ + 0102602, /* OTA 2 */ + 0063777, /* LDA WRDCT */ + 0102702, /* STC 2 */ + 0102602, /* OTA 2 */ + 0103706, /* STC 6,C */ + 0102710, /* STC DC */ + 0067776, /* LDB JMP77 */ + 0074077, /* STB 77B */ + 0024077, /* JMP77 JMP 77B */ + 0177700 /* WRDCT OCT -100 */ + }; + +#define BBDL_MAX_ADDR 0000055 /* ROM index of the maximum address word */ +#define BBDL_DMA_CNTL 0000056 /* ROM index of the DMA control word */ +#define BBDL_DISC_START 0000060 /* ROM index of the disc loader */ + +t_stat drc_boot (int32 unitno, DEVICE *dptr) +{ +const int32 dev = drd_dib.select_code; /* data chan select code */ + +if (unitno != 0) /* boot supported on drive unit 0 only */ + return SCPE_NOFNC; /* report "Command not allowed" if attempted */ + +if (ibl_copy (dr_rom, dev, IBL_S_NOCLR, IBL_S_NOSET)) /* copy the boot ROM to memory and configure */ + return SCPE_IERR; /* return an internal error if the copy failed */ + +WritePW (PR + BBDL_MAX_ADDR, ReadPW (PR + IBL_END)); /* move the maximum address word */ +WritePW (PR + BBDL_DMA_CNTL, dr_rom [BBDL_DMA_CNTL] + dev); /* set up the DMA control word */ + +WritePW (PR + IBL_DPC, dr_rom [IBL_DPC]); /* restore the overwritten word */ +WritePW (PR + IBL_END, dr_rom [IBL_END]); /* restore the overwritten word */ + +PR = PR + BBDL_DISC_START; /* select the starting address */ + +return SCPE_OK; +} diff --git a/HP2100/hp2100_ds.c b/HP2100/hp2100_ds.c index 2a897efd..3325bb51 100644 --- a/HP2100/hp2100_ds.c +++ b/HP2100/hp2100_ds.c @@ -1,1480 +1,1482 @@ -/* hp2100_ds.c: HP 13037D/13175D disc controller/interface simulator - - Copyright (c) 2004-2012, Robert M. Supnik - Copyright (c) 2012-2014 J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the names of the authors shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the authors. - - DS 13037D/13175D disc controller/interface - - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 24-Dec-14 JDB Use T_ADDR_FMT with t_addr values for 64-bit compatibility - 18-Mar-13 JDB Fixed poll_drives definition to match declaration - 24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash - 29-Mar-12 JDB Rewritten to use the MAC/ICD disc controller library - ioIOO now notifies controller service of parameter output - 14-Feb-12 JDB Corrected SRQ generation and FIFO under/overrun detection - Corrected Clear command to conform to the hardware - Fixed Request Status to return Unit Unavailable if illegal - Seek and Cold Load Read now Seek Check if seek in progress - Remodeled command wait for seek completion - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - 21-Jun-11 JDB Corrected status returns for disabled drive, auto-seek - beyond drive limits, Request Sector Address and Wakeup - with invalid or offline unit - Address verification reenabled if auto-seek during - Read Without Verify - 28-Mar-11 JDB Tidied up signal handling - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 31-Dec-07 JDB Corrected and verified ioCRS action - 20-Dec-07 JDB Corrected DPTR register definition from FLDATA to DRDATA - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 03-Aug-06 JDB Fixed REQUEST STATUS command to clear status-1 - Removed redundant attached test in "ds_detach" - 18-Mar-05 RMS Added attached test to detach routine - 01-Mar-05 JDB Added SET UNLOAD/LOAD - - References: - - 13037 Disc Controller Technical Information Package (13037-90902, Aug-1980) - - 7925D Disc Drive Service Manual (07925-90913, Apr-1984) - - HP 12992 Loader ROMs Installation Manual (12992-90001, Apr-1986) - - DVR32 RTE Moving Head Driver source (92084-18711, Revision 5000) - - - The 13037D multiple-access (MAC) disc controller supports from one to eight - HP 7905 (15 MB), 7906 (20MB), 7920 (50 MB), and 7925 (120 MB) disc drives - accessed by one to eight CPUs. The controller hardware consists of a 16-bit - microprogrammed processor constructed from 74S181 bit slices operating at 5 - MHz, a device controller providing the interconnections to the drives and CPU - interfaces, and an error correction controller that enables the correction of - up to 32-bit error bursts. 1024 words of 24-bit firmware are stored in ROM. - - The 13175D disc interface is used to connect the HP 1000 CPU to the 13037 - device controller. In a multiple-CPU system, one interface is strapped to - reset the controller when the CPU's front panel PRESET button is pressed. - - This module simulates a 13037D connected to a single 13175D interface. From - one to eight drives may be connected, and drive types may be freely - intermixed. A unit that is enabled but not attached appears to be a - connected drive that does not have a disc pack in place. A unit that is - disabled appears to be disconnected. - - This simulator is an adaptation of the code originally written by Bob Supnik. - The functions of the controller have been separated from the functions of the - interface, with the former placed into a separate disc controller library. - This allows the library to support other CPU interfaces, such as the 12821A - HP-IB disc interface, that use substantially different communication - protocols. The library functions implement the controller command set for - the drive units. The interface functions handle the transfer of commands and - data to and from the CPU. - - In hardware, the controller runs continuously in one of three states: in the - Poll Loop (idle state), in the Command Wait Loop (wait state), or in command - execution (busy state). In simulation, the controller is run only when a - command is executing or when a transition into or out of the two loops might - occur. Internally, the controller handles these transitions: - - - when a command other than End terminates (busy => wait) - - when the End command terminates (busy => idle) - - when a command timeout occurs (wait => idle) - - when a parameter timeout occurs (busy => idle) - - when a seek completes (if idle and interrupts are enabled, idle => wait) - - The interface must call the controller library to handle these transitions: - - - when a command is received from the CPU (idle or wait => busy) - - when interrupts are enabled (if idle and drive Attention, idle => wait) - - In addition, each transition to the wait state must check for a pending - command, and each transition to the idle state must check for both a pending - command and a drive with Attention status asserted. - - - Implementation notes: - - 1. Although the 13175D has a 16-word FIFO, the "full" level is set at 5 - entries in hardware to avoid a long DCPC preemption time at the start of - a disc write as the FIFO fills. -*/ - - - -#include "hp2100_defs.h" -#include "hp_disclib.h" - - - -/* Program constants */ - -#define DS_DRIVES (DL_MAXDRIVE + 1) /* number of disc drive units */ -#define DS_UNITS (DS_DRIVES + DL_AUXUNITS) /* total number of units */ - -#define ds_cntlr ds_unit [DL_MAXDRIVE + 1] /* controller unit alias */ - -#define FIFO_SIZE 16 /* FIFO depth */ - -#define FIFO_EMPTY (ds.fifo_count == 0) /* FIFO empty test */ -#define FIFO_STOP (ds.fifo_count >= 5) /* FIFO stop filling test */ -#define FIFO_FULL (ds.fifo_count == FIFO_SIZE) /* FIFO full test */ - -#define PRESET_ENABLE TRUE /* Preset Jumper (W4) is enabled */ - - -/* Debug flags */ - -#define DEB_CPU (1 << 0) /* words received from and sent to the CPU */ -#define DEB_CMDS (1 << 1) /* interface commands received from the CPU */ -#define DEB_BUF (1 << 2) /* data read from and written to the card FIFO */ -#define DEB_RWSC (1 << 3) /* device read/write/status/control commands */ -#define DEB_SERV (1 << 4) /* unit service scheduling calls */ - - - -/* Per-card state variables */ - -typedef struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - FLIP_FLOP srq; /* SRQ flip-flop */ - FLIP_FLOP edt; /* EDT flip-flop */ - FLIP_FLOP cmfol; /* command follows flip-flop */ - FLIP_FLOP cmrdy; /* command ready flip-flop */ - uint16 fifo [FIFO_SIZE]; /* FIFO buffer */ - uint32 fifo_count; /* FIFO occupancy counter */ - REG *fifo_reg; /* FIFO register pointer */ - } CARD_STATE; - - -/* MAC disc state variables */ - -static UNIT ds_unit [DS_UNITS]; /* unit array */ - -static CARD_STATE ds; /* card state */ - -static uint16 buffer [DL_BUFSIZE]; /* command/status/sector buffer */ - -static CNTLR_VARS mac_cntlr = /* MAC controller */ - { CNTLR_INIT (MAC, buffer, &ds_cntlr) }; - - - -/* MAC disc global VM routines */ - -IOHANDLER ds_io; -t_stat ds_service_drive (UNIT *uptr); -t_stat ds_service_controller (UNIT *uptr); -t_stat ds_service_timer (UNIT *uptr); -t_stat ds_reset (DEVICE *dptr); -t_stat ds_attach (UNIT *uptr, char *cptr); -t_stat ds_detach (UNIT *uptr); -t_stat ds_boot (int32 unitno, DEVICE *dptr); - -/* MAC disc global SCP routines */ - -t_stat ds_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc); - -/* MAC disc local utility routines */ - -static void start_command (void); -static void poll_interface (void); -static void poll_drives (void); -static void fifo_load (uint16 data); -static uint16 fifo_unload (void); -static void fifo_clear (void); -static t_stat activate_unit (UNIT *uptr); - - - -/* MAC disc VM data structures. - - ds_dib DS device information block - ds_unit DS unit list - ds_reg DS register list - ds_mod DS modifier list - ds_deb DS debug table - ds_dev DS device descriptor - - For the drive models, the modifiers provide this SHOW behavior: - - - when detached and autosized, prints "autosize" - - when detached and not autosized, prints the model number - - when attached, prints the model number (regardless of autosizing) - - - Implementation notes: - - 1. The validation routine does not allow the model number or autosizing - option to be changed when the unit is attached. Therefore, specifying - UNIT_ATT in the mask field has no adverse effect. - - 2. The modifier DEVNO is deprecated in favor of SC but is retained for - compatibility. -*/ - - -DEVICE ds_dev; - -static DIB ds_dib = { &ds_io, DS }; - -#define UNIT_FLAGS (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD) - -static UNIT ds_unit [] = { - { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 0 */ - { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 1 */ - { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 2 */ - { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 3 */ - { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 4 */ - { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 5 */ - { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 6 */ - { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 7 */ - { UDATA (&ds_service_controller, UNIT_DIS, 0) }, /* controller unit */ - { UDATA (&ds_service_timer, UNIT_DIS, 0) } /* timer unit */ - }; - -static REG ds_reg [] = { - { FLDATA (CMFOL, ds.cmfol, 0) }, - { FLDATA (CMRDY, ds.cmrdy, 0) }, - { DRDATA (FCNT, ds.fifo_count, 5) }, - { BRDATA (FIFO, ds.fifo, 8, 16, FIFO_SIZE), REG_CIRC }, - { ORDATA (FREG, ds.fifo_reg, 32), REG_HRO }, - - { ORDATA (CNTYPE, mac_cntlr.type, 2), REG_HRO }, - { ORDATA (STATE, mac_cntlr.state, 2) }, - { ORDATA (OPCODE, mac_cntlr.opcode, 6) }, - { ORDATA (STATUS, mac_cntlr.status, 6) }, - { FLDATA (EOC, mac_cntlr.eoc, 0) }, - { FLDATA (EOD, mac_cntlr.eod, 0) }, - { ORDATA (SPDU, mac_cntlr.spd_unit, 16) }, - { ORDATA (FLMASK, mac_cntlr.file_mask, 4) }, - { ORDATA (RETRY, mac_cntlr.retry, 4), REG_HRO }, - { ORDATA (CYL, mac_cntlr.cylinder, 16) }, - { ORDATA (HEAD, mac_cntlr.head, 6) }, - { ORDATA (SECTOR, mac_cntlr.sector, 8) }, - { ORDATA (VFYCNT, mac_cntlr.verify_count, 16) }, - { ORDATA (LASPOL, mac_cntlr.poll_unit, 3) }, - { HRDATA (BUFPTR, mac_cntlr.buffer, 32), REG_HRO }, - { BRDATA (BUFFER, buffer, 8, 16, DL_BUFSIZE) }, - { DRDATA (INDEX, mac_cntlr.index, 8) }, - { DRDATA (LENGTH, mac_cntlr.length, 8) }, - { HRDATA (AUXPTR, mac_cntlr.aux, 32), REG_HRO }, - { DRDATA (STIME, mac_cntlr.seek_time, 24), PV_LEFT | REG_NZ }, - { DRDATA (ITIME, mac_cntlr.sector_time, 24), PV_LEFT | REG_NZ }, - { DRDATA (CTIME, mac_cntlr.cmd_time, 24), PV_LEFT | REG_NZ }, - { DRDATA (DTIME, mac_cntlr.data_time, 24), PV_LEFT | REG_NZ }, - { DRDATA (WTIME, mac_cntlr.wait_time, 31), PV_LEFT | REG_NZ }, - - { FLDATA (CTL, ds.control, 0) }, - { FLDATA (FLG, ds.flag, 0) }, - { FLDATA (FBF, ds.flagbuf, 0) }, - { FLDATA (SRQ, ds.srq, 0) }, - { FLDATA (EDT, ds.edt, 0) }, - - { URDATA (UCYL, ds_unit[0].CYL, 10, 10, 0, DS_UNITS, PV_LEFT) }, - { URDATA (UOP, ds_unit[0].OP, 8, 6, 0, DS_UNITS, PV_RZRO) }, - { URDATA (USTAT, ds_unit[0].STAT, 2, 8, 0, DS_UNITS, PV_RZRO) }, - { URDATA (UPHASE, ds_unit[0].PHASE, 8, 3, 0, DS_UNITS, PV_RZRO) }, - { URDATA (UPOS, ds_unit[0].pos, 8, T_ADDR_W, 0, DS_UNITS, PV_LEFT) }, - { URDATA (UWAIT, ds_unit[0].wait, 8, 32, 0, DS_UNITS, PV_LEFT) }, - - { ORDATA (SC, ds_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, ds_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -static MTAB ds_mod [] = { -/* mask match pstring mstring valid disp desc */ - { UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", &ds_load_unload, NULL, NULL }, - { UNIT_UNLOAD, 0, "heads loaded", "LOADED", &ds_load_unload, NULL, NULL }, - - { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL, NULL, NULL }, - { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL, NULL, NULL }, - - { UNIT_FMT, UNIT_FMT, "format enabled", "FORMAT", NULL, NULL, NULL }, - { UNIT_FMT, 0, "format disabled", "NOFORMAT", NULL, NULL, NULL }, - -/* mask match pstring mstring valid disp desc */ - { UNIT_AUTO | UNIT_ATT, UNIT_AUTO, "autosize", "AUTOSIZE", &dl_set_model, NULL, NULL }, - { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7905, "7905", "7905", &dl_set_model, NULL, NULL }, - { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7906, "7906", "7906", &dl_set_model, NULL, NULL }, - { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7920, "7920", "7920", &dl_set_model, NULL, NULL }, - { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7925, "7925", "7925", &dl_set_model, NULL, NULL }, - { UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7905, "7905", NULL, NULL, NULL, NULL }, - { UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7906, "7906", NULL, NULL, NULL, NULL }, - { UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7920, "7920", NULL, NULL, NULL, NULL }, - { UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7925, "7925", NULL, NULL, NULL, NULL }, - - { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &ds_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &ds_dev }, - { 0 } - }; - -static DEBTAB ds_deb [] = { - { "CPU", DEB_CPU }, - { "CMDS", DEB_CMDS }, - { "BUF", DEB_BUF }, - { "RWSC", DEB_RWSC }, - { "SERV", DEB_SERV }, - { NULL, 0 } - }; - -DEVICE ds_dev = { - "DS", /* device name */ - ds_unit, /* unit array */ - ds_reg, /* register array */ - ds_mod, /* modifier array */ - DS_UNITS, /* number of units */ - 8, /* address radix */ - 27, /* address width = 128 MB */ - 1, /* address increment */ - 8, /* data radix */ - 16, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &ds_reset, /* reset routine */ - &ds_boot, /* boot routine */ - &ds_attach, /* attach routine */ - &ds_detach, /* detach routine */ - &ds_dib, /* device information block */ - DEV_DEBUG | DEV_DISABLE, /* device flags */ - 0, /* debug control flags */ - ds_deb, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL /* logical device name */ - }; - - - -/* MAC disc global VM routines */ - - -/* I/O signal handler. - - The 13175D disc interface data path consists of an input multiplexer/latch - and a 16-word FIFO buffer. The FIFO source may be either the CPU's I/O - input bus or the controller's interface data bus. The output of the FIFO may - be enabled either to the CPU's I/O output bus or the interface data bus. - - The control path consists of the usual control, flag buffer, flag, and SRQ - flip-flops, although flag and SRQ are decoupled to allow the full DCPC - transfer rate through the FIFO (driving SRQ from the flag limits transfers to - every other cycle). SRQ is based on the FIFO level: if data or room in the - FIFO is available, SRQ is set to initiate a transfer. The flag is only used - to signal an interrupt at the end of a command. - - One unusual aspect is that SFC and SFS test different things, rather than - complementary states of the same thing. SFC tests the controller busy state, - and SFS tests the flag flip-flop. - - In addition, the card contains end-of-data-transfer, command-follows, and - command-ready flip-flops. EDT is set when the DCPC EDT signal is asserted - and is used in conjunction with the FIFO level to assert the end-of-data - signal to the controller. The command-follows flip-flop is set by a CLC to - indicate that the next data word output from the CPU is a disc command. The - command-ready flip-flop is set when a command is received to schedule an - interface poll. - - - Implementation notes: - - 1. In hardware, SRQ is enabled only when the controller is reading or - writing the disc (IFIN or IFOUT functions are asserted) and set when the - FIFO is not empty (read) or not full (write). In simulation, SRQ is set - by the unit service read/write data phase transfers and cleared in the - IOI and IOO signal handlers when the FIFO is empty (read) or full - (write). - - 2. The DCPC EDT signal cannot set the controller's end-of-data flag directly - because a write EOD must occur only after the FIFO has been drained. - - 3. Polling the interface or drives must be deferred to the end of I/O signal - handling. If they are performed in the IOO/STC handlers themselves, an - associated CLF might clear the flag that was set by the poll. - - 4. Executing a CLC sets the controller's end-of-data flag, which will abort - a read or write data transfer in progress. Parameter transfers are not - affected. If a command is received when a parameter is expected, the - word is interpreted as data, even though the command-ready flip-flop is - set. The controller firmware only checks DTRDY for a parameter transfer, - and DTRDY is asserted whenever the FIFO is not empty. - - 5. The hardware Interface Function and Flag Buses are not implemented - explicitly. Instead, interface functions and signals are inferred by the - interface from the current command operation and phase. -*/ - -uint32 ds_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -static const char * const output_state [] = { "Data", "Command" }; -const char * const hold_or_clear = (signal_set & ioCLF ? ",C" : ""); - -uint16 data; -t_stat status; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ -t_bool command_issued = FALSE; -t_bool interrupt_enabled = FALSE; - -while (working_set) { - signal = IONEXT (working_set); /* isolate the next signal */ - - switch (signal) { /* dispatch the I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - ds.flag = CLEAR; /* clear the flag */ - ds.flagbuf = CLEAR; /* and flag buffer */ - - if (DEBUG_PRI (ds_dev, DEB_CMDS)) - fputs (">>DS cmds: [CLF] Flag cleared\n", sim_deb); - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - ds.flag = SET; /* set the flag */ - ds.flagbuf = SET; /* and flag buffer */ - - if (DEBUG_PRI (ds_dev, DEB_CMDS)) - fputs (">>DS cmds: [STF] Flag set\n", sim_deb); - break; - - - case ioSFC: /* skip if flag is clear */ - setSKF (mac_cntlr.state != cntlr_busy); /* skip if the controller is not busy */ - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (ds); /* assert SKF if the flag is set */ - break; - - - case ioIOI: /* I/O data input */ - data = fifo_unload (); /* unload the next word from the FIFO */ - stat_data = IORETURN (SCPE_OK, data); /* merge in the return status */ - - if (DEBUG_PRI (ds_dev, DEB_CPU)) - fprintf (sim_deb, ">>DS cpu: [LIx%s] Data = %06o\n", hold_or_clear, data); - - if (FIFO_EMPTY) { /* is the FIFO now empty? */ - if (ds.srq == SET && DEBUG_PRI (ds_dev, DEB_CMDS)) - fprintf (sim_deb, ">>DS cmds: [LIx%s] SRQ cleared\n", hold_or_clear); - - ds.srq = CLEAR; /* clear SRQ */ - - if (ds_cntlr.PHASE == data_phase) { /* is this an outbound parameter? */ - ds_cntlr.wait = mac_cntlr.data_time; /* activate the controller */ - activate_unit (&ds_cntlr); /* to acknowledge the data */ - } - } - break; - - - case ioIOO: /* I/O data output */ - data = IODATA (stat_data); /* mask to just the data word */ - - if (DEBUG_PRI (ds_dev, DEB_CPU)) - fprintf (sim_deb, ">>DS cpu: [OTx%s] %s = %06o\n", - hold_or_clear, output_state [ds.cmfol], data); - - fifo_load (data); /* load the word into the FIFO */ - - if (ds.cmfol == SET) { /* are we expecting a command? */ - ds.cmfol = CLEAR; /* clear the command follows flip-flop */ - ds.cmrdy = SET; /* set the command ready flip-flop */ - command_issued = TRUE; /* and request an interface poll */ - } - - else { /* not a command */ - if (ds_cntlr.PHASE == data_phase) { /* is this an inbound parameter? */ - ds_cntlr.wait = mac_cntlr.data_time; /* activate the controller */ - activate_unit (&ds_cntlr); /* to receive the data */ - } - - if (FIFO_STOP) { /* is the FIFO now full enough? */ - if (ds.srq == SET && DEBUG_PRI (ds_dev, DEB_CMDS)) - fprintf (sim_deb, ">>DS cmds: [OTx%s] SRQ cleared\n", hold_or_clear); - - ds.srq = CLEAR; /* clear SRQ to stop filling */ - } - } - break; - - - case ioPOPIO: /* power-on preset to I/O */ - ds.flag = SET; /* set the flag */ - ds.flagbuf = SET; /* and flag buffer */ - ds.cmrdy = CLEAR; /* clear the command ready flip-flop */ - - if (DEBUG_PRI (ds_dev, DEB_CMDS)) - fputs (">>DS cmds: [POPIO] Flag set\n", sim_deb); - break; - - - case ioCRS: /* control reset */ - if (DEBUG_PRI (ds_dev, DEB_CMDS)) - fputs (">>DS cmds: [CRS] Master reset\n", sim_deb); - - ds.control = CLEAR; /* clear the control */ - ds.cmfol = CLEAR; /* and command follows flip-flops */ - - if (PRESET_ENABLE) { /* is preset enabled for this interface? */ - fifo_clear (); /* clear the FIFO */ - - status = dl_clear_controller (&mac_cntlr, /* do a hard clear of the controller */ - ds_unit, hard_clear); - - stat_data = IORETURN (status, 0); /* return the status from the controller */ - } - break; - - - case ioCLC: /* clear control flip-flop */ - if (DEBUG_PRI (ds_dev, DEB_CMDS)) - fprintf (sim_deb, ">>DS cmds: [CLC%s] Control cleared\n", hold_or_clear); - - ds.control = CLEAR; /* clear the control */ - ds.edt = CLEAR; /* and EDT flip-flops */ - ds.cmfol = SET; /* set the command follows flip-flop */ - mac_cntlr.eod = SET; /* set the controller's EOD flag */ - - fifo_clear (); /* clear the FIFO */ - break; - - - case ioSTC: /* set control flip-flop */ - ds.control = SET; /* set the control flip-flop */ - - interrupt_enabled = TRUE; /* check for drive attention */ - - if (DEBUG_PRI (ds_dev, DEB_CMDS)) - fprintf (sim_deb, ">>DS cmds: [STC%s] Control set\n", hold_or_clear); - break; - - - case ioEDT: /* end data transfer */ - ds.edt = SET; /* set the EDT flip-flop */ - - if (DEBUG_PRI (ds_dev, DEB_CPU)) - fputs (">>DS cpu: [EDT] DCPC transfer ended\n", sim_deb); - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (ds); /* set the standard PRL signal */ - setstdIRQ (ds); /* set the standard IRQ signal */ - setSRQ (dibptr->select_code, ds.srq); /* set the SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - ds.flagbuf = CLEAR; /* clear the flag */ - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove the current signal from the set */ - } - - -if (command_issued) /* was a command received? */ - poll_interface (); /* poll the interface for the next command */ -else if (interrupt_enabled) /* were interrupts enabled? */ - poll_drives (); /* poll the drives for Attention */ - -return stat_data; -} - - -/* Service the disc drive unit. - - The unit service routine is called to execute scheduled controller commands - for the specified unit. The actions to be taken depend on the current state - of the controller and the unit. - - Generally, the controller library service routine handles all of the disc - operations except data transfer to and from the interface. Read transfers - are responsible for loading words from the sector buffer into the FIFO and - enabling SRQ. If the current sector transfer is complete, either due to EDT - assertion or buffer exhaustion, the controller is moved to the end phase to - complete or continue the read with the next sector. In either case, the unit - is rescheduled. If the FIFO overflows, the read terminates with a data - overrun error. - - Write transfers set the initial SRQ to request words from the CPU. As each - word arrives, it is unloaded from the FIFO into the sector buffer, and SRQ is - enabled. If the current sector transfer is complete, the controller is moved - to the end phase. If the FIFO underflows, the write terminates with a data - overrun error. - - The synchronous nature of the disc drive requires that data be supplied or - accepted continuously by the CPU. DCPC generally assures that this occurs, - and the FIFO allows for some latency before an overrun or underrun occurs. - - The other operation the interface must handle is seek completion. The - controller handles seek completion by setting Attention status in the drive's - status word. The interface is responsible for polling the drives if the - controller is idle and interrupts are enabled. - - - Implementation notes: - - 1. Every command except Seek, Recalibrate, and End sets the flag when the - command completes. A command completes when the controller is no longer - busy (it becomes idle for Seek, Recalibrate, and End, or it becomes - waiting for all others). Seek and Recalibrate may generate errors (e.g., - heads unloaded), in which case the flag must be set. But in these cases, - the controller state is waiting, not idle. - - However, it is insufficient simply to check that the controller has moved - to the wait state, because a seek may complete while the controller is - waiting for the next command. For example, a Seek is started on unit 0, - and the controller moves to the idle state. But before the seek - completes, another command is issued that attempts to access unit 1, - which is not ready. The command fails with a Status-2 error, and the - controller moves to the wait state. When the seek completes, the - controller is waiting with error status. We must determine whether the - seek completed successfully or not, as we must interrupt in the latter - case. - - Therefore, we determine seek completion by checking if the Attention - status was set. Attention sets only if the seek completes successfully. - - (Actually, Attention sets if a seek check occurs, but in that case, the - command terminated before the seek ever started. Also, a seek may - complete while the controller is busy, waiting, or idle.) - - 2. For debug printouts, we want to print the name of the command that has - completed when the controller returns to the idle or wait state. - Normally, we would use the controller's "opcode" field to identify the - command that completed. However, while waiting for Seek or Recalibrate - completion, "opcode" may be set to another command if that command does - not access this drive. For example, it might be set to a Read of another - unit, or a Request Status for this unit. So we can't rely on "opcode" to - report the correct name of the completed positioning command. - - However, we cannot rely on "uptr->OP" either, as that can be changed - during the course of a command. For example, Read Without Verify is - changed to Read after a track crossing. - - Instead, we have to determine whether a seek is completing. If it is, - then we report "uptr->OP"; otherwise, we report "opcode". - - 3. The initial write SRQ must set only at the transition from the start - phase to the data phase. If a write command begins with an auto-seek, - the drive service will be entered twice in the start phase (the first - entry performs the seek, and the second begins the write). In hardware, - SRQ does not assert until the write begins. - - 4. The DCPC EDT signal cannot set the controller's end-of-data flag - directly because a write EOD must only occur after the FIFO has been - drained. -*/ - -t_stat ds_service_drive (UNIT *uptr) -{ -static const char completion_message [] = ">>DS rwsc: Unit %d %s command completed\n"; -t_stat result; -t_bool seek_completion; -int32 unit; -FLIP_FLOP entry_srq = ds.srq; /* get the SRQ state on entry */ -CNTLR_PHASE entry_phase = (CNTLR_PHASE) uptr->PHASE; /* get the operation phase on entry */ -uint32 entry_status = uptr->STAT; /* get the drive status on entry */ - -result = dl_service_drive (&mac_cntlr, uptr); /* service the drive */ - -if ((CNTLR_PHASE) uptr->PHASE == data_phase) /* is the drive in the data phase? */ - switch ((CNTLR_OPCODE) uptr->OP) { /* dispatch the current operation */ - - case Read: /* read operations */ - case Read_Full_Sector: - case Read_With_Offset: - case Read_Without_Verify: - if (mac_cntlr.length == 0 || ds.edt == SET) { /* is the data phase complete? */ - mac_cntlr.eod = ds.edt; /* set EOD if DCPC is done */ - uptr->PHASE = end_phase; /* set the end phase */ - uptr->wait = mac_cntlr.cmd_time; /* and schedule the controller */ - } - - else if (FIFO_FULL) /* is the FIFO already full? */ - dl_end_command (&mac_cntlr, data_overrun); /* terminate the command with an overrun */ - - else { - fifo_load (buffer [mac_cntlr.index++]); /* load the next word into the FIFO */ - mac_cntlr.length--; /* count it */ - ds.srq = SET; /* ask DCPC to pick it up */ - ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ - uptr->wait = mac_cntlr.data_time; /* schedule the next data transfer */ - } - - break; - - - case Write: /* write operations */ - case Write_Full_Sector: - case Initialize: - if (entry_phase == start_phase) { /* is this the phase transition? */ - ds.srq = SET; /* start the DCPC transfer */ - ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ - } - - else if (FIFO_EMPTY) /* is the FIFO empty? */ - dl_end_command (&mac_cntlr, data_overrun); /* terminate the command with an underrun */ - - else { - buffer [mac_cntlr.index++] = fifo_unload (); /* unload the next word from the FIFO */ - mac_cntlr.length--; /* count it */ - - if (ds.edt == SET && FIFO_EMPTY) /* if DCPC is complete and the FIFO is empty */ - mac_cntlr.eod = SET; /* then set the end-of-data flag */ - - if (mac_cntlr.length == 0 || mac_cntlr.eod == SET) { /* is the data phase complete? */ - uptr->PHASE = end_phase; /* set the end phase */ - uptr->wait = mac_cntlr.cmd_time; /* and schedule the controller */ - } - - else { - if (ds.edt == CLEAR) { /* if DCPC is still transferring */ - ds.srq = SET; /* then request the next word */ - ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ - } - - uptr->wait = mac_cntlr.data_time; /* schedule the next data transfer */ - } - } - - break; - - - default: /* we were entered with an invalid state */ - result = SCPE_IERR; /* return an internal (programming) error */ - break; - } /* end of data phase operation dispatch */ - - -if (DEBUG_PRI (ds_dev, DEB_CMDS) && entry_srq != ds.srq) - fprintf (sim_deb, ">>DS cmds: SRQ %s\n", ds.srq == SET ? "set" : "cleared"); - - -if (uptr->wait) /* was service requested? */ - activate_unit (uptr); /* schedule the next event */ - -seek_completion = ~entry_status & uptr->STAT & DL_S2ATN; /* seek is complete when Attention sets */ - -if (mac_cntlr.state != cntlr_busy) { /* is the command complete? */ - if (mac_cntlr.state == cntlr_wait && !seek_completion) /* is it command but not seek completion? */ - ds_io (&ds_dib, ioENF, 0); /* set the data flag to interrupt the CPU */ - - poll_interface (); /* poll the interface for the next command */ - poll_drives (); /* poll the drives for Attention */ - } - - -if (DEBUG_PRI (ds_dev, DEB_RWSC)) { - unit = uptr - ds_unit; /* get the unit number */ - - if (result == SCPE_IERR) /* did an internal error occur? */ - fprintf (sim_deb, ">>DS rwsc: Unit %d %s command %s phase service not handled\n", - unit, dl_opcode_name (MAC, (CNTLR_OPCODE) uptr->OP), - dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); - - else if (seek_completion) /* if a seek has completed */ - fprintf (sim_deb, completion_message, /* report the unit command */ - unit, dl_opcode_name (MAC, (CNTLR_OPCODE) uptr->OP)); - - else if (mac_cntlr.state == cntlr_wait) /* if the controller has stopped */ - fprintf (sim_deb, completion_message, /* report the controller command */ - unit, dl_opcode_name (MAC, mac_cntlr.opcode)); - } - -return result; /* return the result of the service */ -} - - -/* Service the controller unit. - - The controller service routine is called to execute scheduled controller - commands that do not access drive units. It is also called to obtain command - parameters from the interface and to return command result values to the - interface. - - Most controller commands are handled completely in the library's service - routine, so we call that first. Commands that neither accept nor supply - parameters are complete when the library routine returns, so all we have to - do is set the interface flag if required. - - For parameter transfers in the data phase, the interface is responsible for - moving words between the sector buffer and the FIFO and setting the flag to - notify the CPU. - - - Implementation notes: - - 1. In hardware, the Read With Offset command sets the data flag after the - offset parameter has been read and the head positioner has been moved by - the indicated amount. The intent is to delay the DCPC start until the - drive is ready to supply data from the disc. - - In simulation, the flag is set as soon as the parameter is received. -*/ - -t_stat ds_service_controller (UNIT *uptr) -{ -t_stat result; -const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP; - -result = dl_service_controller (&mac_cntlr, uptr); /* service the controller */ - -switch ((CNTLR_PHASE) uptr->PHASE) { /* dispatch the current phase */ - - case start_phase: /* most controller operations */ - case end_phase: /* start and end on the same phase */ - switch (opcode) { /* dispatch the current operation */ - - case Request_Status: - case Request_Sector_Address: - case Address_Record: - case Request_Syndrome: - case Load_TIO_Register: - case Request_Disc_Address: - case End: - break; /* complete the operation without setting the flag */ - - - case Clear: - case Set_File_Mask: - case Wakeup: - ds_io (&ds_dib, ioENF, 0); /* complete the operation and set the flag */ - break; - - - default: /* we were entered with an invalid state */ - result = SCPE_IERR; /* return an internal (programming) error */ - break; - } /* end of operation dispatch */ - break; /* end of start and end phase handlers */ - - - case data_phase: - switch (opcode) { /* dispatch the current operation */ - - case Seek: /* operations that accept parameters */ - case Verify: - case Address_Record: - case Read_With_Offset: - case Load_TIO_Register: - buffer [mac_cntlr.index++] = fifo_unload (); /* unload the next word from the FIFO */ - mac_cntlr.length--; /* count it */ - - if (mac_cntlr.length) /* are there more words to transfer? */ - ds_io (&ds_dib, ioENF, 0); /* set the flag to request the next one */ - - else { /* all parameters have been received */ - uptr->PHASE = end_phase; /* set the end phase */ - - if (opcode == Read_With_Offset) /* a Read With Offset command sets the flag */ - ds_io (&ds_dib, ioENF, 0); /* to indicate that offsetting is complete */ - - start_command (); /* the command is now ready to execute */ - } - break; - - - case Request_Status: /* operations that supply parameters */ - case Request_Sector_Address: - case Request_Syndrome: - case Request_Disc_Address: - if (mac_cntlr.length) { /* are there more words to return? */ - fifo_load (buffer [mac_cntlr.index++]); /* load the next word into the FIFO */ - mac_cntlr.length--; /* count it */ - - ds_io (&ds_dib, ioENF, 0); /* set the flag to request pickup by the CPU */ - } - - else { /* all parameters have been sent */ - uptr->PHASE = end_phase; /* set the end phase */ - uptr->wait = mac_cntlr.cmd_time; /* schedule the controller */ - activate_unit (uptr); /* to complete the command */ - } - break; - - - default: /* we were entered with an invalid state */ - result = SCPE_IERR; /* return an internal (programming) error */ - break; - } /* end of operation dispatch */ - break; /* end of data phase handlers */ - } /* end of phase dispatch */ - - -if (result == SCPE_IERR && DEBUG_PRI (ds_dev, DEB_RWSC)) /* did an internal error occur? */ - fprintf (sim_deb, ">>DS rwsc: Controller %s command %s phase service not handled\n", - dl_opcode_name (MAC, opcode), dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); - - -if (mac_cntlr.state != cntlr_busy) { /* has the controller stopped? */ - poll_interface (); /* poll the interface for the next command */ - poll_drives (); /* poll the drives for Attention status */ - - if (DEBUG_PRI (ds_dev, DEB_RWSC)) - fprintf (sim_deb, ">>DS rwsc: Controller %s command completed\n", - dl_opcode_name (MAC, opcode)); - } - -return result; /* return the result of the service */ -} - - -/* Service the command wait timer unit. - - The command wait timer service routine is called if the command wait timer - expires. The library is called to reset the file mask and idle the - controller. Then the interface is polled for a command and the drives are - polled for Attention status. -*/ - -t_stat ds_service_timer (UNIT *uptr) -{ -t_stat result; - -result = dl_service_timer (&mac_cntlr, uptr); /* service the timer */ - -poll_interface (); /* poll the interface for the next command */ -poll_drives (); /* poll the drives for Attention status */ - -return result; /* return the result of the service */ -} - - -/* Reset the simulator. - - In hardware, the PON signal clears the Interface Selected flip-flop, - disconnecting the interface from the disc controller. In simulation, the - interface always remains connected to the controller, so no special action is - needed. - - - Implementation notes: - - 1. During a power-on reset, a pointer to the FIFO simulation register is - saved to allow access to the "qptr" field during FIFO loading and - unloading. This enables SCP to view the FIFO as a circular queue, so - that the bottom word of the FIFO is always displayed as FIFO[0], - regardless of where it is in the actual FIFO array. - - 2. SRQ is denied because neither IFIN nor IFOUT is asserted when the - interface is not selected. -*/ - -t_stat ds_reset (DEVICE *dptr) -{ -uint32 unit; - -if (sim_switches & SWMASK ('P')) { /* is this a power-on reset? */ - ds.fifo_reg = find_reg ("FIFO", NULL, dptr); /* find the FIFO register entry */ - - if (ds.fifo_reg == NULL) /* if it cannot be found, */ - return SCPE_IERR; /* report a programming error */ - - else { /* found it */ - ds.fifo_reg->qptr = 0; /* so reset the FIFO bottom index */ - ds.fifo_count = 0; /* and clear the FIFO */ - } - - for (unit = 0; unit < dptr->numunits; unit++) { /* loop through all of the units */ - sim_cancel (dptr->units + unit); /* cancel activation */ - dptr->units [unit].CYL = 0; /* reset the head position to cylinder 0 */ - dptr->units [unit].pos = 0; /* (irrelevant for the controller and timer) */ - } - } - -IOPRESET (&ds_dib); /* PRESET the device */ -ds.srq = CLEAR; /* clear SRQ */ - -return SCPE_OK; -} - - -/* Attach a drive unit. - - The specified file is attached to the indicated drive unit. The library - attach routine will load the heads. This will set the First Status and - Attention bits in the drive status, so we poll the drives to ensure that the - CPU is notified that the drive is now online. - - - Implementation notes: - - 1. If we are called during a RESTORE command, the drive status will not be - changed, so polling the drives will have no effect. -*/ - -t_stat ds_attach (UNIT *uptr, char *cptr) -{ -t_stat result; - -result = dl_attach (&mac_cntlr, uptr, cptr); /* attach the drive */ - -if (result == SCPE_OK) /* was the attach successful? */ - poll_drives (); /* poll the drives to notify the CPU */ - -return result; -} - - -/* Detach a drive unit. - - The specified file is detached from the indicated drive unit. The library - detach routine will unload the heads. This will set the Attention bit in the - drive status, so we poll the drives to ensure that the CPU is notified that - the drive is now offline. -*/ - -t_stat ds_detach (UNIT *uptr) -{ -t_stat result; - -result = dl_detach (&mac_cntlr, uptr); /* detach the drive */ - -if (result == SCPE_OK) /* was the detach successful? */ - poll_drives (); /* poll the drives to notify the CPU */ - -return result; -} - - -/* Boot a MAC disc drive. - - The MAC disc bootstrap program is loaded from the HP 12992B Boot Loader ROM - into memory, the I/O instructions are configured for the interface card's - select code, and the program is run to boot from the specified unit. The - loader supports booting from cylinder 0 of drive unit 0 only. Before - execution, the S register is automatically set as follows: - - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - ------ ------ ---------------------- --------- --------- - ROM # 0 1 select code reserved head - - The boot routine sets bits 15-6 of the S register to appropriate values. - Bits 5-3 and 1-0 retain their original values, so S should be set before - booting. These bits are typically set to 0, although bit 5 is set for an RTE - reconfiguration boot, and bits 1-0 may be set if booting from a head other - than 0 is desired. - - - Implementation notes: - - 1. The Loader ROMs manual indicates that bits 2-0 select the head to use, - implying that heads 0-7 are valid. However, Table 5 has entries only for - heads 0-3, and the boot loader code will malfunction if heads 4-7 are - specified. The code masks the head number to three bits but forms the - Cold Load Read command by shifting the head number six bits to the left. - As the head field in the command is only two bits wide, specifying heads - 4-7 will result in bit 2 being shifted into the opcode field, resulting - in a Recalibrate command. -*/ - - -const BOOT_ROM ds_rom = { - 0017727, /* START JSB STAT GET STATUS */ - 0002021, /* SSA,RSS IS DRIVE READY ? */ - 0027742, /* JMP DMA YES, SET UP DMA */ - 0013714, /* AND B20 NO, CHECK STATUS BITS */ - 0002002, /* SZA IS DRIVE FAULTY OR HARD DOWN ? */ - 0102030, /* HLT 30B YES, HALT 30B, "RUN" TO TRY AGAIN */ - 0027700, /* JMP START NO, TRY AGAIN FOR DISC READY */ - 0102011, /* ADDR1 OCT 102011 */ - 0102055, /* ADDR2 OCT 102055 */ - 0164000, /* CNT DEC -6144 */ - 0000007, /* D7 OCT 7 */ - 0001400, /* STCMD OCT 1400 */ - 0000020, /* B20 OCT 20 */ - 0017400, /* STMSK OCT 17400 */ - 0000000, /* NOP */ - 0000000, /* NOP */ - 0000000, /* NOP */ - 0000000, /* NOP */ - 0000000, /* NOP */ - 0000000, /* NOP */ - 0000000, /* NOP */ - 0000000, /* NOP */ - 0000000, /* NOP */ - 0000000, /* STAT NOP STATUS CHECK SUBROUTINE */ - 0107710, /* CLC DC,C SET STATUS COMMAND MODE */ - 0063713, /* LDA STCMD GET STATUS COMMAND */ - 0102610, /* OTA DC OUTPUT STATUS COMMAND */ - 0102310, /* SFS DC WAIT FOR STATUS#1 WORD */ - 0027733, /* JMP *-1 */ - 0107510, /* LIB DC,C B-REG = STATUS#1 WORD */ - 0102310, /* SFS DC WAIT FOR STATUS#2 WORD */ - 0027736, /* JMP *-1 */ - 0103510, /* LIA DC,C A-REG = STATUS#2 WORD */ - 0127727, /* JMP STAT,I RETURN */ - 0067776, /* DMA LDB DMACW GET DMA CONTROL WORD */ - 0106606, /* OTB 6 OUTPUT DMA CONTROL WORD */ - 0067707, /* LDB ADDR1 GET MEMORY ADDRESS */ - 0106702, /* CLC 2 SET MEMORY ADDRESS INPUT MODE */ - 0106602, /* OTB 2 OUTPUT MEMORY ADDRESS TO DMA */ - 0102702, /* STC 2 SET WORD COUNT INPUT MODE */ - 0067711, /* LDB CNT GET WORD COUNT */ - 0106602, /* OTB 2 OUTPUT WORD COUNT TO DMA */ - 0106710, /* CLDLD CLC DC SET COMMAND INPUT MODE */ - 0102501, /* LIA 1 LOAD SWITCH */ - 0106501, /* LIB 1 REGISTER SETTINGS */ - 0013712, /* AND D7 ISOLATE HEAD NUMBER */ - 0005750, /* BLF,CLE,SLB BIT 12=0? */ - 0027762, /* JMP *+3 NO,MANUAL BOOT */ - 0002002, /* SZA YES,RPL BOOT. HEAD#=0? */ - 0001000, /* ALS NO,HEAD#1, MAKE HEAD#=2 */ - 0001720, /* ALF,ALS FORM COLD LOAD */ - 0001000, /* ALS COMMAND WORD */ - 0103706, /* STC 6,C ACTIVATE DMA */ - 0103610, /* OTA DC,C OUTPUT COLD LOAD COMMAND */ - 0102310, /* SFS DC IS COLD LOAD COMPLETED ? */ - 0027766, /* JMP *-1 NO, WAIT */ - 0017727, /* JSB STAT YES, GET STATUS */ - 0060001, /* LDA 1 */ - 0013715, /* AND STMSK A-REG = STATUS BITS OF STATUS#1 WD */ - 0002002, /* SZA IS TRANSFER OK ? */ - 0027700, /* JMP START NO,TRY AGAIN */ - 0117710, /* EXIT JSB ADDR2,I YES, EXEC LOADED PROGRAM _@ 2055B */ - 0000010, /* DMACW ABS DC */ - 0170100, /* ABS -START */ - }; - -t_stat ds_boot (int32 unitno, DEVICE *dptr) -{ -if (unitno != 0) /* boot supported on drive unit 0 only */ - return SCPE_NOFNC; /* report "Command not allowed" if attempted */ - -if (ibl_copy (ds_rom, ds_dib.select_code, /* copy the boot ROM to memory and configure */ - IBL_OPT | IBL_DS_HEAD, /* the S register accordingly */ - IBL_DS | IBL_MAN | IBL_SET_SC (ds_dib.select_code))) - return SCPE_IERR; /* return an internal error if the copy failed */ -else - return SCPE_OK; -} - - - -/* MAC disc global SCP routines */ - - -/* Load or unload the drive heads. - - The SCP command SET DSn UNLOADED simulates setting the hardware RUN/STOP - switch to STOP. The heads are unloaded, and the drive is spun down. - - The SET DSn LOADED command simulates setting the switch to RUN. The drive is - spun up, and the heads are loaded. - - The library handles command validation and setting the appropriate drive unit - status. -*/ - -t_stat ds_load_unload (UNIT *uptr, int32 value, char *cptr, void *desc) -{ -const t_bool load = (value != UNIT_UNLOAD); /* true if the heads are loading */ - -return dl_load_unload (&mac_cntlr, uptr, load); /* load or unload the heads */ -} - - - -/* MAC disc local utility routines */ - - -/* Start a command. - - The previously prepared command is executed by calling the corresponding - library routine. On entry, the controller's opcode field contains the - command to start, and the buffer contains the command word in element 0 and - the parameters required by the command, if any, beginning in element 1. - - If the command started, the returned pointer will point at the unit to - activate (if that unit's "wait" field is non-zero). If the returned pointer - is NULL, the command failed to start, and the controller status has been set - to indicate the reason. The interface flag is set to notify the CPU of the - failure. - - - Implementation notes: - - 1. If a command that accesses the drive is attempted on a drive currently - seeking, the returned pointer will be valid, but the unit's "wait" time - will be zero. The unit must not be activated (as it already is active). - When the seek completes, the command will be executed automatically. - - If a Seek or Cold Load Read command is attempted on a drive currently - seeking, seek completion will occur normally, but Seek Check status will - be set. - - 2. For debug printouts, we want to print the name of the command (Seek or - Recalibrate) in progress when a new command is started. However, when - the library routine returns, the unit operation and controller opcode - have been changed to reflect the new command. Therefore, we must record - the operation in progress before calling the library. - - The problem is in determining which unit's operation code to record. We - cannot blindly use the unit field from the new command, as recorded in - the controller, as preparation has ensured only that the target unit - number is legal but not necessarily valid. Therefore, we must validate - the unit number before accessing the unit's operation code. - - If the unit number is invalid, the command will not start, but the - compiler does not know this. Therefore, we must ensure that the saved - operation code is initialized, or a "variable used uninitialized" warning - will occur. -*/ - -static void start_command (void) -{ -int32 unit, time; -UNIT *uptr; -CNTLR_OPCODE drive_command; - -unit = GET_S1UNIT (mac_cntlr.spd_unit); /* get the (prepared) unit from the command */ - -if (unit <= DL_MAXDRIVE) /* is the unit number valid? */ - drive_command = (CNTLR_OPCODE) ds_unit [unit].OP; /* get the opcode from the unit that will be used */ -else /* the unit is invalid, so the command will not start */ - drive_command = End; /* but the compiler doesn't know this! */ - -uptr = dl_start_command (&mac_cntlr, ds_unit, DL_MAXDRIVE); /* ask the controller to start the command */ - -if (uptr) { /* did the command start? */ - time = uptr->wait; /* save the activation time */ - - if (time) /* was the unit scheduled? */ - activate_unit (uptr); /* activate it (and clear the "wait" field) */ - - if (DEBUG_PRI (ds_dev, DEB_RWSC)) { - unit = uptr - ds_unit; /* get the unit number */ - - if (time == 0) /* was the unit busy? */ - fprintf (sim_deb, ">>DS rwsc: Unit %d %s in progress\n", - unit, dl_opcode_name (MAC, drive_command)); - - fputs (">>DS rwsc: ", sim_deb); - - if (unit > DL_MAXDRIVE) - fputs ("Controller ", sim_deb); - else - fprintf (sim_deb, "Unit %d position %" T_ADDR_FMT "d ", unit, uptr->pos); - - fprintf (sim_deb, "%s command initiated\n", - dl_opcode_name (MAC, mac_cntlr.opcode)); - } - } - -else /* the command failed to start */ - ds_io (&ds_dib, ioENF, 0); /* so set the flag to notify the CPU */ - -return; -} - - -/* Poll the interface for a new command. - - If a new command is available, and the controller is not busy, prepare the - command for execution. If preparation succeeded, and the command needs - parameters before executing, set the flag to request the first one from the - CPU. If no parameters are needed, the command is ready to execute. - - If preparation failed, set the flag to notify the CPU. The controller - status contains the reason for the failure. -*/ - -static void poll_interface (void) -{ -if (ds.cmrdy == SET && mac_cntlr.state != cntlr_busy) { /* are the interface and controller ready? */ - buffer [0] = fifo_unload (); /* unload the command into the buffer */ - - if (dl_prepare_command (&mac_cntlr, ds_unit, DL_MAXDRIVE)) { /* prepare the command; did it succeed? */ - if (mac_cntlr.length) /* does the command require parameters? */ - ds_io (&ds_dib, ioENF, 0); /* set the flag to request the first one */ - else /* if not waiting for parameters */ - start_command (); /* start the command */ - } - - else /* preparation failed */ - ds_io (&ds_dib, ioENF, 0); /* so set the flag to notify the CPU */ - - ds.cmrdy = CLEAR; /* flush the command from the interface */ - } - -return; -} - - -/* Poll the drives for attention requests. - - If the controller is idle and interrupts are allowed, the drives are polled - to see if any drive is requesting attention. If one is found, the controller - resets that drive's Attention status, saves the drive's unit number, sets - Drive Attention status, and waits for a command from the CPU. The interface - sets the flag to notify the CPU. -*/ - -static void poll_drives (void) -{ -if (mac_cntlr.state == cntlr_idle && ds.control == SET) /* is the controller idle and interrupts are allowed? */ - if (dl_poll_drives (&mac_cntlr, ds_unit, DL_MAXDRIVE)) /* poll the drives; was Attention seen? */ - ds_io (&ds_dib, ioENF, 0); /* request an interrupt */ -return; -} - - -/* Load a word into the FIFO. - - A word is loaded into the next available location in the FIFO, and the FIFO - occupancy count is incremented. If the FIFO is full on entry, the load is - ignored. - - - Implementation notes: - - 1. The FIFO is implemented as circular queue to take advantage of REG_CIRC - EXAMINE semantics. REG->qptr is the index of the first word currently in - the FIFO. By specifying REG_CIRC, examining FIFO[0-n] will always - display the words in load order, regardless of the actual array index of - the start of the list. The number of words currently present in the FIFO - is kept in fifo_count (0 = empty, 1-16 = number of words available). - - If fifo_count < FIFO_SIZE, (REG->qptr + fifo_count) mod FIFO_SIZE is the - index of the new word location. Loading stores the word there and then - increments fifo_count. - - 2. Because the load and unload routines need access to qptr in the REG - structure for the FIFO array, a pointer to the REG is stored in the - fifo_reg variable during device reset. -*/ - -static void fifo_load (uint16 data) -{ -uint32 index; - -if (FIFO_FULL) { /* is the FIFO already full? */ - if (DEBUG_PRI (ds_dev, DEB_BUF)) - fprintf (sim_deb, ">>DS buf: Attempted load to full FIFO, data %06o\n", data); - - return; /* return with the load ignored */ - } - -index = (ds.fifo_reg->qptr + ds.fifo_count) % FIFO_SIZE; /* calculate the index of the next available location */ - -ds.fifo [index] = data; /* store the word in the FIFO */ -ds.fifo_count = ds.fifo_count + 1; /* increment the count of words stored */ - -if (DEBUG_PRI (ds_dev, DEB_BUF)) - fprintf (sim_deb, ">>DS buf: Data %06o loaded into FIFO (%d)\n", - data, ds.fifo_count); - -return; -} - - -/* Unload a word from the FIFO. - - A word is unloaded from the first location in the FIFO, and the FIFO - occupancy count is decremented. If the FIFO is empty on entry, the unload - returns dummy data. - - - Implementation notes: - - 1. If fifo_count > 0, REG->qptr is the index of the word to remove. Removal - gets the word and then increments qptr (mod FIFO_SIZE) and decrements - fifo_count. -*/ - -static uint16 fifo_unload (void) -{ -uint16 data; - -if (FIFO_EMPTY) { /* is the FIFO already empty? */ - if (DEBUG_PRI (ds_dev, DEB_BUF)) - fputs (">>DS buf: Attempted unload from empty FIFO\n", sim_deb); - - return 0; /* return with no data */ - } - -data = ds.fifo [ds.fifo_reg->qptr]; /* get the word from the FIFO */ - -ds.fifo_reg->qptr = (ds.fifo_reg->qptr + 1) % FIFO_SIZE; /* update the FIFO queue pointer */ -ds.fifo_count = ds.fifo_count - 1; /* decrement the count of words stored */ - -if (DEBUG_PRI (ds_dev, DEB_BUF)) - fprintf (sim_deb, ">>DS buf: Data %06o unloaded from FIFO (%d)\n", - data, ds.fifo_count); - -return data; -} - - -/* Clear the FIFO. - - The FIFO is cleared by setting the occupancy counter to zero. -*/ - -static void fifo_clear (void) -{ -ds.fifo_count = 0; /* clear the FIFO */ - -if (DEBUG_PRI (ds_dev, DEB_BUF)) - fputs (">>DS buf: FIFO cleared\n", sim_deb); - -return; -} - - -/* Activate the unit. - - The specified unit is activated using the unit's "wait" time. If debugging - is enabled, the activation is logged to the debug file. -*/ - -static t_stat activate_unit (UNIT *uptr) -{ -int32 unit; -t_stat result; - -if (DEBUG_PRI (ds_dev, DEB_SERV)) { - unit = uptr - ds_unit; /* calculate the unit number */ - - if (uptr == &ds_cntlr) - fprintf (sim_deb, ">>DS serv: Controller delay %d service scheduled\n", - uptr->wait); - else - fprintf (sim_deb, ">>DS serv: Unit %d delay %d service scheduled\n", - unit, uptr->wait); - } - -result = sim_activate (uptr, uptr->wait); /* activate the unit */ -uptr->wait = 0; /* reset the activation time */ - -return result; /* return the activation status */ -} +/* hp2100_ds.c: HP 13037D/13175D disc controller/interface simulator + + Copyright (c) 2004-2012, Robert M. Supnik + Copyright (c) 2012-2016 J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. + + DS 13037D/13175D disc controller/interface + + 13-May-16 JDB Modified for revised SCP API function parameter types + 04-Mar-16 JDB Name changed to "hp2100_disclib" until HP 3000 integration + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 24-Dec-14 JDB Use T_ADDR_FMT with t_addr values for 64-bit compatibility + 18-Mar-13 JDB Fixed poll_drives definition to match declaration + 24-Oct-12 JDB Changed CNTLR_OPCODE to title case to avoid name clash + 29-Mar-12 JDB Rewritten to use the MAC/ICD disc controller library + ioIOO now notifies controller service of parameter output + 14-Feb-12 JDB Corrected SRQ generation and FIFO under/overrun detection + Corrected Clear command to conform to the hardware + Fixed Request Status to return Unit Unavailable if illegal + Seek and Cold Load Read now Seek Check if seek in progress + Remodeled command wait for seek completion + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + 21-Jun-11 JDB Corrected status returns for disabled drive, auto-seek + beyond drive limits, Request Sector Address and Wakeup + with invalid or offline unit + Address verification reenabled if auto-seek during + Read Without Verify + 28-Mar-11 JDB Tidied up signal handling + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 31-Dec-07 JDB Corrected and verified ioCRS action + 20-Dec-07 JDB Corrected DPTR register definition from FLDATA to DRDATA + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 03-Aug-06 JDB Fixed REQUEST STATUS command to clear status-1 + Removed redundant attached test in "ds_detach" + 18-Mar-05 RMS Added attached test to detach routine + 01-Mar-05 JDB Added SET UNLOAD/LOAD + + References: + - 13037 Disc Controller Technical Information Package (13037-90902, Aug-1980) + - 7925D Disc Drive Service Manual (07925-90913, Apr-1984) + - HP 12992 Loader ROMs Installation Manual (12992-90001, Apr-1986) + - DVR32 RTE Moving Head Driver source (92084-18711, Revision 5000) + + + The 13037D multiple-access (MAC) disc controller supports from one to eight + HP 7905 (15 MB), 7906 (20MB), 7920 (50 MB), and 7925 (120 MB) disc drives + accessed by one to eight CPUs. The controller hardware consists of a 16-bit + microprogrammed processor constructed from 74S181 bit slices operating at 5 + MHz, a device controller providing the interconnections to the drives and CPU + interfaces, and an error correction controller that enables the correction of + up to 32-bit error bursts. 1024 words of 24-bit firmware are stored in ROM. + + The 13175D disc interface is used to connect the HP 1000 CPU to the 13037 + device controller. In a multiple-CPU system, one interface is strapped to + reset the controller when the CPU's front panel PRESET button is pressed. + + This module simulates a 13037D connected to a single 13175D interface. From + one to eight drives may be connected, and drive types may be freely + intermixed. A unit that is enabled but not attached appears to be a + connected drive that does not have a disc pack in place. A unit that is + disabled appears to be disconnected. + + This simulator is an adaptation of the code originally written by Bob Supnik. + The functions of the controller have been separated from the functions of the + interface, with the former placed into a separate disc controller library. + This allows the library to support other CPU interfaces, such as the 12821A + HP-IB disc interface, that use substantially different communication + protocols. The library functions implement the controller command set for + the drive units. The interface functions handle the transfer of commands and + data to and from the CPU. + + In hardware, the controller runs continuously in one of three states: in the + Poll Loop (idle state), in the Command Wait Loop (wait state), or in command + execution (busy state). In simulation, the controller is run only when a + command is executing or when a transition into or out of the two loops might + occur. Internally, the controller handles these transitions: + + - when a command other than End terminates (busy => wait) + - when the End command terminates (busy => idle) + - when a command timeout occurs (wait => idle) + - when a parameter timeout occurs (busy => idle) + - when a seek completes (if idle and interrupts are enabled, idle => wait) + + The interface must call the controller library to handle these transitions: + + - when a command is received from the CPU (idle or wait => busy) + - when interrupts are enabled (if idle and drive Attention, idle => wait) + + In addition, each transition to the wait state must check for a pending + command, and each transition to the idle state must check for both a pending + command and a drive with Attention status asserted. + + + Implementation notes: + + 1. Although the 13175D has a 16-word FIFO, the "full" level is set at 5 + entries in hardware to avoid a long DCPC preemption time at the start of + a disc write as the FIFO fills. +*/ + + + +#include "hp2100_defs.h" +#include "hp2100_disclib.h" + + + +/* Program constants */ + +#define DS_DRIVES (DL_MAXDRIVE + 1) /* number of disc drive units */ +#define DS_UNITS (DS_DRIVES + DL_AUXUNITS) /* total number of units */ + +#define ds_cntlr ds_unit [DL_MAXDRIVE + 1] /* controller unit alias */ + +#define FIFO_SIZE 16 /* FIFO depth */ + +#define FIFO_EMPTY (ds.fifo_count == 0) /* FIFO empty test */ +#define FIFO_STOP (ds.fifo_count >= 5) /* FIFO stop filling test */ +#define FIFO_FULL (ds.fifo_count == FIFO_SIZE) /* FIFO full test */ + +#define PRESET_ENABLE TRUE /* Preset Jumper (W4) is enabled */ + + +/* Debug flags */ + +#define DEB_CPU (1 << 0) /* words received from and sent to the CPU */ +#define DEB_CMDS (1 << 1) /* interface commands received from the CPU */ +#define DEB_BUF (1 << 2) /* data read from and written to the card FIFO */ +#define DEB_RWSC (1 << 3) /* device read/write/status/control commands */ +#define DEB_SERV (1 << 4) /* unit service scheduling calls */ + + + +/* Per-card state variables */ + +typedef struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + FLIP_FLOP srq; /* SRQ flip-flop */ + FLIP_FLOP edt; /* EDT flip-flop */ + FLIP_FLOP cmfol; /* command follows flip-flop */ + FLIP_FLOP cmrdy; /* command ready flip-flop */ + uint16 fifo [FIFO_SIZE]; /* FIFO buffer */ + uint32 fifo_count; /* FIFO occupancy counter */ + REG *fifo_reg; /* FIFO register pointer */ + } CARD_STATE; + + +/* MAC disc state variables */ + +static UNIT ds_unit [DS_UNITS]; /* unit array */ + +static CARD_STATE ds; /* card state */ + +static uint16 buffer [DL_BUFSIZE]; /* command/status/sector buffer */ + +static CNTLR_VARS mac_cntlr = /* MAC controller */ + { CNTLR_INIT (MAC, buffer, &ds_cntlr) }; + + + +/* MAC disc global VM routines */ + +IOHANDLER ds_io; +t_stat ds_service_drive (UNIT *uptr); +t_stat ds_service_controller (UNIT *uptr); +t_stat ds_service_timer (UNIT *uptr); +t_stat ds_reset (DEVICE *dptr); +t_stat ds_attach (UNIT *uptr, CONST char *cptr); +t_stat ds_detach (UNIT *uptr); +t_stat ds_boot (int32 unitno, DEVICE *dptr); + +/* MAC disc global SCP routines */ + +t_stat ds_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc); + +/* MAC disc local utility routines */ + +static void start_command (void); +static void poll_interface (void); +static void poll_drives (void); +static void fifo_load (uint16 data); +static uint16 fifo_unload (void); +static void fifo_clear (void); +static t_stat activate_unit (UNIT *uptr); + + + +/* MAC disc VM data structures. + + ds_dib DS device information block + ds_unit DS unit list + ds_reg DS register list + ds_mod DS modifier list + ds_deb DS debug table + ds_dev DS device descriptor + + For the drive models, the modifiers provide this SHOW behavior: + + - when detached and autosized, prints "autosize" + - when detached and not autosized, prints the model number + - when attached, prints the model number (regardless of autosizing) + + + Implementation notes: + + 1. The validation routine does not allow the model number or autosizing + option to be changed when the unit is attached. Therefore, specifying + UNIT_ATT in the mask field has no adverse effect. + + 2. The modifier DEVNO is deprecated in favor of SC but is retained for + compatibility. +*/ + + +DEVICE ds_dev; + +static DIB ds_dib = { &ds_io, DS }; + +#define UNIT_FLAGS (UNIT_FIX | UNIT_ATTABLE | UNIT_ROABLE | UNIT_DISABLE | UNIT_UNLOAD) + +static UNIT ds_unit [] = { + { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 0 */ + { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 1 */ + { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 2 */ + { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 3 */ + { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 4 */ + { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 5 */ + { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 6 */ + { UDATA (&ds_service_drive, UNIT_FLAGS | MODEL_7905, D7905_WORDS) }, /* drive unit 7 */ + { UDATA (&ds_service_controller, UNIT_DIS, 0) }, /* controller unit */ + { UDATA (&ds_service_timer, UNIT_DIS, 0) } /* timer unit */ + }; + +static REG ds_reg [] = { + { FLDATA (CMFOL, ds.cmfol, 0) }, + { FLDATA (CMRDY, ds.cmrdy, 0) }, + { DRDATA (FCNT, ds.fifo_count, 5) }, + { BRDATA (FIFO, ds.fifo, 8, 16, FIFO_SIZE), REG_CIRC }, + { ORDATA (FREG, ds.fifo_reg, 32), REG_HRO }, + + { ORDATA (CNTYPE, mac_cntlr.type, 2), REG_HRO }, + { ORDATA (STATE, mac_cntlr.state, 2) }, + { ORDATA (OPCODE, mac_cntlr.opcode, 6) }, + { ORDATA (STATUS, mac_cntlr.status, 6) }, + { FLDATA (EOC, mac_cntlr.eoc, 0) }, + { FLDATA (EOD, mac_cntlr.eod, 0) }, + { ORDATA (SPDU, mac_cntlr.spd_unit, 16) }, + { ORDATA (FLMASK, mac_cntlr.file_mask, 4) }, + { ORDATA (RETRY, mac_cntlr.retry, 4), REG_HRO }, + { ORDATA (CYL, mac_cntlr.cylinder, 16) }, + { ORDATA (HEAD, mac_cntlr.head, 6) }, + { ORDATA (SECTOR, mac_cntlr.sector, 8) }, + { ORDATA (VFYCNT, mac_cntlr.verify_count, 16) }, + { ORDATA (LASPOL, mac_cntlr.poll_unit, 3) }, + { HRDATA (BUFPTR, mac_cntlr.buffer, 32), REG_HRO }, + { BRDATA (BUFFER, buffer, 8, 16, DL_BUFSIZE) }, + { DRDATA (INDEX, mac_cntlr.index, 8) }, + { DRDATA (LENGTH, mac_cntlr.length, 8) }, + { HRDATA (AUXPTR, mac_cntlr.aux, 32), REG_HRO }, + { DRDATA (STIME, mac_cntlr.seek_time, 24), PV_LEFT | REG_NZ }, + { DRDATA (ITIME, mac_cntlr.sector_time, 24), PV_LEFT | REG_NZ }, + { DRDATA (CTIME, mac_cntlr.cmd_time, 24), PV_LEFT | REG_NZ }, + { DRDATA (DTIME, mac_cntlr.data_time, 24), PV_LEFT | REG_NZ }, + { DRDATA (WTIME, mac_cntlr.wait_time, 31), PV_LEFT | REG_NZ }, + + { FLDATA (CTL, ds.control, 0) }, + { FLDATA (FLG, ds.flag, 0) }, + { FLDATA (FBF, ds.flagbuf, 0) }, + { FLDATA (SRQ, ds.srq, 0) }, + { FLDATA (EDT, ds.edt, 0) }, + + { URDATA (UCYL, ds_unit[0].CYL, 10, 10, 0, DS_UNITS, PV_LEFT) }, + { URDATA (UOP, ds_unit[0].OP, 8, 6, 0, DS_UNITS, PV_RZRO) }, + { URDATA (USTAT, ds_unit[0].STAT, 2, 8, 0, DS_UNITS, PV_RZRO) }, + { URDATA (UPHASE, ds_unit[0].PHASE, 8, 3, 0, DS_UNITS, PV_RZRO) }, + { URDATA (UPOS, ds_unit[0].pos, 8, T_ADDR_W, 0, DS_UNITS, PV_LEFT) }, + { URDATA (UWAIT, ds_unit[0].wait, 8, 32, 0, DS_UNITS, PV_LEFT) }, + + { ORDATA (SC, ds_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, ds_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +static MTAB ds_mod [] = { +/* mask match pstring mstring valid disp desc */ + { UNIT_UNLOAD, UNIT_UNLOAD, "heads unloaded", "UNLOADED", &ds_load_unload, NULL, NULL }, + { UNIT_UNLOAD, 0, "heads loaded", "LOADED", &ds_load_unload, NULL, NULL }, + + { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL, NULL, NULL }, + { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL, NULL, NULL }, + + { UNIT_FMT, UNIT_FMT, "format enabled", "FORMAT", NULL, NULL, NULL }, + { UNIT_FMT, 0, "format disabled", "NOFORMAT", NULL, NULL, NULL }, + +/* mask match pstring mstring valid disp desc */ + { UNIT_AUTO | UNIT_ATT, UNIT_AUTO, "autosize", "AUTOSIZE", &dl_set_model, NULL, NULL }, + { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7905, "7905", "7905", &dl_set_model, NULL, NULL }, + { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7906, "7906", "7906", &dl_set_model, NULL, NULL }, + { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7920, "7920", "7920", &dl_set_model, NULL, NULL }, + { UNIT_AUTO | UNIT_ATT | UNIT_MODEL, MODEL_7925, "7925", "7925", &dl_set_model, NULL, NULL }, + { UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7905, "7905", NULL, NULL, NULL, NULL }, + { UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7906, "7906", NULL, NULL, NULL, NULL }, + { UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7920, "7920", NULL, NULL, NULL, NULL }, + { UNIT_ATT | UNIT_MODEL, UNIT_ATT | MODEL_7925, "7925", NULL, NULL, NULL, NULL }, + + { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &ds_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &ds_dev }, + { 0 } + }; + +static DEBTAB ds_deb [] = { + { "CPU", DEB_CPU }, + { "CMDS", DEB_CMDS }, + { "BUF", DEB_BUF }, + { "RWSC", DEB_RWSC }, + { "SERV", DEB_SERV }, + { NULL, 0 } + }; + +DEVICE ds_dev = { + "DS", /* device name */ + ds_unit, /* unit array */ + ds_reg, /* register array */ + ds_mod, /* modifier array */ + DS_UNITS, /* number of units */ + 8, /* address radix */ + 27, /* address width = 128 MB */ + 1, /* address increment */ + 8, /* data radix */ + 16, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &ds_reset, /* reset routine */ + &ds_boot, /* boot routine */ + &ds_attach, /* attach routine */ + &ds_detach, /* detach routine */ + &ds_dib, /* device information block */ + DEV_DEBUG | DEV_DISABLE, /* device flags */ + 0, /* debug control flags */ + ds_deb, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL /* logical device name */ + }; + + + +/* MAC disc global VM routines */ + + +/* I/O signal handler. + + The 13175D disc interface data path consists of an input multiplexer/latch + and a 16-word FIFO buffer. The FIFO source may be either the CPU's I/O + input bus or the controller's interface data bus. The output of the FIFO may + be enabled either to the CPU's I/O output bus or the interface data bus. + + The control path consists of the usual control, flag buffer, flag, and SRQ + flip-flops, although flag and SRQ are decoupled to allow the full DCPC + transfer rate through the FIFO (driving SRQ from the flag limits transfers to + every other cycle). SRQ is based on the FIFO level: if data or room in the + FIFO is available, SRQ is set to initiate a transfer. The flag is only used + to signal an interrupt at the end of a command. + + One unusual aspect is that SFC and SFS test different things, rather than + complementary states of the same thing. SFC tests the controller busy state, + and SFS tests the flag flip-flop. + + In addition, the card contains end-of-data-transfer, command-follows, and + command-ready flip-flops. EDT is set when the DCPC EDT signal is asserted + and is used in conjunction with the FIFO level to assert the end-of-data + signal to the controller. The command-follows flip-flop is set by a CLC to + indicate that the next data word output from the CPU is a disc command. The + command-ready flip-flop is set when a command is received to schedule an + interface poll. + + + Implementation notes: + + 1. In hardware, SRQ is enabled only when the controller is reading or + writing the disc (IFIN or IFOUT functions are asserted) and set when the + FIFO is not empty (read) or not full (write). In simulation, SRQ is set + by the unit service read/write data phase transfers and cleared in the + IOI and IOO signal handlers when the FIFO is empty (read) or full + (write). + + 2. The DCPC EDT signal cannot set the controller's end-of-data flag directly + because a write EOD must occur only after the FIFO has been drained. + + 3. Polling the interface or drives must be deferred to the end of I/O signal + handling. If they are performed in the IOO/STC handlers themselves, an + associated CLF might clear the flag that was set by the poll. + + 4. Executing a CLC sets the controller's end-of-data flag, which will abort + a read or write data transfer in progress. Parameter transfers are not + affected. If a command is received when a parameter is expected, the + word is interpreted as data, even though the command-ready flip-flop is + set. The controller firmware only checks DTRDY for a parameter transfer, + and DTRDY is asserted whenever the FIFO is not empty. + + 5. The hardware Interface Function and Flag Buses are not implemented + explicitly. Instead, interface functions and signals are inferred by the + interface from the current command operation and phase. +*/ + +uint32 ds_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +static const char * const output_state [] = { "Data", "Command" }; +const char * const hold_or_clear = (signal_set & ioCLF ? ",C" : ""); + +uint16 data; +t_stat status; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ +t_bool command_issued = FALSE; +t_bool interrupt_enabled = FALSE; + +while (working_set) { + signal = IONEXT (working_set); /* isolate the next signal */ + + switch (signal) { /* dispatch the I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + ds.flag = CLEAR; /* clear the flag */ + ds.flagbuf = CLEAR; /* and flag buffer */ + + if (DEBUG_PRI (ds_dev, DEB_CMDS)) + fputs (">>DS cmds: [CLF] Flag cleared\n", sim_deb); + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + ds.flag = SET; /* set the flag */ + ds.flagbuf = SET; /* and flag buffer */ + + if (DEBUG_PRI (ds_dev, DEB_CMDS)) + fputs (">>DS cmds: [STF] Flag set\n", sim_deb); + break; + + + case ioSFC: /* skip if flag is clear */ + setSKF (mac_cntlr.state != cntlr_busy); /* skip if the controller is not busy */ + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (ds); /* assert SKF if the flag is set */ + break; + + + case ioIOI: /* I/O data input */ + data = fifo_unload (); /* unload the next word from the FIFO */ + stat_data = IORETURN (SCPE_OK, data); /* merge in the return status */ + + if (DEBUG_PRI (ds_dev, DEB_CPU)) + fprintf (sim_deb, ">>DS cpu: [LIx%s] Data = %06o\n", hold_or_clear, data); + + if (FIFO_EMPTY) { /* is the FIFO now empty? */ + if (ds.srq == SET && DEBUG_PRI (ds_dev, DEB_CMDS)) + fprintf (sim_deb, ">>DS cmds: [LIx%s] SRQ cleared\n", hold_or_clear); + + ds.srq = CLEAR; /* clear SRQ */ + + if (ds_cntlr.PHASE == data_phase) { /* is this an outbound parameter? */ + ds_cntlr.wait = mac_cntlr.data_time; /* activate the controller */ + activate_unit (&ds_cntlr); /* to acknowledge the data */ + } + } + break; + + + case ioIOO: /* I/O data output */ + data = IODATA (stat_data); /* mask to just the data word */ + + if (DEBUG_PRI (ds_dev, DEB_CPU)) + fprintf (sim_deb, ">>DS cpu: [OTx%s] %s = %06o\n", + hold_or_clear, output_state [ds.cmfol], data); + + fifo_load (data); /* load the word into the FIFO */ + + if (ds.cmfol == SET) { /* are we expecting a command? */ + ds.cmfol = CLEAR; /* clear the command follows flip-flop */ + ds.cmrdy = SET; /* set the command ready flip-flop */ + command_issued = TRUE; /* and request an interface poll */ + } + + else { /* not a command */ + if (ds_cntlr.PHASE == data_phase) { /* is this an inbound parameter? */ + ds_cntlr.wait = mac_cntlr.data_time; /* activate the controller */ + activate_unit (&ds_cntlr); /* to receive the data */ + } + + if (FIFO_STOP) { /* is the FIFO now full enough? */ + if (ds.srq == SET && DEBUG_PRI (ds_dev, DEB_CMDS)) + fprintf (sim_deb, ">>DS cmds: [OTx%s] SRQ cleared\n", hold_or_clear); + + ds.srq = CLEAR; /* clear SRQ to stop filling */ + } + } + break; + + + case ioPOPIO: /* power-on preset to I/O */ + ds.flag = SET; /* set the flag */ + ds.flagbuf = SET; /* and flag buffer */ + ds.cmrdy = CLEAR; /* clear the command ready flip-flop */ + + if (DEBUG_PRI (ds_dev, DEB_CMDS)) + fputs (">>DS cmds: [POPIO] Flag set\n", sim_deb); + break; + + + case ioCRS: /* control reset */ + if (DEBUG_PRI (ds_dev, DEB_CMDS)) + fputs (">>DS cmds: [CRS] Master reset\n", sim_deb); + + ds.control = CLEAR; /* clear the control */ + ds.cmfol = CLEAR; /* and command follows flip-flops */ + + if (PRESET_ENABLE) { /* is preset enabled for this interface? */ + fifo_clear (); /* clear the FIFO */ + + status = dl_clear_controller (&mac_cntlr, /* do a hard clear of the controller */ + ds_unit, hard_clear); + + stat_data = IORETURN (status, 0); /* return the status from the controller */ + } + break; + + + case ioCLC: /* clear control flip-flop */ + if (DEBUG_PRI (ds_dev, DEB_CMDS)) + fprintf (sim_deb, ">>DS cmds: [CLC%s] Control cleared\n", hold_or_clear); + + ds.control = CLEAR; /* clear the control */ + ds.edt = CLEAR; /* and EDT flip-flops */ + ds.cmfol = SET; /* set the command follows flip-flop */ + mac_cntlr.eod = SET; /* set the controller's EOD flag */ + + fifo_clear (); /* clear the FIFO */ + break; + + + case ioSTC: /* set control flip-flop */ + ds.control = SET; /* set the control flip-flop */ + + interrupt_enabled = TRUE; /* check for drive attention */ + + if (DEBUG_PRI (ds_dev, DEB_CMDS)) + fprintf (sim_deb, ">>DS cmds: [STC%s] Control set\n", hold_or_clear); + break; + + + case ioEDT: /* end data transfer */ + ds.edt = SET; /* set the EDT flip-flop */ + + if (DEBUG_PRI (ds_dev, DEB_CPU)) + fputs (">>DS cpu: [EDT] DCPC transfer ended\n", sim_deb); + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (ds); /* set the standard PRL signal */ + setstdIRQ (ds); /* set the standard IRQ signal */ + setSRQ (dibptr->select_code, ds.srq); /* set the SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + ds.flagbuf = CLEAR; /* clear the flag */ + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove the current signal from the set */ + } + + +if (command_issued) /* was a command received? */ + poll_interface (); /* poll the interface for the next command */ +else if (interrupt_enabled) /* were interrupts enabled? */ + poll_drives (); /* poll the drives for Attention */ + +return stat_data; +} + + +/* Service the disc drive unit. + + The unit service routine is called to execute scheduled controller commands + for the specified unit. The actions to be taken depend on the current state + of the controller and the unit. + + Generally, the controller library service routine handles all of the disc + operations except data transfer to and from the interface. Read transfers + are responsible for loading words from the sector buffer into the FIFO and + enabling SRQ. If the current sector transfer is complete, either due to EDT + assertion or buffer exhaustion, the controller is moved to the end phase to + complete or continue the read with the next sector. In either case, the unit + is rescheduled. If the FIFO overflows, the read terminates with a data + overrun error. + + Write transfers set the initial SRQ to request words from the CPU. As each + word arrives, it is unloaded from the FIFO into the sector buffer, and SRQ is + enabled. If the current sector transfer is complete, the controller is moved + to the end phase. If the FIFO underflows, the write terminates with a data + overrun error. + + The synchronous nature of the disc drive requires that data be supplied or + accepted continuously by the CPU. DCPC generally assures that this occurs, + and the FIFO allows for some latency before an overrun or underrun occurs. + + The other operation the interface must handle is seek completion. The + controller handles seek completion by setting Attention status in the drive's + status word. The interface is responsible for polling the drives if the + controller is idle and interrupts are enabled. + + + Implementation notes: + + 1. Every command except Seek, Recalibrate, and End sets the flag when the + command completes. A command completes when the controller is no longer + busy (it becomes idle for Seek, Recalibrate, and End, or it becomes + waiting for all others). Seek and Recalibrate may generate errors (e.g., + heads unloaded), in which case the flag must be set. But in these cases, + the controller state is waiting, not idle. + + However, it is insufficient simply to check that the controller has moved + to the wait state, because a seek may complete while the controller is + waiting for the next command. For example, a Seek is started on unit 0, + and the controller moves to the idle state. But before the seek + completes, another command is issued that attempts to access unit 1, + which is not ready. The command fails with a Status-2 error, and the + controller moves to the wait state. When the seek completes, the + controller is waiting with error status. We must determine whether the + seek completed successfully or not, as we must interrupt in the latter + case. + + Therefore, we determine seek completion by checking if the Attention + status was set. Attention sets only if the seek completes successfully. + + (Actually, Attention sets if a seek check occurs, but in that case, the + command terminated before the seek ever started. Also, a seek may + complete while the controller is busy, waiting, or idle.) + + 2. For debug printouts, we want to print the name of the command that has + completed when the controller returns to the idle or wait state. + Normally, we would use the controller's "opcode" field to identify the + command that completed. However, while waiting for Seek or Recalibrate + completion, "opcode" may be set to another command if that command does + not access this drive. For example, it might be set to a Read of another + unit, or a Request Status for this unit. So we can't rely on "opcode" to + report the correct name of the completed positioning command. + + However, we cannot rely on "uptr->OP" either, as that can be changed + during the course of a command. For example, Read Without Verify is + changed to Read after a track crossing. + + Instead, we have to determine whether a seek is completing. If it is, + then we report "uptr->OP"; otherwise, we report "opcode". + + 3. The initial write SRQ must set only at the transition from the start + phase to the data phase. If a write command begins with an auto-seek, + the drive service will be entered twice in the start phase (the first + entry performs the seek, and the second begins the write). In hardware, + SRQ does not assert until the write begins. + + 4. The DCPC EDT signal cannot set the controller's end-of-data flag + directly because a write EOD must only occur after the FIFO has been + drained. +*/ + +t_stat ds_service_drive (UNIT *uptr) +{ +static const char completion_message [] = ">>DS rwsc: Unit %d %s command completed\n"; +t_stat result; +t_bool seek_completion; +int32 unit; +FLIP_FLOP entry_srq = ds.srq; /* get the SRQ state on entry */ +CNTLR_PHASE entry_phase = (CNTLR_PHASE) uptr->PHASE; /* get the operation phase on entry */ +uint32 entry_status = uptr->STAT; /* get the drive status on entry */ + +result = dl_service_drive (&mac_cntlr, uptr); /* service the drive */ + +if ((CNTLR_PHASE) uptr->PHASE == data_phase) /* is the drive in the data phase? */ + switch ((CNTLR_OPCODE) uptr->OP) { /* dispatch the current operation */ + + case Read: /* read operations */ + case Read_Full_Sector: + case Read_With_Offset: + case Read_Without_Verify: + if (mac_cntlr.length == 0 || ds.edt == SET) { /* is the data phase complete? */ + mac_cntlr.eod = ds.edt; /* set EOD if DCPC is done */ + uptr->PHASE = end_phase; /* set the end phase */ + uptr->wait = mac_cntlr.cmd_time; /* and schedule the controller */ + } + + else if (FIFO_FULL) /* is the FIFO already full? */ + dl_end_command (&mac_cntlr, data_overrun); /* terminate the command with an overrun */ + + else { + fifo_load (buffer [mac_cntlr.index++]); /* load the next word into the FIFO */ + mac_cntlr.length--; /* count it */ + ds.srq = SET; /* ask DCPC to pick it up */ + ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ + uptr->wait = mac_cntlr.data_time; /* schedule the next data transfer */ + } + + break; + + + case Write: /* write operations */ + case Write_Full_Sector: + case Initialize: + if (entry_phase == start_phase) { /* is this the phase transition? */ + ds.srq = SET; /* start the DCPC transfer */ + ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ + } + + else if (FIFO_EMPTY) /* is the FIFO empty? */ + dl_end_command (&mac_cntlr, data_overrun); /* terminate the command with an underrun */ + + else { + buffer [mac_cntlr.index++] = fifo_unload (); /* unload the next word from the FIFO */ + mac_cntlr.length--; /* count it */ + + if (ds.edt == SET && FIFO_EMPTY) /* if DCPC is complete and the FIFO is empty */ + mac_cntlr.eod = SET; /* then set the end-of-data flag */ + + if (mac_cntlr.length == 0 || mac_cntlr.eod == SET) { /* is the data phase complete? */ + uptr->PHASE = end_phase; /* set the end phase */ + uptr->wait = mac_cntlr.cmd_time; /* and schedule the controller */ + } + + else { + if (ds.edt == CLEAR) { /* if DCPC is still transferring */ + ds.srq = SET; /* then request the next word */ + ds_io (&ds_dib, ioSIR, 0); /* and recalculate the interrupts */ + } + + uptr->wait = mac_cntlr.data_time; /* schedule the next data transfer */ + } + } + + break; + + + default: /* we were entered with an invalid state */ + result = SCPE_IERR; /* return an internal (programming) error */ + break; + } /* end of data phase operation dispatch */ + + +if (DEBUG_PRI (ds_dev, DEB_CMDS) && entry_srq != ds.srq) + fprintf (sim_deb, ">>DS cmds: SRQ %s\n", ds.srq == SET ? "set" : "cleared"); + + +if (uptr->wait) /* was service requested? */ + activate_unit (uptr); /* schedule the next event */ + +seek_completion = ~entry_status & uptr->STAT & DL_S2ATN; /* seek is complete when Attention sets */ + +if (mac_cntlr.state != cntlr_busy) { /* is the command complete? */ + if (mac_cntlr.state == cntlr_wait && !seek_completion) /* is it command but not seek completion? */ + ds_io (&ds_dib, ioENF, 0); /* set the data flag to interrupt the CPU */ + + poll_interface (); /* poll the interface for the next command */ + poll_drives (); /* poll the drives for Attention */ + } + + +if (DEBUG_PRI (ds_dev, DEB_RWSC)) { + unit = uptr - ds_unit; /* get the unit number */ + + if (result == SCPE_IERR) /* did an internal error occur? */ + fprintf (sim_deb, ">>DS rwsc: Unit %d %s command %s phase service not handled\n", + unit, dl_opcode_name (MAC, (CNTLR_OPCODE) uptr->OP), + dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); + + else if (seek_completion) /* if a seek has completed */ + fprintf (sim_deb, completion_message, /* report the unit command */ + unit, dl_opcode_name (MAC, (CNTLR_OPCODE) uptr->OP)); + + else if (mac_cntlr.state == cntlr_wait) /* if the controller has stopped */ + fprintf (sim_deb, completion_message, /* report the controller command */ + unit, dl_opcode_name (MAC, mac_cntlr.opcode)); + } + +return result; /* return the result of the service */ +} + + +/* Service the controller unit. + + The controller service routine is called to execute scheduled controller + commands that do not access drive units. It is also called to obtain command + parameters from the interface and to return command result values to the + interface. + + Most controller commands are handled completely in the library's service + routine, so we call that first. Commands that neither accept nor supply + parameters are complete when the library routine returns, so all we have to + do is set the interface flag if required. + + For parameter transfers in the data phase, the interface is responsible for + moving words between the sector buffer and the FIFO and setting the flag to + notify the CPU. + + + Implementation notes: + + 1. In hardware, the Read With Offset command sets the data flag after the + offset parameter has been read and the head positioner has been moved by + the indicated amount. The intent is to delay the DCPC start until the + drive is ready to supply data from the disc. + + In simulation, the flag is set as soon as the parameter is received. +*/ + +t_stat ds_service_controller (UNIT *uptr) +{ +t_stat result; +const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OP; + +result = dl_service_controller (&mac_cntlr, uptr); /* service the controller */ + +switch ((CNTLR_PHASE) uptr->PHASE) { /* dispatch the current phase */ + + case start_phase: /* most controller operations */ + case end_phase: /* start and end on the same phase */ + switch (opcode) { /* dispatch the current operation */ + + case Request_Status: + case Request_Sector_Address: + case Address_Record: + case Request_Syndrome: + case Load_TIO_Register: + case Request_Disc_Address: + case End: + break; /* complete the operation without setting the flag */ + + + case Clear: + case Set_File_Mask: + case Wakeup: + ds_io (&ds_dib, ioENF, 0); /* complete the operation and set the flag */ + break; + + + default: /* we were entered with an invalid state */ + result = SCPE_IERR; /* return an internal (programming) error */ + break; + } /* end of operation dispatch */ + break; /* end of start and end phase handlers */ + + + case data_phase: + switch (opcode) { /* dispatch the current operation */ + + case Seek: /* operations that accept parameters */ + case Verify: + case Address_Record: + case Read_With_Offset: + case Load_TIO_Register: + buffer [mac_cntlr.index++] = fifo_unload (); /* unload the next word from the FIFO */ + mac_cntlr.length--; /* count it */ + + if (mac_cntlr.length) /* are there more words to transfer? */ + ds_io (&ds_dib, ioENF, 0); /* set the flag to request the next one */ + + else { /* all parameters have been received */ + uptr->PHASE = end_phase; /* set the end phase */ + + if (opcode == Read_With_Offset) /* a Read With Offset command sets the flag */ + ds_io (&ds_dib, ioENF, 0); /* to indicate that offsetting is complete */ + + start_command (); /* the command is now ready to execute */ + } + break; + + + case Request_Status: /* operations that supply parameters */ + case Request_Sector_Address: + case Request_Syndrome: + case Request_Disc_Address: + if (mac_cntlr.length) { /* are there more words to return? */ + fifo_load (buffer [mac_cntlr.index++]); /* load the next word into the FIFO */ + mac_cntlr.length--; /* count it */ + + ds_io (&ds_dib, ioENF, 0); /* set the flag to request pickup by the CPU */ + } + + else { /* all parameters have been sent */ + uptr->PHASE = end_phase; /* set the end phase */ + uptr->wait = mac_cntlr.cmd_time; /* schedule the controller */ + activate_unit (uptr); /* to complete the command */ + } + break; + + + default: /* we were entered with an invalid state */ + result = SCPE_IERR; /* return an internal (programming) error */ + break; + } /* end of operation dispatch */ + break; /* end of data phase handlers */ + } /* end of phase dispatch */ + + +if (result == SCPE_IERR && DEBUG_PRI (ds_dev, DEB_RWSC)) /* did an internal error occur? */ + fprintf (sim_deb, ">>DS rwsc: Controller %s command %s phase service not handled\n", + dl_opcode_name (MAC, opcode), dl_phase_name ((CNTLR_PHASE) uptr->PHASE)); + + +if (mac_cntlr.state != cntlr_busy) { /* has the controller stopped? */ + poll_interface (); /* poll the interface for the next command */ + poll_drives (); /* poll the drives for Attention status */ + + if (DEBUG_PRI (ds_dev, DEB_RWSC)) + fprintf (sim_deb, ">>DS rwsc: Controller %s command completed\n", + dl_opcode_name (MAC, opcode)); + } + +return result; /* return the result of the service */ +} + + +/* Service the command wait timer unit. + + The command wait timer service routine is called if the command wait timer + expires. The library is called to reset the file mask and idle the + controller. Then the interface is polled for a command and the drives are + polled for Attention status. +*/ + +t_stat ds_service_timer (UNIT *uptr) +{ +t_stat result; + +result = dl_service_timer (&mac_cntlr, uptr); /* service the timer */ + +poll_interface (); /* poll the interface for the next command */ +poll_drives (); /* poll the drives for Attention status */ + +return result; /* return the result of the service */ +} + + +/* Reset the simulator. + + In hardware, the PON signal clears the Interface Selected flip-flop, + disconnecting the interface from the disc controller. In simulation, the + interface always remains connected to the controller, so no special action is + needed. + + + Implementation notes: + + 1. During a power-on reset, a pointer to the FIFO simulation register is + saved to allow access to the "qptr" field during FIFO loading and + unloading. This enables SCP to view the FIFO as a circular queue, so + that the bottom word of the FIFO is always displayed as FIFO[0], + regardless of where it is in the actual FIFO array. + + 2. SRQ is denied because neither IFIN nor IFOUT is asserted when the + interface is not selected. +*/ + +t_stat ds_reset (DEVICE *dptr) +{ +uint32 unit; + +if (sim_switches & SWMASK ('P')) { /* is this a power-on reset? */ + ds.fifo_reg = find_reg ("FIFO", NULL, dptr); /* find the FIFO register entry */ + + if (ds.fifo_reg == NULL) /* if it cannot be found, */ + return SCPE_IERR; /* report a programming error */ + + else { /* found it */ + ds.fifo_reg->qptr = 0; /* so reset the FIFO bottom index */ + ds.fifo_count = 0; /* and clear the FIFO */ + } + + for (unit = 0; unit < dptr->numunits; unit++) { /* loop through all of the units */ + sim_cancel (dptr->units + unit); /* cancel activation */ + dptr->units [unit].CYL = 0; /* reset the head position to cylinder 0 */ + dptr->units [unit].pos = 0; /* (irrelevant for the controller and timer) */ + } + } + +IOPRESET (&ds_dib); /* PRESET the device */ +ds.srq = CLEAR; /* clear SRQ */ + +return SCPE_OK; +} + + +/* Attach a drive unit. + + The specified file is attached to the indicated drive unit. The library + attach routine will load the heads. This will set the First Status and + Attention bits in the drive status, so we poll the drives to ensure that the + CPU is notified that the drive is now online. + + + Implementation notes: + + 1. If we are called during a RESTORE command, the drive status will not be + changed, so polling the drives will have no effect. +*/ + +t_stat ds_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat result; + +result = dl_attach (&mac_cntlr, uptr, cptr); /* attach the drive */ + +if (result == SCPE_OK) /* was the attach successful? */ + poll_drives (); /* poll the drives to notify the CPU */ + +return result; +} + + +/* Detach a drive unit. + + The specified file is detached from the indicated drive unit. The library + detach routine will unload the heads. This will set the Attention bit in the + drive status, so we poll the drives to ensure that the CPU is notified that + the drive is now offline. +*/ + +t_stat ds_detach (UNIT *uptr) +{ +t_stat result; + +result = dl_detach (&mac_cntlr, uptr); /* detach the drive */ + +if (result == SCPE_OK) /* was the detach successful? */ + poll_drives (); /* poll the drives to notify the CPU */ + +return result; +} + + +/* Boot a MAC disc drive. + + The MAC disc bootstrap program is loaded from the HP 12992B Boot Loader ROM + into memory, the I/O instructions are configured for the interface card's + select code, and the program is run to boot from the specified unit. The + loader supports booting from cylinder 0 of drive unit 0 only. Before + execution, the S register is automatically set as follows: + + 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + ------ ------ ---------------------- --------- --------- + ROM # 0 1 select code reserved head + + The boot routine sets bits 15-6 of the S register to appropriate values. + Bits 5-3 and 1-0 retain their original values, so S should be set before + booting. These bits are typically set to 0, although bit 5 is set for an RTE + reconfiguration boot, and bits 1-0 may be set if booting from a head other + than 0 is desired. + + + Implementation notes: + + 1. The Loader ROMs manual indicates that bits 2-0 select the head to use, + implying that heads 0-7 are valid. However, Table 5 has entries only for + heads 0-3, and the boot loader code will malfunction if heads 4-7 are + specified. The code masks the head number to three bits but forms the + Cold Load Read command by shifting the head number six bits to the left. + As the head field in the command is only two bits wide, specifying heads + 4-7 will result in bit 2 being shifted into the opcode field, resulting + in a Recalibrate command. +*/ + + +const BOOT_ROM ds_rom = { + 0017727, /* START JSB STAT GET STATUS */ + 0002021, /* SSA,RSS IS DRIVE READY ? */ + 0027742, /* JMP DMA YES, SET UP DMA */ + 0013714, /* AND B20 NO, CHECK STATUS BITS */ + 0002002, /* SZA IS DRIVE FAULTY OR HARD DOWN ? */ + 0102030, /* HLT 30B YES, HALT 30B, "RUN" TO TRY AGAIN */ + 0027700, /* JMP START NO, TRY AGAIN FOR DISC READY */ + 0102011, /* ADDR1 OCT 102011 */ + 0102055, /* ADDR2 OCT 102055 */ + 0164000, /* CNT DEC -6144 */ + 0000007, /* D7 OCT 7 */ + 0001400, /* STCMD OCT 1400 */ + 0000020, /* B20 OCT 20 */ + 0017400, /* STMSK OCT 17400 */ + 0000000, /* NOP */ + 0000000, /* NOP */ + 0000000, /* NOP */ + 0000000, /* NOP */ + 0000000, /* NOP */ + 0000000, /* NOP */ + 0000000, /* NOP */ + 0000000, /* NOP */ + 0000000, /* NOP */ + 0000000, /* STAT NOP STATUS CHECK SUBROUTINE */ + 0107710, /* CLC DC,C SET STATUS COMMAND MODE */ + 0063713, /* LDA STCMD GET STATUS COMMAND */ + 0102610, /* OTA DC OUTPUT STATUS COMMAND */ + 0102310, /* SFS DC WAIT FOR STATUS#1 WORD */ + 0027733, /* JMP *-1 */ + 0107510, /* LIB DC,C B-REG = STATUS#1 WORD */ + 0102310, /* SFS DC WAIT FOR STATUS#2 WORD */ + 0027736, /* JMP *-1 */ + 0103510, /* LIA DC,C A-REG = STATUS#2 WORD */ + 0127727, /* JMP STAT,I RETURN */ + 0067776, /* DMA LDB DMACW GET DMA CONTROL WORD */ + 0106606, /* OTB 6 OUTPUT DMA CONTROL WORD */ + 0067707, /* LDB ADDR1 GET MEMORY ADDRESS */ + 0106702, /* CLC 2 SET MEMORY ADDRESS INPUT MODE */ + 0106602, /* OTB 2 OUTPUT MEMORY ADDRESS TO DMA */ + 0102702, /* STC 2 SET WORD COUNT INPUT MODE */ + 0067711, /* LDB CNT GET WORD COUNT */ + 0106602, /* OTB 2 OUTPUT WORD COUNT TO DMA */ + 0106710, /* CLDLD CLC DC SET COMMAND INPUT MODE */ + 0102501, /* LIA 1 LOAD SWITCH */ + 0106501, /* LIB 1 REGISTER SETTINGS */ + 0013712, /* AND D7 ISOLATE HEAD NUMBER */ + 0005750, /* BLF,CLE,SLB BIT 12=0? */ + 0027762, /* JMP *+3 NO,MANUAL BOOT */ + 0002002, /* SZA YES,RPL BOOT. HEAD#=0? */ + 0001000, /* ALS NO,HEAD#1, MAKE HEAD#=2 */ + 0001720, /* ALF,ALS FORM COLD LOAD */ + 0001000, /* ALS COMMAND WORD */ + 0103706, /* STC 6,C ACTIVATE DMA */ + 0103610, /* OTA DC,C OUTPUT COLD LOAD COMMAND */ + 0102310, /* SFS DC IS COLD LOAD COMPLETED ? */ + 0027766, /* JMP *-1 NO, WAIT */ + 0017727, /* JSB STAT YES, GET STATUS */ + 0060001, /* LDA 1 */ + 0013715, /* AND STMSK A-REG = STATUS BITS OF STATUS#1 WD */ + 0002002, /* SZA IS TRANSFER OK ? */ + 0027700, /* JMP START NO,TRY AGAIN */ + 0117710, /* EXIT JSB ADDR2,I YES, EXEC LOADED PROGRAM _@ 2055B */ + 0000010, /* DMACW ABS DC */ + 0170100, /* ABS -START */ + }; + +t_stat ds_boot (int32 unitno, DEVICE *dptr) +{ +if (unitno != 0) /* boot supported on drive unit 0 only */ + return SCPE_NOFNC; /* report "Command not allowed" if attempted */ + +if (ibl_copy (ds_rom, ds_dib.select_code, /* copy the boot ROM to memory and configure */ + IBL_OPT | IBL_DS_HEAD, /* the S register accordingly */ + IBL_DS | IBL_MAN | IBL_SET_SC (ds_dib.select_code))) + return SCPE_IERR; /* return an internal error if the copy failed */ +else + return SCPE_OK; +} + + + +/* MAC disc global SCP routines */ + + +/* Load or unload the drive heads. + + The SCP command SET DSn UNLOADED simulates setting the hardware RUN/STOP + switch to STOP. The heads are unloaded, and the drive is spun down. + + The SET DSn LOADED command simulates setting the switch to RUN. The drive is + spun up, and the heads are loaded. + + The library handles command validation and setting the appropriate drive unit + status. +*/ + +t_stat ds_load_unload (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +const t_bool load = (value != UNIT_UNLOAD); /* true if the heads are loading */ + +return dl_load_unload (&mac_cntlr, uptr, load); /* load or unload the heads */ +} + + + +/* MAC disc local utility routines */ + + +/* Start a command. + + The previously prepared command is executed by calling the corresponding + library routine. On entry, the controller's opcode field contains the + command to start, and the buffer contains the command word in element 0 and + the parameters required by the command, if any, beginning in element 1. + + If the command started, the returned pointer will point at the unit to + activate (if that unit's "wait" field is non-zero). If the returned pointer + is NULL, the command failed to start, and the controller status has been set + to indicate the reason. The interface flag is set to notify the CPU of the + failure. + + + Implementation notes: + + 1. If a command that accesses the drive is attempted on a drive currently + seeking, the returned pointer will be valid, but the unit's "wait" time + will be zero. The unit must not be activated (as it already is active). + When the seek completes, the command will be executed automatically. + + If a Seek or Cold Load Read command is attempted on a drive currently + seeking, seek completion will occur normally, but Seek Check status will + be set. + + 2. For debug printouts, we want to print the name of the command (Seek or + Recalibrate) in progress when a new command is started. However, when + the library routine returns, the unit operation and controller opcode + have been changed to reflect the new command. Therefore, we must record + the operation in progress before calling the library. + + The problem is in determining which unit's operation code to record. We + cannot blindly use the unit field from the new command, as recorded in + the controller, as preparation has ensured only that the target unit + number is legal but not necessarily valid. Therefore, we must validate + the unit number before accessing the unit's operation code. + + If the unit number is invalid, the command will not start, but the + compiler does not know this. Therefore, we must ensure that the saved + operation code is initialized, or a "variable used uninitialized" warning + will occur. +*/ + +static void start_command (void) +{ +int32 unit, time; +UNIT *uptr; +CNTLR_OPCODE drive_command; + +unit = GET_S1UNIT (mac_cntlr.spd_unit); /* get the (prepared) unit from the command */ + +if (unit <= DL_MAXDRIVE) /* is the unit number valid? */ + drive_command = (CNTLR_OPCODE) ds_unit [unit].OP; /* get the opcode from the unit that will be used */ +else /* the unit is invalid, so the command will not start */ + drive_command = End; /* but the compiler doesn't know this! */ + +uptr = dl_start_command (&mac_cntlr, ds_unit, DL_MAXDRIVE); /* ask the controller to start the command */ + +if (uptr) { /* did the command start? */ + time = uptr->wait; /* save the activation time */ + + if (time) /* was the unit scheduled? */ + activate_unit (uptr); /* activate it (and clear the "wait" field) */ + + if (DEBUG_PRI (ds_dev, DEB_RWSC)) { + unit = uptr - ds_unit; /* get the unit number */ + + if (time == 0) /* was the unit busy? */ + fprintf (sim_deb, ">>DS rwsc: Unit %d %s in progress\n", + unit, dl_opcode_name (MAC, drive_command)); + + fputs (">>DS rwsc: ", sim_deb); + + if (unit > DL_MAXDRIVE) + fputs ("Controller ", sim_deb); + else + fprintf (sim_deb, "Unit %d position %" T_ADDR_FMT "d ", unit, uptr->pos); + + fprintf (sim_deb, "%s command initiated\n", + dl_opcode_name (MAC, mac_cntlr.opcode)); + } + } + +else /* the command failed to start */ + ds_io (&ds_dib, ioENF, 0); /* so set the flag to notify the CPU */ + +return; +} + + +/* Poll the interface for a new command. + + If a new command is available, and the controller is not busy, prepare the + command for execution. If preparation succeeded, and the command needs + parameters before executing, set the flag to request the first one from the + CPU. If no parameters are needed, the command is ready to execute. + + If preparation failed, set the flag to notify the CPU. The controller + status contains the reason for the failure. +*/ + +static void poll_interface (void) +{ +if (ds.cmrdy == SET && mac_cntlr.state != cntlr_busy) { /* are the interface and controller ready? */ + buffer [0] = fifo_unload (); /* unload the command into the buffer */ + + if (dl_prepare_command (&mac_cntlr, ds_unit, DL_MAXDRIVE)) { /* prepare the command; did it succeed? */ + if (mac_cntlr.length) /* does the command require parameters? */ + ds_io (&ds_dib, ioENF, 0); /* set the flag to request the first one */ + else /* if not waiting for parameters */ + start_command (); /* start the command */ + } + + else /* preparation failed */ + ds_io (&ds_dib, ioENF, 0); /* so set the flag to notify the CPU */ + + ds.cmrdy = CLEAR; /* flush the command from the interface */ + } + +return; +} + + +/* Poll the drives for attention requests. + + If the controller is idle and interrupts are allowed, the drives are polled + to see if any drive is requesting attention. If one is found, the controller + resets that drive's Attention status, saves the drive's unit number, sets + Drive Attention status, and waits for a command from the CPU. The interface + sets the flag to notify the CPU. +*/ + +static void poll_drives (void) +{ +if (mac_cntlr.state == cntlr_idle && ds.control == SET) /* is the controller idle and interrupts are allowed? */ + if (dl_poll_drives (&mac_cntlr, ds_unit, DL_MAXDRIVE)) /* poll the drives; was Attention seen? */ + ds_io (&ds_dib, ioENF, 0); /* request an interrupt */ +return; +} + + +/* Load a word into the FIFO. + + A word is loaded into the next available location in the FIFO, and the FIFO + occupancy count is incremented. If the FIFO is full on entry, the load is + ignored. + + + Implementation notes: + + 1. The FIFO is implemented as circular queue to take advantage of REG_CIRC + EXAMINE semantics. REG->qptr is the index of the first word currently in + the FIFO. By specifying REG_CIRC, examining FIFO[0-n] will always + display the words in load order, regardless of the actual array index of + the start of the list. The number of words currently present in the FIFO + is kept in fifo_count (0 = empty, 1-16 = number of words available). + + If fifo_count < FIFO_SIZE, (REG->qptr + fifo_count) mod FIFO_SIZE is the + index of the new word location. Loading stores the word there and then + increments fifo_count. + + 2. Because the load and unload routines need access to qptr in the REG + structure for the FIFO array, a pointer to the REG is stored in the + fifo_reg variable during device reset. +*/ + +static void fifo_load (uint16 data) +{ +uint32 index; + +if (FIFO_FULL) { /* is the FIFO already full? */ + if (DEBUG_PRI (ds_dev, DEB_BUF)) + fprintf (sim_deb, ">>DS buf: Attempted load to full FIFO, data %06o\n", data); + + return; /* return with the load ignored */ + } + +index = (ds.fifo_reg->qptr + ds.fifo_count) % FIFO_SIZE; /* calculate the index of the next available location */ + +ds.fifo [index] = data; /* store the word in the FIFO */ +ds.fifo_count = ds.fifo_count + 1; /* increment the count of words stored */ + +if (DEBUG_PRI (ds_dev, DEB_BUF)) + fprintf (sim_deb, ">>DS buf: Data %06o loaded into FIFO (%d)\n", + data, ds.fifo_count); + +return; +} + + +/* Unload a word from the FIFO. + + A word is unloaded from the first location in the FIFO, and the FIFO + occupancy count is decremented. If the FIFO is empty on entry, the unload + returns dummy data. + + + Implementation notes: + + 1. If fifo_count > 0, REG->qptr is the index of the word to remove. Removal + gets the word and then increments qptr (mod FIFO_SIZE) and decrements + fifo_count. +*/ + +static uint16 fifo_unload (void) +{ +uint16 data; + +if (FIFO_EMPTY) { /* is the FIFO already empty? */ + if (DEBUG_PRI (ds_dev, DEB_BUF)) + fputs (">>DS buf: Attempted unload from empty FIFO\n", sim_deb); + + return 0; /* return with no data */ + } + +data = ds.fifo [ds.fifo_reg->qptr]; /* get the word from the FIFO */ + +ds.fifo_reg->qptr = (ds.fifo_reg->qptr + 1) % FIFO_SIZE; /* update the FIFO queue pointer */ +ds.fifo_count = ds.fifo_count - 1; /* decrement the count of words stored */ + +if (DEBUG_PRI (ds_dev, DEB_BUF)) + fprintf (sim_deb, ">>DS buf: Data %06o unloaded from FIFO (%d)\n", + data, ds.fifo_count); + +return data; +} + + +/* Clear the FIFO. + + The FIFO is cleared by setting the occupancy counter to zero. +*/ + +static void fifo_clear (void) +{ +ds.fifo_count = 0; /* clear the FIFO */ + +if (DEBUG_PRI (ds_dev, DEB_BUF)) + fputs (">>DS buf: FIFO cleared\n", sim_deb); + +return; +} + + +/* Activate the unit. + + The specified unit is activated using the unit's "wait" time. If debugging + is enabled, the activation is logged to the debug file. +*/ + +static t_stat activate_unit (UNIT *uptr) +{ +int32 unit; +t_stat result; + +if (DEBUG_PRI (ds_dev, DEB_SERV)) { + unit = uptr - ds_unit; /* calculate the unit number */ + + if (uptr == &ds_cntlr) + fprintf (sim_deb, ">>DS serv: Controller delay %d service scheduled\n", + uptr->wait); + else + fprintf (sim_deb, ">>DS serv: Unit %d delay %d service scheduled\n", + unit, uptr->wait); + } + +result = sim_activate (uptr, uptr->wait); /* activate the unit */ +uptr->wait = 0; /* reset the activation time */ + +return result; /* return the activation status */ +} diff --git a/HP2100/hp2100_fp.c b/HP2100/hp2100_fp.c index 20ea6c47..c7125113 100644 --- a/HP2100/hp2100_fp.c +++ b/HP2100/hp2100_fp.c @@ -1,410 +1,410 @@ -/* hp2100_fp.c: HP 2100 floating point instructions - - Copyright (c) 2002-2015, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - 03-Jan-15 JDB Made the utility routines static - 21-Jan-08 JDB Corrected fp_unpack mantissa high-word return - (from Mark Pizzolato) - 01-Dec-06 JDB Reworked FFP helpers for 1000-F support, deleted f_pwr2 - 22-Jul-05 RMS Fixed compiler warning in Solaris (from Doug Glyn) - 25-Feb-05 JDB Added FFP helpers f_pack, f_unpack, f_pwr2 - 11-Feb-05 JDB Fixed missing negative overflow renorm in StoreFP - 26-Dec-04 RMS Separated A/B from M[0/1] for DMA IO (from Dave Bryan) - 15-Jul-03 RMS Fixed signed/unsigned warning - 21-Oct-02 RMS Recoded for compatibility with 21MX microcode algorithms - - - The HP2100 uses a unique binary floating point format: - - 15 14 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |S | fraction high | : A - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | fraction low | exponent |XS| : A + 1 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - 15 8 7 1 0 - - where S = 0 for plus fraction, 1 for minus fraction - fraction = s.bbbbb..., 24 binary digits - exponent = 2**+/-n - XS = 0 for plus exponent, 1 for minus exponent - - Numbers can be normalized or unnormalized but are always normalized - when loaded. - - Unpacked floating point numbers are stored in structure ufp - - exp = exponent, 2's complement - h'l = fraction, 2's comp, left justified - - This routine tries to reproduce the algorithms of the 2100/21MX - microcode in order to achieve 'bug-for-bug' compatibility. In - particular, - - - The FIX code produces various results in B. - - The fraction multiply code uses 16b x 16b multiplies to produce - a 31b result. It always loses the low order bit of the product. - - The fraction divide code is an approximation that may produce - an error of 1 LSB. - - Signs are tracked implicitly as part of the fraction. Unnormalized - inputs may cause the packup code to produce the wrong sign. - - "Unclean" zeros (zero fraction, non-zero exponent) are processed - like normal operands. - - Implementation notes: - - 1. The 2100/1000-M/E Fast FORTRAN Processor (FFP) and 1000 F-Series Floating - Point Processor (FPP) simulations require that the host compiler support - 64-bit integers and the HAVE_INT64 symbol be defined during compilation. - If this symbol is defined, two-word floating-point operations are handled - in the FPP code, and this module is not used. If it is not defined, then - FFP and FPP operations are not available, and this module provides the - floating-point support. -*/ - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" -#include "hp2100_fp.h" - -#if !defined (HAVE_INT64) /* int64 support unavailable */ - -struct ufp { /* unpacked fp */ - int32 exp; /* exp */ - uint32 fr; /* frac */ - }; - -#define FP_V_SIGN 31 /* sign */ -#define FP_M_SIGN 01 -#define FP_V_FR 8 /* fraction */ -#define FP_M_FR 077777777 -#define FP_V_EXP 1 /* exponent */ -#define FP_M_EXP 0177 -#define FP_V_EXPS 0 /* exp sign */ -#define FP_M_EXPS 01 -#define FP_SIGN (FP_M_SIGN << FP_V_SIGN) -#define FP_FR (FP_M_FR << FP_V_FR) -#define FP_EXP (FP_M_EXP << FP_V_EXP) -#define FP_EXPS (FP_M_EXPS << FP_V_EXPS) -#define FP_GETSIGN(x) (((x) >> FP_V_SIGN) & FP_M_SIGN) -#define FP_GETEXP(x) (((x) >> FP_V_EXP) & FP_M_EXP) -#define FP_GETEXPS(x) (((x) >> FP_V_EXPS) & FP_M_EXPS) - -#define FP_NORM (1 << (FP_V_SIGN - 1)) /* normalized */ -#define FP_LOW (1 << FP_V_FR) -#define FP_RNDP (1 << (FP_V_FR - 1)) /* round for plus */ -#define FP_RNDM (FP_RNDP - 1) /* round for minus */ - -#define FPAB ((((uint32) AR) << 16) | ((uint32) BR)) - -/* Fraction shift; 0 < shift < 32 */ - -#define FR_ARS(v,s) (((v) >> (s)) | (((v) & FP_SIGN)? \ - (((uint32) DMASK32) << (32 - (s))): 0)) & DMASK32 - -#define FR_NEG(v) ((~(v) + 1) & DMASK32) - -/* Utility routines */ - -static uint32 UnpackFP (struct ufp *fop, uint32 opnd); -static void NormFP (struct ufp *fop); -static uint32 PackFP (struct ufp *fop); -static uint32 StoreFP (struct ufp *fop); - -/* Floating to integer conversion */ - -uint32 f_fix (void) -{ -struct ufp fop; -uint32 res = 0; - -UnpackFP (&fop, FPAB); /* unpack op */ -if (fop.exp < 0) { /* exp < 0? */ - AR = 0; /* result = 0 */ - return 0; /* B unchanged */ - } -if (fop.exp > 15) { /* exp > 15? */ - BR = AR; /* B has high bits */ - AR = 077777; /* result = 77777 */ - return 1; /* overflow */ - } -if (fop.exp < 15) { /* if not aligned */ - res = FR_ARS (fop.fr, 15 - fop.exp); /* shift right */ - AR = (res >> 16) & DMASK; /* AR gets result */ - } -BR = AR; -if ((AR & SIGN) && ((fop.fr | res) & DMASK)) /* any low bits lost? */ - AR = (AR + 1) & DMASK; /* round up */ -return 0; -} - -/* Integer to floating conversion */ - -uint32 f_flt (void) -{ -struct ufp res = { 15, 0 }; /* +, 2**15 */ - -res.fr = ((uint32) AR) << 16; /* left justify */ -StoreFP (&res); /* store result */ -return 0; /* clr overflow */ -} - -/* Floating point add/subtract */ - -uint32 f_as (uint32 opnd, t_bool sub) -{ -struct ufp fop1, fop2, t; -int32 ediff; - -UnpackFP (&fop1, FPAB); /* unpack A-B */ -UnpackFP (&fop2, opnd); /* get op */ -if (sub) { /* subtract? */ - fop2.fr = FR_NEG (fop2.fr); /* negate frac */ - if (fop2.fr == ((uint32) FP_SIGN)) { /* -1/2? */ - fop2.fr = fop2.fr >> 1; /* special case */ - fop2.exp = fop2.exp + 1; - } - } -if (fop1.fr == 0) fop1 = fop2; /* op1 = 0? res = op2 */ -else if (fop2.fr != 0) { /* op2 = 0? no add */ - if (fop1.exp < fop2.exp) { /* |op1| < |op2|? */ - t = fop2; /* swap operands */ - fop2 = fop1; - fop1 = t; - } - ediff = fop1.exp - fop2.exp; /* get exp diff */ - if (ediff <= 24) { - if (ediff) fop2.fr = FR_ARS (fop2.fr, ediff); /* denorm, signed */ - if ((fop1.fr ^ fop2.fr) & FP_SIGN) /* unlike signs? */ - fop1.fr = fop1.fr + fop2.fr; /* eff subtract */ - else { /* like signs */ - fop1.fr = fop1.fr + fop2.fr; /* eff add */ - if (fop2.fr & FP_SIGN) { /* both -? */ - if ((fop1.fr & FP_SIGN) == 0) { /* overflow? */ - fop1.fr = FP_SIGN | (fop1.fr >> 1); /* renormalize */ - fop1.exp = fop1.exp + 1; /* incr exp */ - } - } - else if (fop1.fr & FP_SIGN) { /* both +, cry out? */ - fop1.fr = fop1.fr >> 1; /* renormalize */ - fop1.exp = fop1.exp + 1; /* incr exp */ - } - } /* end else like */ - } /* end if ediff */ - } /* end if fop2 */ -return StoreFP (&fop1); /* store result */ -} - -/* Floating point multiply - passes diagnostic */ - -uint32 f_mul (uint32 opnd) -{ -struct ufp fop1, fop2; -struct ufp res = { 0, 0 }; -int32 shi1, shi2, t1, t2, t3, t4, t5; - -UnpackFP (&fop1, FPAB); /* unpack A-B */ -UnpackFP (&fop2, opnd); /* unpack op */ -if (fop1.fr && fop2.fr) { /* if both != 0 */ - res.exp = fop1.exp + fop2.exp + 1; /* exp = sum */ - shi1 = SEXT (fop1.fr >> 16); /* mpy hi */ - shi2 = SEXT (fop2.fr >> 16); /* mpc hi */ - t1 = shi2 * ((int32) ((fop1.fr >> 1) & 077600)); /* mpc hi * (mpy lo/2) */ - t2 = shi1 * ((int32) ((fop2.fr >> 1) & 077600)); /* mpc lo * (mpy hi/2) */ - t3 = t1 + t2; /* cross product */ - t4 = (shi1 * shi2) & ~1; /* mpy hi * mpc hi */ - t5 = (SEXT (t3 >> 16)) << 1; /* add in cross */ - res.fr = (t4 + t5) & DMASK32; /* bit<0> is lost */ - } -return StoreFP (&res); /* store */ -} - -/* Floating point divide - reverse engineered from diagnostic */ - -static uint32 divx (uint32 ba, uint32 dvr, uint32 *rem) -{ -int32 sdvd = 0, sdvr = 0; -uint32 q, r; - -if (ba & FP_SIGN) sdvd = 1; /* 32b/16b signed dvd */ -if (dvr & SIGN) sdvr = 1; /* use old-fashioned */ -if (sdvd) ba = (~ba + 1) & DMASK32; /* unsigned divides, */ -if (sdvr) dvr = (~dvr + 1) & DMASK; /* as results may ovflo */ -q = ba / dvr; -r = ba % dvr; -if (sdvd ^ sdvr) q = (~q + 1) & DMASK; -if (sdvd) r = (~r + 1) & DMASK; -if (rem) *rem = r; -return q; -} - -uint32 f_div (uint32 opnd) -{ -struct ufp fop1, fop2; -struct ufp quo = { 0, 0 }; -uint32 ba, q0, q1, q2, dvrh; - -UnpackFP (&fop1, FPAB); /* unpack A-B */ -UnpackFP (&fop2, opnd); /* unpack op */ -dvrh = (fop2.fr >> 16) & DMASK; /* high divisor */ -if (dvrh == 0) { /* div by zero? */ - AR = 0077777; /* return most pos */ - BR = 0177776; - return 1; - } -if (fop1.fr) { /* dvd != 0? */ - quo.exp = fop1.exp - fop2.exp + 1; /* exp = diff */ - ba = FR_ARS (fop1.fr, 2); /* prevent ovflo */ - q0 = divx (ba, dvrh, &ba); /* Q0 = dvd / dvrh */ - ba = (ba & ~1) << 16; /* remainder */ - ba = FR_ARS (ba, 1); /* prevent ovflo */ - q1 = divx (ba, dvrh, NULL); /* Q1 = rem / dvrh */ - ba = (fop2.fr & 0xFF00) << 13; /* dvrl / 8 */ - q2 = divx (ba, dvrh, NULL); /* dvrl / dvrh */ - ba = -(SEXT (q2)) * (SEXT (q0)); /* -Q0 * Q2 */ - ba = (ba >> 16) & 0xFFFF; /* save ms half */ - if (q1 & SIGN) quo.fr = quo.fr - 0x00010000; /* Q1 < 0? -1 */ - if (ba & SIGN) quo.fr = quo.fr - 0x00010000; /* -Q0*Q2 < 0? */ - quo.fr = quo.fr + ((ba << 2) & 0xFFFF) + q1; /* rest prod, add Q1 */ - quo.fr = quo.fr << 1; /* shift result */ - quo.fr = quo.fr + (q0 << 16); /* add Q0 */ - } /* end if fop1.h */ -return StoreFP (&quo); /* store result */ -} - -/* Utility routines */ - -/* Unpack operand */ - -static uint32 UnpackFP (struct ufp *fop, uint32 opnd) -{ -fop->fr = opnd & FP_FR; /* get frac */ -fop->exp = FP_GETEXP (opnd); /* get exp */ -if (FP_GETEXPS (opnd)) fop->exp = fop->exp | ~FP_M_EXP; /* < 0? sext */ -return FP_GETSIGN (opnd); /* return sign */ -} - -/* Normalize unpacked floating point number */ - -static void NormFP (struct ufp *fop) -{ -if (fop->fr) { /* any fraction? */ - uint32 test = (fop->fr >> 1) & FP_NORM; - while ((fop->fr & FP_NORM) == test) { /* until norm */ - fop->exp = fop->exp - 1; - fop->fr = (fop->fr << 1); - } - } -else fop->exp = 0; /* clean 0 */ -return; -} - -/* Pack fp number */ - -static uint32 PackFP (struct ufp *fop) -{ -return (fop->fr & FP_FR) | /* merge frac */ - ((fop->exp & FP_M_EXP) << FP_V_EXP) | /* and exp */ - ((fop->exp < 0)? (1 << FP_V_EXPS): 0); /* add exp sign */ -} - -/* Round fp number, store, generate overflow */ - -static uint32 StoreFP (struct ufp *fop) -{ -uint32 sign, svfr, hi, ov = 0; - -NormFP (fop); /* normalize */ -svfr = fop->fr; /* save fraction */ -sign = FP_GETSIGN (fop->fr); /* save sign */ -fop->fr = (fop->fr + (sign? FP_RNDM: FP_RNDP)) & FP_FR; /* round */ -if ((fop->fr ^ svfr) & FP_SIGN) { /* sign change? */ - fop->fr = fop->fr >> 1; /* renormalize */ - fop->exp = fop->exp + 1; - } -else NormFP (fop); /* check for norm */ -if (fop->fr == 0) hi = 0; /* result 0? */ -else if (fop->exp < -(FP_M_EXP + 1)) { /* underflow? */ - hi = 0; /* store clean 0 */ - ov = 1; - } -else if (fop->exp > FP_M_EXP) { /* overflow? */ - hi = 0x7FFFFFFE; /* all 1's */ - ov = 1; - } -else hi = PackFP (fop); /* pack mant and exp */ -AR = (hi >> 16) & DMASK; -BR = hi & DMASK; -return ov; -} - - -/* Single-precision Fast FORTRAN Processor helpers. */ - -/* Pack mantissa and exponent and return fp value. */ - -uint32 fp_pack (OP *result, OP mantissa, int32 exponent, OPSIZE precision) -{ -struct ufp fop; -uint32 val; - -fop.fr = ((uint32) mantissa.fpk[0] << 16) | mantissa.fpk[1]; -fop.exp = exponent; -val = PackFP (&fop); -result->fpk[0] = (int16) (val >> 16); -result->fpk[1] = (int16) val; -return 0; -} - -/* Normalize, round, and pack mantissa and exponent and return fp value. */ - -uint32 fp_nrpack (OP *result, OP mantissa, int32 exponent, OPSIZE precision) -{ -struct ufp fop; -uint32 ovf; - -fop.fr = ((uint32) mantissa.fpk[0] << 16) | mantissa.fpk[1]; -fop.exp = exponent; -ovf = StoreFP (&fop); -result->fpk[0] = AR; -result->fpk[1] = BR; -return ovf; -} - -/* Unpack fp number in into mantissa and exponent. */ - -uint32 fp_unpack (OP *mantissa, int32 *exponent, OP packed, OPSIZE precision) -{ -struct ufp fop; -uint32 operand; - -operand = ((uint32) packed.fpk[0] << 16) | packed.fpk[1]; -UnpackFP (&fop, operand); -mantissa->fpk[0] = (uint16) (fop.fr >> 16); -mantissa->fpk[1] = (uint16) fop.fr; -*exponent = fop.exp; -return 0; -} - -#endif /* int64 support unavailable */ +/* hp2100_fp.c: HP 2100 floating point instructions + + Copyright (c) 2002-2015, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + 03-Jan-15 JDB Made the utility routines static + 21-Jan-08 JDB Corrected fp_unpack mantissa high-word return + (from Mark Pizzolato) + 01-Dec-06 JDB Reworked FFP helpers for 1000-F support, deleted f_pwr2 + 22-Jul-05 RMS Fixed compiler warning in Solaris (from Doug Glyn) + 25-Feb-05 JDB Added FFP helpers f_pack, f_unpack, f_pwr2 + 11-Feb-05 JDB Fixed missing negative overflow renorm in StoreFP + 26-Dec-04 RMS Separated A/B from M[0/1] for DMA IO (from Dave Bryan) + 15-Jul-03 RMS Fixed signed/unsigned warning + 21-Oct-02 RMS Recoded for compatibility with 21MX microcode algorithms + + + The HP2100 uses a unique binary floating point format: + + 15 14 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |S | fraction high | : A + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | fraction low | exponent |XS| : A + 1 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + 15 8 7 1 0 + + where S = 0 for plus fraction, 1 for minus fraction + fraction = s.bbbbb..., 24 binary digits + exponent = 2**+/-n + XS = 0 for plus exponent, 1 for minus exponent + + Numbers can be normalized or unnormalized but are always normalized + when loaded. + + Unpacked floating point numbers are stored in structure ufp + + exp = exponent, 2's complement + h'l = fraction, 2's comp, left justified + + This routine tries to reproduce the algorithms of the 2100/21MX + microcode in order to achieve 'bug-for-bug' compatibility. In + particular, + + - The FIX code produces various results in B. + - The fraction multiply code uses 16b x 16b multiplies to produce + a 31b result. It always loses the low order bit of the product. + - The fraction divide code is an approximation that may produce + an error of 1 LSB. + - Signs are tracked implicitly as part of the fraction. Unnormalized + inputs may cause the packup code to produce the wrong sign. + - "Unclean" zeros (zero fraction, non-zero exponent) are processed + like normal operands. + + Implementation notes: + + 1. The 2100/1000-M/E Fast FORTRAN Processor (FFP) and 1000 F-Series Floating + Point Processor (FPP) simulations require that the host compiler support + 64-bit integers and the HAVE_INT64 symbol be defined during compilation. + If this symbol is defined, two-word floating-point operations are handled + in the FPP code, and this module is not used. If it is not defined, then + FFP and FPP operations are not available, and this module provides the + floating-point support. +*/ + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" +#include "hp2100_fp.h" + +#if !defined (HAVE_INT64) /* int64 support unavailable */ + +struct ufp { /* unpacked fp */ + int32 exp; /* exp */ + uint32 fr; /* frac */ + }; + +#define FP_V_SIGN 31 /* sign */ +#define FP_M_SIGN 01 +#define FP_V_FR 8 /* fraction */ +#define FP_M_FR 077777777 +#define FP_V_EXP 1 /* exponent */ +#define FP_M_EXP 0177 +#define FP_V_EXPS 0 /* exp sign */ +#define FP_M_EXPS 01 +#define FP_SIGN (FP_M_SIGN << FP_V_SIGN) +#define FP_FR (FP_M_FR << FP_V_FR) +#define FP_EXP (FP_M_EXP << FP_V_EXP) +#define FP_EXPS (FP_M_EXPS << FP_V_EXPS) +#define FP_GETSIGN(x) (((x) >> FP_V_SIGN) & FP_M_SIGN) +#define FP_GETEXP(x) (((x) >> FP_V_EXP) & FP_M_EXP) +#define FP_GETEXPS(x) (((x) >> FP_V_EXPS) & FP_M_EXPS) + +#define FP_NORM (1 << (FP_V_SIGN - 1)) /* normalized */ +#define FP_LOW (1 << FP_V_FR) +#define FP_RNDP (1 << (FP_V_FR - 1)) /* round for plus */ +#define FP_RNDM (FP_RNDP - 1) /* round for minus */ + +#define FPAB ((((uint32) AR) << 16) | ((uint32) BR)) + +/* Fraction shift; 0 < shift < 32 */ + +#define FR_ARS(v,s) (((v) >> (s)) | (((v) & FP_SIGN)? \ + (((uint32) DMASK32) << (32 - (s))): 0)) & DMASK32 + +#define FR_NEG(v) ((~(v) + 1) & DMASK32) + +/* Utility routines */ + +static uint32 UnpackFP (struct ufp *fop, uint32 opnd); +static void NormFP (struct ufp *fop); +static uint32 PackFP (struct ufp *fop); +static uint32 StoreFP (struct ufp *fop); + +/* Floating to integer conversion */ + +uint32 f_fix (void) +{ +struct ufp fop; +uint32 res = 0; + +UnpackFP (&fop, FPAB); /* unpack op */ +if (fop.exp < 0) { /* exp < 0? */ + AR = 0; /* result = 0 */ + return 0; /* B unchanged */ + } +if (fop.exp > 15) { /* exp > 15? */ + BR = AR; /* B has high bits */ + AR = 077777; /* result = 77777 */ + return 1; /* overflow */ + } +if (fop.exp < 15) { /* if not aligned */ + res = FR_ARS (fop.fr, 15 - fop.exp); /* shift right */ + AR = (res >> 16) & DMASK; /* AR gets result */ + } +BR = AR; +if ((AR & SIGN) && ((fop.fr | res) & DMASK)) /* any low bits lost? */ + AR = (AR + 1) & DMASK; /* round up */ +return 0; +} + +/* Integer to floating conversion */ + +uint32 f_flt (void) +{ +struct ufp res = { 15, 0 }; /* +, 2**15 */ + +res.fr = ((uint32) AR) << 16; /* left justify */ +StoreFP (&res); /* store result */ +return 0; /* clr overflow */ +} + +/* Floating point add/subtract */ + +uint32 f_as (uint32 opnd, t_bool sub) +{ +struct ufp fop1, fop2, t; +int32 ediff; + +UnpackFP (&fop1, FPAB); /* unpack A-B */ +UnpackFP (&fop2, opnd); /* get op */ +if (sub) { /* subtract? */ + fop2.fr = FR_NEG (fop2.fr); /* negate frac */ + if (fop2.fr == ((uint32) FP_SIGN)) { /* -1/2? */ + fop2.fr = fop2.fr >> 1; /* special case */ + fop2.exp = fop2.exp + 1; + } + } +if (fop1.fr == 0) fop1 = fop2; /* op1 = 0? res = op2 */ +else if (fop2.fr != 0) { /* op2 = 0? no add */ + if (fop1.exp < fop2.exp) { /* |op1| < |op2|? */ + t = fop2; /* swap operands */ + fop2 = fop1; + fop1 = t; + } + ediff = fop1.exp - fop2.exp; /* get exp diff */ + if (ediff <= 24) { + if (ediff) fop2.fr = FR_ARS (fop2.fr, ediff); /* denorm, signed */ + if ((fop1.fr ^ fop2.fr) & FP_SIGN) /* unlike signs? */ + fop1.fr = fop1.fr + fop2.fr; /* eff subtract */ + else { /* like signs */ + fop1.fr = fop1.fr + fop2.fr; /* eff add */ + if (fop2.fr & FP_SIGN) { /* both -? */ + if ((fop1.fr & FP_SIGN) == 0) { /* overflow? */ + fop1.fr = FP_SIGN | (fop1.fr >> 1); /* renormalize */ + fop1.exp = fop1.exp + 1; /* incr exp */ + } + } + else if (fop1.fr & FP_SIGN) { /* both +, cry out? */ + fop1.fr = fop1.fr >> 1; /* renormalize */ + fop1.exp = fop1.exp + 1; /* incr exp */ + } + } /* end else like */ + } /* end if ediff */ + } /* end if fop2 */ +return StoreFP (&fop1); /* store result */ +} + +/* Floating point multiply - passes diagnostic */ + +uint32 f_mul (uint32 opnd) +{ +struct ufp fop1, fop2; +struct ufp res = { 0, 0 }; +int32 shi1, shi2, t1, t2, t3, t4, t5; + +UnpackFP (&fop1, FPAB); /* unpack A-B */ +UnpackFP (&fop2, opnd); /* unpack op */ +if (fop1.fr && fop2.fr) { /* if both != 0 */ + res.exp = fop1.exp + fop2.exp + 1; /* exp = sum */ + shi1 = SEXT (fop1.fr >> 16); /* mpy hi */ + shi2 = SEXT (fop2.fr >> 16); /* mpc hi */ + t1 = shi2 * ((int32) ((fop1.fr >> 1) & 077600)); /* mpc hi * (mpy lo/2) */ + t2 = shi1 * ((int32) ((fop2.fr >> 1) & 077600)); /* mpc lo * (mpy hi/2) */ + t3 = t1 + t2; /* cross product */ + t4 = (shi1 * shi2) & ~1; /* mpy hi * mpc hi */ + t5 = (SEXT (t3 >> 16)) << 1; /* add in cross */ + res.fr = (t4 + t5) & DMASK32; /* bit<0> is lost */ + } +return StoreFP (&res); /* store */ +} + +/* Floating point divide - reverse engineered from diagnostic */ + +static uint32 divx (uint32 ba, uint32 dvr, uint32 *rem) +{ +int32 sdvd = 0, sdvr = 0; +uint32 q, r; + +if (ba & FP_SIGN) sdvd = 1; /* 32b/16b signed dvd */ +if (dvr & SIGN) sdvr = 1; /* use old-fashioned */ +if (sdvd) ba = (~ba + 1) & DMASK32; /* unsigned divides, */ +if (sdvr) dvr = (~dvr + 1) & DMASK; /* as results may ovflo */ +q = ba / dvr; +r = ba % dvr; +if (sdvd ^ sdvr) q = (~q + 1) & DMASK; +if (sdvd) r = (~r + 1) & DMASK; +if (rem) *rem = r; +return q; +} + +uint32 f_div (uint32 opnd) +{ +struct ufp fop1, fop2; +struct ufp quo = { 0, 0 }; +uint32 ba, q0, q1, q2, dvrh; + +UnpackFP (&fop1, FPAB); /* unpack A-B */ +UnpackFP (&fop2, opnd); /* unpack op */ +dvrh = (fop2.fr >> 16) & DMASK; /* high divisor */ +if (dvrh == 0) { /* div by zero? */ + AR = 0077777; /* return most pos */ + BR = 0177776; + return 1; + } +if (fop1.fr) { /* dvd != 0? */ + quo.exp = fop1.exp - fop2.exp + 1; /* exp = diff */ + ba = FR_ARS (fop1.fr, 2); /* prevent ovflo */ + q0 = divx (ba, dvrh, &ba); /* Q0 = dvd / dvrh */ + ba = (ba & ~1) << 16; /* remainder */ + ba = FR_ARS (ba, 1); /* prevent ovflo */ + q1 = divx (ba, dvrh, NULL); /* Q1 = rem / dvrh */ + ba = (fop2.fr & 0xFF00) << 13; /* dvrl / 8 */ + q2 = divx (ba, dvrh, NULL); /* dvrl / dvrh */ + ba = -(SEXT (q2)) * (SEXT (q0)); /* -Q0 * Q2 */ + ba = (ba >> 16) & 0xFFFF; /* save ms half */ + if (q1 & SIGN) quo.fr = quo.fr - 0x00010000; /* Q1 < 0? -1 */ + if (ba & SIGN) quo.fr = quo.fr - 0x00010000; /* -Q0*Q2 < 0? */ + quo.fr = quo.fr + ((ba << 2) & 0xFFFF) + q1; /* rest prod, add Q1 */ + quo.fr = quo.fr << 1; /* shift result */ + quo.fr = quo.fr + (q0 << 16); /* add Q0 */ + } /* end if fop1.h */ +return StoreFP (&quo); /* store result */ +} + +/* Utility routines */ + +/* Unpack operand */ + +static uint32 UnpackFP (struct ufp *fop, uint32 opnd) +{ +fop->fr = opnd & FP_FR; /* get frac */ +fop->exp = FP_GETEXP (opnd); /* get exp */ +if (FP_GETEXPS (opnd)) fop->exp = fop->exp | ~FP_M_EXP; /* < 0? sext */ +return FP_GETSIGN (opnd); /* return sign */ +} + +/* Normalize unpacked floating point number */ + +static void NormFP (struct ufp *fop) +{ +if (fop->fr) { /* any fraction? */ + uint32 test = (fop->fr >> 1) & FP_NORM; + while ((fop->fr & FP_NORM) == test) { /* until norm */ + fop->exp = fop->exp - 1; + fop->fr = (fop->fr << 1); + } + } +else fop->exp = 0; /* clean 0 */ +return; +} + +/* Pack fp number */ + +static uint32 PackFP (struct ufp *fop) +{ +return (fop->fr & FP_FR) | /* merge frac */ + ((fop->exp & FP_M_EXP) << FP_V_EXP) | /* and exp */ + ((fop->exp < 0)? (1 << FP_V_EXPS): 0); /* add exp sign */ +} + +/* Round fp number, store, generate overflow */ + +static uint32 StoreFP (struct ufp *fop) +{ +uint32 sign, svfr, hi, ov = 0; + +NormFP (fop); /* normalize */ +svfr = fop->fr; /* save fraction */ +sign = FP_GETSIGN (fop->fr); /* save sign */ +fop->fr = (fop->fr + (sign? FP_RNDM: FP_RNDP)) & FP_FR; /* round */ +if ((fop->fr ^ svfr) & FP_SIGN) { /* sign change? */ + fop->fr = fop->fr >> 1; /* renormalize */ + fop->exp = fop->exp + 1; + } +else NormFP (fop); /* check for norm */ +if (fop->fr == 0) hi = 0; /* result 0? */ +else if (fop->exp < -(FP_M_EXP + 1)) { /* underflow? */ + hi = 0; /* store clean 0 */ + ov = 1; + } +else if (fop->exp > FP_M_EXP) { /* overflow? */ + hi = 0x7FFFFFFE; /* all 1's */ + ov = 1; + } +else hi = PackFP (fop); /* pack mant and exp */ +AR = (hi >> 16) & DMASK; +BR = hi & DMASK; +return ov; +} + + +/* Single-precision Fast FORTRAN Processor helpers. */ + +/* Pack mantissa and exponent and return fp value. */ + +uint32 fp_pack (OP *result, OP mantissa, int32 exponent, OPSIZE precision) +{ +struct ufp fop; +uint32 val; + +fop.fr = ((uint32) mantissa.fpk[0] << 16) | mantissa.fpk[1]; +fop.exp = exponent; +val = PackFP (&fop); +result->fpk[0] = (int16) (val >> 16); +result->fpk[1] = (int16) val; +return 0; +} + +/* Normalize, round, and pack mantissa and exponent and return fp value. */ + +uint32 fp_nrpack (OP *result, OP mantissa, int32 exponent, OPSIZE precision) +{ +struct ufp fop; +uint32 ovf; + +fop.fr = ((uint32) mantissa.fpk[0] << 16) | mantissa.fpk[1]; +fop.exp = exponent; +ovf = StoreFP (&fop); +result->fpk[0] = AR; +result->fpk[1] = BR; +return ovf; +} + +/* Unpack fp number in into mantissa and exponent. */ + +uint32 fp_unpack (OP *mantissa, int32 *exponent, OP packed, OPSIZE precision) +{ +struct ufp fop; +uint32 operand; + +operand = ((uint32) packed.fpk[0] << 16) | packed.fpk[1]; +UnpackFP (&fop, operand); +mantissa->fpk[0] = (uint16) (fop.fr >> 16); +mantissa->fpk[1] = (uint16) fop.fr; +*exponent = fop.exp; +return 0; +} + +#endif /* int64 support unavailable */ diff --git a/HP2100/hp2100_fp.h b/HP2100/hp2100_fp.h index a3d5b33a..09a3fc72 100644 --- a/HP2100/hp2100_fp.h +++ b/HP2100/hp2100_fp.h @@ -1,48 +1,48 @@ -/* hp2100_fp.h: HP 2100/21MX floating point definitions - - Copyright (c) 2002-2013, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - 14-Mar-13 MP Changed guard macro name to avoid reserved namespace - 01-Dec-06 JDB Reworked FFP helpers for 1000-F support, deleted f_pwr2 - 26-Sep-06 JDB Moved from hp2100_fp.c to simplify extensions -*/ - -#ifndef HP2100_FP_H_ -#define HP2100_FP_H_ - -/* Firmware floating-point routines */ - -uint32 f_as (uint32 op, t_bool sub); /* FAD/FSB */ -uint32 f_mul (uint32 op); /* FMP */ -uint32 f_div (uint32 op); /* FDV */ -uint32 f_fix (void); /* FIX */ -uint32 f_flt (void); /* FLT */ - -/* Firmware FFP helpers */ - -uint32 fp_pack (OP *result, OP mantissa, int32 exponent, OPSIZE precision); -uint32 fp_nrpack (OP *result, OP mantissa, int32 exponent, OPSIZE precision); -uint32 fp_unpack (OP *mantissa, int32 *exponent, OP packed, OPSIZE precision); - -#endif +/* hp2100_fp.h: HP 2100/21MX floating point definitions + + Copyright (c) 2002-2013, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + 14-Mar-13 MP Changed guard macro name to avoid reserved namespace + 01-Dec-06 JDB Reworked FFP helpers for 1000-F support, deleted f_pwr2 + 26-Sep-06 JDB Moved from hp2100_fp.c to simplify extensions +*/ + +#ifndef HP2100_FP_H_ +#define HP2100_FP_H_ + +/* Firmware floating-point routines */ + +uint32 f_as (uint32 op, t_bool sub); /* FAD/FSB */ +uint32 f_mul (uint32 op); /* FMP */ +uint32 f_div (uint32 op); /* FDV */ +uint32 f_fix (void); /* FIX */ +uint32 f_flt (void); /* FLT */ + +/* Firmware FFP helpers */ + +uint32 fp_pack (OP *result, OP mantissa, int32 exponent, OPSIZE precision); +uint32 fp_nrpack (OP *result, OP mantissa, int32 exponent, OPSIZE precision); +uint32 fp_unpack (OP *mantissa, int32 *exponent, OP packed, OPSIZE precision); + +#endif diff --git a/HP2100/hp2100_fp1.c b/HP2100/hp2100_fp1.c index 47d24b84..2dc48449 100644 --- a/HP2100/hp2100_fp1.c +++ b/HP2100/hp2100_fp1.c @@ -1,1461 +1,1462 @@ -/* hp2100_fp1.c: HP 1000 multiple-precision floating point routines - - Copyright (c) 2005-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be used - in advertising or otherwise to promote the sale, use or other dealings in - this Software without prior written authorization from the author. - - 24-Dec-14 JDB Added casts for explicit downward conversions - Changed fp_ucom return from uint32 to uint16 - 18-Mar-13 JDB Changed type of mantissa masks array to to unsigned - 06-Feb-12 JDB Added missing precision on constant "one" in fp_trun - 21-Jun-11 JDB Completed the comments for divide; no code changes - 08-Jun-08 JDB Quieted bogus gcc warning in fp_exec - 10-May-08 JDB Fixed uninitialized return in fp_accum when setting - 19-Mar-08 JDB Reworked "complement" to avoid inlining bug in gcc-4.x - 01-Dec-06 JDB Reworked into generalized multiple-precision ops for FPP - 12-Oct-06 JDB Altered x_trun for F-Series FFP compatibility - Added F-Series ..TCM FFP helpers - - Primary references: - - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation - (92851-90001, Mar-1981) - - HP 1000 M/E/F-Series Computers Technical Reference Handbook - (5955-0282, Mar-1980) - - DOS/RTE Relocatable Library Reference Manual - (24998-90001, Oct-1981) - - - This module implements multiple-precision floating-point operations to - support the 1000 F-Series hardware Floating Point Processor. It employs - 64-bit integer arithmetic for speed and simplicity of implementation. The - host compiler must support 64-bit integers, and the HAVE_INT64 symbol must be - defined during compilation. If this symbol is not defined, then FPP support - is not available. - - HP 2100/1000 computers used a proprietary floating-point format. The 2100 - had optional firmware that provided two-word floating-point add, subtract, - multiply, and divide, as well as single-integer fix and float. The 1000-M/E - provided the same two-word firmware operations as standard equipment. - Three-word extended-precision instructions for the 2100 and 1000-M/E were - provided by the optional Fast FORTRAN Processor firmware. - - The 1000-F substituted a hardware floating point processor for the firmware - in previous machines. In addition to the two- and three-word formats, the - F-Series introduced a four-word double-precision format. A five-word format - that provided extra range in the exponent by unpacking it from the mantissa - was also provided, although this capability was not documented in the user - manual. In addition, the FPP improved the accuracy of floating-point - calculations, as the firmware versions sacrificed a few bits of precision to - gain speed. Consequently, operations on the F-Series may return results that - differ slightly from the same operations on the M/E-Series or the 2100. - - F-Series units after date code 1920 also provided two-word double-integer - instructions in firmware, as well as double-integer fix and float operations. - - The original 32-bit floating-point format is as follows: - - 15 14 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |MS| mantissa high | : M - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa low | exponent |XS| : M + 1 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - 15 8 7 1 0 - - Both 23-bit mantissa and 7-bit exponent are in twos-complement form. The - exponent sign bit has been rotated into the LSB of the second word. - - The extended-precision floating-point format is a 48-bit extension of the - 32-bit format used for single precision. A packed extended-precision value - consists of a 39-bit mantissa and a 7-bit exponent. The format is as - follows: - - 15 14 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |MS| mantissa high | : M - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa middle | : M + 1 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa low | exponent |XS| : M + 2 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - 15 8 7 1 0 - - The double-precision floating-point format is similar to the 48-bit - extended-precision format, although with a 55-bit mantissa: - - 15 14 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |MS| mantissa high | : M - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa middle high | : M + 1 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa middle low | : M + 2 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa low | exponent |XS| : M + 3 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - 15 8 7 1 0 - - The FPP also supports a special five-word expanded-exponent format: - - 15 14 0 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - |MS| mantissa high | : M - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa middle high | : M + 1 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa middle low | : M + 2 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | mantissa low | : M + 3 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | exponent |XS| : M + 4 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - 15 8 7 1 0 - - The exponent is a full 16-bit twos-complement value, but the allowed range is - only 10 bits, i.e., -512 to +511. - - In a normalized value, the sign and MSB of the mantissa differ. Zero is - represented by all words = 0. - - Internally, unpacked floating-point values are contained in a structure - having a signed 64-bit mantissa and a signed 32-bit exponent. Mantissas are - left-justified with the unused bits masked to zero. Exponents are - right-justified. The precision is indicated by the value of a structure - field. - - HP terminology for the three-word floating-point format is confused. Some - documents refer to it as "double precision," while others use "extended - precision." The instruction mnemonics begin with "X" (e.g., .XADD), - suggesting the extended-precision term. - - HP apparently intended that the four-word double-precision format would be - called "triple-precision," as the instruction mnemonics begin with "T" (e.g., - ".TADD" for the four-word add instruction). The source files for the - software simulations of these instructions for the M/E-Series also explicitly - refer to "triple precision math." However, the engineering documentation and - the F-Series reference manual both use the double-precision term. - - This module adopts the single/extended/double terminology and uses the - initial letters of the instructions (F/X/T) to indicate the precision used. - - The FPP hardware consisted of two circuit boards that interfaced to the main - CPU via the Microprogrammable Processor Port (MPP) that had been introduced - with the 1000 E-Series. One board contained argument registers and ALUs, - split into separate mantissa and exponent parts. The other contained a state - machine sequencer. FPP results were copied automatically to the argument - registers in addition to being available over the MPP, so that chained - operations could be executed from these "accumulators" without reloading. - - The FPP operated independently of the CPU. An opcode, specifying one of the - six operations (add, subtract, multiply, divide, fix, or float) was sent to - the FPP, and a start command was given. Operands of appropriate precision - were then supplied to the FPP. Once the operands were received, the FPP - would execute and set a flag when the operation was complete. The result - would then be retrieved from the FPP. The floating-point instruction - firmware in the CPU initiated the desired FPP operation and handled operand - reads from and result writes to main memory. - - Under simulation, "fp_exec" provides the six arithmetic operations analogous - to FPP execution. The remainder of the functions are helpers that were - provided by firmware in the 1000-F but that can reuse code needed to simulate - the FPP hardware. As with the hardware, "fp_exec" retains the last result - in an internal accumulator that may be referenced in subsequent operations. - - NOTE: this module also provides the floating-point support for the firmware - single-precision 1000-M/E base set and extended-precision FFP instructions. - Because the firmware and hardware implementations returned slightly different - results, particularly with respect to round-off, conditional checks are - implemented in the arithmetic routines. In some cases, entirely different - algorithms are used to ensure fidelity with the real machines. Functionally, - this means that the 2100/1000-M/E and 1000-F floating-point diagnostics are - not interchangeable, and failures are to be expected if a diagnostic is run - on the wrong machine. -*/ - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "hp2100_cpu1.h" -#include "hp2100_fp1.h" - - -#if defined (HAVE_INT64) /* we need int64 support */ - -/* Field widths. */ - -#define IN_W_SIGN 1 -#define IN_W_SMAGN 15 -#define IN_W_DMAGN 31 - -#define FP_W_MSIGN 1 -#define FP_W_FMANT 23 -#define FP_W_XMANT 39 -#define FP_W_TMANT 55 -#define FP_W_EMANT 55 -#define FP_W_EXPANDEXP 9 -#define FP_W_EXP 7 -#define FP_W_ESIGN 1 - -/* Starting bit numbers. */ - -#define IN_V_SIGN (64 - IN_W_SIGN) -#define IN_V_SNUM (64 - IN_W_SIGN - IN_W_SMAGN) -#define IN_V_DNUM (64 - IN_W_SIGN - IN_W_DMAGN) - -#define FP_V_FNUM (64 - FP_W_MSIGN - FP_W_FMANT - FP_W_EXP - FP_W_ESIGN) -#define FP_V_XNUM (64 - FP_W_MSIGN - FP_W_XMANT - FP_W_EXP - FP_W_ESIGN) -#define FP_V_TNUM (64 - FP_W_MSIGN - FP_W_TMANT - FP_W_EXP - FP_W_ESIGN) -#define FP_V_ENUM (64 - FP_W_MSIGN - FP_W_EMANT - FP_W_EXP - FP_W_ESIGN) - -#define FP_V_MSIGN (64 - FP_W_MSIGN) -#define FP_V_FMANT (64 - FP_W_MSIGN - FP_W_FMANT) -#define FP_V_XMANT (64 - FP_W_MSIGN - FP_W_XMANT) -#define FP_V_TMANT (64 - FP_W_MSIGN - FP_W_TMANT) -#define FP_V_EMANT (64 - FP_W_MSIGN - FP_W_EMANT) -#define FP_V_EXP 1 -#define FP_V_ESIGN 0 - -/* Right-aligned field masks. */ - -#define IN_M_SIGN (((t_uint64) 1 << IN_W_SIGN) - 1) -#define IN_M_SMAGN (((t_uint64) 1 << IN_W_SMAGN) - 1) -#define IN_M_DMAGN (((t_uint64) 1 << IN_W_DMAGN) - 1) - -#define FP_M_MSIGN (((t_uint64) 1 << FP_W_MSIGN) - 1) -#define FP_M_FMANT (((t_uint64) 1 << FP_W_FMANT) - 1) -#define FP_M_XMANT (((t_uint64) 1 << FP_W_XMANT) - 1) -#define FP_M_TMANT (((t_uint64) 1 << FP_W_TMANT) - 1) -#define FP_M_EMANT (((t_uint64) 1 << FP_W_EMANT) - 1) - -#define FP_M_EXPANDEXP ((1 << FP_W_EXPANDEXP) - 1) -#define FP_M_EXP ((1 << FP_W_EXP) - 1) -#define FP_M_ESIGN ((1 << FP_W_ESIGN) - 1) - -/* In-place field masks. */ - -#define IN_SIGN (IN_M_SIGN << IN_V_SIGN) -#define IN_SMAGN (IN_M_SMAGN << IN_V_SNUM) -#define IN_DMAGN (IN_M_DMAGN << IN_V_DNUM) - -#define FP_MSIGN (FP_M_MSIGN << FP_V_MSIGN) -#define FP_FMANT (FP_M_FMANT << FP_V_FMANT) -#define FP_XMANT (FP_M_XMANT << FP_V_XMANT) -#define FP_TMANT (FP_M_TMANT << FP_V_TMANT) -#define FP_EMANT (FP_M_EMANT << FP_V_EMANT) -#define FP_EXP (FP_M_EXP << FP_V_EXP) -#define FP_ESIGN (FP_M_ESIGN << FP_V_ESIGN) - -/* In-place record masks. */ - -#define IN_SSMAGN (IN_SIGN | IN_SMAGN) -#define IN_SDMAGN (IN_SIGN | IN_DMAGN) - -#define FP_SFMANT (FP_MSIGN | FP_FMANT) -#define FP_SXMANT (FP_MSIGN | FP_XMANT) -#define FP_STMANT (FP_MSIGN | FP_TMANT) -#define FP_SEMANT (FP_MSIGN | FP_EMANT) -#define FP_SEXP (FP_ESIGN | FP_EXP) - -/* Minima and maxima. */ - -#define FP_ONEHALF ((t_int64) 1 << (FP_V_MSIGN - 1)) /* mantissa = 0.5 */ -#define FP_MAXPMANT ((t_int64) FP_EMANT) /* maximum pos mantissa */ -#define FP_MAXNMANT ((t_int64) FP_MSIGN) /* maximum neg mantissa */ -#define FP_MAXPEXP (FP_M_EXPANDEXP) /* maximum pos expanded exponent */ -#define FP_MAXNEXP (-(FP_MAXPEXP + 1)) /* maximum neg expanded exponent */ - -/* Floating-point helpers. */ - -#define DENORM(x) ((((x) ^ (x) << 1) & FP_MSIGN) == 0) - -#define TO_EXP(e) (int8) ((e >> FP_V_EXP & FP_M_EXP) | \ - (e & FP_M_ESIGN ? ~FP_M_EXP : 0)) - -/* Property constants. */ - -static const t_int64 p_half_lsb[6] = { ((t_int64) 1 << IN_V_SNUM) - 1, /* different than FP! */ - ((t_int64) 1 << IN_V_DNUM) - 1, /* different than FP! */ - (t_int64) 1 << (FP_V_FMANT - 1), - (t_int64) 1 << (FP_V_XMANT - 1), - (t_int64) 1 << (FP_V_TMANT - 1), - (t_int64) 1 << (FP_V_EMANT - 1) }; - -static const t_int64 n_half_lsb[6] = { 0, - 0, - ((t_int64) 1 << (FP_V_FMANT - 1)) - 1, - ((t_int64) 1 << (FP_V_XMANT - 1)) - 1, - ((t_int64) 1 << (FP_V_TMANT - 1)) - 1, - ((t_int64) 1 << (FP_V_EMANT - 1)) - 1 }; - -static const uint32 op_start[6] = { IN_V_SNUM, - IN_V_DNUM, - FP_V_FMANT, - FP_V_XMANT, - FP_V_TMANT, - FP_V_EMANT }; - -static const t_uint64 mant_mask[6] = { IN_SSMAGN, - IN_SDMAGN, - FP_SFMANT, - FP_SXMANT, - FP_STMANT, - FP_SEMANT }; - -static const uint32 op_bits[6] = { IN_W_SMAGN, - IN_W_DMAGN, - FP_W_FMANT + FP_W_MSIGN, - FP_W_XMANT + FP_W_MSIGN, - FP_W_TMANT + FP_W_MSIGN, - FP_W_EMANT + FP_W_MSIGN }; - -static const t_int64 op_mask[6] = { ~(t_int64) 0 << IN_V_SNUM, - ~(t_int64) 0 << IN_V_DNUM, - ~(t_int64) 0 << FP_V_FNUM, - ~(t_int64) 0 << FP_V_XNUM, - ~(t_int64) 0 << FP_V_TNUM, - ~(t_int64) 0 << FP_V_ENUM }; - -static const uint32 int_p_max[2] = { IN_M_SMAGN, - IN_M_DMAGN }; - - -/* Internal unpacked floating-point representation. */ - -typedef struct { - t_int64 mantissa; - int32 exponent; - OPSIZE precision; - } FPU; - - - -/* Low-level helper routines. */ - - -/* Arithmetic shift right for mantissa only. - - Returns TRUE if any one-bits are shifted out (for F-series only). -*/ - -static t_bool asr (FPU *operand, int32 shift) -{ -t_uint64 mask; -t_bool bits_lost; - -if (UNIT_CPU_MODEL == UNIT_1000_F) { /* F-series? */ - mask = ((t_uint64) 1 << shift) - 1; /* mask for lost bits */ - bits_lost = ((operand->mantissa & mask) != 0); /* flag if any lost */ - } -else - bits_lost = FALSE; - -operand->mantissa = operand->mantissa >> shift; /* mantissa is int, so ASR */ -return bits_lost; -} - - -/* Logical shift right for mantissa and exponent. - - Shifts mantissa and corrects exponent for mantissa overflow. - Returns TRUE if any one-bits are shifted out (for F-series only). -*/ - -static t_bool lsrx (FPU *operand, int32 shift) -{ -t_uint64 mask; -t_bool bits_lost; - -if (UNIT_CPU_MODEL == UNIT_1000_F) { /* F-series? */ - mask = ((t_uint64) 1 << shift) - 1; /* mask for lost bits */ - bits_lost = ((operand->mantissa & mask) != 0); /* flag if any lost */ - } -else - bits_lost = FALSE; - -operand->mantissa = (t_uint64) operand->mantissa >> shift; /* uint, so LSR */ -operand->exponent = operand->exponent + shift; /* correct exponent */ -return bits_lost; -} - - -/* Unpack an operand into a long integer. - - Returns a left-aligned integer or mantissa. Does not mask to precision; this - should be done subsequently if desired. -*/ - -static t_int64 unpack_int (OP packed, OPSIZE precision) -{ -uint32 i; -t_uint64 unpacked = 0; - -if (precision == in_s) - unpacked = (t_uint64) packed.word << 48; /* unpack single integer */ - -else if (precision == in_d) - unpacked = (t_uint64) packed.dword << 32; /* unpack double integer */ - -else { - if (precision == fp_e) /* five word operand? */ - precision = fp_t; /* only four mantissa words */ - - for (i = 0; i < 4; i++) /* unpack fp 2 to 4 words */ - if (i < TO_COUNT (precision)) - unpacked = unpacked << 16 | packed.fpk[i]; - else - unpacked = unpacked << 16; - } - -return (t_int64) unpacked; -} - - -/* Unpack a packed operand. - - The packed value is split into separate mantissa and exponent variables. The - multiple words of the mantissa are concatenated into a single 64-bit signed - value, and the exponent is shifted with recovery of the sign. -*/ - -static FPU unpack (OP packed, OPSIZE precision) -{ -FPU unpacked; - -unpacked.precision = precision; /* set value's precision */ - -unpacked.mantissa = /* unpack and mask mantissa */ - unpack_int (packed, precision) & (t_int64) mant_mask[precision]; - -switch (precision) { - - case fp_f: - case fp_x: - case fp_t: - unpacked.exponent = /* unpack exponent from correct word */ - TO_EXP (packed.fpk[(uint32) precision - 1]); - break; - - case fp_e: - unpacked.exponent = /* unpack expanded exponent */ - (int16) (packed.fpk[4] >> FP_V_EXP | /* rotate sign into place */ - (packed.fpk[4] & 1 ? SIGN : 0)); - break; - - case fp_a: /* no action for value in accum */ - case in_s: /* integers don't use exponent */ - case in_d: /* integers don't use exponent */ - default: - unpacked.exponent = 0; - break; - } - -return unpacked; -} - - -/* Pack a long integer into an operand. */ - -static OP pack_int (t_int64 unpacked, OPSIZE precision) -{ -int32 i; -OP packed; - -if (precision == in_s) - packed.word = (uint16) (unpacked >> 48) & DMASK; /* pack single integer */ - -else if (precision == in_d) - packed.dword = (uint32) (unpacked >> 32) & DMASK32; /* pack double integer */ - -else { - if (precision == fp_e) /* five word operand? */ - precision = fp_t; /* only four mantissa words */ - - for (i = 3; i >= 0; i--) { /* pack fp 2 to 4 words */ - packed.fpk[i] = (uint16) unpacked & DMASK; - unpacked = unpacked >> 16; - } - } - -return packed; -} - - -/* Pack an unpacked floating-point number. - - The 64-bit mantissa is split into the appropriate number of 16-bit words. - The exponent is rotated to incorporate the sign bit and merged into the - appropriate word. -*/ - -static OP pack (FPU unpacked) -{ -OP packed; -uint8 exp; - -packed = pack_int (unpacked.mantissa, unpacked.precision); /* pack mantissa */ - -exp = (uint8) (unpacked.exponent << FP_V_EXP | /* rotate exponent */ - (unpacked.exponent < 0) << FP_V_ESIGN); - -switch (unpacked.precision) { /* merge exponent into correct word */ - - case in_s: /* no action for integers */ - case in_d: - break; - - case fp_f: /* merge into last word */ - case fp_x: - case fp_t: - packed.fpk[(uint32) unpacked.precision - 1] = - (packed.fpk[(uint32) unpacked.precision - 1] & ~FP_SEXP) | exp; - break; - - case fp_e: /* place in separate word */ - packed.fpk[4] = (uint16) (unpacked.exponent << FP_V_EXP | - (unpacked.exponent < 0) << FP_V_ESIGN); - break; - - case fp_a: /* no action for value in accum */ - break; - } - -return packed; -} - - -/* Normalize an unpacked floating-point number. - - Floating-point numbers are in normal form if the sign bit and the MSB of the - mantissa differ. Unnormalized numbers are shifted as needed with appropriate - exponent modification. -*/ - -static void normalize (FPU *unpacked) -{ - -if (unpacked->mantissa) /* non-zero? */ - while (DENORM (unpacked->mantissa)) { /* normal form? */ - unpacked->exponent = unpacked->exponent - 1; /* no, so left shift */ - unpacked->mantissa = unpacked->mantissa << 1; - } -else - unpacked->exponent = 0; /* clean for zero */ -return; -} - - -/* Round an unpacked floating-point number and check for overflow. - - An unpacked floating-point number is rounded by adding one-half of the LSB - value, maintaining symmetry around zero. If rounding resulted in a mantissa - overflow, the result logically is shifted to the right with an appropriate - exponent modification. Finally, the result is checked for exponent underflow - or overflow, and the appropriate approximation (zero or infinity) is - returned. - - Rounding in hardware involves a special mantissa extension register that - holds three "guard" bits and one "sticky" bit. These represent the value of - bits right-shifted out the mantissa register. Under simulation, we track - such right-shifts and utilize the lower eight bits of the 64-bit mantissa - value to simulate the extension register. - - Overflow depends on whether the FPP expanded-exponent form is being used - (this expands the exponent range by two bits). If overflow is detected, the - value representing infinity is dependent on whether the operation is on - behalf of the Fast FORTRAN Processor. The F-Series FPP returns positive - infinity on both positive and negative overflow for all precisions. The 2100 - and M/E-Series FFPs return negative infinity on negative overflow of - extended-precision values. Single-precision overflows on these machines - always return positive infinity. - - The number to be rounded must be normalized upon entry. -*/ - -static uint32 roundovf (FPU *unpacked, t_bool expand) -{ -uint32 overflow; -t_bool sign; - -sign = (unpacked->mantissa < 0); /* save mantissa sign */ - -if (sign) /* round and mask the number */ - unpacked->mantissa = - (unpacked->mantissa + n_half_lsb[unpacked->precision]) & - (t_int64) mant_mask[unpacked->precision]; -else - unpacked->mantissa = - (unpacked->mantissa + p_half_lsb[unpacked->precision]) & - (t_int64) mant_mask[unpacked->precision]; - -if (sign != (unpacked->mantissa < 0)) /* mantissa overflow? */ - lsrx (unpacked, 1); /* correct by shifting */ -else - normalize (unpacked); /* renorm may be needed */ - -if (unpacked->mantissa == 0) { /* result zero? */ - unpacked->mantissa = 0; /* return zero */ - unpacked->exponent = 0; - overflow = 0; /* with overflow clear */ - } -else if (unpacked->exponent < /* result underflow? */ - (FP_MAXNEXP >> (expand ? 0 : 2))) { - unpacked->mantissa = 0; /* return zero */ - unpacked->exponent = 0; - overflow = 1; /* and set overflow */ - } -else if (unpacked->exponent > /* result overflow? */ - (FP_MAXPEXP >> (expand ? 0 : 2))) { - if (sign && /* negative value? */ - (unpacked->precision == fp_x) && /* extended precision? */ - (UNIT_CPU_MODEL != UNIT_1000_F)) { /* not F-series? */ - unpacked->mantissa = FP_MAXNMANT; /* return negative infinity */ - unpacked->exponent = FP_MAXPEXP & FP_M_EXP; - } - else { - unpacked->mantissa = FP_MAXPMANT; /* return positive infinity */ - unpacked->exponent = FP_MAXPEXP & FP_M_EXP; - } - overflow = 1; /* and set overflow */ - } -else - overflow = 0; /* value is in range */ - -return overflow; -} - - -/* Normalize, round, and pack an unpacked floating-point number. */ - -static uint32 nrpack (OP *packed, FPU unpacked, t_bool expand) -{ -uint32 overflow; - -normalize (&unpacked); /* normalize for rounding */ -overflow = roundovf (&unpacked, expand); /* round and check for overflow */ -*packed = pack (unpacked); /* pack result */ - -return overflow; -} - - - -/* Low-level arithmetic routines. */ - - -/* Complement an unpacked number. */ - -static void complement (FPU *result) -{ -if (result->mantissa == FP_MAXNMANT) { /* maximum negative? */ - result->mantissa = FP_ONEHALF; /* complement of -1.0 * 2 ^ n */ - result->exponent = result->exponent + 1; /* is 0.5 * 2 ^ (n + 1) */ - } -else - result->mantissa = -result->mantissa; /* negate mantissa */ -return; -} - - -/* Add two unpacked numbers. - - The mantissas are first aligned if necessary by scaling the smaller of the - two operands. If the magnitude of the difference between the exponents is - greater than the number of significant bits, then the smaller number has been - scaled to zero (swamped), and so the sum is simply the larger operand. - Otherwise, the sum is computed and checked for overflow, which has occurred - if the signs of the operands are the same but differ from that of the result. - Scaling and renormalization is performed if overflow occurred. -*/ - -static void add (FPU *sum, FPU augend, FPU addend) -{ -int32 magn; -t_bool bits_lost; - -if (augend.mantissa == 0) - *sum = addend; /* X + 0 = X */ - -else if (addend.mantissa == 0) - *sum = augend; /* 0 + X = X */ - -else { - magn = augend.exponent - addend.exponent; /* difference exponents */ - - if (magn > 0) { /* addend smaller? */ - *sum = augend; /* preset augend */ - bits_lost = asr (&addend, magn); /* align addend */ - } - else { /* augend smaller? */ - *sum = addend; /* preset addend */ - magn = -magn; /* make difference positive */ - bits_lost = asr (&augend, magn); /* align augend */ - } - - if (magn <= (int32) op_bits[augend.precision]) { /* value swamped? */ - sum->mantissa = /* no, add mantissas */ - addend.mantissa + augend.mantissa; - - if (((addend.mantissa < 0) == (augend.mantissa < 0)) && /* mantissa overflow? */ - ((addend.mantissa < 0) != (sum->mantissa < 0))) { - bits_lost = bits_lost | lsrx (sum, 1); /* restore value */ - sum->mantissa = /* restore sign */ - sum-> mantissa | (addend.mantissa & FP_MSIGN); - } - - if (bits_lost) /* any bits lost? */ - sum->mantissa = sum->mantissa | 1; /* include one for rounding */ - } - } -return; -} - - -/* Multiply two unpacked numbers. - - The single-precision firmware (FMP) operates differently from the firmware - extended-precision (.XMPY) and the hardware multiplies of any precision. - Firmware implementations use the MPY micro-order to form 16-bit x 16-bit = - 32-bit partial products and sum them to form the result. The hardware uses a - series of shifts and adds. This means that firmware FMP and hardware FMP - return slightly different values, as may be seen by attempting to run the - firmware FMP diagnostic on the FPP. - - The FMP microcode calls a signed multiply routine to calculate three partial - products (all but LSB * LSB). Because the LSBs are unsigned, i.e., all bits - significant, the two MSB * LSB products are calculated using LSB/2. The - unsigned right-shift ensures a positive LSB with no significant bits lost, - because the lower eight bits are unused (they held the vacated exponent). In - order to sum the partial products, the LSB of the result of MSB * MSB is also - right-shifted before addition. Note, though, that this loses a significant - bit. After summation, the result is left-shifted to correct for the original - right shifts. - - The .XMPY microcode negates both operands as necessary to produce positive - values and then forms six of the nine 16-bit x 16-bit = 32-bit unsigned - multiplications required for a full 96-bit product. Given a 48-bit - multiplicand "a1a2a3" and a 48-bit multiplier "b1b2b3", the firmware performs - these calculations to develop a 48-bit product: - - a1 a2 a3 - +-------+-------+-------+ - b1 b2 b3 - +-------+-------+-------+ - _________________________ - - a1 * b3 [p1] - +-------+-------+ - a2 * b2 [p2] - +-------+-------+ - a1 * b2 [p3] - +-------+-------+ - a3 * b1 [p4] - +-------+-------+ - a2 * b1 [p5] - +-------+-------+ - a1 * b1 [p6] - +-------+-------+ - _________________________________ - - product - +-------+-------+-------+ - - The least-significant words of partial products [p1], [p2], and [p4] are used - only to develop a carry bit into the 48-bit sum. The product is complemented - as necessary to restore the sign. - - The basic FPP hardware algorithm scans the multiplier and adds a shifted copy - of the multiplicand whenever a one-bit is detected. To avoid successive adds - when a string of ones is encountered (because adds are more expensive than - shifts), the hardware instead adds the multiplicand shifted by N + 1 + P and - subtracts the multiplicand shifted by P to obtain the equivalent value with a - maximum of two operations. - - Instead of implementing either the .XMPY firmware algorithm or the hardware - shift-and-add algorithm directly, it is more efficient under simulation to - use 32 x 32 = 64-bit multiplications, thereby reducing the number required - from six to four (64-bit "c1c2" x 64-bit "d1d2"): - - ah al - +-------+-------+ - bh bl - +-------+-------+ - _________________ - - al * bl [ll] - +-------+-------+ - ah * bl [hl] - +-------+-------+ - al * bh [lh] - +-------+-------+ - ah * bh [hh] - +-------+-------+ - _________________________________ - - product - +-------+-------+ - - However, the FMP algorithm is implemented directly from the microcode to - preserve the fidelity of the simulation, i.e., to lose the same amount - of precision. -*/ - -static void multiply (FPU *product, FPU multiplicand, FPU multiplier) -{ -uint32 ah, al, bh, bl, sign = 0; -t_uint64 hh, hl, lh, ll, carry; -int16 ch, cl, dh, dl; -t_bool firmware; - -product->precision = multiplicand.precision; /* set precision */ - -if ((multiplicand.mantissa == 0) || /* 0 * X = 0 */ - (multiplier.mantissa == 0)) /* X * 0 = 0 */ - product->mantissa = product->exponent = 0; - -else { - firmware = (UNIT_CPU_MODEL != UNIT_1000_F); /* set firmware flag */ - - if (!firmware || (product->precision != fp_f)) { /* hardware? */ - if (multiplicand.mantissa < 0) { /* negative? */ - complement (&multiplicand); /* complement operand */ - sign = ~sign; /* track sign */ - } - if (multiplier.mantissa < 0) { /* negative? */ - complement (&multiplier); /* complement operand */ - sign = ~sign; /* track sign */ - } - } - - product->exponent = /* compute exponent */ - multiplicand.exponent + multiplier.exponent + 1; - - ah = (uint32) (multiplicand.mantissa >> 32); /* split multiplicand */ - al = (uint32) (multiplicand.mantissa & DMASK32); /* into high and low parts */ - bh = (uint32) (multiplier.mantissa >> 32); /* split multiplier */ - bl = (uint32) (multiplier.mantissa & DMASK32); /* into high and low parts */ - - if (firmware && (product->precision == fp_f)) { /* single-precision firmware? */ - ch = (int16) (ah >> 16) & DMASK; /* split 32-bit multiplicand */ - cl = (int16) (ah & 0xfffe); /* into high and low parts */ - dh = (int16) (bh >> 16) & DMASK; /* split 32-bit multiplier */ - dl = (int16) (bh & 0xfffe); /* into high and low parts */ - - hh = (t_uint64) (((int32) ch * dh) & ~1); /* form cross products */ - hl = (t_uint64) (((t_int64) ch * (t_int64) (uint16) dl + - (t_int64) dh * (t_int64) (uint16) cl) & - 0xfffffffffffe0000); - - product->mantissa = (t_uint64) (((t_int64) hh << 32) + /* sum partials */ - ((t_int64) hl << 16)); - } - - else { - hh = ((t_uint64) ah * bh); /* form four cross products */ - hl = ((t_uint64) ah * bl); /* using 32 x 32 = */ - lh = ((t_uint64) al * bh); /* 64-bit multiplies */ - ll = ((t_uint64) al * bl); - - carry = ((ll >> 32) + (uint32) hl + (uint32) lh) >> 32; /* form carry */ - - product->mantissa = hh + (hl >> 32) + (lh >> 32) + carry; /* sum partials */ - - if (sign) /* negate if required */ - complement (product); - } - } -return; -} - - -/* Divide two unpacked numbers. - - As with multiply, the single-precision firmware (FDV) operates differently - from the firmware extended-precision (.XDIV) and the hardware divisions of - any precision. Firmware implementations use the DIV micro-order to form - 32-bit / 16-bit = 16-bit quotients and 16-bit remainders. These are used in - a "divide and correct" algorithm, wherein the quotient is estimated and then - corrected by comparing the dividend to the product of the quotient and the - divisor. The hardware uses a series of shifts and subtracts. This means - that firmware FDV and hardware FDV once again return slightly different - values. - - Under simulation, the classic divide-and-correct method is employed, using - 64-bit / 32-bit = 32-bit divisions. This method considers the 64-bit - dividend and divisor each to consist of two 32-bit "digits." The 64-bit - dividend "a1a2a3a4" is divided by the first 32-bit digit "b1b2" of the 64-bit - divisor "b1b2b3b4", yielding a 32-bit trial quotient digit and a 32-bit - remainder digit. A correction is developed by subtracting the product of the - second 32-bit digit "b3b4" of the divisor and the trial quotient digit from - the remainder (we take advantage of the eight bits vacated by the exponent - during unpacking to ensure that this product will not overflow into the sign - bit). If the remainder is negative, the trial quotient is too large, so it - is decremented, and the (full 64-bit) divisor is added to the correction. - This is repeated until the correction is non-negative, indicating that the - first quotient digit is correct. The process is then repeated using the - remainder as the dividend to develop the second 32-bit digit of the quotient. - The two digits are then concatenated for produce the final 64-bit value. - - (See, "Divide-and-Correct Methods for Multiple Precision Division" by Marvin - L. Stein, Communications of the ACM, August 1964 for background.) - - The microcoded single-precision division avoids overflows by right-shifting - some values, which leads to a loss of precision in the LSBs. We duplicate - the firmware algorithm here to preserve the fidelity of the simulation. -*/ - -static void divide (FPU *quotient, FPU dividend, FPU divisor) -{ -uint32 sign = 0; -t_int64 bh, bl, r1, r0, p1, p0; -t_uint64 q, q1, q0; -t_bool firmware; -int32 ah, div, cp; -int16 dh, dl, pq1, pq2, cq; - -quotient->precision = dividend.precision; /* set precision */ - -if (divisor.mantissa == 0) { /* division by zero? */ - if (dividend.mantissa < 0) - quotient->mantissa = FP_MSIGN; /* return minus infinity */ - else - quotient->mantissa = ~FP_MSIGN; /* or plus infinity */ - quotient->exponent = FP_MAXPEXP + 1; - } - -else if (dividend.mantissa == 0) /* dividend zero? */ - quotient->mantissa = quotient->exponent = 0; /* yes; result is zero */ - -else { - firmware = (UNIT_CPU_MODEL != UNIT_1000_F); /* set firmware flag */ - - if (!firmware || (quotient->precision != fp_f)) { /* hardware or FFP? */ - if (dividend.mantissa < 0) { /* negative? */ - complement (÷nd); /* complement operand */ - sign = ~sign; /* track sign */ - } - if (divisor.mantissa < 0) { /* negative? */ - complement (&divisor); /* complement operand */ - sign = ~sign; /* track sign */ - } - } - - quotient->exponent = /* division subtracts exponents */ - dividend.exponent - divisor.exponent; - - bh = divisor.mantissa >> 32; /* split divisor */ - bl = divisor.mantissa & DMASK32; /* into high and low parts */ - - if (firmware && (quotient->precision == fp_f)) { /* single-precision firmware? */ - quotient->exponent = quotient->exponent + 1; /* fix exponent */ - - ah = (int32) (dividend.mantissa >> 32); /* split dividend */ - dh = (int16) (bh >> 16); /* split divisor again */ - dl = (int16) bh; - - div = ah >> 2; /* ASR 2 to prevent overflow */ - - pq1 = (int16) (div / dh); /* form first partial quotient */ - div = ((div % dh) & ~1) << 15; /* ASR 1, move rem to upper */ - pq2 = (int16) (div / dh); /* form second partial quotient */ - - div = (uint16) dl << 13; /* move divisor LSB to upper, LSR 3 */ - cq = (int16) (div / dh); /* form correction quotient */ - cp = -cq * pq1; /* and correction product */ - - cp = (((cp >> 14) & ~3) + (int32) pq2) << 1; /* add corr prod and 2nd partial quo */ - quotient->mantissa = /* add 1st partial quo and align */ - (t_uint64) (((int32) pq1 << 16) + cp) << 32; - } - - else { /* hardware or FFP */ - q1 = (t_uint64) (dividend.mantissa / bh); /* form 1st trial quotient */ - r1 = dividend.mantissa % bh; /* and remainder */ - p1 = (r1 << 24) - (bl >> 8) * q1; /* calculate correction */ - - while (p1 < 0) { /* correction needed? */ - q1 = q1 - 1; /* trial quotient too large */ - p1 = p1 + (divisor.mantissa >> 8); /* increase remainder */ - } - - q0 = (t_uint64) ((p1 << 8) / bh); /* form 2nd trial quotient */ - r0 = (p1 << 8) % bh; /* and remainder */ - p0 = (r0 << 24) - (bl >> 8) * q0; /* calculate correction */ - - while (p0 < 0) { /* correction needed? */ - q0 = q0 - 1; /* trial quotient too large */ - p0 = p0 + (divisor.mantissa >> 8); /* increase remainder */ - } - - q = (q1 << 32) + q0; /* sum quotient digits */ - - if (q1 & 0xffffffff00000000) { /* did we lose MSB? */ - q = (q >> 1) | 0x8000000000000000; /* shift right and replace bit */ - quotient->exponent = quotient->exponent + 1;/* bump exponent for shift */ - } - - if (q & 0x8000000000000000) /* lose normalization? */ - q = q >> 1; /* correct */ - - quotient->mantissa = (t_int64) q; - } - - if (sign) - complement (quotient); /* negate if required */ - } -return; -} - - -/* Fix an unpacked number. - - A floating-point value is converted to an integer. The desired precision of - the result (single or double integer) must be set before calling. - - Values less than 0.5 (i.e., with negative exponents) underflow to zero. If - the value exceeds the specified integer range, the maximum integer value is - returned and overflow is set. Otherwise, the floating-point value is - right-shifted to zero the exponent. The result is then rounded. -*/ - -static uint32 fix (FPU *result, FPU operand) -{ -uint32 overflow; -t_bool bits_lost; - -if (operand.exponent < 0) { /* value < 0.5? */ - result->mantissa = 0; /* result rounds to zero */ - overflow = 0; /* clear for underflow */ - } - -else if (operand.exponent > /* value > integer size? */ - (int32) op_bits[result->precision]) { - result->mantissa = /* return max int value */ - (t_uint64) int_p_max[result->precision] << - op_start[result->precision]; - overflow = 1; /* and set overflow */ - } - -else { /* value in range */ - bits_lost = asr (&operand, /* shift to zero exponent */ - op_bits[result->precision] - operand.exponent); - - if (operand.mantissa < 0) { /* value negative? */ - if (bits_lost) /* bits lost? */ - operand.mantissa = operand.mantissa | 1; /* include one for rounding */ - - operand.mantissa = operand.mantissa + /* round result */ - p_half_lsb[result->precision]; - } - - result->mantissa = operand.mantissa & /* mask to precision */ - op_mask[result->precision]; - overflow = 0; - } - -result->exponent = 0; /* tidy up for integer value */ -return overflow; -} - - -/* Float an integer to an unpacked number. - - An integer is converted to a floating-point value. The desired precision of - the result must be set before calling. - - Conversion is simply a matter of copying the integer value, setting an - exponent that reflects the right-aligned position of the bits, and - normalizing. -*/ - -static void ffloat (FPU *result, FPU operand) -{ -result->mantissa = operand.mantissa; /* set value */ -result->exponent = op_bits[operand.precision]; /* set exponent */ -normalize (result); /* normalize */ -return; -} - - - -/* High-level floating-point routines. */ - - -/* Determine operand precisions. - - The precisions of the operands and result are determined by decoding an - operation opcode and returned to the caller. Pass NULL for both of the - operands if only the result precision is wanted. Pass NULL for the result if - only the operand precisions are wanted. - - Implementation note: - - 1. gcc-4.3.0 complains at -O3 that operand_l/r may not be initialized. - Because of the mask, the switch statement covers all cases, but gcc - doesn't realize this. The "default" case is redundant but eliminates the - warning. -*/ - -void fp_prec (uint16 opcode, OPSIZE *operand_l, OPSIZE *operand_r, OPSIZE *result) -{ -OPSIZE fp_size, int_size; - -fp_size = (OPSIZE) ((opcode & 0003) + 2); /* fp_f, fp_x, fp_t, fp_e */ -int_size = (OPSIZE) ((opcode & 0004) >> 2); /* in_s, in_d */ - -if (operand_l && operand_r) { /* want operand precisions? */ - switch (opcode & 0120) { /* mask out opcode bit 5 */ - case 0000: /* add/mpy */ - case 0020: /* sub/div */ - *operand_l = fp_size; /* assume first op is fp */ - - if (opcode & 0004) /* operand internal? */ - *operand_r = fp_a; /* second op is accum */ - else - *operand_r = fp_size; /* second op is fp */ - break; - - case 0100: /* fix/accum as integer */ - *operand_l = fp_size; /* first op is fp */ - *operand_r = fp_a; /* second op is always null */ - break; - - case 0120: /* flt/accum as float */ - default: /* keeps compiler quiet for uninit warning */ - *operand_l = int_size; /* first op is integer */ - *operand_r = fp_a; /* second op is always null */ - break; - } - - if (opcode & 0010) /* operand internal? */ - *operand_l = fp_a; /* first op is accum */ - } - -if (result) /* want result precision? */ - if ((opcode & 0120) == 0100) /* fix? */ - *result = int_size; /* result is integer */ - else /* all others */ - *result = fp_size; /* result is fp */ - -return; -} - - -/* Floating Point Processor executor. - - The executor simulates the MPP interface between the CPU and the FPP. The - operation to be performed is specified by the supplied opcode, which conforms - to the FPP hardware interface, as follows: - - Bits Value Action - ---- ----- ---------------------------------------------- - 7 0 Exponent range is standard (+/-127) - 1 Exponent range is expanded (+/-511) - - 6-4 000 Add - 001 Subtract - 010 Multiply - 011 Divide - 100 Fix - 101 Float - 110 (diagnostic) - 111 (diagnostic) - - 3 0 Left operand is supplied - 1 Left operand in accumulator - - 2 0 Right operand is supplied (ADD/SUB/MPY/DIV) - Single integer operation (FIX/FLT) - 1 Right operand in accumulator (ADD/SUB/MPY/DIV) - Double integer operation (FIX/FLT) - - 1-0 00 2-word operation - 01 3-word operation - 10 4-word operation - 11 5-word operation - - If the opcode specifies that the left (or right) operand is in the - accumulator, then the value supplied for that parameter is not used. All - results are automatically left in the accumulator. If the result is not - needed externally, then NULL may be passed for the result parameter. - - To support accumulator set/get operations under simulation, the opcode is - expanded to include a special mode, indicated by bit 15 = 1. In this mode, - if the result parameter is NULL, then the accumulator is set from the value - passed as operand_l. If the result parameter is not null, then the - accumulator value is returned as the result, and operand_l is ignored. The - precision of the operation is performed as specified by the OPSIZE value - passed in bits 2-0 of the opcode. - - The function returns 1 if the operation overflows and 0 if not. -*/ - -uint32 fp_exec (uint16 opcode, OP *result, OP operand_l, OP operand_r) -{ -static FPU accumulator; -FPU uoperand_l, uoperand_r; -OPSIZE op_l_prec, op_r_prec, rslt_prec; -uint32 overflow; - -if (opcode & SIGN) { /* accumulator mode? */ - rslt_prec = (OPSIZE) (opcode & 0017); /* get operation precision */ - - if (result) { /* get accumulator? */ - op_l_prec = accumulator.precision; /* save accum prec temp */ - accumulator.precision = rslt_prec; /* set desired precision */ - *result = pack (accumulator); /* pack accumulator */ - accumulator.precision = op_l_prec; /* restore correct prec */ - } - else /* set accumulator */ - accumulator = unpack (operand_l, rslt_prec); /* unpack from operand */ - - return 0; /* no overflow from accum ops */ - } - -fp_prec (opcode, &op_l_prec, &op_r_prec, &rslt_prec); /* calc precs from opcode */ - -if (op_l_prec == fp_a) /* left operand in accum? */ - uoperand_l = accumulator; /* copy it */ -else /* operand supplied */ - uoperand_l = unpack (operand_l, op_l_prec); /* unpack from parameter */ - -if (op_r_prec == fp_a) /* right operand in accum? */ - uoperand_r = accumulator; /* copy it */ -else /* operand supplied */ - uoperand_r = unpack (operand_r, op_r_prec); /* unpack from parameter */ - - -switch (opcode & 0160) { /* dispatch operation */ - - case 0000: /* add */ - add (&accumulator, uoperand_l, uoperand_r); - break; - - case 0020: /* subtract */ - complement (&uoperand_r); - add (&accumulator, uoperand_l, uoperand_r); - break; - - case 0040: /* multiply */ - multiply (&accumulator, uoperand_l, uoperand_r); - break; - - case 0060: /* divide */ - divide (&accumulator, uoperand_l, uoperand_r); - break; - - case 0100: /* fix */ - accumulator.precision = rslt_prec; - overflow = fix (&accumulator, uoperand_l); - - if (result) /* result wanted? */ - *result = pack_int (accumulator.mantissa, /* pack integer */ - rslt_prec); - return overflow; - - case 0120: /* float */ - accumulator.precision = rslt_prec; - ffloat (&accumulator, uoperand_l); - - if (result) /* result wanted? */ - *result = pack (accumulator); /* pack FP (FLT does not round) */ - return 0; - - case 0140: /* (diagnostic) */ - case 0160: /* (diagnostic) */ - return 0; - } - -if (UNIT_CPU_MODEL != UNIT_1000_F) /* firmware implementation? */ - accumulator.mantissa = accumulator.mantissa & /* mask to precision */ - op_mask[accumulator.precision]; - -normalize (&accumulator); /* normalize */ -overflow = roundovf (&accumulator, opcode & 0200); /* round and check for overflow */ - -if (result) /* result wanted? */ - *result = pack (accumulator); /* pack result */ - -return overflow; -} - - -/* Set or get accumulator at desired precision. - - This function provides access to the FPP accumulator. In hardware, the - accumulator may be read at a given precision by sending the FPP an opcode - encoded with the desired precision and then reading words from the FPP - /without/ initiating the operation, i.e., without starting the processor. - - Under simulation, pass this function a NULL operand and the desired - precision to read the accumulator. Pass a pointer to an operand and the - desired precision to set the accumulator; the return value in this case is - not defined. -*/ - -OP fp_accum (const OP *operand, OPSIZE precision) -{ -OP result = NOP; -uint16 opcode = (uint16) precision | SIGN; /* add special mode bit */ - -if (operand) - fp_exec (opcode, NULL, *operand, NOP); /* set accum */ -else - fp_exec (opcode, &result, NOP, NOP); /* get accum */ -return result; -} - - -/* Pack an unpacked floating-point number. - - An unpacked mantissa is passed as a "packed" number with an unused exponent. - The mantissa and separately-passed exponent are packed into the in-memory - floating-point format. Note that all bits are significant in the mantissa - (no masking is done). -*/ - -uint32 fp_pack (OP *result, OP mantissa, int32 exponent, OPSIZE precision) -{ -FPU unpacked; - -unpacked.mantissa = unpack_int (mantissa, precision); /* unpack mantissa */ -unpacked.exponent = exponent; /* set exponent */ -unpacked.precision = precision; /* set precision */ -*result = pack (unpacked); /* pack them */ -return 0; -} - - -/* Normalize, round, and pack an unpacked floating-point number. - - An unpacked mantissa is passed as a "packed" number with an unused exponent. - The mantissa and separately-passed exponent are normalized, rounded, and - packed into the in-memory floating-point format. Note that all bits are - significant in the mantissa (no masking is done). -*/ - -uint32 fp_nrpack (OP *result, OP mantissa, int32 exponent, OPSIZE precision) -{ -FPU unpacked; - -unpacked.mantissa = unpack_int (mantissa, precision); /* unpack mantissa */ -unpacked.exponent = exponent; /* set exponent */ -unpacked.precision = precision; /* set precision */ -return nrpack (result, unpacked, FALSE); /* norm/rnd/pack them */ -} - - -/* Unpack a packed floating-point number. - - A floating-point number, packed into the in-memory format, is unpacked into - separate mantissa and exponent values. The unpacked mantissa is returned in - a "packed" structure with an exponent of zero. Mantissa or exponent may be - null if that part isn't wanted. -*/ - -uint32 fp_unpack (OP *mantissa, int32 *exponent, OP packed, OPSIZE precision) - -{ -FPU unpacked; - -unpacked = unpack (packed, precision); /* unpack mantissa and exponent */ - -if (exponent) /* exponent wanted? */ - *exponent = unpacked.exponent; /* return exponent */ - -if (mantissa) /* mantissa wanted? */ - *mantissa = pack_int (unpacked.mantissa, fp_t); /* return full-size mantissa */ -return 0; -} - - -/* Complement an unpacked mantissa. - - An unpacked mantissa is passed as a "packed" number with a zero exponent. - The exponent increment, i.e., either zero or one, depending on whether a - renormalization was required, is returned. Note that all bits are - significant in the mantissa. -*/ - -uint16 fp_ucom (OP *mantissa, OPSIZE precision) -{ -FPU unpacked; - -unpacked.mantissa = unpack_int (*mantissa, precision); /* unpack mantissa */ -unpacked.exponent = 0; /* clear undefined exponent */ -unpacked.precision = precision; /* set precision */ -complement (&unpacked); /* negate it */ -*mantissa = pack_int (unpacked.mantissa, precision); /* replace mantissa */ -return (uint16) unpacked.exponent; /* return exponent increment */ -} - - -/* Complement a floating-point number. */ - -uint32 fp_pcom (OP *packed, OPSIZE precision) -{ -FPU unpacked; - -unpacked = unpack (*packed, precision); /* unpack the number */ -complement (&unpacked); /* negate it */ -return nrpack (packed, unpacked, FALSE); /* and norm/rnd/pack */ -} - - -/* Truncate a floating-point number. */ - -uint32 fp_trun (OP *result, OP source, OPSIZE precision) -{ -t_bool bits_lost; -FPU unpacked; -FPU one = { FP_ONEHALF, 1, fp_t }; /* 0.5 * 2 ** 1 = 1.0 */ -OP zero = { { 0, 0, 0, 0, 0 } }; /* 0.0 */ -t_uint64 mask = mant_mask[precision] & ~FP_MSIGN; - -unpacked = unpack (source, precision); -if (unpacked.exponent < 0) /* number < 0.5? */ - *result = zero; /* return 0 */ -else if (unpacked.exponent >= (int32) op_bits[precision]) /* no fractional bits? */ - *result = source; /* already integer */ -else { - mask = (mask >> unpacked.exponent) & mask; /* mask fractional bits */ - bits_lost = ((unpacked.mantissa & mask) != 0); /* flag if bits lost */ - unpacked.mantissa = unpacked.mantissa & ~mask; /* mask off fraction */ - if ((unpacked.mantissa < 0) && bits_lost) /* negative? */ - add (&unpacked, unpacked, one); /* truncate toward zero */ - nrpack (result, unpacked, FALSE); /* (overflow cannot occur) */ - } -return 0; /* clear overflow on return */ -} - - -/* Convert a floating-point number from one precision to another. */ - -uint32 fp_cvt (OP *result, OPSIZE source_precision, OPSIZE dest_precision) -{ -FPU unpacked; - -unpacked = unpack (*result, source_precision); -unpacked.precision = dest_precision; -return nrpack (result, unpacked, FALSE); /* norm/rnd/pack */ -} - - -#endif /* end of int64 support */ +/* hp2100_fp1.c: HP 1000 multiple-precision floating point routines + + Copyright (c) 2005-2016, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be used + in advertising or otherwise to promote the sale, use or other dealings in + this Software without prior written authorization from the author. + + 16-May-16 JDB Reformulated the definitions of op_mask + 24-Dec-14 JDB Added casts for explicit downward conversions + Changed fp_ucom return from uint32 to uint16 + 18-Mar-13 JDB Changed type of mantissa masks array to to unsigned + 06-Feb-12 JDB Added missing precision on constant "one" in fp_trun + 21-Jun-11 JDB Completed the comments for divide; no code changes + 08-Jun-08 JDB Quieted bogus gcc warning in fp_exec + 10-May-08 JDB Fixed uninitialized return in fp_accum when setting + 19-Mar-08 JDB Reworked "complement" to avoid inlining bug in gcc-4.x + 01-Dec-06 JDB Reworked into generalized multiple-precision ops for FPP + 12-Oct-06 JDB Altered x_trun for F-Series FFP compatibility + Added F-Series ..TCM FFP helpers + + Primary references: + - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation + (92851-90001, Mar-1981) + - HP 1000 M/E/F-Series Computers Technical Reference Handbook + (5955-0282, Mar-1980) + - DOS/RTE Relocatable Library Reference Manual + (24998-90001, Oct-1981) + + + This module implements multiple-precision floating-point operations to + support the 1000 F-Series hardware Floating Point Processor. It employs + 64-bit integer arithmetic for speed and simplicity of implementation. The + host compiler must support 64-bit integers, and the HAVE_INT64 symbol must be + defined during compilation. If this symbol is not defined, then FPP support + is not available. + + HP 2100/1000 computers used a proprietary floating-point format. The 2100 + had optional firmware that provided two-word floating-point add, subtract, + multiply, and divide, as well as single-integer fix and float. The 1000-M/E + provided the same two-word firmware operations as standard equipment. + Three-word extended-precision instructions for the 2100 and 1000-M/E were + provided by the optional Fast FORTRAN Processor firmware. + + The 1000-F substituted a hardware floating point processor for the firmware + in previous machines. In addition to the two- and three-word formats, the + F-Series introduced a four-word double-precision format. A five-word format + that provided extra range in the exponent by unpacking it from the mantissa + was also provided, although this capability was not documented in the user + manual. In addition, the FPP improved the accuracy of floating-point + calculations, as the firmware versions sacrificed a few bits of precision to + gain speed. Consequently, operations on the F-Series may return results that + differ slightly from the same operations on the M/E-Series or the 2100. + + F-Series units after date code 1920 also provided two-word double-integer + instructions in firmware, as well as double-integer fix and float operations. + + The original 32-bit floating-point format is as follows: + + 15 14 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |MS| mantissa high | : M + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa low | exponent |XS| : M + 1 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + 15 8 7 1 0 + + Both 23-bit mantissa and 7-bit exponent are in twos-complement form. The + exponent sign bit has been rotated into the LSB of the second word. + + The extended-precision floating-point format is a 48-bit extension of the + 32-bit format used for single precision. A packed extended-precision value + consists of a 39-bit mantissa and a 7-bit exponent. The format is as + follows: + + 15 14 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |MS| mantissa high | : M + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa middle | : M + 1 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa low | exponent |XS| : M + 2 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + 15 8 7 1 0 + + The double-precision floating-point format is similar to the 48-bit + extended-precision format, although with a 55-bit mantissa: + + 15 14 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |MS| mantissa high | : M + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa middle high | : M + 1 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa middle low | : M + 2 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa low | exponent |XS| : M + 3 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + 15 8 7 1 0 + + The FPP also supports a special five-word expanded-exponent format: + + 15 14 0 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + |MS| mantissa high | : M + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa middle high | : M + 1 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa middle low | : M + 2 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | mantissa low | : M + 3 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + | exponent |XS| : M + 4 + +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + 15 8 7 1 0 + + The exponent is a full 16-bit twos-complement value, but the allowed range is + only 10 bits, i.e., -512 to +511. + + In a normalized value, the sign and MSB of the mantissa differ. Zero is + represented by all words = 0. + + Internally, unpacked floating-point values are contained in a structure + having a signed 64-bit mantissa and a signed 32-bit exponent. Mantissas are + left-justified with the unused bits masked to zero. Exponents are + right-justified. The precision is indicated by the value of a structure + field. + + HP terminology for the three-word floating-point format is confused. Some + documents refer to it as "double precision," while others use "extended + precision." The instruction mnemonics begin with "X" (e.g., .XADD), + suggesting the extended-precision term. + + HP apparently intended that the four-word double-precision format would be + called "triple-precision," as the instruction mnemonics begin with "T" (e.g., + ".TADD" for the four-word add instruction). The source files for the + software simulations of these instructions for the M/E-Series also explicitly + refer to "triple precision math." However, the engineering documentation and + the F-Series reference manual both use the double-precision term. + + This module adopts the single/extended/double terminology and uses the + initial letters of the instructions (F/X/T) to indicate the precision used. + + The FPP hardware consisted of two circuit boards that interfaced to the main + CPU via the Microprogrammable Processor Port (MPP) that had been introduced + with the 1000 E-Series. One board contained argument registers and ALUs, + split into separate mantissa and exponent parts. The other contained a state + machine sequencer. FPP results were copied automatically to the argument + registers in addition to being available over the MPP, so that chained + operations could be executed from these "accumulators" without reloading. + + The FPP operated independently of the CPU. An opcode, specifying one of the + six operations (add, subtract, multiply, divide, fix, or float) was sent to + the FPP, and a start command was given. Operands of appropriate precision + were then supplied to the FPP. Once the operands were received, the FPP + would execute and set a flag when the operation was complete. The result + would then be retrieved from the FPP. The floating-point instruction + firmware in the CPU initiated the desired FPP operation and handled operand + reads from and result writes to main memory. + + Under simulation, "fp_exec" provides the six arithmetic operations analogous + to FPP execution. The remainder of the functions are helpers that were + provided by firmware in the 1000-F but that can reuse code needed to simulate + the FPP hardware. As with the hardware, "fp_exec" retains the last result + in an internal accumulator that may be referenced in subsequent operations. + + NOTE: this module also provides the floating-point support for the firmware + single-precision 1000-M/E base set and extended-precision FFP instructions. + Because the firmware and hardware implementations returned slightly different + results, particularly with respect to round-off, conditional checks are + implemented in the arithmetic routines. In some cases, entirely different + algorithms are used to ensure fidelity with the real machines. Functionally, + this means that the 2100/1000-M/E and 1000-F floating-point diagnostics are + not interchangeable, and failures are to be expected if a diagnostic is run + on the wrong machine. +*/ + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "hp2100_cpu1.h" +#include "hp2100_fp1.h" + + +#if defined (HAVE_INT64) /* we need int64 support */ + +/* Field widths. */ + +#define IN_W_SIGN 1 +#define IN_W_SMAGN 15 +#define IN_W_DMAGN 31 + +#define FP_W_MSIGN 1 +#define FP_W_FMANT 23 +#define FP_W_XMANT 39 +#define FP_W_TMANT 55 +#define FP_W_EMANT 55 +#define FP_W_EXPANDEXP 9 +#define FP_W_EXP 7 +#define FP_W_ESIGN 1 + +/* Starting bit numbers. */ + +#define IN_V_SIGN (64 - IN_W_SIGN) +#define IN_V_SNUM (64 - IN_W_SIGN - IN_W_SMAGN) +#define IN_V_DNUM (64 - IN_W_SIGN - IN_W_DMAGN) + +#define FP_V_FNUM (64 - FP_W_MSIGN - FP_W_FMANT - FP_W_EXP - FP_W_ESIGN) +#define FP_V_XNUM (64 - FP_W_MSIGN - FP_W_XMANT - FP_W_EXP - FP_W_ESIGN) +#define FP_V_TNUM (64 - FP_W_MSIGN - FP_W_TMANT - FP_W_EXP - FP_W_ESIGN) +#define FP_V_ENUM (64 - FP_W_MSIGN - FP_W_EMANT - FP_W_EXP - FP_W_ESIGN) + +#define FP_V_MSIGN (64 - FP_W_MSIGN) +#define FP_V_FMANT (64 - FP_W_MSIGN - FP_W_FMANT) +#define FP_V_XMANT (64 - FP_W_MSIGN - FP_W_XMANT) +#define FP_V_TMANT (64 - FP_W_MSIGN - FP_W_TMANT) +#define FP_V_EMANT (64 - FP_W_MSIGN - FP_W_EMANT) +#define FP_V_EXP 1 +#define FP_V_ESIGN 0 + +/* Right-aligned field masks. */ + +#define IN_M_SIGN (((t_uint64) 1 << IN_W_SIGN) - 1) +#define IN_M_SMAGN (((t_uint64) 1 << IN_W_SMAGN) - 1) +#define IN_M_DMAGN (((t_uint64) 1 << IN_W_DMAGN) - 1) + +#define FP_M_MSIGN (((t_uint64) 1 << FP_W_MSIGN) - 1) +#define FP_M_FMANT (((t_uint64) 1 << FP_W_FMANT) - 1) +#define FP_M_XMANT (((t_uint64) 1 << FP_W_XMANT) - 1) +#define FP_M_TMANT (((t_uint64) 1 << FP_W_TMANT) - 1) +#define FP_M_EMANT (((t_uint64) 1 << FP_W_EMANT) - 1) + +#define FP_M_EXPANDEXP ((1 << FP_W_EXPANDEXP) - 1) +#define FP_M_EXP ((1 << FP_W_EXP) - 1) +#define FP_M_ESIGN ((1 << FP_W_ESIGN) - 1) + +/* In-place field masks. */ + +#define IN_SIGN (IN_M_SIGN << IN_V_SIGN) +#define IN_SMAGN (IN_M_SMAGN << IN_V_SNUM) +#define IN_DMAGN (IN_M_DMAGN << IN_V_DNUM) + +#define FP_MSIGN (FP_M_MSIGN << FP_V_MSIGN) +#define FP_FMANT (FP_M_FMANT << FP_V_FMANT) +#define FP_XMANT (FP_M_XMANT << FP_V_XMANT) +#define FP_TMANT (FP_M_TMANT << FP_V_TMANT) +#define FP_EMANT (FP_M_EMANT << FP_V_EMANT) +#define FP_EXP (FP_M_EXP << FP_V_EXP) +#define FP_ESIGN (FP_M_ESIGN << FP_V_ESIGN) + +/* In-place record masks. */ + +#define IN_SSMAGN (IN_SIGN | IN_SMAGN) +#define IN_SDMAGN (IN_SIGN | IN_DMAGN) + +#define FP_SFMANT (FP_MSIGN | FP_FMANT) +#define FP_SXMANT (FP_MSIGN | FP_XMANT) +#define FP_STMANT (FP_MSIGN | FP_TMANT) +#define FP_SEMANT (FP_MSIGN | FP_EMANT) +#define FP_SEXP (FP_ESIGN | FP_EXP) + +/* Minima and maxima. */ + +#define FP_ONEHALF ((t_int64) 1 << (FP_V_MSIGN - 1)) /* mantissa = 0.5 */ +#define FP_MAXPMANT ((t_int64) FP_EMANT) /* maximum pos mantissa */ +#define FP_MAXNMANT ((t_int64) FP_MSIGN) /* maximum neg mantissa */ +#define FP_MAXPEXP (FP_M_EXPANDEXP) /* maximum pos expanded exponent */ +#define FP_MAXNEXP (-(FP_MAXPEXP + 1)) /* maximum neg expanded exponent */ + +/* Floating-point helpers. */ + +#define DENORM(x) ((((x) ^ (x) << 1) & FP_MSIGN) == 0) + +#define TO_EXP(e) (int8) ((e >> FP_V_EXP & FP_M_EXP) | \ + (e & FP_M_ESIGN ? ~FP_M_EXP : 0)) + +/* Property constants. */ + +static const t_int64 p_half_lsb[6] = { ((t_int64) 1 << IN_V_SNUM) - 1, /* different than FP! */ + ((t_int64) 1 << IN_V_DNUM) - 1, /* different than FP! */ + (t_int64) 1 << (FP_V_FMANT - 1), + (t_int64) 1 << (FP_V_XMANT - 1), + (t_int64) 1 << (FP_V_TMANT - 1), + (t_int64) 1 << (FP_V_EMANT - 1) }; + +static const t_int64 n_half_lsb[6] = { 0, + 0, + ((t_int64) 1 << (FP_V_FMANT - 1)) - 1, + ((t_int64) 1 << (FP_V_XMANT - 1)) - 1, + ((t_int64) 1 << (FP_V_TMANT - 1)) - 1, + ((t_int64) 1 << (FP_V_EMANT - 1)) - 1 }; + +static const uint32 op_start[6] = { IN_V_SNUM, + IN_V_DNUM, + FP_V_FMANT, + FP_V_XMANT, + FP_V_TMANT, + FP_V_EMANT }; + +static const t_uint64 mant_mask[6] = { IN_SSMAGN, + IN_SDMAGN, + FP_SFMANT, + FP_SXMANT, + FP_STMANT, + FP_SEMANT }; + +static const uint32 op_bits[6] = { IN_W_SMAGN, + IN_W_DMAGN, + FP_W_FMANT + FP_W_MSIGN, + FP_W_XMANT + FP_W_MSIGN, + FP_W_TMANT + FP_W_MSIGN, + FP_W_EMANT + FP_W_MSIGN }; + +static const t_int64 op_mask[6] = { ~(((t_int64) 1 << IN_V_SNUM) - 1), + ~(((t_int64) 1 << IN_V_DNUM) - 1), + ~(((t_int64) 1 << FP_V_FNUM) - 1), + ~(((t_int64) 1 << FP_V_XNUM) - 1), + ~(((t_int64) 1 << FP_V_TNUM) - 1), + ~(((t_int64) 1 << FP_V_ENUM) - 1) }; + +static const uint32 int_p_max[2] = { IN_M_SMAGN, + IN_M_DMAGN }; + + +/* Internal unpacked floating-point representation. */ + +typedef struct { + t_int64 mantissa; + int32 exponent; + OPSIZE precision; + } FPU; + + + +/* Low-level helper routines. */ + + +/* Arithmetic shift right for mantissa only. + + Returns TRUE if any one-bits are shifted out (for F-series only). +*/ + +static t_bool asr (FPU *operand, int32 shift) +{ +t_uint64 mask; +t_bool bits_lost; + +if (UNIT_CPU_MODEL == UNIT_1000_F) { /* F-series? */ + mask = ((t_uint64) 1 << shift) - 1; /* mask for lost bits */ + bits_lost = ((operand->mantissa & mask) != 0); /* flag if any lost */ + } +else + bits_lost = FALSE; + +operand->mantissa = operand->mantissa >> shift; /* mantissa is int, so ASR */ +return bits_lost; +} + + +/* Logical shift right for mantissa and exponent. + + Shifts mantissa and corrects exponent for mantissa overflow. + Returns TRUE if any one-bits are shifted out (for F-series only). +*/ + +static t_bool lsrx (FPU *operand, int32 shift) +{ +t_uint64 mask; +t_bool bits_lost; + +if (UNIT_CPU_MODEL == UNIT_1000_F) { /* F-series? */ + mask = ((t_uint64) 1 << shift) - 1; /* mask for lost bits */ + bits_lost = ((operand->mantissa & mask) != 0); /* flag if any lost */ + } +else + bits_lost = FALSE; + +operand->mantissa = (t_uint64) operand->mantissa >> shift; /* uint, so LSR */ +operand->exponent = operand->exponent + shift; /* correct exponent */ +return bits_lost; +} + + +/* Unpack an operand into a long integer. + + Returns a left-aligned integer or mantissa. Does not mask to precision; this + should be done subsequently if desired. +*/ + +static t_int64 unpack_int (OP packed, OPSIZE precision) +{ +uint32 i; +t_uint64 unpacked = 0; + +if (precision == in_s) + unpacked = (t_uint64) packed.word << 48; /* unpack single integer */ + +else if (precision == in_d) + unpacked = (t_uint64) packed.dword << 32; /* unpack double integer */ + +else { + if (precision == fp_e) /* five word operand? */ + precision = fp_t; /* only four mantissa words */ + + for (i = 0; i < 4; i++) /* unpack fp 2 to 4 words */ + if (i < TO_COUNT (precision)) + unpacked = unpacked << 16 | packed.fpk[i]; + else + unpacked = unpacked << 16; + } + +return (t_int64) unpacked; +} + + +/* Unpack a packed operand. + + The packed value is split into separate mantissa and exponent variables. The + multiple words of the mantissa are concatenated into a single 64-bit signed + value, and the exponent is shifted with recovery of the sign. +*/ + +static FPU unpack (OP packed, OPSIZE precision) +{ +FPU unpacked; + +unpacked.precision = precision; /* set value's precision */ + +unpacked.mantissa = /* unpack and mask mantissa */ + unpack_int (packed, precision) & (t_int64) mant_mask[precision]; + +switch (precision) { + + case fp_f: + case fp_x: + case fp_t: + unpacked.exponent = /* unpack exponent from correct word */ + TO_EXP (packed.fpk[(uint32) precision - 1]); + break; + + case fp_e: + unpacked.exponent = /* unpack expanded exponent */ + (int16) (packed.fpk[4] >> FP_V_EXP | /* rotate sign into place */ + (packed.fpk[4] & 1 ? SIGN : 0)); + break; + + case fp_a: /* no action for value in accum */ + case in_s: /* integers don't use exponent */ + case in_d: /* integers don't use exponent */ + default: + unpacked.exponent = 0; + break; + } + +return unpacked; +} + + +/* Pack a long integer into an operand. */ + +static OP pack_int (t_int64 unpacked, OPSIZE precision) +{ +int32 i; +OP packed; + +if (precision == in_s) + packed.word = (uint16) (unpacked >> 48) & DMASK; /* pack single integer */ + +else if (precision == in_d) + packed.dword = (uint32) (unpacked >> 32) & DMASK32; /* pack double integer */ + +else { + if (precision == fp_e) /* five word operand? */ + precision = fp_t; /* only four mantissa words */ + + for (i = 3; i >= 0; i--) { /* pack fp 2 to 4 words */ + packed.fpk[i] = (uint16) unpacked & DMASK; + unpacked = unpacked >> 16; + } + } + +return packed; +} + + +/* Pack an unpacked floating-point number. + + The 64-bit mantissa is split into the appropriate number of 16-bit words. + The exponent is rotated to incorporate the sign bit and merged into the + appropriate word. +*/ + +static OP pack (FPU unpacked) +{ +OP packed; +uint8 exp; + +packed = pack_int (unpacked.mantissa, unpacked.precision); /* pack mantissa */ + +exp = (uint8) (unpacked.exponent << FP_V_EXP | /* rotate exponent */ + (unpacked.exponent < 0) << FP_V_ESIGN); + +switch (unpacked.precision) { /* merge exponent into correct word */ + + case in_s: /* no action for integers */ + case in_d: + break; + + case fp_f: /* merge into last word */ + case fp_x: + case fp_t: + packed.fpk[(uint32) unpacked.precision - 1] = + (packed.fpk[(uint32) unpacked.precision - 1] & ~FP_SEXP) | exp; + break; + + case fp_e: /* place in separate word */ + packed.fpk[4] = (uint16) (unpacked.exponent << FP_V_EXP | + (unpacked.exponent < 0) << FP_V_ESIGN); + break; + + case fp_a: /* no action for value in accum */ + break; + } + +return packed; +} + + +/* Normalize an unpacked floating-point number. + + Floating-point numbers are in normal form if the sign bit and the MSB of the + mantissa differ. Unnormalized numbers are shifted as needed with appropriate + exponent modification. +*/ + +static void normalize (FPU *unpacked) +{ + +if (unpacked->mantissa) /* non-zero? */ + while (DENORM (unpacked->mantissa)) { /* normal form? */ + unpacked->exponent = unpacked->exponent - 1; /* no, so left shift */ + unpacked->mantissa = unpacked->mantissa << 1; + } +else + unpacked->exponent = 0; /* clean for zero */ +return; +} + + +/* Round an unpacked floating-point number and check for overflow. + + An unpacked floating-point number is rounded by adding one-half of the LSB + value, maintaining symmetry around zero. If rounding resulted in a mantissa + overflow, the result logically is shifted to the right with an appropriate + exponent modification. Finally, the result is checked for exponent underflow + or overflow, and the appropriate approximation (zero or infinity) is + returned. + + Rounding in hardware involves a special mantissa extension register that + holds three "guard" bits and one "sticky" bit. These represent the value of + bits right-shifted out the mantissa register. Under simulation, we track + such right-shifts and utilize the lower eight bits of the 64-bit mantissa + value to simulate the extension register. + + Overflow depends on whether the FPP expanded-exponent form is being used + (this expands the exponent range by two bits). If overflow is detected, the + value representing infinity is dependent on whether the operation is on + behalf of the Fast FORTRAN Processor. The F-Series FPP returns positive + infinity on both positive and negative overflow for all precisions. The 2100 + and M/E-Series FFPs return negative infinity on negative overflow of + extended-precision values. Single-precision overflows on these machines + always return positive infinity. + + The number to be rounded must be normalized upon entry. +*/ + +static uint32 roundovf (FPU *unpacked, t_bool expand) +{ +uint32 overflow; +t_bool sign; + +sign = (unpacked->mantissa < 0); /* save mantissa sign */ + +if (sign) /* round and mask the number */ + unpacked->mantissa = + (unpacked->mantissa + n_half_lsb[unpacked->precision]) & + (t_int64) mant_mask[unpacked->precision]; +else + unpacked->mantissa = + (unpacked->mantissa + p_half_lsb[unpacked->precision]) & + (t_int64) mant_mask[unpacked->precision]; + +if (sign != (unpacked->mantissa < 0)) /* mantissa overflow? */ + lsrx (unpacked, 1); /* correct by shifting */ +else + normalize (unpacked); /* renorm may be needed */ + +if (unpacked->mantissa == 0) { /* result zero? */ + unpacked->mantissa = 0; /* return zero */ + unpacked->exponent = 0; + overflow = 0; /* with overflow clear */ + } +else if (unpacked->exponent < /* result underflow? */ + (FP_MAXNEXP >> (expand ? 0 : 2))) { + unpacked->mantissa = 0; /* return zero */ + unpacked->exponent = 0; + overflow = 1; /* and set overflow */ + } +else if (unpacked->exponent > /* result overflow? */ + (FP_MAXPEXP >> (expand ? 0 : 2))) { + if (sign && /* negative value? */ + (unpacked->precision == fp_x) && /* extended precision? */ + (UNIT_CPU_MODEL != UNIT_1000_F)) { /* not F-series? */ + unpacked->mantissa = FP_MAXNMANT; /* return negative infinity */ + unpacked->exponent = FP_MAXPEXP & FP_M_EXP; + } + else { + unpacked->mantissa = FP_MAXPMANT; /* return positive infinity */ + unpacked->exponent = FP_MAXPEXP & FP_M_EXP; + } + overflow = 1; /* and set overflow */ + } +else + overflow = 0; /* value is in range */ + +return overflow; +} + + +/* Normalize, round, and pack an unpacked floating-point number. */ + +static uint32 nrpack (OP *packed, FPU unpacked, t_bool expand) +{ +uint32 overflow; + +normalize (&unpacked); /* normalize for rounding */ +overflow = roundovf (&unpacked, expand); /* round and check for overflow */ +*packed = pack (unpacked); /* pack result */ + +return overflow; +} + + + +/* Low-level arithmetic routines. */ + + +/* Complement an unpacked number. */ + +static void complement (FPU *result) +{ +if (result->mantissa == FP_MAXNMANT) { /* maximum negative? */ + result->mantissa = FP_ONEHALF; /* complement of -1.0 * 2 ^ n */ + result->exponent = result->exponent + 1; /* is 0.5 * 2 ^ (n + 1) */ + } +else + result->mantissa = -result->mantissa; /* negate mantissa */ +return; +} + + +/* Add two unpacked numbers. + + The mantissas are first aligned if necessary by scaling the smaller of the + two operands. If the magnitude of the difference between the exponents is + greater than the number of significant bits, then the smaller number has been + scaled to zero (swamped), and so the sum is simply the larger operand. + Otherwise, the sum is computed and checked for overflow, which has occurred + if the signs of the operands are the same but differ from that of the result. + Scaling and renormalization is performed if overflow occurred. +*/ + +static void add (FPU *sum, FPU augend, FPU addend) +{ +int32 magn; +t_bool bits_lost; + +if (augend.mantissa == 0) + *sum = addend; /* X + 0 = X */ + +else if (addend.mantissa == 0) + *sum = augend; /* 0 + X = X */ + +else { + magn = augend.exponent - addend.exponent; /* difference exponents */ + + if (magn > 0) { /* addend smaller? */ + *sum = augend; /* preset augend */ + bits_lost = asr (&addend, magn); /* align addend */ + } + else { /* augend smaller? */ + *sum = addend; /* preset addend */ + magn = -magn; /* make difference positive */ + bits_lost = asr (&augend, magn); /* align augend */ + } + + if (magn <= (int32) op_bits[augend.precision]) { /* value swamped? */ + sum->mantissa = /* no, add mantissas */ + addend.mantissa + augend.mantissa; + + if (((addend.mantissa < 0) == (augend.mantissa < 0)) && /* mantissa overflow? */ + ((addend.mantissa < 0) != (sum->mantissa < 0))) { + bits_lost = bits_lost | lsrx (sum, 1); /* restore value */ + sum->mantissa = /* restore sign */ + sum-> mantissa | (addend.mantissa & FP_MSIGN); + } + + if (bits_lost) /* any bits lost? */ + sum->mantissa = sum->mantissa | 1; /* include one for rounding */ + } + } +return; +} + + +/* Multiply two unpacked numbers. + + The single-precision firmware (FMP) operates differently from the firmware + extended-precision (.XMPY) and the hardware multiplies of any precision. + Firmware implementations use the MPY micro-order to form 16-bit x 16-bit = + 32-bit partial products and sum them to form the result. The hardware uses a + series of shifts and adds. This means that firmware FMP and hardware FMP + return slightly different values, as may be seen by attempting to run the + firmware FMP diagnostic on the FPP. + + The FMP microcode calls a signed multiply routine to calculate three partial + products (all but LSB * LSB). Because the LSBs are unsigned, i.e., all bits + significant, the two MSB * LSB products are calculated using LSB/2. The + unsigned right-shift ensures a positive LSB with no significant bits lost, + because the lower eight bits are unused (they held the vacated exponent). In + order to sum the partial products, the LSB of the result of MSB * MSB is also + right-shifted before addition. Note, though, that this loses a significant + bit. After summation, the result is left-shifted to correct for the original + right shifts. + + The .XMPY microcode negates both operands as necessary to produce positive + values and then forms six of the nine 16-bit x 16-bit = 32-bit unsigned + multiplications required for a full 96-bit product. Given a 48-bit + multiplicand "a1a2a3" and a 48-bit multiplier "b1b2b3", the firmware performs + these calculations to develop a 48-bit product: + + a1 a2 a3 + +-------+-------+-------+ + b1 b2 b3 + +-------+-------+-------+ + _________________________ + + a1 * b3 [p1] + +-------+-------+ + a2 * b2 [p2] + +-------+-------+ + a1 * b2 [p3] + +-------+-------+ + a3 * b1 [p4] + +-------+-------+ + a2 * b1 [p5] + +-------+-------+ + a1 * b1 [p6] + +-------+-------+ + _________________________________ + + product + +-------+-------+-------+ + + The least-significant words of partial products [p1], [p2], and [p4] are used + only to develop a carry bit into the 48-bit sum. The product is complemented + as necessary to restore the sign. + + The basic FPP hardware algorithm scans the multiplier and adds a shifted copy + of the multiplicand whenever a one-bit is detected. To avoid successive adds + when a string of ones is encountered (because adds are more expensive than + shifts), the hardware instead adds the multiplicand shifted by N + 1 + P and + subtracts the multiplicand shifted by P to obtain the equivalent value with a + maximum of two operations. + + Instead of implementing either the .XMPY firmware algorithm or the hardware + shift-and-add algorithm directly, it is more efficient under simulation to + use 32 x 32 = 64-bit multiplications, thereby reducing the number required + from six to four (64-bit "c1c2" x 64-bit "d1d2"): + + ah al + +-------+-------+ + bh bl + +-------+-------+ + _________________ + + al * bl [ll] + +-------+-------+ + ah * bl [hl] + +-------+-------+ + al * bh [lh] + +-------+-------+ + ah * bh [hh] + +-------+-------+ + _________________________________ + + product + +-------+-------+ + + However, the FMP algorithm is implemented directly from the microcode to + preserve the fidelity of the simulation, i.e., to lose the same amount + of precision. +*/ + +static void multiply (FPU *product, FPU multiplicand, FPU multiplier) +{ +uint32 ah, al, bh, bl, sign = 0; +t_uint64 hh, hl, lh, ll, carry; +int16 ch, cl, dh, dl; +t_bool firmware; + +product->precision = multiplicand.precision; /* set precision */ + +if ((multiplicand.mantissa == 0) || /* 0 * X = 0 */ + (multiplier.mantissa == 0)) /* X * 0 = 0 */ + product->mantissa = product->exponent = 0; + +else { + firmware = (UNIT_CPU_MODEL != UNIT_1000_F); /* set firmware flag */ + + if (!firmware || (product->precision != fp_f)) { /* hardware? */ + if (multiplicand.mantissa < 0) { /* negative? */ + complement (&multiplicand); /* complement operand */ + sign = ~sign; /* track sign */ + } + if (multiplier.mantissa < 0) { /* negative? */ + complement (&multiplier); /* complement operand */ + sign = ~sign; /* track sign */ + } + } + + product->exponent = /* compute exponent */ + multiplicand.exponent + multiplier.exponent + 1; + + ah = (uint32) (multiplicand.mantissa >> 32); /* split multiplicand */ + al = (uint32) (multiplicand.mantissa & DMASK32); /* into high and low parts */ + bh = (uint32) (multiplier.mantissa >> 32); /* split multiplier */ + bl = (uint32) (multiplier.mantissa & DMASK32); /* into high and low parts */ + + if (firmware && (product->precision == fp_f)) { /* single-precision firmware? */ + ch = (int16) (ah >> 16) & DMASK; /* split 32-bit multiplicand */ + cl = (int16) (ah & 0xfffe); /* into high and low parts */ + dh = (int16) (bh >> 16) & DMASK; /* split 32-bit multiplier */ + dl = (int16) (bh & 0xfffe); /* into high and low parts */ + + hh = (t_uint64) (((int32) ch * dh) & ~1); /* form cross products */ + hl = (t_uint64) (((t_int64) ch * (t_int64) (uint16) dl + + (t_int64) dh * (t_int64) (uint16) cl) & + 0xfffffffffffe0000); + + product->mantissa = (t_uint64) (((t_int64) hh << 32) + /* sum partials */ + ((t_int64) hl << 16)); + } + + else { + hh = ((t_uint64) ah * bh); /* form four cross products */ + hl = ((t_uint64) ah * bl); /* using 32 x 32 = */ + lh = ((t_uint64) al * bh); /* 64-bit multiplies */ + ll = ((t_uint64) al * bl); + + carry = ((ll >> 32) + (uint32) hl + (uint32) lh) >> 32; /* form carry */ + + product->mantissa = hh + (hl >> 32) + (lh >> 32) + carry; /* sum partials */ + + if (sign) /* negate if required */ + complement (product); + } + } +return; +} + + +/* Divide two unpacked numbers. + + As with multiply, the single-precision firmware (FDV) operates differently + from the firmware extended-precision (.XDIV) and the hardware divisions of + any precision. Firmware implementations use the DIV micro-order to form + 32-bit / 16-bit = 16-bit quotients and 16-bit remainders. These are used in + a "divide and correct" algorithm, wherein the quotient is estimated and then + corrected by comparing the dividend to the product of the quotient and the + divisor. The hardware uses a series of shifts and subtracts. This means + that firmware FDV and hardware FDV once again return slightly different + values. + + Under simulation, the classic divide-and-correct method is employed, using + 64-bit / 32-bit = 32-bit divisions. This method considers the 64-bit + dividend and divisor each to consist of two 32-bit "digits." The 64-bit + dividend "a1a2a3a4" is divided by the first 32-bit digit "b1b2" of the 64-bit + divisor "b1b2b3b4", yielding a 32-bit trial quotient digit and a 32-bit + remainder digit. A correction is developed by subtracting the product of the + second 32-bit digit "b3b4" of the divisor and the trial quotient digit from + the remainder (we take advantage of the eight bits vacated by the exponent + during unpacking to ensure that this product will not overflow into the sign + bit). If the remainder is negative, the trial quotient is too large, so it + is decremented, and the (full 64-bit) divisor is added to the correction. + This is repeated until the correction is non-negative, indicating that the + first quotient digit is correct. The process is then repeated using the + remainder as the dividend to develop the second 32-bit digit of the quotient. + The two digits are then concatenated for produce the final 64-bit value. + + (See, "Divide-and-Correct Methods for Multiple Precision Division" by Marvin + L. Stein, Communications of the ACM, August 1964 for background.) + + The microcoded single-precision division avoids overflows by right-shifting + some values, which leads to a loss of precision in the LSBs. We duplicate + the firmware algorithm here to preserve the fidelity of the simulation. +*/ + +static void divide (FPU *quotient, FPU dividend, FPU divisor) +{ +uint32 sign = 0; +t_int64 bh, bl, r1, r0, p1, p0; +t_uint64 q, q1, q0; +t_bool firmware; +int32 ah, div, cp; +int16 dh, dl, pq1, pq2, cq; + +quotient->precision = dividend.precision; /* set precision */ + +if (divisor.mantissa == 0) { /* division by zero? */ + if (dividend.mantissa < 0) + quotient->mantissa = FP_MSIGN; /* return minus infinity */ + else + quotient->mantissa = ~FP_MSIGN; /* or plus infinity */ + quotient->exponent = FP_MAXPEXP + 1; + } + +else if (dividend.mantissa == 0) /* dividend zero? */ + quotient->mantissa = quotient->exponent = 0; /* yes; result is zero */ + +else { + firmware = (UNIT_CPU_MODEL != UNIT_1000_F); /* set firmware flag */ + + if (!firmware || (quotient->precision != fp_f)) { /* hardware or FFP? */ + if (dividend.mantissa < 0) { /* negative? */ + complement (÷nd); /* complement operand */ + sign = ~sign; /* track sign */ + } + if (divisor.mantissa < 0) { /* negative? */ + complement (&divisor); /* complement operand */ + sign = ~sign; /* track sign */ + } + } + + quotient->exponent = /* division subtracts exponents */ + dividend.exponent - divisor.exponent; + + bh = divisor.mantissa >> 32; /* split divisor */ + bl = divisor.mantissa & DMASK32; /* into high and low parts */ + + if (firmware && (quotient->precision == fp_f)) { /* single-precision firmware? */ + quotient->exponent = quotient->exponent + 1; /* fix exponent */ + + ah = (int32) (dividend.mantissa >> 32); /* split dividend */ + dh = (int16) (bh >> 16); /* split divisor again */ + dl = (int16) bh; + + div = ah >> 2; /* ASR 2 to prevent overflow */ + + pq1 = (int16) (div / dh); /* form first partial quotient */ + div = ((div % dh) & ~1) << 15; /* ASR 1, move rem to upper */ + pq2 = (int16) (div / dh); /* form second partial quotient */ + + div = (uint16) dl << 13; /* move divisor LSB to upper, LSR 3 */ + cq = (int16) (div / dh); /* form correction quotient */ + cp = -cq * pq1; /* and correction product */ + + cp = (((cp >> 14) & ~3) + (int32) pq2) << 1; /* add corr prod and 2nd partial quo */ + quotient->mantissa = /* add 1st partial quo and align */ + (t_uint64) (((int32) pq1 << 16) + cp) << 32; + } + + else { /* hardware or FFP */ + q1 = (t_uint64) (dividend.mantissa / bh); /* form 1st trial quotient */ + r1 = dividend.mantissa % bh; /* and remainder */ + p1 = (r1 << 24) - (bl >> 8) * q1; /* calculate correction */ + + while (p1 < 0) { /* correction needed? */ + q1 = q1 - 1; /* trial quotient too large */ + p1 = p1 + (divisor.mantissa >> 8); /* increase remainder */ + } + + q0 = (t_uint64) ((p1 << 8) / bh); /* form 2nd trial quotient */ + r0 = (p1 << 8) % bh; /* and remainder */ + p0 = (r0 << 24) - (bl >> 8) * q0; /* calculate correction */ + + while (p0 < 0) { /* correction needed? */ + q0 = q0 - 1; /* trial quotient too large */ + p0 = p0 + (divisor.mantissa >> 8); /* increase remainder */ + } + + q = (q1 << 32) + q0; /* sum quotient digits */ + + if (q1 & 0xffffffff00000000) { /* did we lose MSB? */ + q = (q >> 1) | 0x8000000000000000; /* shift right and replace bit */ + quotient->exponent = quotient->exponent + 1;/* bump exponent for shift */ + } + + if (q & 0x8000000000000000) /* lose normalization? */ + q = q >> 1; /* correct */ + + quotient->mantissa = (t_int64) q; + } + + if (sign) + complement (quotient); /* negate if required */ + } +return; +} + + +/* Fix an unpacked number. + + A floating-point value is converted to an integer. The desired precision of + the result (single or double integer) must be set before calling. + + Values less than 0.5 (i.e., with negative exponents) underflow to zero. If + the value exceeds the specified integer range, the maximum integer value is + returned and overflow is set. Otherwise, the floating-point value is + right-shifted to zero the exponent. The result is then rounded. +*/ + +static uint32 fix (FPU *result, FPU operand) +{ +uint32 overflow; +t_bool bits_lost; + +if (operand.exponent < 0) { /* value < 0.5? */ + result->mantissa = 0; /* result rounds to zero */ + overflow = 0; /* clear for underflow */ + } + +else if (operand.exponent > /* value > integer size? */ + (int32) op_bits[result->precision]) { + result->mantissa = /* return max int value */ + (t_uint64) int_p_max[result->precision] << + op_start[result->precision]; + overflow = 1; /* and set overflow */ + } + +else { /* value in range */ + bits_lost = asr (&operand, /* shift to zero exponent */ + op_bits[result->precision] - operand.exponent); + + if (operand.mantissa < 0) { /* value negative? */ + if (bits_lost) /* bits lost? */ + operand.mantissa = operand.mantissa | 1; /* include one for rounding */ + + operand.mantissa = operand.mantissa + /* round result */ + p_half_lsb[result->precision]; + } + + result->mantissa = operand.mantissa & /* mask to precision */ + op_mask[result->precision]; + overflow = 0; + } + +result->exponent = 0; /* tidy up for integer value */ +return overflow; +} + + +/* Float an integer to an unpacked number. + + An integer is converted to a floating-point value. The desired precision of + the result must be set before calling. + + Conversion is simply a matter of copying the integer value, setting an + exponent that reflects the right-aligned position of the bits, and + normalizing. +*/ + +static void ffloat (FPU *result, FPU operand) +{ +result->mantissa = operand.mantissa; /* set value */ +result->exponent = op_bits[operand.precision]; /* set exponent */ +normalize (result); /* normalize */ +return; +} + + + +/* High-level floating-point routines. */ + + +/* Determine operand precisions. + + The precisions of the operands and result are determined by decoding an + operation opcode and returned to the caller. Pass NULL for both of the + operands if only the result precision is wanted. Pass NULL for the result if + only the operand precisions are wanted. + + Implementation note: + + 1. gcc-4.3.0 complains at -O3 that operand_l/r may not be initialized. + Because of the mask, the switch statement covers all cases, but gcc + doesn't realize this. The "default" case is redundant but eliminates the + warning. +*/ + +void fp_prec (uint16 opcode, OPSIZE *operand_l, OPSIZE *operand_r, OPSIZE *result) +{ +OPSIZE fp_size, int_size; + +fp_size = (OPSIZE) ((opcode & 0003) + 2); /* fp_f, fp_x, fp_t, fp_e */ +int_size = (OPSIZE) ((opcode & 0004) >> 2); /* in_s, in_d */ + +if (operand_l && operand_r) { /* want operand precisions? */ + switch (opcode & 0120) { /* mask out opcode bit 5 */ + case 0000: /* add/mpy */ + case 0020: /* sub/div */ + *operand_l = fp_size; /* assume first op is fp */ + + if (opcode & 0004) /* operand internal? */ + *operand_r = fp_a; /* second op is accum */ + else + *operand_r = fp_size; /* second op is fp */ + break; + + case 0100: /* fix/accum as integer */ + *operand_l = fp_size; /* first op is fp */ + *operand_r = fp_a; /* second op is always null */ + break; + + case 0120: /* flt/accum as float */ + default: /* keeps compiler quiet for uninit warning */ + *operand_l = int_size; /* first op is integer */ + *operand_r = fp_a; /* second op is always null */ + break; + } + + if (opcode & 0010) /* operand internal? */ + *operand_l = fp_a; /* first op is accum */ + } + +if (result) /* want result precision? */ + if ((opcode & 0120) == 0100) /* fix? */ + *result = int_size; /* result is integer */ + else /* all others */ + *result = fp_size; /* result is fp */ + +return; +} + + +/* Floating Point Processor executor. + + The executor simulates the MPP interface between the CPU and the FPP. The + operation to be performed is specified by the supplied opcode, which conforms + to the FPP hardware interface, as follows: + + Bits Value Action + ---- ----- ---------------------------------------------- + 7 0 Exponent range is standard (+/-127) + 1 Exponent range is expanded (+/-511) + + 6-4 000 Add + 001 Subtract + 010 Multiply + 011 Divide + 100 Fix + 101 Float + 110 (diagnostic) + 111 (diagnostic) + + 3 0 Left operand is supplied + 1 Left operand in accumulator + + 2 0 Right operand is supplied (ADD/SUB/MPY/DIV) + Single integer operation (FIX/FLT) + 1 Right operand in accumulator (ADD/SUB/MPY/DIV) + Double integer operation (FIX/FLT) + + 1-0 00 2-word operation + 01 3-word operation + 10 4-word operation + 11 5-word operation + + If the opcode specifies that the left (or right) operand is in the + accumulator, then the value supplied for that parameter is not used. All + results are automatically left in the accumulator. If the result is not + needed externally, then NULL may be passed for the result parameter. + + To support accumulator set/get operations under simulation, the opcode is + expanded to include a special mode, indicated by bit 15 = 1. In this mode, + if the result parameter is NULL, then the accumulator is set from the value + passed as operand_l. If the result parameter is not null, then the + accumulator value is returned as the result, and operand_l is ignored. The + precision of the operation is performed as specified by the OPSIZE value + passed in bits 2-0 of the opcode. + + The function returns 1 if the operation overflows and 0 if not. +*/ + +uint32 fp_exec (uint16 opcode, OP *result, OP operand_l, OP operand_r) +{ +static FPU accumulator; +FPU uoperand_l, uoperand_r; +OPSIZE op_l_prec, op_r_prec, rslt_prec; +uint32 overflow; + +if (opcode & SIGN) { /* accumulator mode? */ + rslt_prec = (OPSIZE) (opcode & 0017); /* get operation precision */ + + if (result) { /* get accumulator? */ + op_l_prec = accumulator.precision; /* save accum prec temp */ + accumulator.precision = rslt_prec; /* set desired precision */ + *result = pack (accumulator); /* pack accumulator */ + accumulator.precision = op_l_prec; /* restore correct prec */ + } + else /* set accumulator */ + accumulator = unpack (operand_l, rslt_prec); /* unpack from operand */ + + return 0; /* no overflow from accum ops */ + } + +fp_prec (opcode, &op_l_prec, &op_r_prec, &rslt_prec); /* calc precs from opcode */ + +if (op_l_prec == fp_a) /* left operand in accum? */ + uoperand_l = accumulator; /* copy it */ +else /* operand supplied */ + uoperand_l = unpack (operand_l, op_l_prec); /* unpack from parameter */ + +if (op_r_prec == fp_a) /* right operand in accum? */ + uoperand_r = accumulator; /* copy it */ +else /* operand supplied */ + uoperand_r = unpack (operand_r, op_r_prec); /* unpack from parameter */ + + +switch (opcode & 0160) { /* dispatch operation */ + + case 0000: /* add */ + add (&accumulator, uoperand_l, uoperand_r); + break; + + case 0020: /* subtract */ + complement (&uoperand_r); + add (&accumulator, uoperand_l, uoperand_r); + break; + + case 0040: /* multiply */ + multiply (&accumulator, uoperand_l, uoperand_r); + break; + + case 0060: /* divide */ + divide (&accumulator, uoperand_l, uoperand_r); + break; + + case 0100: /* fix */ + accumulator.precision = rslt_prec; + overflow = fix (&accumulator, uoperand_l); + + if (result) /* result wanted? */ + *result = pack_int (accumulator.mantissa, /* pack integer */ + rslt_prec); + return overflow; + + case 0120: /* float */ + accumulator.precision = rslt_prec; + ffloat (&accumulator, uoperand_l); + + if (result) /* result wanted? */ + *result = pack (accumulator); /* pack FP (FLT does not round) */ + return 0; + + case 0140: /* (diagnostic) */ + case 0160: /* (diagnostic) */ + return 0; + } + +if (UNIT_CPU_MODEL != UNIT_1000_F) /* firmware implementation? */ + accumulator.mantissa = accumulator.mantissa & /* mask to precision */ + op_mask[accumulator.precision]; + +normalize (&accumulator); /* normalize */ +overflow = roundovf (&accumulator, opcode & 0200); /* round and check for overflow */ + +if (result) /* result wanted? */ + *result = pack (accumulator); /* pack result */ + +return overflow; +} + + +/* Set or get accumulator at desired precision. + + This function provides access to the FPP accumulator. In hardware, the + accumulator may be read at a given precision by sending the FPP an opcode + encoded with the desired precision and then reading words from the FPP + /without/ initiating the operation, i.e., without starting the processor. + + Under simulation, pass this function a NULL operand and the desired + precision to read the accumulator. Pass a pointer to an operand and the + desired precision to set the accumulator; the return value in this case is + not defined. +*/ + +OP fp_accum (const OP *operand, OPSIZE precision) +{ +OP result = NOP; +uint16 opcode = (uint16) precision | SIGN; /* add special mode bit */ + +if (operand) + fp_exec (opcode, NULL, *operand, NOP); /* set accum */ +else + fp_exec (opcode, &result, NOP, NOP); /* get accum */ +return result; +} + + +/* Pack an unpacked floating-point number. + + An unpacked mantissa is passed as a "packed" number with an unused exponent. + The mantissa and separately-passed exponent are packed into the in-memory + floating-point format. Note that all bits are significant in the mantissa + (no masking is done). +*/ + +uint32 fp_pack (OP *result, OP mantissa, int32 exponent, OPSIZE precision) +{ +FPU unpacked; + +unpacked.mantissa = unpack_int (mantissa, precision); /* unpack mantissa */ +unpacked.exponent = exponent; /* set exponent */ +unpacked.precision = precision; /* set precision */ +*result = pack (unpacked); /* pack them */ +return 0; +} + + +/* Normalize, round, and pack an unpacked floating-point number. + + An unpacked mantissa is passed as a "packed" number with an unused exponent. + The mantissa and separately-passed exponent are normalized, rounded, and + packed into the in-memory floating-point format. Note that all bits are + significant in the mantissa (no masking is done). +*/ + +uint32 fp_nrpack (OP *result, OP mantissa, int32 exponent, OPSIZE precision) +{ +FPU unpacked; + +unpacked.mantissa = unpack_int (mantissa, precision); /* unpack mantissa */ +unpacked.exponent = exponent; /* set exponent */ +unpacked.precision = precision; /* set precision */ +return nrpack (result, unpacked, FALSE); /* norm/rnd/pack them */ +} + + +/* Unpack a packed floating-point number. + + A floating-point number, packed into the in-memory format, is unpacked into + separate mantissa and exponent values. The unpacked mantissa is returned in + a "packed" structure with an exponent of zero. Mantissa or exponent may be + null if that part isn't wanted. +*/ + +uint32 fp_unpack (OP *mantissa, int32 *exponent, OP packed, OPSIZE precision) + +{ +FPU unpacked; + +unpacked = unpack (packed, precision); /* unpack mantissa and exponent */ + +if (exponent) /* exponent wanted? */ + *exponent = unpacked.exponent; /* return exponent */ + +if (mantissa) /* mantissa wanted? */ + *mantissa = pack_int (unpacked.mantissa, fp_t); /* return full-size mantissa */ +return 0; +} + + +/* Complement an unpacked mantissa. + + An unpacked mantissa is passed as a "packed" number with a zero exponent. + The exponent increment, i.e., either zero or one, depending on whether a + renormalization was required, is returned. Note that all bits are + significant in the mantissa. +*/ + +uint16 fp_ucom (OP *mantissa, OPSIZE precision) +{ +FPU unpacked; + +unpacked.mantissa = unpack_int (*mantissa, precision); /* unpack mantissa */ +unpacked.exponent = 0; /* clear undefined exponent */ +unpacked.precision = precision; /* set precision */ +complement (&unpacked); /* negate it */ +*mantissa = pack_int (unpacked.mantissa, precision); /* replace mantissa */ +return (uint16) unpacked.exponent; /* return exponent increment */ +} + + +/* Complement a floating-point number. */ + +uint32 fp_pcom (OP *packed, OPSIZE precision) +{ +FPU unpacked; + +unpacked = unpack (*packed, precision); /* unpack the number */ +complement (&unpacked); /* negate it */ +return nrpack (packed, unpacked, FALSE); /* and norm/rnd/pack */ +} + + +/* Truncate a floating-point number. */ + +uint32 fp_trun (OP *result, OP source, OPSIZE precision) +{ +t_bool bits_lost; +FPU unpacked; +FPU one = { FP_ONEHALF, 1, fp_t }; /* 0.5 * 2 ** 1 = 1.0 */ +OP zero = { { 0, 0, 0, 0, 0 } }; /* 0.0 */ +t_uint64 mask = mant_mask[precision] & ~FP_MSIGN; + +unpacked = unpack (source, precision); +if (unpacked.exponent < 0) /* number < 0.5? */ + *result = zero; /* return 0 */ +else if (unpacked.exponent >= (int32) op_bits[precision]) /* no fractional bits? */ + *result = source; /* already integer */ +else { + mask = (mask >> unpacked.exponent) & mask; /* mask fractional bits */ + bits_lost = ((unpacked.mantissa & mask) != 0); /* flag if bits lost */ + unpacked.mantissa = unpacked.mantissa & ~mask; /* mask off fraction */ + if ((unpacked.mantissa < 0) && bits_lost) /* negative? */ + add (&unpacked, unpacked, one); /* truncate toward zero */ + nrpack (result, unpacked, FALSE); /* (overflow cannot occur) */ + } +return 0; /* clear overflow on return */ +} + + +/* Convert a floating-point number from one precision to another. */ + +uint32 fp_cvt (OP *result, OPSIZE source_precision, OPSIZE dest_precision) +{ +FPU unpacked; + +unpacked = unpack (*result, source_precision); +unpacked.precision = dest_precision; +return nrpack (result, unpacked, FALSE); /* norm/rnd/pack */ +} + + +#endif /* end of int64 support */ diff --git a/HP2100/hp2100_fp1.h b/HP2100/hp2100_fp1.h index 4b25da83..4de87a1e 100644 --- a/HP2100/hp2100_fp1.h +++ b/HP2100/hp2100_fp1.h @@ -1,55 +1,55 @@ -/* hp2100_fp1.h: HP 2100/1000 multiple-precision floating point definitions - - Copyright (c) 2005-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be used - in advertising or otherwise to promote the sale, use or other dealings in - this Software without prior written authorization from the author. - - 24-Dec-14 JDB Changed fp_ucom return from uint32 to uint16 - 14-Mar-13 MP Changed guard macro name to avoid reserved namespace - 16-Oct-06 JDB Generalized FP calling sequences for F-Series - 12-Oct-06 JDB Altered x_trun for F-Series FFP compatibility -*/ - -#ifndef HP2100_FP1_H_ -#define HP2100_FP1_H_ 0 - - -/* Special operands. */ - -#define ACCUM NULL /* result not returned */ -static const OP NOP = { { 0, 0, 0, 0, 0 } }; /* unneeded operand */ - - -/* Generalized floating-point handlers. */ - -void fp_prec (uint16 opcode, OPSIZE *operand_l, OPSIZE *operand_r, OPSIZE *result); -uint32 fp_exec (uint16 opcode, OP *result, OP operand_l, OP operand_r); -OP fp_accum (const OP *operand, OPSIZE precision); -uint32 fp_pack (OP *result, OP mantissa, int32 exponent, OPSIZE precision); -uint32 fp_nrpack (OP *result, OP mantissa, int32 exponent, OPSIZE precision); -uint32 fp_unpack (OP *mantissa, int32 *exponent, OP packed, OPSIZE precision); -uint16 fp_ucom (OP *mantissa, OPSIZE precision); -uint32 fp_pcom (OP *packed, OPSIZE precision); -uint32 fp_trun (OP *result, OP source, OPSIZE precision); -uint32 fp_cvt (OP *result, OPSIZE source_precision, OPSIZE dest_precision); - -#endif +/* hp2100_fp1.h: HP 2100/1000 multiple-precision floating point definitions + + Copyright (c) 2005-2014, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be used + in advertising or otherwise to promote the sale, use or other dealings in + this Software without prior written authorization from the author. + + 24-Dec-14 JDB Changed fp_ucom return from uint32 to uint16 + 14-Mar-13 MP Changed guard macro name to avoid reserved namespace + 16-Oct-06 JDB Generalized FP calling sequences for F-Series + 12-Oct-06 JDB Altered x_trun for F-Series FFP compatibility +*/ + +#ifndef HP2100_FP1_H_ +#define HP2100_FP1_H_ 0 + + +/* Special operands. */ + +#define ACCUM NULL /* result not returned */ +static const OP NOP = { { 0, 0, 0, 0, 0 } }; /* unneeded operand */ + + +/* Generalized floating-point handlers. */ + +void fp_prec (uint16 opcode, OPSIZE *operand_l, OPSIZE *operand_r, OPSIZE *result); +uint32 fp_exec (uint16 opcode, OP *result, OP operand_l, OP operand_r); +OP fp_accum (const OP *operand, OPSIZE precision); +uint32 fp_pack (OP *result, OP mantissa, int32 exponent, OPSIZE precision); +uint32 fp_nrpack (OP *result, OP mantissa, int32 exponent, OPSIZE precision); +uint32 fp_unpack (OP *mantissa, int32 *exponent, OP packed, OPSIZE precision); +uint16 fp_ucom (OP *mantissa, OPSIZE precision); +uint32 fp_pcom (OP *packed, OPSIZE precision); +uint32 fp_trun (OP *result, OP source, OPSIZE precision); +uint32 fp_cvt (OP *result, OPSIZE source_precision, OPSIZE dest_precision); + +#endif diff --git a/HP2100/hp2100_ipl.c b/HP2100/hp2100_ipl.c index 61c785a8..84546db4 100644 --- a/HP2100/hp2100_ipl.c +++ b/HP2100/hp2100_ipl.c @@ -1,795 +1,876 @@ -/******************************************************************************* - * * - * WARNING: THE 4.0 VERSION OF THIS FILE IS DIFFERENT (SOCKET API CHANGED)!!! * - * * - *******************************************************************************/ - -/* hp2100_ipl.c: HP 2000 interprocessor link simulator - - Copyright (c) 2002-2014, Robert M Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - IPLI, IPLO 12875A interprocessor link - - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 25-Oct-12 JDB Removed DEV_NET to allow restoration of listening ports - 09-May-12 JDB Separated assignments from conditional expressions - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - Added CARD_INDEX casts to dib.card_index - 07-Apr-11 JDB A failed STC may now be retried - 28-Mar-11 JDB Tidied up signal handling - 27-Mar-11 JDB Consolidated reporting of consecutive CRS signals - 29-Oct-10 JDB Revised for new multi-card paradigm - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach - 15-Jul-08 JDB Revised EDT handler to refine completion delay conditions - 09-Jul-08 JDB Revised ipl_boot to use ibl_copy - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 01-Mar-07 JDB IPLI EDT delays DMA completion interrupt for TSB - Added debug printouts - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 16-Aug-05 RMS Fixed C++ declaration and cast problems - 07-Oct-04 JDB Fixed enable/disable from either device - 26-Apr-04 RMS Fixed SFS x,C and SFC x,C - Implemented DMA SRQ (follows FLG) - 21-Dec-03 RMS Adjusted ipl_ptime for TSB (from Mike Gemeny) - 09-May-03 RMS Added network device flag - 31-Jan-03 RMS Links are full duplex (found by Mike Gemeny) - - Reference: - - 12875A Processor Interconnect Kit Operating and Service Manual - (12875-90002, Jan-1974) - - - The 12875A Processor Interconnect Kit consists four 12566A Microcircuit - Interface cards. Two are used in each processor. One card in each system is - used to initiate transmissions to the other, and the second card is used to - receive transmissions from the other. Each pair of cards forms a - bidirectional link, as the sixteen data lines are cross-connected, so that - data sent and status returned are supported. In each processor, data is sent - on the lower priority card and received on the higher priority card. Two - sets of cards are used to support simultaneous transmission in both - directions. -*/ - - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "sim_sock.h" -#include "sim_tmxr.h" - -typedef enum { ipli, iplo } CARD_INDEX; /* card index number */ - -#define CARD_COUNT 2 /* count of cards supported */ - -#define UNIT_V_DIAG (UNIT_V_UF + 0) /* diagnostic mode */ -#define UNIT_V_ACTV (UNIT_V_UF + 1) /* making connection */ -#define UNIT_V_ESTB (UNIT_V_UF + 2) /* connection established */ -#define UNIT_V_HOLD (UNIT_V_UF + 3) /* character holding */ -#define UNIT_DIAG (1 << UNIT_V_DIAG) -#define UNIT_ACTV (1 << UNIT_V_ACTV) -#define UNIT_ESTB (1 << UNIT_V_ESTB) -#define UNIT_HOLD (1 << UNIT_V_HOLD) -#define IBUF buf /* input buffer */ -#define OBUF wait /* output buffer */ -#define DSOCKET u3 /* data socket */ -#define LSOCKET u4 /* listening socket */ - -/* Debug flags */ - -#define DEB_CMDS (1 << 0) /* Command initiation and completion */ -#define DEB_CPU (1 << 1) /* CPU I/O */ -#define DEB_XFER (1 << 2) /* Socket receive and transmit */ - -extern DIB ptr_dib; /* need PTR select code for boot */ - -int32 ipl_edtdelay = 1; /* EDT delay (msec) */ -int32 ipl_ptime = 31; /* polling interval */ -int32 ipl_stopioe = 0; /* stop on error */ - -typedef struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - int32 hold; /* holding character */ - } CARD_STATE; - -CARD_STATE ipl [CARD_COUNT]; /* per-card state */ - -IOHANDLER iplio; - -t_stat ipl_svc (UNIT *uptr); -t_stat ipl_reset (DEVICE *dptr); -t_stat ipl_attach (UNIT *uptr, char *cptr); -t_stat ipl_detach (UNIT *uptr); -t_stat ipl_boot (int32 unitno, DEVICE *dptr); -t_stat ipl_dscln (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat ipl_setdiag (UNIT *uptr, int32 val, char *cptr, void *desc); -t_bool ipl_check_conn (UNIT *uptr); - -/* Debug flags table */ - -DEBTAB ipl_deb [] = { - { "CMDS", DEB_CMDS }, - { "CPU", DEB_CPU }, - { "XFER", DEB_XFER }, - { NULL, 0 } - }; - -/* Common structures */ - -DEVICE ipli_dev, iplo_dev; - -static DEVICE *dptrs [] = { &ipli_dev, &iplo_dev }; - - -UNIT ipl_unit [] = { - { UDATA (&ipl_svc, UNIT_ATTABLE, 0) }, - { UDATA (&ipl_svc, UNIT_ATTABLE, 0) } - }; - -#define ipli_unit ipl_unit [ipli] -#define iplo_unit ipl_unit [iplo] - - -DIB ipl_dib [] = { - { &iplio, IPLI, 0 }, - { &iplio, IPLO, 1 } - }; - -#define ipli_dib ipl_dib [ipli] -#define iplo_dib ipl_dib [iplo] - - -/* IPLI data structures - - ipli_dev IPLI device descriptor - ipli_unit IPLI unit descriptor - ipli_reg IPLI register list -*/ - -REG ipli_reg [] = { - { ORDATA (IBUF, ipli_unit.IBUF, 16) }, - { ORDATA (OBUF, ipli_unit.OBUF, 16) }, - { FLDATA (CTL, ipl [ipli].control, 0) }, - { FLDATA (FLG, ipl [ipli].flag, 0) }, - { FLDATA (FBF, ipl [ipli].flagbuf, 0) }, - { ORDATA (HOLD, ipl [ipli].hold, 8) }, - { DRDATA (TIME, ipl_ptime, 24), PV_LEFT }, - { FLDATA (STOP_IOE, ipl_stopioe, 0) }, - { ORDATA (SC, ipli_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, ipli_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB ipl_mod [] = { - { UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAG", &ipl_setdiag }, - { UNIT_DIAG, 0, "link mode", "LINK", &ipl_setdiag }, - { MTAB_XTD | MTAB_VDV, 0, NULL, "DISCONNECT", - &ipl_dscln, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &ipli_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &ipli_dev }, - { 0 } - }; - -DEVICE ipli_dev = { - "IPLI", &ipli_unit, ipli_reg, ipl_mod, - 1, 10, 31, 1, 16, 16, - &tmxr_ex, &tmxr_dep, &ipl_reset, - &ipl_boot, &ipl_attach, &ipl_detach, - &ipli_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG, - 0, ipl_deb, NULL, NULL - }; - -/* IPLO data structures - - iplo_dev IPLO device descriptor - iplo_unit IPLO unit descriptor - iplo_reg IPLO register list -*/ - -REG iplo_reg [] = { - { ORDATA (IBUF, iplo_unit.IBUF, 16) }, - { ORDATA (OBUF, iplo_unit.OBUF, 16) }, - { FLDATA (CTL, ipl [iplo].control, 0) }, - { FLDATA (FLG, ipl [iplo].flag, 0) }, - { FLDATA (FBF, ipl [iplo].flagbuf, 0) }, - { ORDATA (HOLD, ipl [iplo].hold, 8) }, - { DRDATA (TIME, ipl_ptime, 24), PV_LEFT }, - { ORDATA (SC, iplo_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, iplo_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -DEVICE iplo_dev = { - "IPLO", &iplo_unit, iplo_reg, ipl_mod, - 1, 10, 31, 1, 16, 16, - &tmxr_ex, &tmxr_dep, &ipl_reset, - &ipl_boot, &ipl_attach, &ipl_detach, - &iplo_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG, - 0, ipl_deb, NULL, NULL - }; - - -/* I/O signal handler for the IPLI and IPLO devices. - - In the link mode, the IPLI and IPLO devices are linked via network - connections to the corresponding cards in another CPU instance. In the - diagnostic mode, we simulate the attachment of the interprocessor cable - between IPLI and IPLO in this machine. - - Implementation notes: - - 1. 2000 Access has a race condition that manifests itself by an apparently - normal boot and operational system console but no PLEASE LOG IN response - to terminals connected to the multiplexer. The frequency of occurrence - is higher on multiprocessor host systems, where the SP and IOP instances - may execute concurrently. - - The cause is this code in the SP disc loader source (2883.asm, 7900.asm, - 790X.asm, 79X3.asm, and 79XX.asm): - - LDA SDVTR REQUEST - JSB IOPMA,I DEVICE TABLE - [...] - STC DMAHS,C TURN ON DMA - SFS DMAHS WAIT FOR - JMP *-1 DEVICE TABLE - STC CH2,C SET CORRECT - CLC CH2 FLAG DIRECTION - - The STC/CLC normally would cause a second "request device table" command - to be recognized by the IOP, except that the IOP DMA setup routine - "DMAXF" (in D61.asm) has specified an end-of-block CLC that holds off the - IPL interrupt, and the completion interrupt routine "DMACP" ends with a - STC,C that clears the IPL flag. - - In hardware, the two CPUs are essentially interlocked by the DMA - transfer, and DMA completion interrupts occur almost simultaneously. - Therefore, the STC/CLC in the SP is guaranteed to occur before the STC,C - in the IOP. Under simulation, and especially on multiprocessor hosts, - that guarantee does not hold. If the STC/CLC occurs after the STC,C, - then the IOP starts a second device table DMA transfer, which the SP is - not expecting. The IOP never processes the subsequent "start - timesharing" command, and the muxtiplexer is non-reponsive. - - We employ a workaround that decreases the incidence of the problem: DMA - output completion interrupts are delayed to allow the other SIMH instance - a chance to process its own DMA completion. We do this by processing the - EDT (End Data Transfer) I/O backplane signal and "sleep"ing for a short - time if the transfer was an output transfer to the input channel, i.e., - a data response to the SP. This improves the race condition by delaying - the IOP until the SP has a chance to receive the last word, recognize its - own DMA input completion, drop out of the SFS loop, and execute the - STC/CLC. - - The condition is only improved, and not solved, because "sleep"ing the - IOP doesn't guarantee that the SP will actually execute. It's possible - that a higher-priority host process will preempt the SP, and that at the - sleep expiration, the SP still has not executed the STC/CLC. Still, in - testing, the incidence dropped dramatically, so the problem is much less - intrusive. - - 2. The operating manual for the 12920A Terminal Multiplexer says that "at - least 100 milliseconds of CLC 0s must be programmed" by systems employing - the multiplexer to ensure that the multiplexer resets. In practice, such - systems issue 128K CLC 0 instructions. As we provide debug logging of - IPL resets, a CRS counter is used to ensure that only one debug line is - printed in response to these 128K CRS invocations. - - 3. The STC handler may return "Unit not attached", "I/O error", or "No - connection on interprocessor link" status if the link fails or is - improperly configured. If the error is corrected, the operation may be - retried by resuming simulated execution. -*/ - -uint32 iplio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -CARD_INDEX card = (CARD_INDEX) dibptr->card_index; /* set card selector */ -UNIT *const uptr = &(ipl_unit [card]); /* associated unit pointer */ -const char *iotype [] = { "Status", "Command" }; -int32 sta; -char msg [2]; -static uint32 crs_count [CARD_COUNT] = { 0, 0 }; /* per-card cntrs for ioCRS repeat */ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -if (crs_count [card] && !(signal_set & ioCRS)) { /* counting CRSes and not present? */ - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) /* report reset count */ - fprintf (sim_deb, ">>%s cmds: [CRS] Control cleared %d times\n", - dptrs [card]->name, crs_count [card]); - - crs_count [card] = 0; /* clear counter */ - } - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - ipl [card].flag = ipl [card].flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - ipl [card].flag = ipl [card].flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (ipl [card]); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (ipl [card]); - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, uptr->IBUF); /* get return data */ - - if (DEBUG_PRJ (dptrs [card], DEB_CPU)) - fprintf (sim_deb, ">>%s cpu: [LIx] %s = %06o\n", dptrs [card]->name, iotype [card ^ 1], uptr->IBUF); - break; - - - case ioIOO: /* I/O data output */ - uptr->OBUF = IODATA (stat_data); /* clear supplied status */ - - if (DEBUG_PRJ (dptrs [card], DEB_CPU)) - fprintf (sim_deb, ">>%s cpu: [OTx] %s = %06o\n", dptrs [card]->name, iotype [card], uptr->OBUF); - break; - - - case ioPOPIO: /* power-on preset to I/O */ - ipl [card].flag = ipl [card].flagbuf = SET; /* set flag buffer and flag */ - uptr->OBUF = 0; /* clear output buffer */ - break; - - - case ioCRS: /* control reset */ - if (crs_count [card] == 0) /* first reset? */ - ipl [card].control = CLEAR; /* clear control */ - - crs_count [card] = crs_count [card] + 1; /* increment count */ - break; - - - case ioCLC: /* clear control flip-flop */ - ipl [card].control = CLEAR; /* clear ctl */ - - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: [CLC] Control cleared\n", dptrs [card]->name); - break; - - - case ioSTC: /* set control flip-flop */ - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, ">>%s cmds: [STC] Control set\n", dptrs [card]->name); - - if (uptr->flags & UNIT_ATT) { /* attached? */ - if (!ipl_check_conn (uptr)) /* not established? */ - return IORETURN (STOP_NOCONN, 0); /* lose */ - - msg [0] = (uptr->OBUF >> 8) & 0377; - msg [1] = uptr->OBUF & 0377; - sta = sim_write_sock (uptr->DSOCKET, msg, 2); - - if (DEBUG_PRJ (dptrs [card], DEB_XFER)) - fprintf (sim_deb, - ">>%s xfer: [STC] Socket write = %06o, status = %d\n", - dptrs [card]->name, uptr->OBUF, sta); - - if (sta == SOCKET_ERROR) { - printf ("IPL socket write error\n"); - return IORETURN (SCPE_IOERR, 0); - } - - ipl [card].control = SET; /* set ctl */ - - sim_os_sleep (0); - } - - else if (uptr->flags & UNIT_DIAG) { /* diagnostic mode? */ - ipl [card].control = SET; /* set ctl */ - ipl_unit [card ^ 1].IBUF = uptr->OBUF; /* output to other */ - iplio ((DIB *) dptrs [card ^ 1]->ctxt, ioENF, 0); /* set other flag */ - } - - else - return IORETURN (SCPE_UNATT, 0); /* lose */ - break; - - - case ioEDT: /* end data transfer */ - if ((cpu_unit.flags & UNIT_IOP) && /* are we the IOP? */ - (signal_set & ioIOO) && /* and doing output? */ - (card == ipli)) { /* on the input card? */ - - if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) - fprintf (sim_deb, - ">>%s cmds: [EDT] Delaying DMA completion interrupt for %d msec\n", - dptrs [card]->name, ipl_edtdelay); - - sim_os_ms_sleep (ipl_edtdelay); /* delay completion */ - } - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (ipl [card]); - setstdIRQ (ipl [card]); - setstdSRQ (ipl [card]); - break; - - - case ioIAK: /* interrupt acknowledge */ - ipl [card].flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Unit service - poll for input */ - -t_stat ipl_svc (UNIT *uptr) -{ -CARD_INDEX card; -int32 nb; -char msg [2]; - -if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */ - return SCPE_OK; - -sim_activate (uptr, ipl_ptime); /* reactivate */ - -if (!ipl_check_conn (uptr)) /* check for conn */ - return SCPE_OK; /* not connected */ - -nb = sim_read_sock (uptr->DSOCKET, msg, ((uptr->flags & UNIT_HOLD)? 1: 2)); - -if (nb < 0) { /* connection closed? */ - printf ("IPL socket read error\n"); - return SCPE_IOERR; - } - -else if (nb == 0) /* no data? */ - return SCPE_OK; - -card = (CARD_INDEX) (uptr == &iplo_unit); /* set card selector */ - -if (uptr->flags & UNIT_HOLD) { /* holdover byte? */ - uptr->IBUF = (ipl [card].hold << 8) | (((int32) msg [0]) & 0377); - uptr->flags = uptr->flags & ~UNIT_HOLD; - } -else if (nb == 1) { - ipl [card].hold = ((int32) msg [0]) & 0377; - uptr->flags = uptr->flags | UNIT_HOLD; - } -else - uptr->IBUF = ((((int32) msg [0]) & 0377) << 8) | (((int32) msg [1]) & 0377); - -iplio ((DIB *) dptrs [card]->ctxt, ioENF, 0); /* set flag */ - -if (DEBUG_PRJ (dptrs [card], DEB_XFER)) - fprintf (sim_deb, ">>%s xfer: Socket read = %06o, status = %d\n", - dptrs [card]->name, uptr->IBUF, nb); - -return SCPE_OK; -} - - -t_bool ipl_check_conn (UNIT *uptr) -{ -SOCKET sock; - -if (uptr->flags & UNIT_ESTB) /* established? */ - return TRUE; - -if (uptr->flags & UNIT_ACTV) { /* active connect? */ - if (sim_check_conn (uptr->DSOCKET, 0) <= 0) - return FALSE; - } - -else { - sock = sim_accept_conn (uptr->LSOCKET, NULL); /* poll connect */ - - if (sock == INVALID_SOCKET) /* got a live one? */ - return FALSE; - - uptr->DSOCKET = sock; /* save data socket */ - } - -uptr->flags = uptr->flags | UNIT_ESTB; /* conn established */ -return TRUE; -} - - -/* Reset routine. - - Implementation notes: - - 1. We set up the first poll for socket connections to occur "immediately" - upon execution, so that clients will be connected before execution - begins. Otherwise, a fast program may access the IPL before the poll - service routine activates. -*/ - -t_stat ipl_reset (DEVICE *dptr) -{ -UNIT *uptr = dptr->units; -DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ -CARD_INDEX card = (CARD_INDEX) dibptr->card_index; /* card number */ - -hp_enbdis_pair (dptr, dptrs [card ^ 1]); /* make pair cons */ - -if (sim_switches & SWMASK ('P')) /* initialization reset? */ - uptr->IBUF = uptr->OBUF = 0; /* clr buffers */ - -IOPRESET (dibptr); /* PRESET device (does not use PON) */ - -if (uptr->flags & UNIT_ATT) /* socket attached? */ - sim_activate (uptr, POLL_FIRST); /* activate first poll "immediately" */ -else - sim_cancel (uptr); /* deactivate unit */ - -uptr->flags = uptr->flags & ~UNIT_HOLD; /* clear holding flag */ -return SCPE_OK; -} - - -/* Attach routine - - attach -l - listen for connection on port - attach -c - connect to ip address and port -*/ - -t_stat ipl_attach (UNIT *uptr, char *cptr) -{ -SOCKET newsock; -uint32 i, t, ipa, ipp, oldf; -char *tptr; -t_stat r; - -r = get_ipaddr (cptr, &ipa, &ipp); -if ((r != SCPE_OK) || (ipp == 0)) - return SCPE_ARG; -oldf = uptr->flags; -if (oldf & UNIT_ATT) - ipl_detach (uptr); -if ((sim_switches & SWMASK ('C')) || - ((sim_switches & SIM_SW_REST) && (oldf & UNIT_ACTV))) { - if (ipa == 0) - ipa = 0x7F000001; - newsock = sim_connect_sock (ipa, ipp); - if (newsock == INVALID_SOCKET) - return SCPE_IOERR; - printf ("Connecting to IP address %d.%d.%d.%d, port %d\n", - (ipa >> 24) & 0xff, (ipa >> 16) & 0xff, - (ipa >> 8) & 0xff, ipa & 0xff, ipp); - if (sim_log) - fprintf (sim_log, - "Connecting to IP address %d.%d.%d.%d, port %d\n", - (ipa >> 24) & 0xff, (ipa >> 16) & 0xff, - (ipa >> 8) & 0xff, ipa & 0xff, ipp); - uptr->flags = uptr->flags | UNIT_ACTV; - uptr->LSOCKET = 0; - uptr->DSOCKET = newsock; - } -else { - if (ipa != 0) - return SCPE_ARG; - newsock = sim_master_sock (ipp); - if (newsock == INVALID_SOCKET) - return SCPE_IOERR; - printf ("Listening on port %d\n", ipp); - if (sim_log) - fprintf (sim_log, "Listening on port %d\n", ipp); - uptr->flags = uptr->flags & ~UNIT_ACTV; - uptr->LSOCKET = newsock; - uptr->DSOCKET = 0; - } -uptr->IBUF = uptr->OBUF = 0; -uptr->flags = (uptr->flags | UNIT_ATT) & ~(UNIT_ESTB | UNIT_HOLD); -tptr = (char *) malloc (strlen (cptr) + 1); /* get string buf */ -if (tptr == NULL) { /* no memory? */ - ipl_detach (uptr); /* close sockets */ - return SCPE_MEM; - } -strcpy (tptr, cptr); /* copy ipaddr:port */ -uptr->filename = tptr; /* save */ -sim_activate (uptr, POLL_FIRST); /* activate first poll "immediately" */ -if (sim_switches & SWMASK ('W')) { /* wait? */ - for (i = 0; i < 30; i++) { /* check for 30 sec */ - t = ipl_check_conn (uptr); - if (t) /* established? */ - break; - if ((i % 10) == 0) /* status every 10 sec */ - printf ("Waiting for connnection\n"); - sim_os_sleep (1); /* sleep 1 sec */ - } - if (t) /* if connected (set by "ipl_check_conn" above) */ - printf ("Connection established\n"); /* then report */ - } -return SCPE_OK; -} - -/* Detach routine */ - -t_stat ipl_detach (UNIT *uptr) -{ -if (!(uptr->flags & UNIT_ATT)) /* attached? */ - return SCPE_OK; - -if (uptr->flags & UNIT_ACTV) - sim_close_sock (uptr->DSOCKET, 1); - -else { - if (uptr->flags & UNIT_ESTB) /* if established, */ - sim_close_sock (uptr->DSOCKET, 0); /* close data socket */ - sim_close_sock (uptr->LSOCKET, 1); /* closen listen socket */ - } - -free (uptr->filename); /* free string */ -uptr->filename = NULL; -uptr->LSOCKET = 0; -uptr->DSOCKET = 0; -uptr->flags = uptr->flags & ~(UNIT_ATT | UNIT_ACTV | UNIT_ESTB); -sim_cancel (uptr); /* don't poll */ -return SCPE_OK; -} - -/* Disconnect routine */ - -t_stat ipl_dscln (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -if (cptr) - return SCPE_ARG; -if (((uptr->flags & UNIT_ATT) == 0) || - (uptr->flags & UNIT_ACTV) || - ((uptr->flags & UNIT_ESTB) == 0)) - return SCPE_NOFNC; -sim_close_sock (uptr->DSOCKET, 0); -uptr->DSOCKET = 0; -uptr->flags = uptr->flags & ~UNIT_ESTB; -return SCPE_OK; -} - -/* Diagnostic/normal mode routine */ - -t_stat ipl_setdiag (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -if (val) { - ipli_unit.flags = ipli_unit.flags | UNIT_DIAG; - iplo_unit.flags = iplo_unit.flags | UNIT_DIAG; - } -else { - ipli_unit.flags = ipli_unit.flags & ~UNIT_DIAG; - iplo_unit.flags = iplo_unit.flags & ~UNIT_DIAG; - } -return SCPE_OK; -} - -/* Interprocessor link bootstrap routine (HP Access Manual) */ - -#define MAX_BASE 073 -#define IPL_PNTR 074 -#define PTR_PNTR 075 -#define IPL_DEVA 076 -#define PTR_DEVA 077 - -static const BOOT_ROM ipl_rom = { - 0163774, /*BBL LDA ICK,I ; IPL sel code */ - 0027751, /* JMP CFG ; go configure */ - 0107700, /*ST CLC 0,C ; intr off */ - 0002702, /* CLA,CCE,SZA ; skip in */ - 0063772, /*CN LDA M26 ; feed frame */ - 0002307, /*EOC CCE,INA,SZA,RSS ; end of file? */ - 0027760, /* JMP EOT ; yes */ - 0017736, /* JSB READ ; get #char */ - 0007307, /* CMB,CCE,INB,SZB,RSS ; 2's comp; null? */ - 0027705, /* JMP EOC ; read next */ - 0077770, /* STB WC ; word in rec */ - 0017736, /* JSB READ ; get feed frame */ - 0017736, /* JSB READ ; get address */ - 0074000, /* STB 0 ; init csum */ - 0077771, /* STB AD ; save addr */ - 0067771, /*CK LDB AD ; check addr */ - 0047773, /* ADB MAXAD ; below loader */ - 0002040, /* SEZ ; E =0 => OK */ - 0102055, /* HLT 55 */ - 0017736, /* JSB READ ; get word */ - 0040001, /* ADA 1 ; cont checksum */ - 0177771, /* STB AD,I ; store word */ - 0037771, /* ISZ AD */ - 0000040, /* CLE ; force wd read */ - 0037770, /* ISZ WC ; block done? */ - 0027717, /* JMP CK ; no */ - 0017736, /* JSB READ ; get checksum */ - 0054000, /* CPB 0 ; ok? */ - 0027704, /* JMP CN ; next block */ - 0102011, /* HLT 11 ; bad csum */ - 0000000, /*RD 0 */ - 0006600, /* CLB,CME ; E reg byte ptr */ - 0103700, /*IO1 STC RDR,C ; start reader */ - 0102300, /*IO2 SFS RDR ; wait */ - 0027741, /* JMP *-1 */ - 0106400, /*IO3 MIB RDR ; get byte */ - 0002041, /* SEZ,RSS ; E set? */ - 0127736, /* JMP RD,I ; no, done */ - 0005767, /* BLF,CLE,BLF ; shift byte */ - 0027740, /* JMP IO1 ; again */ - 0163775, /* LDA PTR,I ; get ptr code */ - 0043765, /*CFG ADA SFS ; config IO */ - 0073741, /* STA IO2 */ - 0043766, /* ADA STC */ - 0073740, /* STA IO1 */ - 0043767, /* ADA MIB */ - 0073743, /* STA IO3 */ - 0027702, /* JMP ST */ - 0063777, /*EOT LDA PSC ; put select codes */ - 0067776, /* LDB ISC ; where xloader wants */ - 0102077, /* HLT 77 */ - 0027702, /* JMP ST */ - 0000000, /* NOP */ - 0102300, /*SFS SFS 0 */ - 0001400, /*STC 1400 */ - 0002500, /*MIB 2500 */ - 0000000, /*WC 0 */ - 0000000, /*AD 0 */ - 0177746, /*M26 -26 */ - 0000000, /*MAX -BBL */ - 0007776, /*ICK ISC */ - 0007777, /*PTR IPT */ - 0000000, /*ISC 0 */ - 0000000 /*IPT 0 */ - }; - -t_stat ipl_boot (int32 unitno, DEVICE *dptr) -{ -const int32 devi = ipli_dib.select_code; -const int32 devp = ptr_dib.select_code; - -if (ibl_copy (ipl_rom, devi, IBL_S_CLR, /* copy the boot ROM to memory and configure */ - IBL_SET_SC (devi) | devp)) /* the S register accordingly */ - return SCPE_IERR; /* return an internal error if the copy failed */ - -WritePW (PC + MAX_BASE, (~PC + 1) & DMASK); /* fix ups */ -WritePW (PC + IPL_PNTR, ipl_rom [IPL_PNTR] | PC); -WritePW (PC + PTR_PNTR, ipl_rom [PTR_PNTR] | PC); -WritePW (PC + IPL_DEVA, devi); -WritePW (PC + PTR_DEVA, devp); -return SCPE_OK; -} +/* hp2100_ipl.c: HP 2000 interprocessor link simulator + + Copyright (c) 2002-2016, Robert M Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + IPLI, IPLO 12875A interprocessor link + + 05-Aug-16 JDB Renamed the P register from "PC" to "PR" + 13-May-16 JDB Modified for revised SCP API function parameter types + 14-Sep-15 JDB Exposed "ipl_edtdelay" via a REG_HIDDEN to allow user tuning + Corrected typos in comments and strings + 05-Jun-15 JDB Merged 3.x and 4.x versions using conditionals + 11-Feb-15 MP Revised ipl_detach and ipl_dscln for new sim_close_sock API + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 12-Dec-12 MP Revised ipl_attach for new socket API + 25-Oct-12 JDB Removed DEV_NET to allow restoration of listening ports + 09-May-12 JDB Separated assignments from conditional expressions + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + Added CARD_INDEX casts to dib.card_index + 07-Apr-11 JDB A failed STC may now be retried + 28-Mar-11 JDB Tidied up signal handling + 27-Mar-11 JDB Consolidated reporting of consecutive CRS signals + 29-Oct-10 JDB Revised for new multi-card paradigm + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach + 15-Jul-08 JDB Revised EDT handler to refine completion delay conditions + 09-Jul-08 JDB Revised ipl_boot to use ibl_copy + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 01-Mar-07 JDB IPLI EDT delays DMA completion interrupt for TSB + Added debug printouts + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 16-Aug-05 RMS Fixed C++ declaration and cast problems + 07-Oct-04 JDB Fixed enable/disable from either device + 26-Apr-04 RMS Fixed SFS x,C and SFC x,C + Implemented DMA SRQ (follows FLG) + 21-Dec-03 RMS Adjusted ipl_ptime for TSB (from Mike Gemeny) + 09-May-03 RMS Added network device flag + 31-Jan-03 RMS Links are full duplex (found by Mike Gemeny) + + Reference: + - 12875A Processor Interconnect Kit Operating and Service Manual + (12875-90002, Jan-1974) + + + The 12875A Processor Interconnect Kit consists four 12566A Microcircuit + Interface cards. Two are used in each processor. One card in each system is + used to initiate transmissions to the other, and the second card is used to + receive transmissions from the other. Each pair of cards forms a + bidirectional link, as the sixteen data lines are cross-connected, so that + data sent and status returned are supported. In each processor, data is sent + on the lower priority card and received on the higher priority card. Two + sets of cards are used to support simultaneous transmission in both + directions. +*/ + + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "sim_sock.h" +#include "sim_tmxr.h" +#include "sim_rev.h" + + +#if (SIM_MAJOR >= 4) + #define sim_close_sock(socket,master) sim_close_sock (socket) +#endif + + +typedef enum { ipli, iplo } CARD_INDEX; /* card index number */ + +#define CARD_COUNT 2 /* count of cards supported */ + +#define UNIT_V_DIAG (UNIT_V_UF + 0) /* diagnostic mode */ +#define UNIT_V_ACTV (UNIT_V_UF + 1) /* making connection */ +#define UNIT_V_ESTB (UNIT_V_UF + 2) /* connection established */ +#define UNIT_V_HOLD (UNIT_V_UF + 3) /* character holding */ +#define UNIT_DIAG (1 << UNIT_V_DIAG) +#define UNIT_ACTV (1 << UNIT_V_ACTV) +#define UNIT_ESTB (1 << UNIT_V_ESTB) +#define UNIT_HOLD (1 << UNIT_V_HOLD) +#define IBUF buf /* input buffer */ +#define OBUF wait /* output buffer */ +#define DSOCKET u3 /* data socket */ +#define LSOCKET u4 /* listening socket */ + +/* Debug flags */ + +#define DEB_CMDS (1 << 0) /* Command initiation and completion */ +#define DEB_CPU (1 << 1) /* CPU I/O */ +#define DEB_XFER (1 << 2) /* Socket receive and transmit */ + +extern DIB ptr_dib; /* need PTR select code for boot */ + +int32 ipl_edtdelay = 1; /* EDT delay (msec) */ +int32 ipl_ptime = 31; /* polling interval */ +int32 ipl_stopioe = 0; /* stop on error */ + +typedef struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + int32 hold; /* holding character */ + } CARD_STATE; + +CARD_STATE ipl [CARD_COUNT]; /* per-card state */ + +IOHANDLER iplio; + +t_stat ipl_svc (UNIT *uptr); +t_stat ipl_reset (DEVICE *dptr); +t_stat ipl_attach (UNIT *uptr, CONST char *cptr); +t_stat ipl_detach (UNIT *uptr); +t_stat ipl_boot (int32 unitno, DEVICE *dptr); +t_stat ipl_dscln (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat ipl_setdiag (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_bool ipl_check_conn (UNIT *uptr); + +/* Debug flags table */ + +DEBTAB ipl_deb [] = { + { "CMDS", DEB_CMDS }, + { "CPU", DEB_CPU }, + { "XFER", DEB_XFER }, + { NULL, 0 } + }; + +/* Common structures */ + +DEVICE ipli_dev, iplo_dev; + +static DEVICE *dptrs [] = { &ipli_dev, &iplo_dev }; + + +UNIT ipl_unit [] = { + { UDATA (&ipl_svc, UNIT_ATTABLE, 0) }, + { UDATA (&ipl_svc, UNIT_ATTABLE, 0) } + }; + +#define ipli_unit ipl_unit [ipli] +#define iplo_unit ipl_unit [iplo] + + +DIB ipl_dib [] = { + { &iplio, IPLI, 0 }, + { &iplio, IPLO, 1 } + }; + +#define ipli_dib ipl_dib [ipli] +#define iplo_dib ipl_dib [iplo] + + +/* IPLI data structures + + ipli_dev IPLI device descriptor + ipli_unit IPLI unit descriptor + ipli_reg IPLI register list +*/ + +REG ipli_reg [] = { + { ORDATA (IBUF, ipli_unit.IBUF, 16) }, + { ORDATA (OBUF, ipli_unit.OBUF, 16) }, + { FLDATA (CTL, ipl [ipli].control, 0) }, + { FLDATA (FLG, ipl [ipli].flag, 0) }, + { FLDATA (FBF, ipl [ipli].flagbuf, 0) }, + { ORDATA (HOLD, ipl [ipli].hold, 8) }, + { DRDATA (TIME, ipl_ptime, 24), PV_LEFT }, + { FLDATA (STOP_IOE, ipl_stopioe, 0) }, + { DRDATA (EDTDELAY, ipl_edtdelay, 32), REG_HIDDEN | PV_LEFT }, + { ORDATA (SC, ipli_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, ipli_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB ipl_mod [] = { + { UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAG", &ipl_setdiag }, + { UNIT_DIAG, 0, "link mode", "LINK", &ipl_setdiag }, + { MTAB_XTD | MTAB_VDV, 0, NULL, "DISCONNECT", + &ipl_dscln, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &ipli_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &ipli_dev }, + { 0 } + }; + +DEVICE ipli_dev = { + "IPLI", &ipli_unit, ipli_reg, ipl_mod, + 1, 10, 31, 1, 16, 16, + &tmxr_ex, &tmxr_dep, &ipl_reset, + &ipl_boot, &ipl_attach, &ipl_detach, + &ipli_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG, + 0, ipl_deb, NULL, NULL + }; + +/* IPLO data structures + + iplo_dev IPLO device descriptor + iplo_unit IPLO unit descriptor + iplo_reg IPLO register list +*/ + +REG iplo_reg [] = { + { ORDATA (IBUF, iplo_unit.IBUF, 16) }, + { ORDATA (OBUF, iplo_unit.OBUF, 16) }, + { FLDATA (CTL, ipl [iplo].control, 0) }, + { FLDATA (FLG, ipl [iplo].flag, 0) }, + { FLDATA (FBF, ipl [iplo].flagbuf, 0) }, + { ORDATA (HOLD, ipl [iplo].hold, 8) }, + { DRDATA (TIME, ipl_ptime, 24), PV_LEFT }, + { ORDATA (SC, iplo_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, iplo_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +DEVICE iplo_dev = { + "IPLO", &iplo_unit, iplo_reg, ipl_mod, + 1, 10, 31, 1, 16, 16, + &tmxr_ex, &tmxr_dep, &ipl_reset, + &ipl_boot, &ipl_attach, &ipl_detach, + &iplo_dib, DEV_DISABLE | DEV_DIS | DEV_DEBUG, + 0, ipl_deb, NULL, NULL + }; + + +/* I/O signal handler for the IPLI and IPLO devices. + + In the link mode, the IPLI and IPLO devices are linked via network + connections to the corresponding cards in another CPU instance. In the + diagnostic mode, we simulate the attachment of the interprocessor cable + between IPLI and IPLO in this machine. + + Implementation notes: + + 1. 2000 Access has a race condition that manifests itself by an apparently + normal boot and operational system console but no PLEASE LOG IN response + to terminals connected to the multiplexer. The frequency of occurrence + is higher on multiprocessor host systems, where the SP and IOP instances + may execute concurrently. + + The cause is this code in the SP disc loader source (2883.asm, 7900.asm, + 790X.asm, 79X3.asm, and 79XX.asm): + + LDA SDVTR REQUEST + JSB IOPMA,I DEVICE TABLE + [...] + STC DMAHS,C TURN ON DMA + SFS DMAHS WAIT FOR + JMP *-1 DEVICE TABLE + STC CH2,C SET CORRECT + CLC CH2 FLAG DIRECTION + + The STC/CLC normally would cause a second "request device table" command + to be recognized by the IOP, except that the IOP DMA setup routine + "DMAXF" (in D61.asm) has specified an end-of-block CLC that holds off the + IPL interrupt, and the completion interrupt routine "DMCMP" ends with a + STC,C that clears the IPL flag. + + In hardware, the two CPUs are essentially interlocked by the DMA + transfer, and DMA completion interrupts occur almost simultaneously. + Therefore, the STC/CLC in the SP is guaranteed to occur before the STC,C + in the IOP. Under simulation, and especially on multiprocessor hosts, + that guarantee does not hold. If the STC/CLC occurs after the STC,C, + then the IOP starts a second device table DMA transfer, which the SP is + not expecting. The IOP never processes the subsequent "start + timesharing" command, and the multiplexer is non-responsive. + + We employ a workaround that decreases the incidence of the problem: DMA + output completion interrupts are delayed to allow the other SIMH instance + a chance to process its own DMA completion. We do this by processing the + EDT (End Data Transfer) I/O backplane signal and "sleep"ing for a short + time if the transfer was an output transfer to the input channel, i.e., + a data response to the SP. This improves the race condition by delaying + the IOP until the SP has a chance to receive the last word, recognize its + own DMA input completion, drop out of the SFS loop, and execute the + STC/CLC. The delay, "ipl_edtdelay", is initialized to one millisecond + but is exposed via a hidden IPLI register, "EDTDELAY", that allows the + user to lengthen the delay if necessary. + + The condition is only improved, and not solved, because "sleep"ing the + IOP doesn't guarantee that the SP will actually execute. It's possible + that a higher-priority host process will preempt the SP, and that at the + sleep expiration, the SP still has not executed the STC/CLC. Still, in + testing, the incidence dropped dramatically, so the problem is much less + intrusive. + + 2. The operating manual for the 12920A Terminal Multiplexer says that "at + least 100 milliseconds of CLC 0s must be programmed" by systems employing + the multiplexer to ensure that the multiplexer resets. In practice, such + systems issue 128K CLC 0 instructions. As we provide debug logging of + IPL resets, a CRS counter is used to ensure that only one debug line is + printed in response to these 128K CRS invocations. + + 3. The STC handler may return "Unit not attached", "I/O error", or "No + connection on interprocessor link" status if the link fails or is + improperly configured. If the error is corrected, the operation may be + retried by resuming simulated execution. +*/ + +uint32 iplio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +CARD_INDEX card = (CARD_INDEX) dibptr->card_index; /* set card selector */ +UNIT *const uptr = &(ipl_unit [card]); /* associated unit pointer */ +const char *iotype [] = { "Status", "Command" }; +int32 sta; +char msg [2]; +static uint32 crs_count [CARD_COUNT] = { 0, 0 }; /* per-card cntrs for ioCRS repeat */ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +if (crs_count [card] && !(signal_set & ioCRS)) { /* counting CRSes and not present? */ + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) /* report reset count */ + fprintf (sim_deb, ">>%s cmds: [CRS] Control cleared %d times\n", + dptrs [card]->name, crs_count [card]); + + crs_count [card] = 0; /* clear counter */ + } + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + ipl [card].flag = ipl [card].flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + ipl [card].flag = ipl [card].flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (ipl [card]); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (ipl [card]); + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, uptr->IBUF); /* get return data */ + + if (DEBUG_PRJ (dptrs [card], DEB_CPU)) + fprintf (sim_deb, ">>%s cpu: [LIx] %s = %06o\n", dptrs [card]->name, iotype [card ^ 1], uptr->IBUF); + break; + + + case ioIOO: /* I/O data output */ + uptr->OBUF = IODATA (stat_data); /* clear supplied status */ + + if (DEBUG_PRJ (dptrs [card], DEB_CPU)) + fprintf (sim_deb, ">>%s cpu: [OTx] %s = %06o\n", dptrs [card]->name, iotype [card], uptr->OBUF); + break; + + + case ioPOPIO: /* power-on preset to I/O */ + ipl [card].flag = ipl [card].flagbuf = SET; /* set flag buffer and flag */ + uptr->OBUF = 0; /* clear output buffer */ + break; + + + case ioCRS: /* control reset */ + if (crs_count [card] == 0) /* first reset? */ + ipl [card].control = CLEAR; /* clear control */ + + crs_count [card] = crs_count [card] + 1; /* increment count */ + break; + + + case ioCLC: /* clear control flip-flop */ + ipl [card].control = CLEAR; /* clear ctl */ + + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: [CLC] Control cleared\n", dptrs [card]->name); + break; + + + case ioSTC: /* set control flip-flop */ + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, ">>%s cmds: [STC] Control set\n", dptrs [card]->name); + + if (uptr->flags & UNIT_ATT) { /* attached? */ + if (!ipl_check_conn (uptr)) /* not established? */ + return IORETURN (STOP_NOCONN, 0); /* lose */ + + msg [0] = (uptr->OBUF >> 8) & 0377; + msg [1] = uptr->OBUF & 0377; + sta = sim_write_sock (uptr->DSOCKET, msg, 2); + + if (DEBUG_PRJ (dptrs [card], DEB_XFER)) + fprintf (sim_deb, + ">>%s xfer: [STC] Socket write = %06o, status = %d\n", + dptrs [card]->name, uptr->OBUF, sta); + + if (sta == SOCKET_ERROR) { + printf ("IPL socket write error\n"); + return IORETURN (SCPE_IOERR, 0); + } + + ipl [card].control = SET; /* set ctl */ + + sim_os_sleep (0); + } + + else if (uptr->flags & UNIT_DIAG) { /* diagnostic mode? */ + ipl [card].control = SET; /* set ctl */ + ipl_unit [card ^ 1].IBUF = uptr->OBUF; /* output to other */ + iplio ((DIB *) dptrs [card ^ 1]->ctxt, ioENF, 0); /* set other flag */ + } + + else + return IORETURN (SCPE_UNATT, 0); /* lose */ + break; + + + case ioEDT: /* end data transfer */ + if ((cpu_unit.flags & UNIT_IOP) && /* are we the IOP? */ + (signal_set & ioIOO) && /* and doing output? */ + (card == ipli)) { /* on the input card? */ + + if (DEBUG_PRJ (dptrs [card], DEB_CMDS)) + fprintf (sim_deb, + ">>%s cmds: [EDT] Delaying DMA completion interrupt for %d msec\n", + dptrs [card]->name, ipl_edtdelay); + + sim_os_ms_sleep (ipl_edtdelay); /* delay completion */ + } + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (ipl [card]); + setstdIRQ (ipl [card]); + setstdSRQ (ipl [card]); + break; + + + case ioIAK: /* interrupt acknowledge */ + ipl [card].flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Unit service - poll for input */ + +t_stat ipl_svc (UNIT *uptr) +{ +CARD_INDEX card; +int32 nb; +char msg [2]; + +if ((uptr->flags & UNIT_ATT) == 0) /* not attached? */ + return SCPE_OK; + +sim_activate (uptr, ipl_ptime); /* reactivate */ + +if (!ipl_check_conn (uptr)) /* check for conn */ + return SCPE_OK; /* not connected */ + +nb = sim_read_sock (uptr->DSOCKET, msg, ((uptr->flags & UNIT_HOLD)? 1: 2)); + +if (nb < 0) { /* connection closed? */ + printf ("IPL socket read error\n"); + return SCPE_IOERR; + } + +else if (nb == 0) /* no data? */ + return SCPE_OK; + +card = (CARD_INDEX) (uptr == &iplo_unit); /* set card selector */ + +if (uptr->flags & UNIT_HOLD) { /* holdover byte? */ + uptr->IBUF = (ipl [card].hold << 8) | (((int32) msg [0]) & 0377); + uptr->flags = uptr->flags & ~UNIT_HOLD; + } +else if (nb == 1) { + ipl [card].hold = ((int32) msg [0]) & 0377; + uptr->flags = uptr->flags | UNIT_HOLD; + } +else + uptr->IBUF = ((((int32) msg [0]) & 0377) << 8) | (((int32) msg [1]) & 0377); + +iplio ((DIB *) dptrs [card]->ctxt, ioENF, 0); /* set flag */ + +if (DEBUG_PRJ (dptrs [card], DEB_XFER)) + fprintf (sim_deb, ">>%s xfer: Socket read = %06o, status = %d\n", + dptrs [card]->name, uptr->IBUF, nb); + +return SCPE_OK; +} + + +t_bool ipl_check_conn (UNIT *uptr) +{ +SOCKET sock; + +if (uptr->flags & UNIT_ESTB) /* established? */ + return TRUE; + +if (uptr->flags & UNIT_ACTV) { /* active connect? */ + if (sim_check_conn (uptr->DSOCKET, 0) <= 0) + return FALSE; + } + +else { + sock = sim_accept_conn (uptr->LSOCKET, NULL); /* poll connect */ + + if (sock == INVALID_SOCKET) /* got a live one? */ + return FALSE; + + uptr->DSOCKET = sock; /* save data socket */ + } + +uptr->flags = uptr->flags | UNIT_ESTB; /* conn established */ +return TRUE; +} + + +/* Reset routine. + + Implementation notes: + + 1. We set up the first poll for socket connections to occur "immediately" + upon execution, so that clients will be connected before execution + begins. Otherwise, a fast program may access the IPL before the poll + service routine activates. +*/ + +t_stat ipl_reset (DEVICE *dptr) +{ +UNIT *uptr = dptr->units; +DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ +CARD_INDEX card = (CARD_INDEX) dibptr->card_index; /* card number */ + +hp_enbdis_pair (dptr, dptrs [card ^ 1]); /* make pair cons */ + +if (sim_switches & SWMASK ('P')) /* initialization reset? */ + uptr->IBUF = uptr->OBUF = 0; /* clr buffers */ + +IOPRESET (dibptr); /* PRESET device (does not use PON) */ + +if (uptr->flags & UNIT_ATT) /* socket attached? */ + sim_activate (uptr, POLL_FIRST); /* activate first poll "immediately" */ +else + sim_cancel (uptr); /* deactivate unit */ + +uptr->flags = uptr->flags & ~UNIT_HOLD; /* clear holding flag */ +return SCPE_OK; +} + + +/* Attach routine + + attach -l - listen for connection on port + attach -c - connect to ip address and port +*/ + +t_stat ipl_attach (UNIT *uptr, CONST char *cptr) +{ +SOCKET newsock; +char *tptr = NULL; +t_stat r; + +#if (SIM_MAJOR >= 4) + +uint32 i, t; +char host [CBUFSIZE], port [CBUFSIZE], hostport [2 * CBUFSIZE + 3]; + +t_bool is_active; + +is_active = (uptr->flags & UNIT_ACTV) == UNIT_ACTV; /* is the connection active? */ + +if (uptr->flags & UNIT_ATT) /* if IPL is currently attached, */ + ipl_detach (uptr); /* detach it first */ + +if ((sim_switches & SWMASK ('C')) || /* connecting? */ + ((sim_switches & SIM_SW_REST) && is_active)) { /* or restoring an active connection? */ + r = sim_parse_addr (cptr, host, /* parse the host and port */ + sizeof (host), "localhost", /* from the parameter string */ + port, sizeof (port), + NULL, NULL); + + if ((r != SCPE_OK) || (port [0] == '\0')) /* parse error or missing port number? */ + return SCPE_ARG; /* complain to the user */ + + sprintf(hostport, "%s%s%s%s%s", strchr(host, ':') ? "[" : "", host, strchr(host, ':') ? "]" : "", host [0] ? ":" : "", port); + + newsock = sim_connect_sock (hostport, NULL, NULL); + + if (newsock == INVALID_SOCKET) + return SCPE_IOERR; + + printf ("Connecting to %s\n", hostport); + + if (sim_log) + fprintf (sim_log, "Connecting to %s\n", hostport); + + uptr->flags = uptr->flags | UNIT_ACTV; + uptr->LSOCKET = 0; + uptr->DSOCKET = newsock; + } + +else { + if (sim_parse_addr (cptr, host, sizeof(host), NULL, port, sizeof(port), NULL, NULL)) + return SCPE_ARG; + sprintf(hostport, "%s%s%s%s%s", strchr(host, ':') ? "[" : "", host, strchr(host, ':') ? "]" : "", host [0] ? ":" : "", port); + newsock = sim_master_sock (hostport, &r); + if (r != SCPE_OK) + return r; + if (newsock == INVALID_SOCKET) + return SCPE_IOERR; + printf ("Listening on port %s\n", hostport); + if (sim_log) + fprintf (sim_log, "Listening on port %s\n", hostport); + uptr->flags = uptr->flags & ~UNIT_ACTV; + uptr->LSOCKET = newsock; + uptr->DSOCKET = 0; + } +uptr->IBUF = uptr->OBUF = 0; +uptr->flags = (uptr->flags | UNIT_ATT) & ~(UNIT_ESTB | UNIT_HOLD); +tptr = (char *) malloc (strlen (hostport) + 1); /* get string buf */ +if (tptr == NULL) { /* no memory? */ + ipl_detach (uptr); /* close sockets */ + return SCPE_MEM; + } +strcpy (tptr, hostport); /* copy ipaddr:port */ + +#else + +uint32 i, t, ipa, ipp, oldf; + +r = get_ipaddr (cptr, &ipa, &ipp); +if ((r != SCPE_OK) || (ipp == 0)) + return SCPE_ARG; +oldf = uptr->flags; +if (oldf & UNIT_ATT) + ipl_detach (uptr); +if ((sim_switches & SWMASK ('C')) || + ((sim_switches & SIM_SW_REST) && (oldf & UNIT_ACTV))) { + if (ipa == 0) + ipa = 0x7F000001; + newsock = sim_connect_sock (ipa, ipp); + if (newsock == INVALID_SOCKET) + return SCPE_IOERR; + printf ("Connecting to IP address %d.%d.%d.%d, port %d\n", + (ipa >> 24) & 0xff, (ipa >> 16) & 0xff, + (ipa >> 8) & 0xff, ipa & 0xff, ipp); + if (sim_log) + fprintf (sim_log, + "Connecting to IP address %d.%d.%d.%d, port %d\n", + (ipa >> 24) & 0xff, (ipa >> 16) & 0xff, + (ipa >> 8) & 0xff, ipa & 0xff, ipp); + uptr->flags = uptr->flags | UNIT_ACTV; + uptr->LSOCKET = 0; + uptr->DSOCKET = newsock; + } +else { + if (ipa != 0) + return SCPE_ARG; + newsock = sim_master_sock (ipp); + if (newsock == INVALID_SOCKET) + return SCPE_IOERR; + printf ("Listening on port %d\n", ipp); + if (sim_log) + fprintf (sim_log, "Listening on port %d\n", ipp); + uptr->flags = uptr->flags & ~UNIT_ACTV; + uptr->LSOCKET = newsock; + uptr->DSOCKET = 0; + } +uptr->IBUF = uptr->OBUF = 0; +uptr->flags = (uptr->flags | UNIT_ATT) & ~(UNIT_ESTB | UNIT_HOLD); +tptr = (char *) malloc (strlen (cptr) + 1); /* get string buf */ +if (tptr == NULL) { /* no memory? */ + ipl_detach (uptr); /* close sockets */ + return SCPE_MEM; + } +strcpy (tptr, cptr); /* copy ipaddr:port */ + +#endif + +uptr->filename = tptr; /* save */ +sim_activate (uptr, POLL_FIRST); /* activate first poll "immediately" */ +if (sim_switches & SWMASK ('W')) { /* wait? */ + for (i = 0; i < 30; i++) { /* check for 30 sec */ + t = ipl_check_conn (uptr); + if (t) /* established? */ + break; + if ((i % 10) == 0) /* status every 10 sec */ + printf ("Waiting for connection\n"); + sim_os_sleep (1); /* sleep 1 sec */ + } + if (t) /* if connected (set by "ipl_check_conn" above) */ + printf ("Connection established\n"); /* then report */ + } +return SCPE_OK; +} + +/* Detach routine */ + +t_stat ipl_detach (UNIT *uptr) +{ +if (!(uptr->flags & UNIT_ATT)) /* attached? */ + return SCPE_OK; + +if (uptr->flags & UNIT_ACTV) + sim_close_sock (uptr->DSOCKET, 1); + +else { + if (uptr->flags & UNIT_ESTB) /* if established, */ + sim_close_sock (uptr->DSOCKET, 0); /* close data socket */ + sim_close_sock (uptr->LSOCKET, 1); /* closen listen socket */ + } + +free (uptr->filename); /* free string */ +uptr->filename = NULL; +uptr->LSOCKET = 0; +uptr->DSOCKET = 0; +uptr->flags = uptr->flags & ~(UNIT_ATT | UNIT_ACTV | UNIT_ESTB); +sim_cancel (uptr); /* don't poll */ +return SCPE_OK; +} + +/* Disconnect routine */ + +t_stat ipl_dscln (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +if (cptr) + return SCPE_ARG; +if (((uptr->flags & UNIT_ATT) == 0) || + (uptr->flags & UNIT_ACTV) || + ((uptr->flags & UNIT_ESTB) == 0)) + return SCPE_NOFNC; +sim_close_sock (uptr->DSOCKET, 0); +uptr->DSOCKET = 0; +uptr->flags = uptr->flags & ~UNIT_ESTB; +return SCPE_OK; +} + +/* Diagnostic/normal mode routine */ + +t_stat ipl_setdiag (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +if (val) { + ipli_unit.flags = ipli_unit.flags | UNIT_DIAG; + iplo_unit.flags = iplo_unit.flags | UNIT_DIAG; + } +else { + ipli_unit.flags = ipli_unit.flags & ~UNIT_DIAG; + iplo_unit.flags = iplo_unit.flags & ~UNIT_DIAG; + } +return SCPE_OK; +} + +/* Interprocessor link bootstrap routine (HP Access Manual) */ + +#define MAX_BASE 073 +#define IPL_PNTR 074 +#define PTR_PNTR 075 +#define IPL_DEVA 076 +#define PTR_DEVA 077 + +static const BOOT_ROM ipl_rom = { + 0163774, /*BBL LDA ICK,I ; IPL sel code */ + 0027751, /* JMP CFG ; go configure */ + 0107700, /*ST CLC 0,C ; intr off */ + 0002702, /* CLA,CCE,SZA ; skip in */ + 0063772, /*CN LDA M26 ; feed frame */ + 0002307, /*EOC CCE,INA,SZA,RSS ; end of file? */ + 0027760, /* JMP EOT ; yes */ + 0017736, /* JSB READ ; get #char */ + 0007307, /* CMB,CCE,INB,SZB,RSS ; 2's comp; null? */ + 0027705, /* JMP EOC ; read next */ + 0077770, /* STB WC ; word in rec */ + 0017736, /* JSB READ ; get feed frame */ + 0017736, /* JSB READ ; get address */ + 0074000, /* STB 0 ; init csum */ + 0077771, /* STB AD ; save addr */ + 0067771, /*CK LDB AD ; check addr */ + 0047773, /* ADB MAXAD ; below loader */ + 0002040, /* SEZ ; E =0 => OK */ + 0102055, /* HLT 55 */ + 0017736, /* JSB READ ; get word */ + 0040001, /* ADA 1 ; cont checksum */ + 0177771, /* STB AD,I ; store word */ + 0037771, /* ISZ AD */ + 0000040, /* CLE ; force wd read */ + 0037770, /* ISZ WC ; block done? */ + 0027717, /* JMP CK ; no */ + 0017736, /* JSB READ ; get checksum */ + 0054000, /* CPB 0 ; ok? */ + 0027704, /* JMP CN ; next block */ + 0102011, /* HLT 11 ; bad csum */ + 0000000, /*RD 0 */ + 0006600, /* CLB,CME ; E reg byte ptr */ + 0103700, /*IO1 STC RDR,C ; start reader */ + 0102300, /*IO2 SFS RDR ; wait */ + 0027741, /* JMP *-1 */ + 0106400, /*IO3 MIB RDR ; get byte */ + 0002041, /* SEZ,RSS ; E set? */ + 0127736, /* JMP RD,I ; no, done */ + 0005767, /* BLF,CLE,BLF ; shift byte */ + 0027740, /* JMP IO1 ; again */ + 0163775, /* LDA PTR,I ; get ptr code */ + 0043765, /*CFG ADA SFS ; config IO */ + 0073741, /* STA IO2 */ + 0043766, /* ADA STC */ + 0073740, /* STA IO1 */ + 0043767, /* ADA MIB */ + 0073743, /* STA IO3 */ + 0027702, /* JMP ST */ + 0063777, /*EOT LDA PSC ; put select codes */ + 0067776, /* LDB ISC ; where xloader wants */ + 0102077, /* HLT 77 */ + 0027702, /* JMP ST */ + 0000000, /* NOP */ + 0102300, /*SFS SFS 0 */ + 0001400, /*STC 1400 */ + 0002500, /*MIB 2500 */ + 0000000, /*WC 0 */ + 0000000, /*AD 0 */ + 0177746, /*M26 -26 */ + 0000000, /*MAX -BBL */ + 0007776, /*ICK ISC */ + 0007777, /*PTR IPT */ + 0000000, /*ISC 0 */ + 0000000 /*IPT 0 */ + }; + +t_stat ipl_boot (int32 unitno, DEVICE *dptr) +{ +const int32 devi = ipli_dib.select_code; +const int32 devp = ptr_dib.select_code; + +if (ibl_copy (ipl_rom, devi, IBL_S_CLR, /* copy the boot ROM to memory and configure */ + IBL_SET_SC (devi) | devp)) /* the S register accordingly */ + return SCPE_IERR; /* return an internal error if the copy failed */ + +WritePW (PR + MAX_BASE, (~PR + 1) & DMASK); /* fix ups */ +WritePW (PR + IPL_PNTR, ipl_rom [IPL_PNTR] | PR); +WritePW (PR + PTR_PNTR, ipl_rom [PTR_PNTR] | PR); +WritePW (PR + IPL_DEVA, devi); +WritePW (PR + PTR_DEVA, devp); +return SCPE_OK; +} diff --git a/HP2100/hp2100_lps.c b/HP2100/hp2100_lps.c index ea51c6fb..18919f6c 100644 --- a/HP2100/hp2100_lps.c +++ b/HP2100/hp2100_lps.c @@ -1,6 +1,6 @@ /* hp2100_lps.c: HP 2100 12653A/2767 line printer simulator - Copyright (c) 1993-2012, Robert M. Supnik + Copyright (c) 1993-2016, Robert M. Supnik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -26,6 +26,7 @@ LPS 12653A 2767 line printer 12566B microcircuit interface with loopback diagnostic connector + 13-May-16 JDB Modified for revised SCP API function parameter types 10-Feb-12 JDB Deprecated DEVNO in favor of SC 28-Mar-11 JDB Tidied up signal handling 26-Oct-10 JDB Changed I/O signal handler for revised signal model @@ -210,12 +211,12 @@ IOHANDLER lpsio; t_stat lps_svc (UNIT *uptr); t_stat lps_reset (DEVICE *dptr); -t_stat lps_restart (UNIT *uptr, int32 value, char *cptr, void *desc); -t_stat lps_poweroff (UNIT *uptr, int32 value, char *cptr, void *desc); -t_stat lps_poweron (UNIT *uptr, int32 value, char *cptr, void *desc); -t_stat lps_attach (UNIT *uptr, char *cptr); -t_stat lps_set_timing (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat lps_show_timing (FILE *st, UNIT *uptr, int32 val, void *desc); +t_stat lps_restart (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +t_stat lps_poweroff (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +t_stat lps_poweron (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +t_stat lps_attach (UNIT *uptr, CONST char *cptr); +t_stat lps_set_timing (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat lps_show_timing (FILE *st, UNIT *uptr, int32 val, CONST void *desc); /* LPS data structures @@ -537,7 +538,7 @@ return SCPE_OK; original I/O request. */ -t_stat lps_restart (UNIT *uptr, int32 value, char *cptr, void *desc) +t_stat lps_restart (UNIT *uptr, int32 value, CONST char *cptr, void *desc) { if (lps.control && !sim_is_active (uptr)) sim_activate (uptr, 0); /* reschedule I/O */ @@ -546,7 +547,7 @@ return SCPE_OK; /* Printer power off */ -t_stat lps_poweroff (UNIT *uptr, int32 value, char *cptr, void *desc) +t_stat lps_poweroff (UNIT *uptr, int32 value, CONST char *cptr, void *desc) { lps_power = LPS_OFF; /* change state */ if (DEBUG_PRS (lps_dev)) fputs (">>LPS set: Power state is OFF\n", sim_deb); @@ -555,7 +556,7 @@ return SCPE_OK; /* Printer power on */ -t_stat lps_poweron (UNIT *uptr, int32 value, char *cptr, void *desc) +t_stat lps_poweron (UNIT *uptr, int32 value, CONST char *cptr, void *desc) { if (lps_unit.flags & UNIT_DIAG) { /* diag mode? */ lps_power = LPS_ON; /* no delay */ @@ -575,7 +576,7 @@ return SCPE_OK; /* Attach routine */ -t_stat lps_attach (UNIT *uptr, char *cptr) +t_stat lps_attach (UNIT *uptr, CONST char *cptr) { lps_ccnt = lps_lcnt = 0; /* top of form */ lps_restart (uptr, 0, NULL, NULL); /* restart I/O if hung */ @@ -587,7 +588,7 @@ return attach_unit (uptr, cptr); Realistic timing is factored, depending on CPU model, to account for the timing method employed by the diagnostic. */ -t_stat lps_set_timing (UNIT *uptr, int32 val, char *cptr, void *desc) +t_stat lps_set_timing (UNIT *uptr, int32 val, CONST char *cptr, void *desc) { uint32 i, factor = 1; @@ -603,7 +604,7 @@ return SCPE_OK; /* Show printer timing */ -t_stat lps_show_timing (FILE *st, UNIT *uptr, int32 val, void *desc) +t_stat lps_show_timing (FILE *st, UNIT *uptr, int32 val, CONST void *desc) { if (lps_timing) fputs ("fast timing", st); else fputs ("realistic timing", st); diff --git a/HP2100/hp2100_lpt.c b/HP2100/hp2100_lpt.c index 84795443..d87a8aad 100644 --- a/HP2100/hp2100_lpt.c +++ b/HP2100/hp2100_lpt.c @@ -1,6 +1,6 @@ /* hp2100_lpt.c: HP 2100 12845B line printer simulator - Copyright (c) 1993-2012, Robert M. Supnik + Copyright (c) 1993-2016, Robert M. Supnik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -25,6 +25,7 @@ LPT 12845B 2607 line printer + 13-May-16 JDB Modified for revised SCP API function parameter types 10-Feb-12 JDB Deprecated DEVNO in favor of SC 28-Mar-11 JDB Tidied up signal handling 26-Oct-10 JDB Changed I/O signal handler for revised signal model @@ -112,8 +113,8 @@ IOHANDLER lptio; t_stat lpt_svc (UNIT *uptr); t_stat lpt_reset (DEVICE *dptr); -t_stat lpt_restart (UNIT *uptr, int32 value, char *cptr, void *desc); -t_stat lpt_attach (UNIT *uptr, char *cptr); +t_stat lpt_restart (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +t_stat lpt_attach (UNIT *uptr, CONST char *cptr); /* LPT data structures @@ -332,7 +333,7 @@ return SCPE_OK; original I/O request. */ -t_stat lpt_restart (UNIT *uptr, int32 value, char *cptr, void *desc) +t_stat lpt_restart (UNIT *uptr, int32 value, CONST char *cptr, void *desc) { if (lpt.control && !sim_is_active (uptr)) sim_activate (uptr, 0); /* reschedule I/O */ @@ -342,7 +343,7 @@ return SCPE_OK; /* Attach routine */ -t_stat lpt_attach (UNIT *uptr, char *cptr) +t_stat lpt_attach (UNIT *uptr, CONST char *cptr) { lpt_lcnt = 0; /* top of form */ lpt_restart (uptr, 0, NULL, NULL); /* restart I/O if hung */ diff --git a/HP2100/hp2100_mpx.c b/HP2100/hp2100_mpx.c index f8b2b1f5..ebf49eec 100644 --- a/HP2100/hp2100_mpx.c +++ b/HP2100/hp2100_mpx.c @@ -1,2704 +1,2756 @@ -/* hp2100_mpx.c: HP 12792C eight-channel asynchronous multiplexer simulator - - Copyright (c) 2008-2014, J. David Bryan - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of the author shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from the author. - - MPX 12792C 8-channel multiplexer card - - 24-Dec-14 JDB Added casts for explicit downward conversions - 10-Jan-13 MP Added DEV_MUX and additional DEVICE field values - 28-Dec-12 JDB Allow direct attach to the poll unit only when restoring - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - Removed DEV_NET to allow restoration of listening port - 28-Mar-11 JDB Tidied up signal handling - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 25-Nov-08 JDB Revised for new multiplexer library SHOW routines - 14-Nov-08 JDB Cleaned up VC++ size mismatch warnings for zero assignments - 03-Oct-08 JDB Fixed logic for ENQ/XOFF transmit wait - 07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach - 10-Aug-08 JDB Added REG_FIT to register variables < 32-bit size - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 26-May-08 JDB Created MPX device - - References: - - HP 12792B 8-Channel Asynchronous Multiplexer Subsystem Installation and - Reference Manual (12792-90020, Jul-1984) - - HP 12792B/C 8-Channel Asynchronous Multiplexer Subsystem User's Manual - (5955-8867, Jun-1993) - - HP 12792B/C 8-Channel Asynchronous Multiplexer Subsystem Configuration Guide - (5955-8868, Jun-1993) - - HP 1000 series 8-channel Multiplexer Firmware External Reference Specification - (October 19, 1982) - - HP 12792/12040 Multiplexer Firmware Source (24999-18312, revision C) - - Zilog Components Data Book (00-2034-04, 1985) - - - The 12792A/B/C/D was an eight-line asynchronous serial multiplexer that - connected terminals, modems, serial line printers, and "black box" devices - that used the RS-232 standard to the CPU. It used an on-board microprocessor - and provided input and output buffering to support block-mode reads from HP - 264x and 262x terminals at speeds up to 19.2K baud. The card handled - character editing, echoing, ENQ/ACK handshaking, and read terminator - detection, substantially reducing the load on the CPU over the earlier 12920 - multiplexer. It was supported by HP under RTE-MIII, RTE-IVB, and RTE-6/VM. - Under simulation, it connects with HP terminal emulators via Telnet to a - user-specified port. - - The single interface card contained a Z80 CPU, DMA controller, CTC, four - two-channel SIO UARTs, 16K of RAM, 8K of ROM, and I/O backplane latches and - control circuitry. The card executed a high-level command set, and data - transfer to and from the CPU was via the on-board DMA controller and the DCPC - in the CPU. - - The 12792 for the M/E/F series and the 12040 multiplexer for the A/L series - differed only in backplane design. Early ROMs were card-specific, but later - ones were interchangeable; the code would determine whether it was executing - on an MEF card or an AL card. - - Four major firmware revisions were made. These were labelled "A", "B", "C", - and "D". The A, B, and C revisions were interchangeable from the perspective - of the OS driver; the D was different and required an updated driver. - Specifically: - - Op. Sys. Driver Part Number Rev - -------- ------ -------------------- --- - RTE-MIII DVM00 12792-16002 Rev.2032 A - RTE-IVB DVM00 12792-16002 Rev.5000 ABC - - RTE-6/VM DVM00 12792-16002 Rev.5000 ABC - RTE-6/VM DV800 92084-15068 Rev.6000 D - - RTE-A IDM00 92077-16754 Rev.5020 ABC - RTE-A ID800 92077-16887 Rev.6200 D - - Revisions A-C have an upward-compatible command set that partitions each OS - request into several sub-commands. Each command is initiated by setting the - control flip-flop on the card, which causes a non-maskable interrupt (NMI) on - the card's Z80 processor. - - The D-revision firmware uses a completely different command set. The - commands are slightly modified versions of the original EXEC calls (read, - write, and control) and are generally passed to the card directly for action. - - This simulation supports the C revision. D-revision support may be added - later. - - Twelve programmable baud rates are supported by the multiplexer. These - "realistic" rates are simulated by scheduling I/O service based on the - appropriate number of 1000 E-Series instructions for the rate selected. - - The simulation provides both the "realistic timing" described above, as well - as an optimized "fast timing" option. Optimization makes three improvements: - - 1. Buffered characters are transferred in blocks. - - 2. ENQ/ACK handshaking is done locally without involving the client. - - 3. BS and DEL respond visually more like prior RTE terminal drivers. - - HP did not offer a functional diagnostic for the 12792. Instead, a Z80 - program that tested the operation of the hardware was downloaded to the card, - and a "go/no-go" status was returned to indicate the hardware condition. - Because this is a functional simulation of the multiplexer and not a Z80 - emulation, the diagnostic cannot be used to test the implementation. - - Implementation notes: - - 1. The 12792 had two baud-rate generators that were assigned to lines by the - wiring configuration in the I/O cable connector hood. Two of the four - CTC counters were used to implement the BRGs for all eight lines. Only - subsets of the configurable rates were allowed for lines connected to the - same BRG, and assigning mutually incompatible rates caused corruption of - the rates on lines assigned earlier. Under simulation, any baud rate may - be assigned to any line without interaction, and assignments of lines to - BRGs is not implemented. - - 2. Revisions B and C added support for the 37214A Systems Modem subsystem - and the RTE-A Virtual Control Panel (VCP). Under simulation, the modem - commands return status codes indicating that no modems are present, and - the VCP commands are not implemented. -*/ - - -#include - -#include "hp2100_defs.h" -#include "sim_tmxr.h" - - -/* Bitfield constructor. - - Given a bitfield starting bit number and width in bits, declare two - constants: one for the starting bit number, and one for the positioned field - mask. That is, given a definition such as: - - BITFIELD(SMALLFIELD,5,2) - - ...this macro produces: - - static const uint32 SMALLFIELD_V = 5; - static const uint32 SMALLFIELD = ((1 << (2)) - 1) << (5); - - The latter reduces to 3 << 5, or 0x00000060. - - Note: C requires constant expressions in initializers for objects with static - storage duration, so initializing a static object with a BITFIELD value is - illegal (a "static const" object is not a constant!). -*/ - -#define BITFIELD(NAME,STARTBIT,BITWIDTH) \ - static const uint32 NAME ## _V = STARTBIT; \ - static const uint32 NAME = ((1 << (BITWIDTH)) - 1) << (STARTBIT); - - -/* Program constants */ - -#define MPX_DATE_CODE 2416 /* date code for C firmware */ - -#define RD_BUF_SIZE 514 /* read buffer size */ -#define WR_BUF_SIZE 514 /* write buffer size */ - -#define RD_BUF_LIMIT 254 /* read buffer limit */ -#define WR_BUF_LIMIT 254 /* write buffer limit */ - -#define KEY_DEFAULT 255 /* default port key */ - - -/* Service times: - - DATA_DELAY = 1.25 us (Z80 DMA data word transfer time) - PARAM_DELAY = 25 us (STC to STF for first word of two-word command) - CMD_DELAY = 400 us (STC to STF for one or two-word command execution) -*/ - -#define DATA_DELAY 2 /* data transfer time */ -#define PARAM_DELAY 40 /* parameter request time */ -#define CMD_DELAY 630 /* command completion time */ - - -/* Unit references */ - -#define MPX_PORTS 8 /* number of visible units */ -#define MPX_CNTLS 2 /* number of control units */ - -#define mpx_cntl (mpx_unit [MPX_PORTS + 0]) /* controller unit */ -#define mpx_poll (mpx_unit [MPX_PORTS + 1]) /* Telnet polling unit */ - - -/* Character constants */ - -#define EOT '\004' -#define ENQ '\005' -#define ACK '\006' -#define BS '\010' -#define LF '\012' -#define CR '\015' -#define DC1 '\021' -#define DC2 '\022' -#define DC3 '\023' -#define ESC '\033' -#define RS '\036' -#define DEL '\177' - -#define XON DC1 -#define XOFF DC3 - - -/* Device flags */ - -#define DEV_V_REV_D (DEV_V_UF + 0) /* firmware revision D (not implemented) */ - -#define DEV_REV_D (1 << DEV_V_REV_D) - - -/* Unit flags */ - -#define UNIT_V_FASTTIME (UNIT_V_UF + 0) /* fast timing mode */ -#define UNIT_V_CAPSLOCK (UNIT_V_UF + 1) /* caps lock mode */ - -#define UNIT_FASTTIME (1 << UNIT_V_FASTTIME) -#define UNIT_CAPSLOCK (1 << UNIT_V_CAPSLOCK) - - -/* Debug flags */ - -#define DEB_CMDS (1 << 0) /* commands and status */ -#define DEB_CPU (1 << 1) /* CPU I/O */ -#define DEB_BUF (1 << 2) /* buffer gets and puts */ -#define DEB_XFER (1 << 3) /* character reads and writes */ - - -/* Multiplexer commands for revisions A/B/C. - - Commands are either one or two words in length. The one-word format is: - - +-------------------------------+-------------------------------+ - | 0 . 1 | command opcode | command parameter | - +-------------------------------+-------------------------------+ - 15 - 8 7 - 0 - - The two-word format is: - - +-------------------------------+-------------------------------+ - | 1 . 1 | command opcode | command value | - +-------------------------------+-------------------------------+ - | command parameter | - +---------------------------------------------------------------+ - 15 - 8 7 - 0 - - Commands implemented by firmware revision: - - Rev Cmd Value Operation Status Value(s) Returned - --- --- ----- ------------------------------- ------------------------------- - ABC 100 - No operation 000000 - ABC 101 - Reset to power-on defaults 100000 - ABC 102 - Enable unsolicited input None, unless UI pending - ABC 103 1 Disable unsolicited interrupts 000000 - ABC 103 2 Abort DMA transfer 000000 - ABC 104 - Acknowledge Second word of UI status - ABC 105 key Cancel first receive buffer 000000 - ABC 106 key Cancel all received buffers 000000 - ABC 107 - Fast binary read (none) - - -BC 140 chr VCP put byte 000000 - -BC 141 - VCP put buffer 000000 - -BC 142 - VCP get byte Character from port 0 - -BC 143 - VCP get buffer 000120 - -BC 144 - Exit VCP mode 000000 - -BC 157 - Enter VCP mode 000000 - - ABC 300 - No operation 000000 - ABC 301 key Request write buffer 000000 or 000376 - ABC 302 key Write data to buffer (none) - ABC 303 key Set port key 000000 or date code of firmware - ABC 304 key Set receive type 000000 - ABC 305 key Set character count 000000 - ABC 306 key Set flow control 000000 - ABC 307 key Read data from buffer (none) - ABC 310 - Download executable (none) - - -BC 311 key Connect line 000000 or 140000 if no modem - -BC 312 key Disconnect line 000000 or 140000 if no modem - -BC 315 key Get modem/port status modem status or 000200 if no modem - -BC 316 key Enable/disable modem loopback 000000 or 140000 if no modem - -BC 320 key Terminate active receive buffer 000000 -*/ - - -/* One-word command codes */ - -#define CMD_NOP 0100 /* No operation */ -#define CMD_RESET 0101 /* Reset firmware to power-on defaults */ -#define CMD_ENABLE_UI 0102 /* Enable unsolicited input */ -#define CMD_DISABLE 0103 /* Disable interrupts / Abort DMA Transfer */ -#define CMD_ACK 0104 /* Acknowledge */ -#define CMD_CANCEL 0105 /* Cancel first receive buffer */ -#define CMD_CANCEL_ALL 0106 /* Cancel all received buffers */ -#define CMD_BINARY_READ 0107 /* Fast binary read */ - -#define CMD_VCP_PUT 0140 /* VCP put byte */ -#define CMD_VCP_PUT_BUF 0141 /* VCP put buffer */ -#define CMD_VCP_GET 0142 /* VCP get byte */ -#define CMD_VCP_GET_BUF 0143 /* VCP get buffer */ -#define CMD_VCP_EXIT 0144 /* Exit VCP mode */ -#define CMD_VCP_ENTER 0157 /* Enter VCP mode */ - - -/* Two-word command codes */ - -#define CMD_REQ_WRITE 0301 /* Request write buffer */ -#define CMD_WRITE 0302 /* Write data to buffer */ -#define CMD_SET_KEY 0303 /* Set port key */ -#define CMD_SET_RCV 0304 /* Set receive type */ -#define CMD_SET_COUNT 0305 /* Set character count */ -#define CMD_SET_FLOW 0306 /* Set flow control */ -#define CMD_READ 0307 /* Read data from buffer */ -#define CMD_DL_EXEC 0310 /* Download executable */ - -#define CMD_CN_LINE 0311 /* Connect line */ -#define CMD_DC_LINE 0312 /* Disconnect line */ -#define CMD_GET_STATUS 0315 /* Get modem/port status */ -#define CMD_LOOPBACK 0316 /* Enable/disable modem loopback */ -#define CMD_TERM_BUF 0320 /* Terminate active receive buffer */ - - -/* Sub-command codes */ - -#define SUBCMD_UI 1 /* Disable unsolicited interrupts */ -#define SUBCMD_DMA 2 /* Abort DMA transfer */ - -#define CMD_TWO_WORDS 0200 /* two-word command flag */ - - -/* Unsolicited interrupt reasons */ - -#define UI_REASON_V 8 /* interrupt reason */ -#define UI_REASON (((1 << 8) - 1) << (UI_REASON_V)) /* (UI_REASON_V must be a constant!) */ - -BITFIELD (UI_PORT, 0, 3) /* interrupt port number */ - -#define UI_WRBUF_AVAIL (1 << UI_REASON_V) /* Write buffer available */ -#define UI_LINE_CONN (2 << UI_REASON_V) /* Modem line connected */ -#define UI_LINE_DISC (3 << UI_REASON_V) /* Modem line disconnected */ -#define UI_BRK_RECD (4 << UI_REASON_V) /* Break received */ -#define UI_RDBUF_AVAIL (5 << UI_REASON_V) /* Read buffer available */ - - -/* Return status to CPU */ - -#define ST_OK 0000000 /* Command OK */ -#define ST_DIAG_OK 0000015 /* Diagnostic passes */ -#define ST_VCP_SIZE 0000120 /* VCP buffer size = 80 chars */ -#define ST_NO_SYSMDM 0000200 /* No systems modem card */ -#define ST_TEST_OK 0100000 /* Self test OK */ -#define ST_NO_MODEM 0140000 /* No modem card on port */ -#define ST_BAD_KEY 0135320 /* Bad port key = 0xBAD0 */ - - -/* Bit flags */ - -#define RS_OVERFLOW 0040000 /* Receive status: buffer overflow occurred */ -#define RS_PARTIAL 0020000 /* Receive status: buffer is partial */ -#define RS_ETC_RS 0014000 /* Receive status: terminated by RS */ -#define RS_ETC_DC2 0010000 /* Receive status: terminated by DC2 */ -#define RS_ETC_CR 0004000 /* Receive status: terminated by CR */ -#define RS_ETC_EOT 0000000 /* Receive status: terminated by EOT */ -#define RS_CHAR_COUNT 0003777 /* Receive status: character count */ - -#define WR_NO_ENQACK 0020000 /* Write: no ENQ/ACK this xfer */ -#define WR_ADD_CRLF 0010000 /* Write: add CR/LF if not '_' */ -#define WR_PARTIAL 0004000 /* Write: write is partial */ -#define WR_LENGTH 0003777 /* Write: write length in bytes */ - -#define RT_END_ON_CR 0000200 /* Receive type: end xfer on CR */ -#define RT_END_ON_RS 0000100 /* Receive type: end xfer on RS */ -#define RT_END_ON_EOT 0000040 /* Receive type: end xfer on EOT */ -#define RT_END_ON_DC2 0000020 /* Receive type: end xfer on DC2 */ -#define RT_END_ON_CNT 0000010 /* Receive type: end xfer on count */ -#define RT_END_ON_CHAR 0000004 /* Receive type: end xfer on character */ -#define RT_ENAB_EDIT 0000002 /* Receive type: enable input editing */ -#define RT_ENAB_ECHO 0000001 /* Receive type: enable input echoing */ - -#define FC_FORCE_XON 0000002 /* Flow control: force XON */ -#define FC_XONXOFF 0000001 /* Flow control: enable XON/XOFF */ - -#define CL_GUARD 0000040 /* Connect line: guard tone off or on */ -#define CL_STANDARD 0000020 /* Connect line: standard 212 or V.22 */ -#define CL_BITS 0000010 /* Connect line: bits 10 or 9 */ -#define CL_MODE 0000004 /* Connect line: mode originate or answer */ -#define CL_DIAL 0000002 /* Connect line: dial manual or automatic */ -#define CL_SPEED 0000001 /* Connect line: speed low or high */ - -#define DL_AUTO_ANSWER 0000001 /* Disconnect line: auto-answer enable or disable */ - -#define LB_SPEED 0000004 /* Loopback test: speed low or high */ -#define LB_MODE 0000002 /* Loopback test: mode analog or digital */ -#define LB_TEST 0000001 /* Loopback test: test disable or enable */ - -#define GS_NO_SYSMDM 0000200 /* Get status: systems modem present or absent */ -#define GS_SYSMDM_TO 0000100 /* Get status: systems modem OK or timed out */ -#define GS_NO_MODEM 0000040 /* Get status: modem present or absent */ -#define GS_SPEED 0000020 /* Get status: speed low or high */ -#define GS_LINE 0000001 /* Get status: line disconnected or connected */ - - -/* Bit fields (name, starting bit, bit width) */ - -BITFIELD (CMD_OPCODE, 8, 8) /* Command: opcode */ -BITFIELD (CMD_KEY, 0, 8) /* Command: key */ - -BITFIELD (SK_BPC, 14, 2) /* Set key: bits per character */ -BITFIELD (SK_MODEM, 13, 1) /* Set key: hardwired or modem */ -BITFIELD (SK_BRG, 12, 1) /* Set key: baud rate generator 0/1 */ -BITFIELD (SK_STOPBITS, 10, 2) /* Set key: stop bits */ -BITFIELD (SK_PARITY, 8, 2) /* Set key: parity select */ -BITFIELD (SK_ENQACK, 7, 1) /* Set key: disable or enable ENQ/ACK */ -BITFIELD (SK_BAUDRATE, 3, 4) /* Set key: port baud rate */ -BITFIELD (SK_PORT, 0, 3) /* Set key: port number */ - -BITFIELD (FL_ALERT, 11, 1) /* Port flags: alert for terminate recv buffer */ -BITFIELD (FL_XOFF, 10, 1) /* Port flags: XOFF stopped transmission */ -BITFIELD (FL_BREAK, 9, 1) /* Port flags: UI / break detected */ -BITFIELD (FL_HAVEBUF, 8, 1) /* Port flags: UI / read buffer available */ -BITFIELD (FL_WANTBUF, 7, 1) /* Port flags: UI / write buffer available */ -BITFIELD (FL_RDOVFLOW, 6, 1) /* Port flags: read buffers overflowed */ -BITFIELD (FL_RDFILL, 5, 1) /* Port flags: read buffer is filling */ -BITFIELD (FL_RDEMPT, 4, 1) /* Port flags: read buffer is emptying */ -BITFIELD (FL_WRFILL, 3, 1) /* Port flags: write buffer is filling */ -BITFIELD (FL_WREMPT, 2, 1) /* Port flags: write buffer is emptying */ -BITFIELD (FL_WAITACK, 1, 1) /* Port flags: ENQ sent, waiting for ACK */ -BITFIELD (FL_DO_ENQACK, 0, 1) /* Port flags: do ENQ/ACK handshake */ - -#define SK_BRG_1 SK_BRG -#define SK_BRG_0 0 - -#define FL_RDFLAGS (FL_RDEMPT | FL_RDFILL | FL_RDOVFLOW) -#define FL_WRFLAGS (FL_WREMPT | FL_WRFILL) -#define FL_UI_PENDING (FL_WANTBUF | FL_HAVEBUF | FL_BREAK) - -#define ACK_LIMIT 1000 /* poll timeout for ACK response */ -#define ENQ_LIMIT 80 /* output chars before ENQ */ - - -/* Packed field values */ - -#define SK_BPC_5 (0 << SK_BPC_V) -#define SK_BPC_6 (1 << SK_BPC_V) -#define SK_BPC_7 (2 << SK_BPC_V) -#define SK_BPC_8 (3 << SK_BPC_V) - -#define SK_STOP_1 (1 << SK_STOPBITS_V) -#define SK_STOP_15 (2 << SK_STOPBITS_V) -#define SK_STOP_2 (3 << SK_STOPBITS_V) - -#define SK_BAUD_NOCHG (0 << SK_BAUDRATE_V) -#define SK_BAUD_50 (1 << SK_BAUDRATE_V) -#define SK_BAUD_75 (2 << SK_BAUDRATE_V) -#define SK_BAUD_110 (3 << SK_BAUDRATE_V) -#define SK_BAUD_1345 (4 << SK_BAUDRATE_V) -#define SK_BAUD_150 (5 << SK_BAUDRATE_V) -#define SK_BAUD_300 (6 << SK_BAUDRATE_V) -#define SK_BAUD_1200 (7 << SK_BAUDRATE_V) -#define SK_BAUD_1800 (8 << SK_BAUDRATE_V) -#define SK_BAUD_2400 (9 << SK_BAUDRATE_V) -#define SK_BAUD_4800 (10 << SK_BAUDRATE_V) -#define SK_BAUD_9600 (11 << SK_BAUDRATE_V) -#define SK_BAUD_19200 (12 << SK_BAUDRATE_V) - - -/* Default values */ - -#define SK_PWRUP_0 (SK_BPC_8 | SK_BRG_0 | SK_STOP_1 | SK_BAUD_9600) -#define SK_PWRUP_1 (SK_BPC_8 | SK_BRG_1 | SK_STOP_1 | SK_BAUD_9600) - -#define RT_PWRUP (RT_END_ON_CR | RT_END_ON_CHAR | RT_ENAB_EDIT | RT_ENAB_ECHO) - - -/* Command helpers */ - -#define GET_OPCODE(w) (((w) & CMD_OPCODE) >> CMD_OPCODE_V) -#define GET_KEY(w) (((w) & CMD_KEY) >> CMD_KEY_V) -#define GET_BPC(w) (((w) & SK_BPC) >> SK_BPC_V) -#define GET_BAUDRATE(w) (((w) & SK_BAUDRATE) >> SK_BAUDRATE_V) -#define GET_PORT(w) (((w) & SK_PORT) >> SK_PORT_V) -#define GET_UIREASON(w) (((w) & UI_REASON) >> UI_REASON_V) -#define GET_UIPORT(w) (((w) & UI_PORT) >> UI_PORT_V) - - -/* Multiplexer controller state variables */ - -typedef enum { idle, cmd, param, exec } STATE; - -STATE mpx_state = idle; /* controller state */ - -uint16 mpx_ibuf = 0; /* status/data in */ -uint16 mpx_obuf = 0; /* command/data out */ - -uint32 mpx_cmd = 0; /* current command */ -uint32 mpx_param = 0; /* current parameter */ -uint32 mpx_port = 0; /* current port number for R/W */ -uint32 mpx_portkey = 0; /* current port's key */ - int32 mpx_iolen = 0; /* length of current I/O xfer */ - -t_bool mpx_uien = FALSE; /* unsolicited interrupts enabled */ -uint32 mpx_uicode = 0; /* unsolicited interrupt reason and port */ - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } mpx = { CLEAR, CLEAR, CLEAR }; - -/* Multiplexer per-line state variables */ - -uint8 mpx_key [MPX_PORTS]; /* port keys */ -uint16 mpx_config [MPX_PORTS]; /* port configuration */ -uint16 mpx_rcvtype [MPX_PORTS]; /* receive type */ -uint16 mpx_charcnt [MPX_PORTS]; /* character count */ -uint16 mpx_flowcntl [MPX_PORTS]; /* flow control */ -uint8 mpx_enq_cntr [MPX_PORTS]; /* ENQ character counter */ -uint16 mpx_ack_wait [MPX_PORTS]; /* ACK wait timer */ -uint16 mpx_flags [MPX_PORTS]; /* line state flags */ - -/* Multiplexer buffer selectors */ - -typedef enum { ioread, iowrite } IO_OPER; /* I/O operation */ -typedef enum { get, put } BUF_SELECT; /* buffer selector */ - -static const char *const io_op [] = { "read", /* operation names */ - "write" }; - -static const uint16 buf_size [] = { RD_BUF_SIZE, /* buffer sizes */ - WR_BUF_SIZE }; - -static uint32 emptying_flags [2]; /* buffer emptying flags [IO_OPER] */ -static uint32 filling_flags [2]; /* buffer filling flags [IO_OPER] */ - - -/* Multiplexer per-line buffer variables */ - -typedef uint16 BUF_INDEX [MPX_PORTS] [2]; /* buffer index (read and write) */ - -BUF_INDEX mpx_put; /* read/write buffer add index */ -BUF_INDEX mpx_sep; /* read/write buffer separator index */ -BUF_INDEX mpx_get; /* read/write buffer remove index */ - -uint8 mpx_rbuf [MPX_PORTS] [RD_BUF_SIZE]; /* read buffer */ -uint8 mpx_wbuf [MPX_PORTS] [WR_BUF_SIZE]; /* write buffer */ - - -/* Multiplexer local routines */ - -static t_bool exec_command (void); -static void poll_connection (void); -static void controller_reset (void); -static uint32 service_time (uint16 control_word); -static int32 key_to_port (uint32 key); - -static void buf_init (IO_OPER rw, uint32 port); -static uint8 buf_get (IO_OPER rw, uint32 port); -static void buf_put (IO_OPER rw, uint32 port, uint8 ch); -static void buf_remove (IO_OPER rw, uint32 port); -static void buf_term (IO_OPER rw, uint32 port, uint8 header); -static void buf_free (IO_OPER rw, uint32 port); -static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which); -static uint16 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which); -static uint32 buf_avail (IO_OPER rw, uint32 port); - - -/* Multiplexer global routines */ - -IOHANDLER mpx_io; - -t_stat mpx_line_svc (UNIT *uptr); -t_stat mpx_cntl_svc (UNIT *uptr); -t_stat mpx_poll_svc (UNIT *uptr); -t_stat mpx_reset (DEVICE *dptr); -t_stat mpx_attach (UNIT *uptr, char *cptr); -t_stat mpx_detach (UNIT *uptr); -t_stat mpx_status (FILE *st, UNIT *uptr, int32 val, void *desc); -t_stat mpx_set_frev (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, void *desc); - - -/* MPX data structures. - - mpx_order MPX line connection order table - mpx_ldsc MPX terminal multiplexer line descriptors - mpx_desc MPX terminal multiplexer device descriptor - mpx_dib MPX device information block - mpx_unit MPX unit list - mpx_reg MPX register list - mpx_mod MPX modifier list - mpx_deb MPX debug list - mpx_dev MPX device descriptor - - The first eight units correspond to the eight multiplexer line ports. These - handle character I/O via the Telnet library. A ninth unit acts as the card - controller, executing commands and transferring data to and from the I/O - buffers. A tenth unit is responsible for polling for connections and socket - I/O. It also holds the master socket. - - The character I/O service routines run only when there are characters to read - or write. They operate at the approximate baud rates of the terminals (in - CPU instructions per second) in order to be compatible with the OS drivers. - The controller service routine runs only when a command is executing or a - data transfer to or from the CPU is in progress. The Telnet poll must run - continuously, but it may operate much more slowly, as the only requirement is - that it must not present a perceptible lag to human input. To be compatible - with CPU idling, it is co-scheduled with the master poll timer, which uses a - ten millisecond period. - - The controller and poll units are hidden by disabling them, so as to present - a logical picture of the multiplexer to the user. -*/ - -DEVICE mpx_dev; - -int32 mpx_order [MPX_PORTS] = { -1 }; /* connection order */ -TMLN mpx_ldsc [MPX_PORTS] = { { 0 } }; /* line descriptors */ -TMXR mpx_desc = { MPX_PORTS, 0, 0, mpx_ldsc, mpx_order }; /* device descriptor */ - -DIB mpx_dib = { &mpx_io, MPX }; - -UNIT mpx_unit [] = { - { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 0 */ - { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 1 */ - { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 2 */ - { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 3 */ - { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 4 */ - { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 5 */ - { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 6 */ - { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 7 */ - { UDATA (&mpx_cntl_svc, UNIT_DIS, 0) }, /* controller unit */ - { UDATA (&mpx_poll_svc, UNIT_ATTABLE | UNIT_DIS, POLL_FIRST) } /* Telnet poll unit */ - }; - -REG mpx_reg [] = { - { DRDATA (STATE, mpx_state, 3) }, - { ORDATA (IBUF, mpx_ibuf, 16), REG_FIT }, - { ORDATA (OBUF, mpx_obuf, 16), REG_FIT }, - - { ORDATA (CMD, mpx_cmd, 8) }, - { ORDATA (PARAM, mpx_param, 16) }, - - { DRDATA (PORT, mpx_port, 8), PV_LEFT }, - { DRDATA (PORTKEY, mpx_portkey, 8), PV_LEFT }, - { DRDATA (IOLEN, mpx_iolen, 16), PV_LEFT }, - - { FLDATA (UIEN, mpx_uien, 0) }, - { GRDATA (UIPORT, mpx_uicode, 10, 3, 0) }, - { GRDATA (UICODE, mpx_uicode, 10, 3, UI_REASON_V) }, - - { BRDATA (KEYS, mpx_key, 10, 8, MPX_PORTS) }, - { BRDATA (PCONFIG, mpx_config, 8, 16, MPX_PORTS) }, - { BRDATA (RCVTYPE, mpx_rcvtype, 8, 16, MPX_PORTS) }, - { BRDATA (CHARCNT, mpx_charcnt, 8, 16, MPX_PORTS) }, - { BRDATA (FLOWCNTL, mpx_flowcntl, 8, 16, MPX_PORTS) }, - - { BRDATA (ENQCNTR, mpx_enq_cntr, 10, 7, MPX_PORTS) }, - { BRDATA (ACKWAIT, mpx_ack_wait, 10, 10, MPX_PORTS) }, - { BRDATA (PFLAGS, mpx_flags, 2, 12, MPX_PORTS) }, - - { BRDATA (RBUF, mpx_rbuf, 8, 8, MPX_PORTS * RD_BUF_SIZE) }, - { BRDATA (WBUF, mpx_wbuf, 8, 8, MPX_PORTS * WR_BUF_SIZE) }, - - { BRDATA (GET, mpx_get, 10, 10, MPX_PORTS * 2) }, - { BRDATA (SEP, mpx_sep, 10, 10, MPX_PORTS * 2) }, - { BRDATA (PUT, mpx_put, 10, 10, MPX_PORTS * 2) }, - - { FLDATA (CTL, mpx.control, 0) }, - { FLDATA (FLG, mpx.flag, 0) }, - { FLDATA (FBF, mpx.flagbuf, 0) }, - { ORDATA (SC, mpx_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, mpx_dib.select_code, 6), REG_HRO }, - - { BRDATA (CONNORD, mpx_order, 10, 32, MPX_PORTS), REG_HRO }, - { NULL } - }; - -MTAB mpx_mod [] = { - { UNIT_FASTTIME, UNIT_FASTTIME, "fast timing", "FASTTIME", NULL, NULL, NULL }, - { UNIT_FASTTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL }, - - { UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL }, - { UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL }, - - { MTAB_XTD | MTAB_VDV, 0, "REV", NULL, &mpx_set_frev, &mpx_show_frev, NULL }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LINEORDER", "LINEORDER", &tmxr_set_lnorder, &tmxr_show_lnorder, &mpx_desc }, - - { MTAB_XTD | MTAB_VUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, &mpx_desc }, - { MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, &mpx_desc }, - - { MTAB_XTD | MTAB_VDV, 0, "", NULL, NULL, &mpx_status, &mpx_desc }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, &mpx_desc }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, &mpx_desc }, - { MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT", &tmxr_dscln, NULL, &mpx_desc }, - { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &mpx_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &mpx_dev }, - - { 0 } - }; - -DEBTAB mpx_deb [] = { - { "CMDS", DEB_CMDS }, - { "CPU", DEB_CPU }, - { "BUF", DEB_BUF }, - { "XFER", DEB_XFER }, - { NULL, 0 } - }; - -DEVICE mpx_dev = { - "MPX", /* device name */ - mpx_unit, /* unit array */ - mpx_reg, /* register array */ - mpx_mod, /* modifier array */ - MPX_PORTS + MPX_CNTLS, /* number of units */ - 10, /* address radix */ - 31, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 8, /* data width */ - &tmxr_ex, /* examine routine */ - &tmxr_dep, /* deposit routine */ - &mpx_reset, /* reset routine */ - NULL, /* boot routine */ - &mpx_attach, /* attach routine */ - &mpx_detach, /* detach routine */ - &mpx_dib, /* device information block */ - DEV_DEBUG | DEV_DISABLE | DEV_MUX, /* device flags */ - 0, /* debug control flags */ - mpx_deb, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL, /* logical device name */ - NULL, /* help routine */ - NULL, /* help attach routine*/ - (void *) &mpx_desc /* help context */ - }; - - -/* I/O signal handler. - - Commands are sent to the card via an OTA/B. Issuing an STC SC,C causes the - mux to accept the word (STC causes a NMI on the card). If the command uses - one word, command execution will commence, and the flag will set on - completion. If the command uses two words, the flag will be set, indicating - that the second word should be output via an OTA/B. Command execution will - commence upon receipt, and the flag will set on completion. - - When the flag sets for command completion, status or data may be read from - the card via an LIA/B. If additional status or data words are expected, the - flag will set when they are available. - - A command consists of an opcode in the high byte, and a port key or command - parameter in the low byte. Undefined commands are treated as NOPs. - - The card firmware executes commands as part of a twelve-event round-robin - scheduling poll. The card NMI service routine simply sets a flag that is - interrogated during polling. The poll sequence is advanced after each - command. This implies that successive commands incur a delay of at least one - poll-loop's execution time. On an otherwise quiescent card, this delay is - approximately 460 Z80 instructions, or about 950 usec. The average command - initiation time is half of that, or roughly 425 usec. - - If a detected command requires a second word, the card sits in a tight loop, - waiting for the OTx that indicates that the parameter is available. Command - initiation from parameter receipt is about 25 usec. - - For reads and writes to card buffers, the on-board DMA controller is used. - The CPU uses DCPC to handle the transfer, but the data transfer time is - limited by the Z80 DMA, which can process a word in about 1.25 usec. - - For most cards, the hardware POPIO signal sets the flag buffer and flag - flip-flops, while CRS clears the control flip-flop. For this card, the - control and flags are cleared together by CRS, and POPIO is not used. - - Implementation notes: - - 1. "Enable unsolicited input" is the only command that does not set the - device flag upon completion. Therefore, the CPU has no way of knowing - when the command has completed. Because the command in the input latch - is recorded in the NMI handler, but actual execution only begins when the - scheduler polls for the command indication, it is possible for another - command to be sent to the card before the "Enable unsolicited input" - command is recognized. In this case, the second command overwrites the - first and is executed by the scheduler poll. Under simulation, this - condition occurs when the OTx and STC processors are entered with - mpx_state = cmd. - - 2. The "Fast binary read" command inhibits all other commands until the card - is reset. -*/ - -uint32 mpx_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -static const char *output_state [] = { "Command", "Command override", "Parameter", "Data" }; -static const char *input_state [] = { "Status", "Invalid status", "Parameter", "Data" }; -const char *hold_or_clear = (signal_set & ioCLF ? ",C" : ""); -int32 delay; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - mpx.flag = mpx.flagbuf = CLEAR; /* clear flag and flag buffer */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fputs (">>MPX cmds: [CLF] Flag cleared\n", sim_deb); - break; - - - case ioSTF: /* set flag flip-flop */ - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fputs (">>MPX cmds: [STF] Flag set\n", sim_deb); - /* fall into ENF */ - - case ioENF: /* enable flag */ - mpx.flag = mpx.flagbuf = SET; /* set flag and flag buffer */ - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (mpx); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (mpx); - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, mpx_ibuf); /* return info */ - - if (DEBUG_PRI (mpx_dev, DEB_CPU)) - fprintf (sim_deb, ">>MPX cpu: [LIx%s] %s = %06o\n", - hold_or_clear, input_state [mpx_state], mpx_ibuf); - - if (mpx_state == exec) /* if this is input data word */ - sim_activate (&mpx_cntl, DATA_DELAY); /* continue transmission */ - break; - - - case ioIOO: /* I/O data output */ - mpx_obuf = IODATA (stat_data); /* save word */ - - if (DEBUG_PRI (mpx_dev, DEB_CPU)) - fprintf (sim_deb, ">>MPX cpu: [OTx%s] %s = %06o\n", - hold_or_clear, output_state [mpx_state], mpx_obuf); - - if (mpx_state == param) { /* if this is parameter word */ - sim_activate (&mpx_cntl, CMD_DELAY); /* do command now */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: [OTx%s] Command %03o parameter %06o scheduled, " - "time = %d\n", hold_or_clear, mpx_cmd, mpx_obuf, CMD_DELAY); - } - - else if (mpx_state == exec) /* else if this is output data word */ - sim_activate (&mpx_cntl, DATA_DELAY); /* then do transmission */ - break; - - - case ioCRS: /* control reset */ - controller_reset (); /* reset firmware to power-on defaults */ - mpx_obuf = 0; /* clear output buffer */ - - mpx.control = CLEAR; /* clear control */ - mpx.flagbuf = CLEAR; /* clear flag buffer */ - mpx.flag = CLEAR; /* clear flag */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fputs (">>MPX cmds: [CRS] Controller reset\n", sim_deb); - break; - - - case ioCLC: /* clear control flip-flop */ - mpx.control = CLEAR; /* clear control */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: [CLC%s] Control cleared\n", hold_or_clear); - break; - - - case ioSTC: /* set control flip-flop */ - mpx.control = SET; /* set control */ - - if (mpx_cmd == CMD_BINARY_READ) /* executing fast binary read? */ - break; /* further command execution inhibited */ - - mpx_cmd = GET_OPCODE (mpx_obuf); /* get command opcode */ - mpx_portkey = GET_KEY (mpx_obuf); /* get port key */ - - if (mpx_state == cmd) /* already scheduled? */ - sim_cancel (&mpx_cntl); /* cancel to get full delay */ - - mpx_state = cmd; /* set command state */ - - if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */ - delay = PARAM_DELAY; /* specify parameter wait */ - else /* one-word command */ - delay = CMD_DELAY; /* specify command wait */ - - sim_activate (&mpx_cntl, delay); /* schedule command */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: [STC%s] Command %03o key %d scheduled, " - "time = %d\n", hold_or_clear, mpx_cmd, mpx_portkey, delay); - break; - - - case ioEDT: /* end data transfer */ - if (DEBUG_PRI (mpx_dev, DEB_CPU)) - fputs (">>MPX cpu: [EDT] DCPC transfer ended\n", sim_deb); - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (mpx); /* set standard PRL signal */ - setstdIRQ (mpx); /* set standard IRQ signal */ - setstdSRQ (mpx); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - mpx.flagbuf = CLEAR; /* clear flag buffer */ - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Command executor. - - We are called by the controller service routine to process one- and two-word - commands. For two-word commands, the parameter word is present in mpx_param. - The return value indicates whether the card flag should be set upon - completion. - - Most commands execute and complete directly. The read and write commands, - however, transition to the execution state to simulate the DMA transfer, and - the "Download executable" command does the same to receive the download from - the CPU. - - Several commands were added for the B firmware revision, and the various - revisions of the RTE drivers sent some commands that were never implemented - in the mux firmware. The command protocol treated unknown commands as NOPs, - meaning that the command (and parameter, if it was a two-word command) was - absorbed and the card flag was set as though the command completed normally. - This allowed interoperability between firmware and driver revisions. - - Commands that refer to ports do so indirectly by passing a port key, rather - than a port number. The key-to-port translation is established by the "Set - port key" command. If a key is not found in the table, the command is not - executed, and the status return is ST_BAD_KEY, which in hex is "BAD0". - - Implementation notes: - - 1. The "Reset to power-on defaults" command causes the firmware to disable - interrupts and jump to the power-on initialization routine, exactly as - though the Z80 had received a hardware reset. - - 2. The "Abort DMA transfer" command works because STC causes NMI, so the - command is executed even in the middle of a DMA transfer. The OTx of the - command will be sent to the buffer if a "Write data to buffer" command is - in progress, but the STC will cause this routine to be called, which will - cancel the buffer and return the controller to the idle state. Note that - this command might be sent with no transfer in progress, in which case - nothing is done. - - 3. In response to an "Enable unsolicited interrupts" command, the controller - service is scheduled to check for a pending UI. If one is found, the - first UI status word is placed in the input buffer, and an interrupt is - generated by setting the flag. This causes entry to the driver, which - issues an "Acknowledge" command to obtain the second status word. - - It is possible, however, for the interrupt to be ignored. For example, - the driver may be waiting for a "write buffer available" UI when it is - called to begin a write to a different port. If the flag is set by - the UI after RTE has been entered, the interrupt will be held off, and - the STC sc,C instruction that begins the command sequence will clear the - flag, removing the interrupt entirely. In this case, the controller will - reissue the UI when the next "Enable unsolicited interrupts" command is - sent. - - Note that the firmware reissues the same UI, rather than recomputing UIs - and potentially selecting a different one of higher priority. - - 4. The "Fast binary read" command apparently was intended to facilitate - booting from a 264x tape drive, although no boot loader ROM for the - multiplexer was ever released. It sends the fast binary read escape - sequence (ESC e) to the terminal and then packs each pair of characters - received into a word and sends it to the CPU, accompanied by the device - flag. - - The multiplexer firmware disables interrupts and then manipulates the SIO - for port 0 directly. Significantly, it does no interpretation of the - incoming data and sits in an endless I/O loop, so the only way to exit - the command is to reset the card with a CRS (front panel PRESET or CLC 0 - instruction execution). Sending a command will not work; although the - NMI will interrupt the fast binary read, the NMI handler simply sets a - flag that is tested by the scheduler poll. Because the processor is in - an endless loop, control never returns to the scheduler, so the command - is never seen. - - 5. The "Terminate active receive buffer" behavior is a bit tricky. If the - read buffer has characters, the buffer is terminated as though a - "terminate on count" condition occurred. If the buffer is empty, - however, a "terminate on count = 1" condition is established. When a - character is received, the buffer is terminated, and the buffer - termination count is reset to 254. -*/ - -static t_bool exec_command (void) -{ -int32 port; -uint32 svc_time; -t_bool set_flag = TRUE; /* flag is normally set on completion */ -STATE next_state = idle; /* command normally executes to completion */ - -mpx_ibuf = ST_OK; /* return status is normally OK */ - -switch (mpx_cmd) { - - case CMD_NOP: /* no operation */ - break; /* just ignore */ - - - case CMD_RESET: /* reset firmware */ - controller_reset (); /* reset program variables */ - mpx_ibuf = ST_TEST_OK; /* return self-test OK code */ - break; - - - case CMD_ENABLE_UI: - mpx_uien = TRUE; /* enable unsolicited interrupts */ - sim_activate (&mpx_cntl, CMD_DELAY); /* and schedule controller for UI check */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: Controller status check scheduled, " - "time = %d\n", CMD_DELAY); - - set_flag = FALSE; /* do not set the flag at completion */ - break; - - - case CMD_DISABLE: - switch (mpx_portkey) { - case SUBCMD_UI: - mpx_uien = FALSE; /* disable unsolicited interrupts */ - break; - - case SUBCMD_DMA: - if (mpx_flags [mpx_port] & FL_WRFILL) /* write buffer xfer in progress? */ - buf_cancel (iowrite, mpx_port, put); /* cancel it */ - else if (mpx_flags [mpx_port] & FL_RDEMPT) /* read buffer xfer in progress? */ - buf_cancel (ioread, mpx_port, get); /* cancel it */ - break; - } - break; - - - case CMD_ACK: /* acknowledge unsolicited interrupt */ - switch (mpx_uicode & UI_REASON) { - - case UI_WRBUF_AVAIL: /* write buffer notification */ - mpx_flags [mpx_port] &= ~FL_WANTBUF; /* clear flag */ - mpx_ibuf = WR_BUF_LIMIT; /* report write buffer available */ - break; - - case UI_RDBUF_AVAIL: /* read buffer notification */ - mpx_flags [mpx_port] &= ~FL_HAVEBUF; /* clear flag */ - - mpx_ibuf = (uint16) (buf_get (ioread, mpx_port) << 8 | /* get header value and position */ - buf_len (ioread, mpx_port, get)); /* and include buffer length */ - - if (mpx_flags [mpx_port] & FL_RDOVFLOW) { /* did a buffer overflow? */ - mpx_ibuf = mpx_ibuf | RS_OVERFLOW; /* report it */ - mpx_flags [mpx_port] &= ~FL_RDOVFLOW; /* clear overflow flag */ - } - break; - - case UI_BRK_RECD: /* break received */ - mpx_flags [mpx_port] &= ~FL_BREAK; /* clear flag */ - mpx_ibuf = 0; /* 2nd word is zero */ - break; - } - - mpx_uicode = 0; /* clear notification code */ - break; - - - case CMD_CANCEL: /* cancel first read buffer */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) /* port defined? */ - buf_cancel (ioread, port, get); /* cancel get buffer */ - - if ((buf_avail (ioread, port) == 1) && /* one buffer remaining? */ - !(mpx_flags [port] & FL_RDFILL)) /* and not filling it? */ - mpx_flags [port] |= FL_HAVEBUF; /* indicate buffer availability */ - break; - - - case CMD_CANCEL_ALL: /* cancel all read buffers */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) /* port defined? */ - buf_init (ioread, port); /* reinitialize read buffers */ - break; - - - case CMD_BINARY_READ: /* fast binary read */ - for (port = 0; port < MPX_PORTS; port++) - sim_cancel (&mpx_unit [port]); /* cancel I/O on all lines */ - - mpx_flags [0] = 0; /* clear port 0 state flags */ - mpx_enq_cntr [0] = 0; /* clear port 0 ENQ counter */ - mpx_ack_wait [0] = 0; /* clear port 0 ACK wait timer */ - - tmxr_putc_ln (&mpx_ldsc [0], ESC); /* send fast binary read */ - tmxr_putc_ln (&mpx_ldsc [0], 'e'); /* escape sequence to port 0 */ - tmxr_poll_tx (&mpx_desc); /* flush output */ - - next_state = exec; /* set execution state */ - break; - - - case CMD_REQ_WRITE: /* request write buffer */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) /* port defined? */ - if (buf_avail (iowrite, port) > 0) /* is a buffer available? */ - mpx_ibuf = WR_BUF_LIMIT; /* report write buffer limit */ - - else { - mpx_ibuf = 0; /* report none available */ - mpx_flags [port] |= FL_WANTBUF; /* set buffer request */ - } - break; - - - case CMD_WRITE: /* write to buffer */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) { /* port defined? */ - mpx_port = port; /* save port number */ - mpx_iolen = mpx_param & WR_LENGTH; /* save request length */ - next_state = exec; /* set execution state */ - } - break; - - - case CMD_SET_KEY: /* set port key and configuration */ - port = GET_PORT (mpx_param); /* get target port number */ - mpx_key [port] = (uint8) mpx_portkey; /* set port key */ - mpx_config [port] = (uint16) mpx_param; /* set port configuration word */ - - svc_time = service_time (mpx_config [port]); /* get service time for baud rate */ - - if (svc_time) /* want to change? */ - mpx_unit [port].wait = svc_time; /* set service time */ - - mpx_ibuf = MPX_DATE_CODE; /* return firmware date code */ - break; - - - case CMD_SET_RCV: /* set receive type */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) /* port defined? */ - mpx_rcvtype [port] = (uint16) mpx_param; /* save port receive type */ - break; - - - case CMD_SET_COUNT: /* set character count */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) /* port defined? */ - mpx_charcnt [port] = (uint16) mpx_param; /* save port character count */ - break; - - - case CMD_SET_FLOW: /* set flow control */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) /* port defined? */ - mpx_flowcntl [port] = mpx_param & FC_XONXOFF; /* save port flow control */ - - if (mpx_param & FC_FORCE_XON) /* force XON? */ - mpx_flags [port] &= ~FL_XOFF; /* resume transmission if suspended */ - break; - - - case CMD_READ: /* read from buffer */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) { /* port defined? */ - mpx_port = port; /* save port number */ - mpx_iolen = mpx_param; /* save request length */ - - sim_activate (&mpx_cntl, DATA_DELAY); /* schedule the transfer */ - next_state = exec; /* set execution state */ - set_flag = FALSE; /* no flag until word ready */ - } - break; - - - case CMD_DL_EXEC: /* Download executable */ - mpx_iolen = mpx_param; /* save request length */ - next_state = exec; /* set execution state */ - break; - - - case CMD_CN_LINE: /* connect modem line */ - case CMD_DC_LINE: /* disconnect modem line */ - case CMD_LOOPBACK: /* enable/disable modem loopback */ - mpx_ibuf = ST_NO_MODEM; /* report "no modem installed" */ - break; - - - case CMD_GET_STATUS: /* get modem status */ - mpx_ibuf = ST_NO_SYSMDM; /* report "no systems modem card" */ - break; - - - case CMD_TERM_BUF: /* terminate active receive buffer */ - port = key_to_port (mpx_portkey); /* get port */ - - if (port >= 0) /* port defined? */ - if (buf_len (ioread, port, put) > 0) { /* any chars in buffer? */ - buf_term (ioread, port, 0); /* terminate buffer and set header */ - - if (buf_avail (ioread, port) == 1) /* first read buffer? */ - mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */ - } - - else { /* buffer is empty */ - mpx_charcnt [port] = 1; /* set to terminate on one char */ - mpx_flags [port] |= FL_ALERT; /* set alert flag */ - } - break; - - - case CMD_VCP_PUT: /* VCP put byte */ - case CMD_VCP_PUT_BUF: /* VCP put buffer */ - case CMD_VCP_GET: /* VCP get byte */ - case CMD_VCP_GET_BUF: /* VCP get buffer */ - case CMD_VCP_EXIT: /* Exit VCP mode */ - case CMD_VCP_ENTER: /* Enter VCP mode */ - - default: /* unknown command */ - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: Unknown command %03o ignored\n", mpx_cmd); - } - -mpx_state = next_state; -return set_flag; -} - - -/* Multiplexer controller service. - - The controller service handles commands and data transfers to and from the - CPU. The delay in scheduling the controller service represents the firmware - command or data execution time. The controller may be in one of four states - upon entry: idle, first word of command received (cmd), command parameter - received (param), or data transfer (exec). - - Entry in the command state causes execution of one-word commands and - solicitation of command parameters for two-word commands, which are executed - when entering in the parameter state. - - Entry in the data transfer state moves one word between the CPU and a read or - write buffer. For writes, the write buffer is filled with words from the - CPU. Once the indicated number of words have been transferred, the - appropriate line service is scheduled to send the characters. For reads, - characters are unloaded from the read buffer to the CPU; an odd-length - transfer is padded with a blank. A read of fewer characters than are present - in the buffer will return the remaining characters when the next read is - performed. - - Each read or write is terminated by the CPU sending one additional word (the - RTE drivers send -1). The command completes when this word is acknowledged - by the card setting the device flag. For zero-length writes, this additional - word will be the only word sent. - - Data transfer is also used by the "Download executable" command to absorb the - downloaded program. The firmware jumps to location 5100 hex in the - downloaded program upon completion of reception. It is the responsibility of - the program to return to the multiplexer firmware and to return to the CPU - whatever status is appropriate when it is done. Under simulation, we simply - "sink" the program and return status compatible with the multiplexer - diagnostic program to simulate a passing test. - - Entry in the idle state checks for unsolicited interrupts. UIs are sent to - the host when the controller is idle, UIs have been enabled, and a UI - condition exists. If a UI is not acknowledged, it will remain pending and - will be reissued the next time the controller is idle and UIs have been - enabled. - - UI conditions are kept in the per-port flags. The UI conditions are write - buffer available, read buffer available, break received, modem line - connected, and modem line disconnected. The latter two conditions are not - implemented in this simulation. If a break condition occurs at the same time - as a read buffer completion, the break has priority; the buffer UI will occur - after the break UI is acknowledged. - - The firmware checks for UI condition flags as part of the scheduler polling - loop. Under simulation, though, UIs can occur only in two places: the point - of origin (e.g., termination of a read buffer), or the "Enable unsolicited - input" command executor. UIs will be generated at the point of origin only - if the simulator is idle. If the simulator is not idle, it is assumed that - UIs have been disabled to execute the current command and will be reenabled - when the command sequence is complete. - - When the multiplexer is reset, and before the port keys are set, all ports - enter "echoplex" mode. In this mode, characters received are echoed back as - a functional test. Each port terminates buffers on CR reception. We detect - this condition, cancel the buffer, and discard the buffer termination UI. - - Implementation notes: - - 1. The firmware transfers the full amount requested by the CPU, even if the - transfer is longer than the buffer. Also, zero-length transfers program - the card DMA chip to transfer 0 bytes; this results in a transfer of 217 - bytes, per the Zilog databook. Under simulation, writes beyond the - buffer are accepted from the CPU but discarded, and reads beyond the - buffer return blanks. - - 2. We should never return from this routine in the "cmd" state, so debugging - will report "internal error!" if we do. -*/ - -t_stat mpx_cntl_svc (UNIT *uptr) -{ -uint8 ch; -uint32 i; -t_bool add_crlf; -t_bool set_flag = TRUE; -STATE last_state = mpx_state; - -static const char *cmd_state [] = { "complete", "internal error!", "waiting for parameter", "executing" }; - - -switch (mpx_state) { /* dispatch on current state */ - - case idle: /* controller idle */ - set_flag = FALSE; /* assume no UI */ - - if (mpx_uicode) { /* unacknowledged UI? */ - if (mpx_uien == TRUE) { /* interrupts enabled? */ - mpx_port = GET_UIPORT (mpx_uicode); /* get port number */ - mpx_portkey = mpx_key [mpx_port]; /* get port key */ - mpx_ibuf = (uint16) (mpx_uicode & UI_REASON | mpx_portkey); /* report UI reason and port key */ - set_flag = TRUE; /* reissue host interrupt */ - mpx_uien = FALSE; /* disable UI */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: Port %d key %d unsolicited interrupt reissued, " - "reason = %d\n", mpx_port, mpx_portkey, GET_UIREASON (mpx_uicode)); - } - } - - else { /* no unacknowledged UI */ - for (i = 0; i < MPX_PORTS; i++) { /* check all ports for UIs */ - if (mpx_flags [i] & FL_UI_PENDING) { /* pending UI? */ - mpx_portkey = mpx_key [i]; /* get port key */ - - if (mpx_portkey == KEY_DEFAULT) { /* key defined? */ - if (mpx_flags [i] & FL_HAVEBUF) /* no, is this read buffer avail? */ - buf_cancel (ioread, i, get); /* cancel buffer */ - - mpx_flags [i] &= ~FL_UI_PENDING; /* cancel pending UI */ - } - - else if (mpx_uien == TRUE) { /* interrupts enabled? */ - if ((mpx_flags [i] & FL_WANTBUF) && /* port wants a write buffer? */ - (buf_avail (iowrite, i) > 0)) /* and one is available? */ - mpx_uicode = UI_WRBUF_AVAIL; /* set UI reason */ - - else if (mpx_flags [i] & FL_BREAK) /* received a line BREAK? */ - mpx_uicode = UI_BRK_RECD; /* set UI reason */ - - else if (mpx_flags [i] & FL_HAVEBUF) /* have a read buffer ready? */ - mpx_uicode = UI_RDBUF_AVAIL; /* set UI reason */ - - if (mpx_uicode) { /* UI to send? */ - mpx_port = i; /* set port number for Acknowledge */ - mpx_ibuf = (uint16) (mpx_uicode | mpx_portkey); /* merge UI reason and port key */ - mpx_uicode = mpx_uicode | mpx_port; /* save UI reason and port */ - set_flag = TRUE; /* interrupt host */ - mpx_uien = FALSE; /* disable UI */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: Port %d key %d unsolicited interrupt generated, " - "reason = %d\n", i, mpx_portkey, GET_UIREASON (mpx_uicode)); - - break; /* quit after first UI */ - } - } - } - } - } - break; - - - case cmd: /* command state */ - if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */ - mpx_state = param; /* look for parameter before executing */ - else - set_flag = exec_command (); /* execute one-word command */ - break; - - - case param: /* parameter get state */ - mpx_param = mpx_obuf; /* save parameter */ - set_flag = exec_command (); /* execute two-word command */ - break; - - - case exec: /* execution state */ - switch (mpx_cmd) { - - case CMD_BINARY_READ: /* fast binary read */ - mpx_flags [0] &= ~FL_HAVEBUF; /* data word was picked up by CPU */ - set_flag = FALSE; /* suppress device flag */ - break; - - - case CMD_WRITE: /* transfer data to buffer */ - if (mpx_iolen <= 0) { /* last (or only) entry? */ - mpx_state = idle; /* idle controller */ - - if (mpx_iolen < 0) /* tie-off for buffer complete? */ - break; /* we're done */ - } - - add_crlf = ((mpx_param & /* CRLF should be added */ - (WR_ADD_CRLF | WR_PARTIAL)) == WR_ADD_CRLF); - - for (i = 0; i < 2; i++) /* output one or two chars */ - if (mpx_iolen > 0) { /* more to do? */ - if (i) /* high or low byte? */ - ch = (uint8) (mpx_obuf & 0377); /* low byte */ - else - ch = mpx_obuf >> 8; /* high byte */ - - if ((mpx_iolen == 1) && /* final char? */ - (ch == '_') && add_crlf) { /* underscore and asking for CRLF? */ - - add_crlf = FALSE; /* suppress CRLF */ - - if (DEBUG_PRI (mpx_dev, DEB_BUF)) - fprintf (sim_deb, ">>MPX buf: Port %d character '_' " - "suppressed CR/LF\n", mpx_port); - } - - else if (buf_len (iowrite, mpx_port, put) < WR_BUF_LIMIT) - buf_put (iowrite, mpx_port, ch); /* add char to buffer if space avail */ - - mpx_iolen = mpx_iolen - 1; /* drop remaining count */ - } - - if (mpx_iolen == 0) { /* buffer done? */ - if (add_crlf) { /* want CRLF? */ - buf_put (iowrite, mpx_port, CR); /* add CR to buffer */ - buf_put (iowrite, mpx_port, LF); /* add LF to buffer */ - } - - buf_term (iowrite, mpx_port, (uint8) (mpx_param >> 8)); /* terminate buffer */ - mpx_iolen = -1; /* mark as done */ - } - - if (DEBUG_PRI (mpx_dev, DEB_CMDS) && - (sim_is_active (&mpx_unit [mpx_port]) == 0)) - fprintf (sim_deb, ">>MPX cmds: Port %d service scheduled, " - "time = %d\n", mpx_port, mpx_unit [mpx_port].wait); - - sim_activate (&mpx_unit [mpx_port], /* start line service */ - mpx_unit [mpx_port].wait); - break; - - - case CMD_READ: /* transfer data from buffer */ - if (mpx_iolen < 0) { /* input complete? */ - if (mpx_obuf == 0177777) { /* "tie-off" word received? */ - if (buf_len (ioread, mpx_port, get) == 0) { /* buffer now empty? */ - buf_free (ioread, mpx_port); /* free buffer */ - - if (buf_avail (ioread, mpx_port) == 1) /* another buffer available? */ - mpx_flags [mpx_port] |= FL_HAVEBUF; /* indicate availability */ - } - - mpx_state = idle; /* idle controller */ - } - - else - set_flag = FALSE; /* ignore word */ - - break; - } - - for (i = 0; i < 2; i++) /* input one or two chars */ - if (mpx_iolen > 0) { /* more to transfer? */ - if (buf_len (ioread, mpx_port, get) > 0) /* more chars available? */ - ch = buf_get (ioread, mpx_port); /* get char from buffer */ - else /* buffer exhausted */ - ch = ' '; /* pad with blank */ - - if (i) /* high or low byte? */ - mpx_ibuf = mpx_ibuf | ch; /* low byte */ - else - mpx_ibuf = (uint16) (ch << 8); /* high byte */ - - mpx_iolen = mpx_iolen - 1; /* drop count */ - } - - else /* odd number of chars */ - mpx_ibuf = mpx_ibuf | ' '; /* pad last with blank */ - - if (mpx_iolen == 0) /* end of host xfer? */ - mpx_iolen = -1; /* mark as done */ - - break; - - - case CMD_DL_EXEC: /* sink data from host */ - if (mpx_iolen <= 0) { /* final entry? */ - mpx_state = idle; /* idle controller */ - mpx_ibuf = ST_DIAG_OK; /* return diag passed status */ - } - - else { - if (mpx_iolen > 0) /* more from host? */ - mpx_iolen = mpx_iolen - 2; /* sink two bytes */ - - if (mpx_iolen <= 0) /* finished download? */ - sim_activate (&mpx_cntl, CMD_DELAY); /* schedule completion */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: Download completion scheduled, " - "time = %d\n", CMD_DELAY); - } - - break; - - - default: /* no other entries allowed */ - return SCPE_IERR; /* simulator error! */ - } - break; - } - - -if (DEBUG_PRI (mpx_dev, DEB_CMDS) && /* debug print? */ - (last_state != mpx_state)) { /* and state change? */ - fprintf (sim_deb, ">>MPX cmds: Command %03o ", mpx_cmd); - - if ((mpx_cmd & CMD_TWO_WORDS) && (mpx_state != param)) - fprintf (sim_deb, "parameter %06o ", mpx_param); - - fputs (cmd_state [mpx_state], sim_deb); - fputc ('\n', sim_deb); - } - -if (set_flag) { - mpx_io (&mpx_dib, ioENF, 0); /* set device flag */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fputs (">>MPX cmds: Flag set\n", sim_deb); - } - -return SCPE_OK; -} - - -/* Multiplexer line service. - - The line service routine is used to transmit and receive characters. It is - started when a buffer is ready for output or when the Telnet poll routine - determines that there are characters ready for input, and it is stopped when - there are no more characters to output or input. When a line is quiescent, - this routine does not run. Service times are selected to approximate the - baud rate setting of the multiplexer port. - - "Fast timing" mode enables three optimizations. First, buffered characters - are transferred via Telnet in blocks, rather than a character at a time; this - reduces network traffic and decreases simulator overhead (there is only one - service routine entry per block, rather than one per character). Second, - ENQ/ACK handshaking is done locally, without involving the Telnet client. - Third, when editing and echo is enabled, entering BS echoes a backspace, a - space, and a backspace, and entering DEL echoes a backslash, a carriage - return, and a line feed, providing better compatibility with prior RTE - terminal drivers. - - Each read and write buffer begins with a reserved header byte that stores - per-buffer information, such as whether handshaking should be suppressed - during output, or the specific cause of termination for input. Buffer - termination sets the header byte with the appropriate flags. - - For output, a character counter is maintained and is incremented if ENQ/ACK - handshaking is enabled for the current port and request. If the counter - limit is reached, an ENQ is sent, and a flag is set to suspend transmission - until an ACK is received. If the last character of the buffer is sent, the - write buffer is freed, and a UI check is made if the controller is idle, in - case a write buffer request is pending. - - For input, the character is retrieved from the Telnet buffer. If a BREAK was - received, break status is set, and the character is discarded (the current - multiplexer library implementation always returns a NUL with a BREAK - indication). If the character is an XOFF, and XON/XOFF pacing is enabled, a - flag is set, and transmission is suspended until a corresponding XON is - received. If the character is an ACK and is in response to a previously sent - ENQ, it is discarded, and transmission is reenabled. - - If editing is enabled, a BS will delete the last character in the read - buffer, and a DEL will delete the entire buffer. Otherwise, buffer - termination conditions are checked (end on character, end on count, or - buffer full), and if observed, the read buffer is terminated, and a read - buffer available UI condition is signalled. - - Implementation notes: - - 1. The firmware echoes an entered BS before checking the buffer count to see - if there are any characters to delete. Under simulation, we only echo if - the buffer is not empty. - - 2. The "Fast binary read" command inhibits the normal transmit and receive - processing. Instead, a pair of characters are sought on line 0 to fill - the input buffer. When they are received, the device flag is set. The - CPU will do a LIx sc,C to retrieve the data and reset the flag. -*/ - -t_stat mpx_line_svc (UNIT *uptr) -{ -const int32 port = uptr - mpx_unit; /* port number */ -const uint16 rt = mpx_rcvtype [port]; /* receive type for port */ -const uint32 data_bits = 5 + GET_BPC (mpx_config [port]); /* number of data bits */ -const uint32 data_mask = (1 << data_bits) - 1; /* mask for data bits */ -const t_bool fast_timing = (uptr->flags & UNIT_FASTTIME) != 0; /* port is set for fast timing */ -const t_bool fast_binary_read = (mpx_cmd == CMD_BINARY_READ); /* fast binary read in progress */ - -uint8 ch; -int32 chx; -uint16 read_length; -t_stat status = SCPE_OK; -t_bool recv_loop = !fast_binary_read; /* bypass if fast binary read */ -t_bool xmit_loop = !(fast_binary_read || /* bypass if fast read or output suspended */ - (mpx_flags [port] & (FL_WAITACK | FL_XOFF))); - - -/* Transmission service */ - -while (xmit_loop && (buf_len (iowrite, port, get) > 0)) { /* character available to output? */ - if ((mpx_flags [port] & FL_WREMPT) == 0) { /* has buffer started emptying? */ - chx = buf_get (iowrite, port) << 8; /* get header value and position */ - - if (fast_timing || (chx & WR_NO_ENQACK) || /* do we want handshake? */ - !(mpx_config [port] & SK_ENQACK)) /* and configured for handshake? */ - mpx_flags [port] &= ~FL_DO_ENQACK; /* no, so clear flag */ - else - mpx_flags [port] |= FL_DO_ENQACK; /* yes, so set flag */ - - continue; /* "continue" for zero-length write */ - } - - if (mpx_flags [port] & FL_DO_ENQACK) /* do handshake for this buffer? */ - mpx_enq_cntr [port] = mpx_enq_cntr [port] + 1; /* bump character counter */ - - if (mpx_enq_cntr [port] > ENQ_LIMIT) { /* ready for ENQ? */ - mpx_enq_cntr [port] = 0; /* clear ENQ counter */ - mpx_ack_wait [port] = 0; /* clear ACK wait timer */ - - mpx_flags [port] |= FL_WAITACK; /* set wait for ACK */ - ch = ENQ; - status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit ENQ */ - xmit_loop = FALSE; /* stop further transmission */ - } - - else { /* not ready for ENQ */ - ch = buf_get (iowrite, port) & data_mask; /* get char and mask to bit width */ - status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit the character */ - xmit_loop = (status == SCPE_OK) && fast_timing; /* continue transmission? */ - } - - if ((status == SCPE_OK) && /* transmitted OK? */ - DEBUG_PRI (mpx_dev, DEB_XFER)) - fprintf (sim_deb, ">>MPX xfer: Port %d character %s transmitted\n", - port, fmt_char (ch)); - - else - xmit_loop = FALSE; - - if (buf_len (iowrite, port, get) == 0) { /* buffer complete? */ - buf_free (iowrite, port); /* free buffer */ - - if (mpx_state == idle) /* controller idle? */ - mpx_cntl_svc (&mpx_cntl); /* check for UI */ - } - } - - -/* Reception service */ - -while (recv_loop) { /* OK to process? */ - chx = tmxr_getc_ln (&mpx_ldsc [port]); /* get a new character */ - - if (chx == 0) /* if there are no more characters available */ - break; /* then quit the reception loop */ - - if (chx & SCPE_BREAK) { /* break detected? */ - mpx_flags [port] |= FL_BREAK; /* set break status */ - - if (DEBUG_PRI (mpx_dev, DEB_XFER)) - fputs (">>MPX xfer: Break detected\n", sim_deb); - - if (mpx_state == idle) /* controller idle? */ - mpx_cntl_svc (&mpx_cntl); /* check for UI */ - - continue; /* discard NUL that accompanied BREAK */ - } - - ch = (uint8) (chx & data_mask); /* mask to bits per char */ - - if ((ch == XOFF) && /* XOFF? */ - (mpx_flowcntl [port] & FC_XONXOFF)) { /* and handshaking enabled? */ - mpx_flags [port] |= FL_XOFF; /* suspend transmission */ - - if (DEBUG_PRI (mpx_dev, DEB_XFER)) - fprintf (sim_deb, ">>MPX xfer: Port %d character XOFF " - "suspends transmission\n", port); - - recv_loop = fast_timing; /* set to loop if fast mode */ - continue; - } - - else if ((ch == XON) && /* XON? */ - (mpx_flags [port] & FL_XOFF)) { /* and currently suspended? */ - mpx_flags [port] &= ~FL_XOFF; /* resume transmission */ - - if (DEBUG_PRI (mpx_dev, DEB_XFER)) - fprintf (sim_deb, ">>MPX xfer: Port %d character XON " - "resumes transmission\n", port); - - recv_loop = fast_timing; /* set to loop if fast mode */ - continue; - } - - if (DEBUG_PRI (mpx_dev, DEB_XFER)) - fprintf (sim_deb, ">>MPX xfer: Port %d character %s received\n", - port, fmt_char (ch)); - - if ((ch == ACK) && (mpx_flags [port] & FL_WAITACK)) { /* ACK and waiting for it? */ - mpx_flags [port] = mpx_flags [port] & ~FL_WAITACK; /* clear wait flag */ - recv_loop = FALSE; /* absorb character */ - } - - else if ((buf_avail (ioread, port) == 0) && /* no free buffer available for char? */ - !(mpx_flags [port] & FL_RDFILL)) { /* and not filling last buffer? */ - mpx_flags [port] |= FL_RDOVFLOW; /* set buffer overflow flag */ - recv_loop = fast_timing; /* continue loop if fast mode */ - } - - else { /* buffer is available */ - if (rt & RT_ENAB_EDIT) /* editing enabled? */ - if (ch == BS) { /* backspace? */ - if (buf_len (ioread, port, put) > 0) /* at least one character in buffer? */ - buf_remove (ioread, port); /* remove last char */ - - if (rt & RT_ENAB_ECHO) { /* echo enabled? */ - tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */ - - if (fast_timing) { /* fast timing mode? */ - tmxr_putc_ln (&mpx_ldsc [port], ' '); /* echo space */ - tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */ - } - } - - continue; - } - - else if (ch == DEL) { /* delete line? */ - buf_cancel (ioread, port, put); /* cancel put buffer */ - - if (rt & RT_ENAB_ECHO) { /* echo enabled? */ - if (fast_timing) /* fast timing mode? */ - tmxr_putc_ln (&mpx_ldsc [port], '\\'); /* echo backslash */ - - tmxr_putc_ln (&mpx_ldsc [port], CR); /* echo CR */ - tmxr_putc_ln (&mpx_ldsc [port], LF); /* and LF */ - } - - continue; - } - - if (uptr->flags & UNIT_CAPSLOCK) /* caps lock mode? */ - ch = (uint8) toupper (ch); /* convert to upper case if lower */ - - if (rt & RT_ENAB_ECHO) /* echo enabled? */ - tmxr_putc_ln (&mpx_ldsc [port], ch); /* echo the char */ - - if (rt & RT_END_ON_CHAR) { /* end on character? */ - recv_loop = FALSE; /* assume termination */ - - if ((ch == CR) && (rt & RT_END_ON_CR)) { - if (rt & RT_ENAB_ECHO) /* echo enabled? */ - tmxr_putc_ln (&mpx_ldsc [port], LF); /* send LF */ - mpx_param = RS_ETC_CR; /* set termination condition */ - } - - else if ((ch == RS) && (rt & RT_END_ON_RS)) - mpx_param = RS_ETC_RS; /* set termination condition */ - - else if ((ch == EOT) && (rt & RT_END_ON_EOT)) - mpx_param = RS_ETC_EOT; /* set termination condition */ - - else if ((ch == DC2) && (rt & RT_END_ON_DC2)) - mpx_param = RS_ETC_DC2; /* set termination condition */ - - else - recv_loop = TRUE; /* no termination */ - } - - if (recv_loop) /* no termination condition? */ - buf_put (ioread, port, ch); /* put character in buffer */ - - read_length = buf_len (ioread, port, put); /* get current buffer length */ - - if ((rt & RT_END_ON_CNT) && /* end on count */ - (read_length == mpx_charcnt [port])) { /* and count reached? */ - recv_loop = FALSE; /* set termination */ - mpx_param = 0; /* no extra termination info */ - - if (mpx_flags [port] & FL_ALERT) { /* was this alert for term rcv buffer? */ - mpx_flags [port] &= ~FL_ALERT; /* clear alert flag */ - mpx_charcnt [port] = RD_BUF_LIMIT; /* reset character count */ - } - } - - else if (read_length == RD_BUF_LIMIT) { /* buffer now full? */ - recv_loop = FALSE; /* set termination */ - mpx_param = mpx_param | RS_PARTIAL; /* and partial buffer flag */ - } - - if (recv_loop) /* no termination condition? */ - recv_loop = fast_timing; /* set to loop if fast mode */ - - else { /* termination occurred */ - if (DEBUG_PRI (mpx_dev, DEB_XFER)) { - fprintf (sim_deb, ">>MPX xfer: Port %d read terminated on ", port); - - if (mpx_param & RS_PARTIAL) - fputs ("buffer full\n", sim_deb); - else if (rt & RT_END_ON_CHAR) - fprintf (sim_deb, "character %s\n", fmt_char (ch)); - else - fprintf (sim_deb, "count = %d\n", mpx_charcnt [port]); - } - - if (buf_len (ioread, port, put) == 0) { /* zero-length read? */ - buf_put (ioread, port, 0); /* dummy put to reserve header */ - buf_remove (ioread, port); /* back out dummy char leaving header */ - } - - buf_term (ioread, port, (uint8) (mpx_param >> 8)); /* terminate buffer and set header */ - - if (buf_avail (ioread, port) == 1) /* first read buffer? */ - mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */ - - if (mpx_state == idle) /* controller idle? */ - mpx_cntl_svc (&mpx_cntl); /* check for UI */ - } - } - } - - -/* Housekeeping */ - -if (fast_binary_read) { /* fast binary read in progress? */ - if (port == 0) { /* on port 0? */ - chx = tmxr_getc_ln (&mpx_ldsc [0]); /* see if a character is ready */ - - if (chx && !(mpx_flags [0] & FL_HAVEBUF)) { /* character ready and buffer empty? */ - if (mpx_flags [0] & FL_WANTBUF) { /* second character? */ - mpx_ibuf = mpx_ibuf | (chx & DMASK8); /* merge it into word */ - mpx_flags [0] |= FL_HAVEBUF; /* mark buffer as ready */ - - mpx_io (&mpx_dib, ioENF, 0); /* set device flag */ - - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fputs (">>MPX cmds: Flag and SRQ set\n", sim_deb); - } - - else /* first character */ - mpx_ibuf = (uint16) ((chx & DMASK8) << 8); /* put in top half of word */ - - mpx_flags [0] ^= FL_WANTBUF; /* toggle byte flag */ - } - - sim_activate (uptr, uptr->wait); /* reschedule service for fast response */ - } - } - -else { /* normal service */ - tmxr_poll_tx (&mpx_desc); /* output any accumulated characters */ - - if ((buf_avail (iowrite, port) < 2) && /* more to transmit? */ - !(mpx_flags [port] & (FL_WAITACK | FL_XOFF)) || /* and transmission not suspended */ - tmxr_rqln (&mpx_ldsc [port])) /* or more to receive? */ - sim_activate (uptr, uptr->wait); /* reschedule service */ - else - if (DEBUG_PRI (mpx_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MPX cmds: Port %d service stopped\n", port); - } - -return SCPE_OK; -} - - -/* Telnet poll service. - - This service routine is used to poll for Telnet connections and incoming - characters. It starts when the socket is attached and stops when the socket - is detached. - - Each line is then checked for a pending ENQ/ACK handshake. If one is - pending, the ACK counter is incremented, and if it times out, another ENQ is - sent to avoid stalls. Lines are also checked for available characters, and - the corresponding line I/O service routine is scheduled if needed. -*/ - -t_stat mpx_poll_svc (UNIT *uptr) -{ -uint32 i; -t_stat status = SCPE_OK; - -poll_connection (); /* check for new connection */ - -tmxr_poll_rx (&mpx_desc); /* poll for input */ - -for (i = 0; i < MPX_PORTS; i++) { /* check lines */ - if (mpx_flags [i] & FL_WAITACK) { /* waiting for ACK? */ - mpx_ack_wait [i] = mpx_ack_wait [i] + 1; /* increment ACK wait timer */ - - if (mpx_ack_wait [i] > ACK_LIMIT) { /* has wait timed out? */ - mpx_ack_wait [i] = 0; /* reset counter */ - status = tmxr_putc_ln (&mpx_ldsc [i], ENQ); /* send ENQ again */ - tmxr_poll_tx (&mpx_desc); /* transmit it */ - - if ((status == SCPE_OK) && /* transmitted OK? */ - DEBUG_PRI (mpx_dev, DEB_XFER)) - fprintf (sim_deb, ">>MPX xfer: Port %d character ENQ retransmitted\n", i); - } - } - - if (tmxr_rqln (&mpx_ldsc [i])) /* chars available? */ - sim_activate (&mpx_unit [i], mpx_unit [i].wait); /* activate I/O service */ - } - -if (uptr->wait == POLL_FIRST) /* first poll? */ - uptr->wait = sync_poll (INITIAL); /* initial synchronization */ -else /* not first */ - uptr->wait = sync_poll (SERVICE); /* continue synchronization */ - -sim_activate (uptr, uptr->wait); /* continue polling */ - -return SCPE_OK; -} - - -/* Simulator reset routine. - - The hardware CRS signal generates a reset signal to the Z80 and its - peripherals. This causes execution of the power up initialization code. - - The CRS signal also has these hardware effects: - - clears control - - clears flag - - clears flag buffer - - clears backplane ready - - clears the output buffer register - - Implementation notes: - - 1. Under simulation, we also clear the input buffer register, even though - the hardware doesn't. - - 2. We set up the first poll for Telnet connections to occur "immediately" - upon execution, so that clients will be connected before execution - begins. Otherwise, a fast program may access the multiplexer before the - poll service routine activates. - - 3. We must set the "emptying_flags" and "filling_flags" values here, because - they cannot be initialized statically, even though the values are - constant. -*/ - -t_stat mpx_reset (DEVICE *dptr) -{ -if (sim_switches & SWMASK ('P')) { /* power-on reset? */ - emptying_flags [ioread] = FL_RDEMPT; /* initialize buffer flags constants */ - emptying_flags [iowrite] = FL_WREMPT; - filling_flags [ioread] = FL_RDFILL; - filling_flags [iowrite] = FL_WRFILL; - } - -IOPRESET (&mpx_dib); /* PRESET device (does not use PON) */ - -mpx_ibuf = 0; /* clear input buffer */ - -if (mpx_poll.flags & UNIT_ATT) { /* network attached? */ - mpx_poll.wait = POLL_FIRST; /* set up poll */ - sim_activate (&mpx_poll, mpx_poll.wait); /* start Telnet poll immediately */ - } -else - sim_cancel (&mpx_poll); /* else stop Telnet poll */ - -return SCPE_OK; -} - - -/* Attach the multiplexer to a Telnet port. - - We are called by the ATTACH MPX command to attach the multiplexer to - the listening port indicated by . Logically, it is the multiplexer - device that is attached; however, SIMH only allows units to be attached. - This makes sense for devices such as tape drives, where the attached media is - a property of a specific drive. In our case, though, the listening port is a - property of the multiplexer card, not of any given serial line. As ATTACH - MPX is equivalent to ATTACH MPX0, the port would, by default, be attached to - the first serial line and be reported there in a SHOW MPX command. - - To preserve the logical picture, we attach the port to the Telnet poll unit, - which is normally disabled to inhibit its display. Attaching to a disabled - unit is not allowed, so we first enable the unit, then attach it, then - disable it again. Attachment is reported by the "mpx_status" routine below. - - A direct attach to the poll unit is only allowed when restoring a previously - saved session. - - The Telnet poll service routine is synchronized with the other input polling - devices in the simulator to facilitate idling. -*/ - -t_stat mpx_attach (UNIT *uptr, char *cptr) -{ -t_stat status = SCPE_OK; - -if (uptr != mpx_unit /* not unit 0? */ - && (uptr != &mpx_poll || !(sim_switches & SIM_SW_REST))) /* and not restoring the poll unit? */ - return SCPE_NOATT; /* can't attach */ - -mpx_poll.flags = mpx_poll.flags & ~UNIT_DIS; /* enable unit */ -status = tmxr_attach (&mpx_desc, &mpx_poll, cptr); /* attach to socket */ -mpx_poll.flags = mpx_poll.flags | UNIT_DIS; /* disable unit */ - -if (status == SCPE_OK) { - mpx_poll.wait = POLL_FIRST; /* set up poll */ - sim_activate (&mpx_poll, mpx_poll.wait); /* start poll immediately */ - } -return status; -} - - -/* Detach the multiplexer. - - Normally, we are called by the DETACH MPX command, which is equivalent to - DETACH MPX0. However, we may be called with other units in two cases. - - A DETACH ALL command will call us for unit 9 (the poll unit) if it is - attached. Also, during simulator shutdown, we will be called for units 0-8 - (detach_all in scp.c calls the detach routines of all units that do NOT have - UNIT_ATTABLE), as well as for unit 9 if it is attached. In both cases, it is - imperative that we return SCPE_OK, otherwise any remaining device detaches - will not be performed. -*/ - -t_stat mpx_detach (UNIT *uptr) -{ -t_stat status = SCPE_OK; -int32 i; - -if ((uptr == mpx_unit) || (uptr == &mpx_poll)) { /* base unit or poll unit? */ - status = tmxr_detach (&mpx_desc, &mpx_poll); /* detach socket */ - - for (i = 0; i < MPX_PORTS; i++) { - mpx_ldsc [i].rcve = 0; /* disable line reception */ - sim_cancel (&mpx_unit [i]); /* cancel any scheduled I/O */ - } - - sim_cancel (&mpx_poll); /* stop Telnet poll */ - } - -return status; -} - - -/* Show multiplexer status */ - -t_stat mpx_status (FILE *st, UNIT *uptr, int32 val, void *desc) -{ -if (mpx_poll.flags & UNIT_ATT) /* attached to socket? */ - fprintf (st, "attached to port %s, ", mpx_poll.filename); -else - fprintf (st, "not attached, "); - -tmxr_show_summ (st, uptr, val, desc); /* report connection count */ -return SCPE_OK; -} - - -/* Set firmware revision. - - Currently, we support only revision C, so the MTAB entry does not have an - "mstring" entry. When we add revision D support, an "mstring" entry of "REV" - will enable changing the firmware revision. -*/ - -t_stat mpx_set_frev (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -if ((cptr == NULL) || /* no parameter? */ - (*cptr < 'C') || (*cptr > 'D') || /* or not C or D? */ - (*(cptr + 1) != '\0')) /* or not just one character? */ - return SCPE_ARG; /* bad argument */ - -else { - if (*cptr == 'C') /* setting revision C? */ - mpx_dev.flags = mpx_dev.flags & ~DEV_REV_D; /* clear 'D' flag */ - else if (*cptr == 'D') /* setting revision D? */ - mpx_dev.flags = mpx_dev.flags | DEV_REV_D; /* set 'D' flag */ - - return SCPE_OK; - } -} - - -/* Show firmware revision */ - -t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, void *desc) -{ -if (mpx_dev.flags & DEV_REV_D) - fputs ("12792D", st); -else - fputs ("12792C", st); -return SCPE_OK; -} - - -/* Local routines */ - - -/* Poll for new Telnet connections */ - -static void poll_connection (void) -{ -int32 new_line; - -new_line = tmxr_poll_conn (&mpx_desc); /* check for new connection */ - -if (new_line >= 0) /* new connection established? */ - mpx_ldsc [new_line].rcve = 1; /* enable line to receive */ - -return; -} - - -/* Controller reset. - - This is the card microprocessor reset, not the simulator reset routine. It - simulates a power-on restart of the Z80 firmware. When it is called from the - simulator reset routine, that routine will take care of setting the card - flip-flops appropriately. -*/ - -static void controller_reset (void) -{ -uint32 i; - -mpx_state = idle; /* idle state */ - -mpx_cmd = 0; /* clear command */ -mpx_param = 0; /* clear parameter */ -mpx_uien = FALSE; /* disable interrupts */ - -for (i = 0; i < MPX_PORTS; i++) { /* clear per-line variables */ - buf_init (iowrite, i); /* initialize write buffers */ - buf_init (ioread, i); /* initialize read buffers */ - - mpx_key [i] = KEY_DEFAULT; /* clear port key to default */ - - if (i == 0) /* default port configurations */ - mpx_config [0] = SK_PWRUP_0; /* port 0 is separate from 1-7 */ - else - mpx_config [i] = (uint16) (SK_PWRUP_1 | i); - - mpx_rcvtype [i] = RT_PWRUP; /* power on config for echoplex */ - mpx_charcnt [i] = 0; /* default character count */ - mpx_flowcntl [i] = 0; /* default flow control */ - mpx_flags [i] = 0; /* clear state flags */ - mpx_enq_cntr [i] = 0; /* clear ENQ counter */ - mpx_ack_wait [i] = 0; /* clear ACK wait timer */ - mpx_unit [i].wait = service_time (mpx_config [i]); /* set terminal I/O time */ - - sim_cancel (&mpx_unit [i]); /* cancel line I/O */ - } - -sim_cancel (&mpx_cntl); /* cancel controller */ - -return; -} - - -/* Calculate service time from baud rate. - - Service times are based on 1580 instructions per millisecond, which is the - 1000 E-Series execution speed. Baud rate 0 means "don't change" and is - handled by the "Set port key" command executor. - - Baud rate settings of 13-15 are marked as "reserved" in the user manual, but - the firmware defines these as 38400, 9600, and 9600 baud, respectively. -*/ - -static uint32 service_time (uint16 control_word) -{ -/* Baud Rates 0- 7 : --, 50, 75, 110, 134.5, 150, 300, 1200, */ -/* Baud Rates 8-15 : 1800, 2400, 4800, 9600, 19200, 38400, 9600, 9600 */ -static const int32 ticks [] = { 0, 316000, 210667, 143636, 117472, 105333, 52667, 13167, - 8778, 6583, 3292, 1646, 823, 411, 1646, 1646 }; - -return ticks [GET_BAUDRATE (control_word)]; /* return service time for indicated rate */ -} - - -/* Translate port key to port number. - - Port keys are scanned in reverse port order, so if more than one port has the - same port key, commands specifying that key will affect the highest numbered - port. - - If a port key is the reserved value 255, then the port key has not been set. - In this case, set the input buffer to 0xBAD0 and return -1 to indicate - failure. -*/ - -static int32 key_to_port (uint32 key) -{ -int32 i; - -for (i = MPX_PORTS - 1; i >= 0; i--) /* scan in reverse order */ - if (mpx_key [i] == key) /* key found? */ - return i; /* return port number */ - -mpx_ibuf = ST_BAD_KEY; /* key not found: set status */ -return -1; /* return failure code */ -} - - -/* Buffer manipulation routines. - - The 12792 hardware provides 16K bytes of RAM to the microprocessor. From - this pool, the firmware allocates per-port read/write buffers and state - variables, global variables, and the system stack. Allocations are static - and differ between firmware revisions. - - The A/B/C revisions allocate two 254-byte read buffers and two 254-byte write - buffers per port. Assuming an idle condition, the first write to a port - transfers characters to the first write buffer. When the transfer completes, - the SIO begins transmitting. During transmission, a second write can be - initiated, which transfers characters to the second write buffer. If a third - write is attempted before the first buffer has been released, it will be - denied until the SIO completes transmission; then, if enabled, an unsolicited - interrupt will occur to announce buffer availability. The "active" (filling) - buffer alternates between the two. - - At idle, characters received will fill the first read buffer. When the read - completes according to the previously set termination criteria, an - unsolicited interrupt will occur (if enabled) to announce buffer - availability. If more characters are received before the first buffer has - been transferred to the CPU, they will fill the second buffer. If that read - also completes, additional characters will be discarded until the first - buffer has been emptied. The "active" (emptying) buffer alternates between - the two. - - With this configuration, two one-character writes or reads will allocate both - available buffers, even though each was essentially empty. - - The D revision allocates one 1024-byte FIFO read buffer and one 892-byte - write buffer per port. As with the A/B/C revisions, the first write to a - port transfers characters to the write buffer, and serial transmission begins - when the write completes. However, the write buffer is not a FIFO, so the - host is not permitted another write request until the entire buffer has been - transmitted. - - The read buffer is a FIFO. Characters received are placed into the FIFO as a - stream. Unlike the A/B/C revisions, character editing and termination - conditions are not evaluated until the buffer is read. Therefore, a full - 1024 characters may be received before additional characters would be - discarded. - - When the first character is received, an unsolicited interrupt occurs (if - enabled) to announce data reception. A host read may then be initiated. The - write buffer is used temporarily to process characters from the read buffer. - Characters are copied from the read to the write buffer while editing as - directed by the configuration accompanying the read request (e.g., deleting - the character preceding a BS, stripping CR/LF, etc.). When the termination - condition is found, the read command completes. Incoming characters may be - added to the FIFO while this is occurring. - - In summary, the revision differences in buffer handling are: - - Revisions A/B/C: - - two 254-byte receive buffers - - a buffer is "full" when the terminator character or count is received - - termination type must be established before the corresponding read - - data is echoed as it is received - - Revision D: - - one 1024-byte receive buffer - - buffer is "full" only when 1024 characters are received - - the concept of a buffer terminator does not apply, as the data is not - examined until a read is requested and characters are retrieved from the - FIFO. - - data is not echoed until it is read - - To implement the C revision behavior, while preserving the option of reusing - the buffer handlers for future D revision support, the dual 254-byte buffers - are implemented as a single 514-byte circular FIFO with capacity limited to - 254 bytes per buffer. This reserves space for a CR and LF and for a header - byte in each buffer. The header byte preserves per-buffer state information. - - In this implementation, the buffer "put" index points at the next free - location, and the buffer "get" index points at the next character to - retrieve. In addition to "put" and "get" indexes, a third "separator" index - is maintained to divide the FIFO into two areas corresponding to the two - buffers, and a "buffer filling" flag is maintained for each FIFO that is set - by the fill (put) routine and cleared by the terminate buffer routine. - - Graphically, the implementation is as follows for buffer "B[]", get "G", put - "P", and separator "S" indexes: - - 1. Initialize: 2. Fill first buffer: - G = S = P = 0 B[P] = char; Incr (P) - - |------------------------------| |---------|--------------------| - G G P --> - S S - P - - 3. Terminate first buffer: 4. Fill second buffer: - if S == G then S = P else nop B[P] = char; Incr (P) - - |------------|-----------------| |------------|------|----------| - G /---> S G S P --> - * ----/ P - - 5. Terminate second buffer: 6. Empty first buffer: - if S == G then S = P else nop char = B[G]; Incr (G) - - |------------|------------|----| |----|-------|------------|----| - G S P G --> S P - - 7. First buffer is empty: 8. Free first buffer: - G == S if !filling then S = P else nop - - |------------|------------|----| |------------|------------|----| - G P G /---> S - S * ----/ P - - 9. Empty second buffer: 10. Second buffer empty: - char = B[G]; Incr (G) G == S - - |----------------|--------|----| |-------------------------|----| - G --> S G - P S - P - 11. Free second buffer: - if !filling then S = P else nop - - |-------------------------|----| - G - S - P - - We also provide the following utility routines: - - - Remove Character: Decr (P) - - - Cancel Buffer: if S == G then P = G else G = S - - - Buffer Length: if S < G then return S + BUFSIZE - G else return S - G - - - Buffers Available: if G == P then return 2 else if G != S != P then return - 0 else return 1 - - The "buffer filling" flag is necessary for the "free" routine to decide - whether to advance the separator index. If the first buffer is to be freed, - then G == S and S != P. If the second buffer is already filled, then S = P. - However, if the buffer is still filling, then S must remain at G. This - cannot be determined from G, S, and P alone. - - A "buffer emptying" flag is also employed to record whether the per-buffer - header has been obtained. This allows the buffer length to exclude the - header and reflect only the characters present. -*/ - - -/* Increment a buffer index with wraparound */ - -static uint16 buf_incr (BUF_INDEX index, uint32 port, IO_OPER rw, int increment) -{ -index [port] [rw] = - (index [port] [rw] + buf_size [rw] + increment) % buf_size [rw]; - -return index [port] [rw]; -} - - -/* Initialize the buffer. - - Initialization sets the three indexes to zero and clears the buffer state - flags. -*/ - -static void buf_init (IO_OPER rw, uint32 port) -{ -mpx_get [port] [rw] = 0; /* clear indexes */ -mpx_sep [port] [rw] = 0; -mpx_put [port] [rw] = 0; - -if (rw == ioread) - mpx_flags [mpx_port] &= ~(FL_RDFLAGS); /* clear read buffer flags */ -else - mpx_flags [mpx_port] &= ~(FL_WRFLAGS); /* clear write buffer flags */ -return; -} - - -/* Get a character from the buffer. - - The character indicated by the "get" index is retrieved from the buffer, and - the index is incremented with wraparound. If the buffer is now empty, the - "buffer emptying" flag is cleared. Otherwise, it is set to indicate that - characters have been removed from the buffer. -*/ - -static uint8 buf_get (IO_OPER rw, uint32 port) -{ -uint8 ch; -uint32 index = mpx_get [port] [rw]; /* current get index */ - -if (rw == ioread) - ch = mpx_rbuf [port] [index]; /* get char from read buffer */ -else - ch = mpx_wbuf [port] [index]; /* get char from write buffer */ - -buf_incr (mpx_get, port, rw, +1); /* increment circular get index */ - -if (DEBUG_PRI (mpx_dev, DEB_BUF)) - if (mpx_flags [port] & emptying_flags [rw]) - fprintf (sim_deb, ">>MPX buf: Port %d character %s get from %s buffer " - "[%d]\n", port, fmt_char (ch), io_op [rw], index); - else - fprintf (sim_deb, ">>MPX buf: Port %d header %03o get from %s buffer " - "[%d]\n", port, ch, io_op [rw], index); - -if (mpx_get [port] [rw] == mpx_sep [port] [rw]) /* buffer now empty? */ - mpx_flags [port] &= ~emptying_flags [rw]; /* clear "buffer emptying" flag */ -else - mpx_flags [port] |= emptying_flags [rw]; /* set "buffer emptying" flag */ - -return ch; -} - - -/* Put a character to the buffer. - - The character is written to the buffer in the slot indicated by the "put" - index, and the index is incremented with wraparound. The first character put - to a new buffer reserves space for the header and sets the "buffer filling" - flag. -*/ - -static void buf_put (IO_OPER rw, uint32 port, uint8 ch) -{ -uint32 index; - -if ((mpx_flags [port] & filling_flags [rw]) == 0) { /* first put to this buffer? */ - mpx_flags [port] |= filling_flags [rw]; /* set buffer filling flag */ - index = mpx_put [port] [rw]; /* get current put index */ - buf_incr (mpx_put, port, rw, +1); /* reserve space for header */ - - if (DEBUG_PRI (mpx_dev, DEB_BUF)) - fprintf (sim_deb, ">>MPX buf: Port %d reserved header " - "for %s buffer [%d]\n", port, io_op [rw], index); - } - -index = mpx_put [port] [rw]; /* get current put index */ - -if (rw == ioread) - mpx_rbuf [port] [index] = ch; /* put char in read buffer */ -else - mpx_wbuf [port] [index] = ch; /* put char in write buffer */ - -buf_incr (mpx_put, port, rw, +1); /* increment circular put index */ - -if (DEBUG_PRI (mpx_dev, DEB_BUF)) - fprintf (sim_deb, ">>MPX buf: Port %d character %s put to %s buffer " - "[%d]\n", port, fmt_char (ch), io_op [rw], index); -return; -} - - -/* Remove the last character put to the buffer. - - The most-recent character put to the buffer is removed by decrementing the - "put" index with wraparound. -*/ - -static void buf_remove (IO_OPER rw, uint32 port) -{ -uint8 ch; -uint32 index; - -index = buf_incr (mpx_put, port, rw, -1); /* decrement circular put index */ - -if (DEBUG_PRI (mpx_dev, DEB_BUF)) { - if (rw == ioread) - ch = mpx_rbuf [port] [index]; /* pick up char from read buffer */ - else - ch = mpx_wbuf [port] [index]; /* pick up char from write buffer */ - - fprintf (sim_deb, ">>MPX buf: Port %d character %s removed from %s buffer " - "[%d]\n", port, fmt_char (ch), io_op [rw], index); - } -return; -} - - -/* Terminate the buffer. - - The buffer is marked to indicate that filling is complete and that the next - "put" operation should begin a new buffer. The header value is stored in - first byte of buffer, which is reserved, and the "buffer filling" flag is - cleared. -*/ - -static void buf_term (IO_OPER rw, uint32 port, uint8 header) -{ -uint32 index = mpx_sep [port] [rw]; /* separator index */ - -if (rw == ioread) - mpx_rbuf [port] [index] = header; /* put header in read buffer */ -else - mpx_wbuf [port] [index] = header; /* put header in write buffer */ - -mpx_flags [port] = mpx_flags [port] & ~filling_flags [rw]; /* clear filling flag */ - -if (mpx_get [port] [rw] == index) /* reached separator? */ - mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move sep to end of next buffer */ - -if (DEBUG_PRI (mpx_dev, DEB_BUF)) - fprintf (sim_deb, ">>MPX buf: Port %d header %03o terminated %s buffer\n", - port, header, io_op [rw]); -return; -} - - -/* Free the buffer. - - The buffer is marked to indicate that it is available for reuse, and the - "buffer emptying" flag is reset. -*/ - -static void buf_free (IO_OPER rw, uint32 port) -{ -if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling next buffer? */ - mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */ - /* else it will be moved when terminated */ -mpx_flags [port] = mpx_flags [port] & ~emptying_flags [rw]; /* clear emptying flag */ - -if (DEBUG_PRI (mpx_dev, DEB_BUF)) - fprintf (sim_deb, ">>MPX buf: Port %d released %s buffer\n", port, io_op [rw]); -return; -} - - -/* Cancel the selected buffer. - - The selected buffer is marked to indicate that it is empty. Either the "put" - buffer or the "get" buffer may be selected. -*/ - -static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which) -{ -if (which == put) { /* cancel put buffer? */ - mpx_put [port] [rw] = mpx_sep [port] [rw]; /* move put back to separator */ - mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */ - } - -else { /* cancel get buffer */ - if (mpx_sep [port] [rw] == mpx_get [port] [rw]) { /* filling first buffer? */ - mpx_put [port] [rw] = mpx_get [port] [rw]; /* cancel first buffer */ - mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */ - } - - else { /* not filling first buffer */ - mpx_get [port] [rw] = mpx_sep [port] [rw]; /* cancel first buffer */ - - if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling second buffer? */ - mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */ - } - - mpx_flags [port] &= ~emptying_flags [rw]; /* clear emptying flag */ - } - -if (DEBUG_PRI (mpx_dev, DEB_BUF)) - fprintf (sim_deb, ">>MPX buf: Port %d cancelled %s buffer\n", port, io_op [rw]); -return; -} - - -/* Get the buffer length. - - The current length of the selected buffer (put or get) is returned. For ease - of use, the returned length does NOT include the header byte, i.e., it - reflects only the characters contained in the buffer. - - If the put buffer is selected, and the buffer is filling, or the get buffer - is selected, and the buffer is not emptying, then subtract one from the - length for the allocated header. -*/ - -static uint16 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which) -{ -int16 length; - -if (which == put) - length = mpx_put [port] [rw] - mpx_sep [port] [rw] - /* calculate length */ - ((mpx_flags [port] & filling_flags [rw]) != 0); /* account for allocated header */ - -else { - length = mpx_sep [port] [rw] - mpx_get [port] [rw]; /* calculate length */ - - if (length && !(mpx_flags [port] & emptying_flags [rw])) /* not empty and not yet emptying? */ - length = length - 1; /* account for allocated header */ - } - -if (length < 0) /* is length negative? */ - return length + buf_size [rw]; /* account for wraparound */ -else - return length; -} - - -/* Return the number of free buffers available. - - Either 0, 1, or 2 free buffers will be available. A buffer is available if - it contains no characters (including the header byte). -*/ - -static uint32 buf_avail (IO_OPER rw, uint32 port) -{ -if (mpx_get [port] [rw] == mpx_put [port] [rw]) /* get and put indexes equal? */ - return 2; /* all buffers are free */ - -else if ((mpx_get [port] [rw] != mpx_sep [port] [rw]) && /* get, separator, and put */ - (mpx_sep [port] [rw] != mpx_put [port] [rw])) /* all different? */ - return 0; /* no buffers are free */ - -else - return 1; /* one buffer free */ -} +/* hp2100_mpx.c: HP 12792C eight-channel asynchronous multiplexer simulator + + Copyright (c) 2008-2017, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the author. + + MPX 12792C 8-channel multiplexer card + + 22-Apr-17 JDB Corrected missing compound statements + 02-Aug-16 JDB Burst-fill only the first receive buffer in fast mode + 28-Jul-16 JDB Fixed buffer ready check at read completion + Fixed terminate on character counts > 254 + 13-May-16 JDB Modified for revised SCP API function parameter types + 24-Dec-14 JDB Added casts for explicit downward conversions + 10-Jan-13 MP Added DEV_MUX and additional DEVICE field values + 28-Dec-12 JDB Allow direct attach to the poll unit only when restoring + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + Removed DEV_NET to allow restoration of listening port + 28-Mar-11 JDB Tidied up signal handling + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 25-Nov-08 JDB Revised for new multiplexer library SHOW routines + 14-Nov-08 JDB Cleaned up VC++ size mismatch warnings for zero assignments + 03-Oct-08 JDB Fixed logic for ENQ/XOFF transmit wait + 07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach + 10-Aug-08 JDB Added REG_FIT to register variables < 32-bit size + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 26-May-08 JDB Created MPX device + + References: + - HP 12792B 8-Channel Asynchronous Multiplexer Subsystem Installation and + Reference Manual (12792-90020, Jul-1984) + - HP 12792B/C 8-Channel Asynchronous Multiplexer Subsystem User's Manual + (5955-8867, Jun-1993) + - HP 12792B/C 8-Channel Asynchronous Multiplexer Subsystem Configuration Guide + (5955-8868, Jun-1993) + - HP 1000 series 8-channel Multiplexer Firmware External Reference Specification + (October 19, 1982) + - HP 12792/12040 Multiplexer Firmware Source (24999-18312, revision C) + - Zilog Components Data Book (00-2034-04, 1985) + + + The 12792A/B/C/D was an eight-line asynchronous serial multiplexer that + connected terminals, modems, serial line printers, and "black box" devices + that used the RS-232 standard to the CPU. It used an on-board microprocessor + and provided input and output buffering to support block-mode reads from HP + 264x and 262x terminals at speeds up to 19.2K baud. The card handled + character editing, echoing, ENQ/ACK handshaking, and read terminator + detection, substantially reducing the load on the CPU over the earlier 12920 + multiplexer. It was supported by HP under RTE-MIII, RTE-IVB, and RTE-6/VM. + Under simulation, it connects with HP terminal emulators via Telnet to a + user-specified port. + + The single interface card contained a Z80 CPU, DMA controller, CTC, four + two-channel SIO UARTs, 16K of RAM, 8K of ROM, and I/O backplane latches and + control circuitry. The card executed a high-level command set, and data + transfer to and from the CPU was via the on-board DMA controller and the DCPC + in the CPU. + + The 12792 for the M/E/F series and the 12040 multiplexer for the A/L series + differed only in backplane design. Early ROMs were card-specific, but later + ones were interchangeable; the code would determine whether it was executing + on an MEF card or an AL card. + + Four major firmware revisions were made. These were labelled "A", "B", "C", + and "D". The A, B, and C revisions were interchangeable from the perspective + of the OS driver; the D was different and required an updated driver. + Specifically: + + Op. Sys. Driver Part Number Rev + -------- ------ -------------------- --- + RTE-MIII DVM00 12792-16002 Rev.2032 A + RTE-IVB DVM00 12792-16002 Rev.5000 ABC + + RTE-6/VM DVM00 12792-16002 Rev.5000 ABC + RTE-6/VM DV800 92084-15068 Rev.6000 D + + RTE-A IDM00 92077-16754 Rev.5020 ABC + RTE-A ID800 92077-16887 Rev.6200 D + + Revisions A-C have an upward-compatible command set that partitions each OS + request into several sub-commands. Each command is initiated by setting the + control flip-flop on the card, which causes a non-maskable interrupt (NMI) on + the card's Z80 processor. + + The D-revision firmware uses a completely different command set. The + commands are slightly modified versions of the original EXEC calls (read, + write, and control) and are generally passed to the card directly for action. + + This simulation supports the C revision. D-revision support may be added + later. + + Twelve programmable baud rates are supported by the multiplexer. These + "realistic" rates are simulated by scheduling I/O service based on the + appropriate number of 1000 E-Series instructions for the rate selected. + + The simulation provides both the "realistic timing" described above, as well + as an optimized "fast timing" option. Optimization makes three improvements: + + 1. Buffered characters are transferred in blocks. + + 2. ENQ/ACK handshaking is done locally without involving the client. + + 3. BS and DEL respond visually more like prior RTE terminal drivers. + + HP did not offer a functional diagnostic for the 12792. Instead, a Z80 + program that tested the operation of the hardware was downloaded to the card, + and a "go/no-go" status was returned to indicate the hardware condition. + Because this is a functional simulation of the multiplexer and not a Z80 + emulation, the diagnostic cannot be used to test the implementation. + + + Implementation notes: + + 1. The 12792 had two baud-rate generators that were assigned to lines by the + wiring configuration in the I/O cable connector hood. Two of the four + CTC counters were used to implement the BRGs for all eight lines. Only + subsets of the configurable rates were allowed for lines connected to the + same BRG, and assigning mutually incompatible rates caused corruption of + the rates on lines assigned earlier. Under simulation, any baud rate may + be assigned to any line without interaction, and assignments of lines to + BRGs is not implemented. + + 2. Revisions B and C added support for the 37214A Systems Modem subsystem + and the RTE-A Virtual Control Panel (VCP). Under simulation, the modem + commands return status codes indicating that no modems are present, and + the VCP commands are not implemented. +*/ + + +#include + +#include "hp2100_defs.h" +#include "sim_tmxr.h" + + +/* Bitfield constructor. + + Given a bitfield starting bit number and width in bits, declare two + constants: one for the starting bit number, and one for the positioned field + mask. That is, given a definition such as: + + BITFIELD(SMALLFIELD,5,2) + + ...this macro produces: + + static const uint32 SMALLFIELD_V = 5; + static const uint32 SMALLFIELD = ((1 << (2)) - 1) << (5); + + The latter reduces to 3 << 5, or 0x00000060. + + Note: C requires constant expressions in initializers for objects with static + storage duration, so initializing a static object with a BITFIELD value is + illegal (a "static const" object is not a constant!). +*/ + +#define BITFIELD(NAME,STARTBIT,BITWIDTH) \ + static const uint32 NAME ## _V = STARTBIT; \ + static const uint32 NAME = ((1 << (BITWIDTH)) - 1) << (STARTBIT); + + +/* Program constants */ + +#define MPX_DATE_CODE 2416 /* date code for C firmware */ + +#define RD_BUF_SIZE 514 /* read buffer size */ +#define WR_BUF_SIZE 514 /* write buffer size */ + +#define RD_BUF_LIMIT 254 /* read buffer limit */ +#define WR_BUF_LIMIT 254 /* write buffer limit */ + +#define KEY_DEFAULT 255 /* default port key */ + + +/* Service times: + + DATA_DELAY = 1.25 us (Z80 DMA data word transfer time) + PARAM_DELAY = 25 us (STC to STF for first word of two-word command) + CMD_DELAY = 400 us (STC to STF for one or two-word command execution) +*/ + +#define DATA_DELAY 2 /* data transfer time */ +#define PARAM_DELAY 40 /* parameter request time */ +#define CMD_DELAY 630 /* command completion time */ + + +/* Unit references */ + +#define MPX_PORTS 8 /* number of visible units */ +#define MPX_CNTLS 2 /* number of control units */ + +#define mpx_cntl (mpx_unit [MPX_PORTS + 0]) /* controller unit */ +#define mpx_poll (mpx_unit [MPX_PORTS + 1]) /* Telnet polling unit */ + + +/* Character constants */ + +#define EOT '\004' +#define ENQ '\005' +#define ACK '\006' +#define BS '\010' +#define LF '\012' +#define CR '\015' +#define DC1 '\021' +#define DC2 '\022' +#define DC3 '\023' +#define ESC '\033' +#define RS '\036' +#define DEL '\177' + +#define XON DC1 +#define XOFF DC3 + + +/* Device flags */ + +#define DEV_V_REV_D (DEV_V_UF + 0) /* firmware revision D (not implemented) */ + +#define DEV_REV_D (1 << DEV_V_REV_D) + + +/* Unit flags */ + +#define UNIT_V_FASTTIME (UNIT_V_UF + 0) /* fast timing mode */ +#define UNIT_V_CAPSLOCK (UNIT_V_UF + 1) /* caps lock mode */ + +#define UNIT_FASTTIME (1 << UNIT_V_FASTTIME) +#define UNIT_CAPSLOCK (1 << UNIT_V_CAPSLOCK) + + +/* Debug flags */ + +#define DEB_CMDS (1 << 0) /* commands and status */ +#define DEB_CPU (1 << 1) /* CPU I/O */ +#define DEB_BUF (1 << 2) /* buffer gets and puts */ +#define DEB_XFER (1 << 3) /* character reads and writes */ + + +/* Multiplexer commands for revisions A/B/C. + + Commands are either one or two words in length. The one-word format is: + + +-------------------------------+-------------------------------+ + | 0 . 1 | command opcode | command parameter | + +-------------------------------+-------------------------------+ + 15 - 8 7 - 0 + + The two-word format is: + + +-------------------------------+-------------------------------+ + | 1 . 1 | command opcode | command value | + +-------------------------------+-------------------------------+ + | command parameter | + +---------------------------------------------------------------+ + 15 - 8 7 - 0 + + Commands implemented by firmware revision: + + Rev Cmd Value Operation Status Value(s) Returned + --- --- ----- ------------------------------- ------------------------------- + ABC 100 - No operation 000000 + ABC 101 - Reset to power-on defaults 100000 + ABC 102 - Enable unsolicited input None, unless UI pending + ABC 103 1 Disable unsolicited interrupts 000000 + ABC 103 2 Abort DMA transfer 000000 + ABC 104 - Acknowledge Second word of UI status + ABC 105 key Cancel first receive buffer 000000 + ABC 106 key Cancel all received buffers 000000 + ABC 107 - Fast binary read (none) + + -BC 140 chr VCP put byte 000000 + -BC 141 - VCP put buffer 000000 + -BC 142 - VCP get byte Character from port 0 + -BC 143 - VCP get buffer 000120 + -BC 144 - Exit VCP mode 000000 + -BC 157 - Enter VCP mode 000000 + + ABC 300 - No operation 000000 + ABC 301 key Request write buffer 000000 or 000376 + ABC 302 key Write data to buffer (none) + ABC 303 key Set port key 000000 or date code of firmware + ABC 304 key Set receive type 000000 + ABC 305 key Set character count 000000 + ABC 306 key Set flow control 000000 + ABC 307 key Read data from buffer (none) + ABC 310 - Download executable (none) + + -BC 311 key Connect line 000000 or 140000 if no modem + -BC 312 key Disconnect line 000000 or 140000 if no modem + -BC 315 key Get modem/port status modem status or 000200 if no modem + -BC 316 key Enable/disable modem loopback 000000 or 140000 if no modem + -BC 320 key Terminate active receive buffer 000000 +*/ + + +/* One-word command codes */ + +#define CMD_NOP 0100 /* No operation */ +#define CMD_RESET 0101 /* Reset firmware to power-on defaults */ +#define CMD_ENABLE_UI 0102 /* Enable unsolicited input */ +#define CMD_DISABLE 0103 /* Disable interrupts / Abort DMA Transfer */ +#define CMD_ACK 0104 /* Acknowledge */ +#define CMD_CANCEL 0105 /* Cancel first receive buffer */ +#define CMD_CANCEL_ALL 0106 /* Cancel all received buffers */ +#define CMD_BINARY_READ 0107 /* Fast binary read */ + +#define CMD_VCP_PUT 0140 /* VCP put byte */ +#define CMD_VCP_PUT_BUF 0141 /* VCP put buffer */ +#define CMD_VCP_GET 0142 /* VCP get byte */ +#define CMD_VCP_GET_BUF 0143 /* VCP get buffer */ +#define CMD_VCP_EXIT 0144 /* Exit VCP mode */ +#define CMD_VCP_ENTER 0157 /* Enter VCP mode */ + + +/* Two-word command codes */ + +#define CMD_REQ_WRITE 0301 /* Request write buffer */ +#define CMD_WRITE 0302 /* Write data to buffer */ +#define CMD_SET_KEY 0303 /* Set port key */ +#define CMD_SET_RCV 0304 /* Set receive type */ +#define CMD_SET_COUNT 0305 /* Set character count */ +#define CMD_SET_FLOW 0306 /* Set flow control */ +#define CMD_READ 0307 /* Read data from buffer */ +#define CMD_DL_EXEC 0310 /* Download executable */ + +#define CMD_CN_LINE 0311 /* Connect line */ +#define CMD_DC_LINE 0312 /* Disconnect line */ +#define CMD_GET_STATUS 0315 /* Get modem/port status */ +#define CMD_LOOPBACK 0316 /* Enable/disable modem loopback */ +#define CMD_TERM_BUF 0320 /* Terminate active receive buffer */ + + +/* Sub-command codes */ + +#define SUBCMD_UI 1 /* Disable unsolicited interrupts */ +#define SUBCMD_DMA 2 /* Abort DMA transfer */ + +#define CMD_TWO_WORDS 0200 /* two-word command flag */ + + +/* Unsolicited interrupt reasons */ + +#define UI_REASON_V 8 /* interrupt reason */ +#define UI_REASON (((1 << 8) - 1) << (UI_REASON_V)) /* (UI_REASON_V must be a constant!) */ + +BITFIELD (UI_PORT, 0, 3) /* interrupt port number */ + +#define UI_WRBUF_AVAIL (1 << UI_REASON_V) /* Write buffer available */ +#define UI_LINE_CONN (2 << UI_REASON_V) /* Modem line connected */ +#define UI_LINE_DISC (3 << UI_REASON_V) /* Modem line disconnected */ +#define UI_BRK_RECD (4 << UI_REASON_V) /* Break received */ +#define UI_RDBUF_AVAIL (5 << UI_REASON_V) /* Read buffer available */ + + +/* Return status to CPU */ + +#define ST_OK 0000000 /* Command OK */ +#define ST_DIAG_OK 0000015 /* Diagnostic passes */ +#define ST_VCP_SIZE 0000120 /* VCP buffer size = 80 chars */ +#define ST_NO_SYSMDM 0000200 /* No systems modem card */ +#define ST_TEST_OK 0100000 /* Self test OK */ +#define ST_NO_MODEM 0140000 /* No modem card on port */ +#define ST_BAD_KEY 0135320 /* Bad port key = 0xBAD0 */ + + +/* Bit flags */ + +#define RS_OVERFLOW 0040000 /* Receive status: buffer overflow occurred */ +#define RS_PARTIAL 0020000 /* Receive status: buffer is partial */ +#define RS_ETC_RS 0014000 /* Receive status: terminated by RS */ +#define RS_ETC_DC2 0010000 /* Receive status: terminated by DC2 */ +#define RS_ETC_CR 0004000 /* Receive status: terminated by CR */ +#define RS_ETC_EOT 0000000 /* Receive status: terminated by EOT */ +#define RS_CHAR_COUNT 0003777 /* Receive status: character count */ + +#define WR_NO_ENQACK 0020000 /* Write: no ENQ/ACK this xfer */ +#define WR_ADD_CRLF 0010000 /* Write: add CR/LF if not '_' */ +#define WR_PARTIAL 0004000 /* Write: write is partial */ +#define WR_LENGTH 0003777 /* Write: write length in bytes */ + +#define RT_END_ON_CR 0000200 /* Receive type: end xfer on CR */ +#define RT_END_ON_RS 0000100 /* Receive type: end xfer on RS */ +#define RT_END_ON_EOT 0000040 /* Receive type: end xfer on EOT */ +#define RT_END_ON_DC2 0000020 /* Receive type: end xfer on DC2 */ +#define RT_END_ON_CNT 0000010 /* Receive type: end xfer on count */ +#define RT_END_ON_CHAR 0000004 /* Receive type: end xfer on character */ +#define RT_ENAB_EDIT 0000002 /* Receive type: enable input editing */ +#define RT_ENAB_ECHO 0000001 /* Receive type: enable input echoing */ + +#define FC_FORCE_XON 0000002 /* Flow control: force XON */ +#define FC_XONXOFF 0000001 /* Flow control: enable XON/XOFF */ + +#define CL_GUARD 0000040 /* Connect line: guard tone off or on */ +#define CL_STANDARD 0000020 /* Connect line: standard 212 or V.22 */ +#define CL_BITS 0000010 /* Connect line: bits 10 or 9 */ +#define CL_MODE 0000004 /* Connect line: mode originate or answer */ +#define CL_DIAL 0000002 /* Connect line: dial manual or automatic */ +#define CL_SPEED 0000001 /* Connect line: speed low or high */ + +#define DL_AUTO_ANSWER 0000001 /* Disconnect line: auto-answer enable or disable */ + +#define LB_SPEED 0000004 /* Loopback test: speed low or high */ +#define LB_MODE 0000002 /* Loopback test: mode analog or digital */ +#define LB_TEST 0000001 /* Loopback test: test disable or enable */ + +#define GS_NO_SYSMDM 0000200 /* Get status: systems modem present or absent */ +#define GS_SYSMDM_TO 0000100 /* Get status: systems modem OK or timed out */ +#define GS_NO_MODEM 0000040 /* Get status: modem present or absent */ +#define GS_SPEED 0000020 /* Get status: speed low or high */ +#define GS_LINE 0000001 /* Get status: line disconnected or connected */ + + +/* Bit fields (name, starting bit, bit width) */ + +BITFIELD (CMD_OPCODE, 8, 8) /* Command: opcode */ +BITFIELD (CMD_KEY, 0, 8) /* Command: key */ + +BITFIELD (SK_BPC, 14, 2) /* Set key: bits per character */ +BITFIELD (SK_MODEM, 13, 1) /* Set key: hardwired or modem */ +BITFIELD (SK_BRG, 12, 1) /* Set key: baud rate generator 0/1 */ +BITFIELD (SK_STOPBITS, 10, 2) /* Set key: stop bits */ +BITFIELD (SK_PARITY, 8, 2) /* Set key: parity select */ +BITFIELD (SK_ENQACK, 7, 1) /* Set key: disable or enable ENQ/ACK */ +BITFIELD (SK_BAUDRATE, 3, 4) /* Set key: port baud rate */ +BITFIELD (SK_PORT, 0, 3) /* Set key: port number */ + +BITFIELD (FL_ALERT, 11, 1) /* Port flags: alert for terminate recv buffer */ +BITFIELD (FL_XOFF, 10, 1) /* Port flags: XOFF stopped transmission */ +BITFIELD (FL_BREAK, 9, 1) /* Port flags: UI / break detected */ +BITFIELD (FL_HAVEBUF, 8, 1) /* Port flags: UI / read buffer available */ +BITFIELD (FL_WANTBUF, 7, 1) /* Port flags: UI / write buffer available */ +BITFIELD (FL_RDOVFLOW, 6, 1) /* Port flags: read buffers overflowed */ +BITFIELD (FL_RDFILL, 5, 1) /* Port flags: read buffer is filling */ +BITFIELD (FL_RDEMPT, 4, 1) /* Port flags: read buffer is emptying */ +BITFIELD (FL_WRFILL, 3, 1) /* Port flags: write buffer is filling */ +BITFIELD (FL_WREMPT, 2, 1) /* Port flags: write buffer is emptying */ +BITFIELD (FL_WAITACK, 1, 1) /* Port flags: ENQ sent, waiting for ACK */ +BITFIELD (FL_DO_ENQACK, 0, 1) /* Port flags: do ENQ/ACK handshake */ + +#define SK_BRG_1 SK_BRG +#define SK_BRG_0 0 + +#define FL_RDFLAGS (FL_RDEMPT | FL_RDFILL | FL_RDOVFLOW) +#define FL_WRFLAGS (FL_WREMPT | FL_WRFILL) +#define FL_UI_PENDING (FL_WANTBUF | FL_HAVEBUF | FL_BREAK) + +#define ACK_LIMIT 1000 /* poll timeout for ACK response */ +#define ENQ_LIMIT 80 /* output chars before ENQ */ + + +/* Packed field values */ + +#define SK_BPC_5 (0 << SK_BPC_V) +#define SK_BPC_6 (1 << SK_BPC_V) +#define SK_BPC_7 (2 << SK_BPC_V) +#define SK_BPC_8 (3 << SK_BPC_V) + +#define SK_STOP_1 (1 << SK_STOPBITS_V) +#define SK_STOP_15 (2 << SK_STOPBITS_V) +#define SK_STOP_2 (3 << SK_STOPBITS_V) + +#define SK_BAUD_NOCHG (0 << SK_BAUDRATE_V) +#define SK_BAUD_50 (1 << SK_BAUDRATE_V) +#define SK_BAUD_75 (2 << SK_BAUDRATE_V) +#define SK_BAUD_110 (3 << SK_BAUDRATE_V) +#define SK_BAUD_1345 (4 << SK_BAUDRATE_V) +#define SK_BAUD_150 (5 << SK_BAUDRATE_V) +#define SK_BAUD_300 (6 << SK_BAUDRATE_V) +#define SK_BAUD_1200 (7 << SK_BAUDRATE_V) +#define SK_BAUD_1800 (8 << SK_BAUDRATE_V) +#define SK_BAUD_2400 (9 << SK_BAUDRATE_V) +#define SK_BAUD_4800 (10 << SK_BAUDRATE_V) +#define SK_BAUD_9600 (11 << SK_BAUDRATE_V) +#define SK_BAUD_19200 (12 << SK_BAUDRATE_V) + + +/* Default values */ + +#define SK_PWRUP_0 (SK_BPC_8 | SK_BRG_0 | SK_STOP_1 | SK_BAUD_9600) +#define SK_PWRUP_1 (SK_BPC_8 | SK_BRG_1 | SK_STOP_1 | SK_BAUD_9600) + +#define RT_PWRUP (RT_END_ON_CR | RT_END_ON_CHAR | RT_ENAB_EDIT | RT_ENAB_ECHO) + + +/* Command helpers */ + +#define GET_OPCODE(w) (((w) & CMD_OPCODE) >> CMD_OPCODE_V) +#define GET_KEY(w) (((w) & CMD_KEY) >> CMD_KEY_V) +#define GET_BPC(w) (((w) & SK_BPC) >> SK_BPC_V) +#define GET_BAUDRATE(w) (((w) & SK_BAUDRATE) >> SK_BAUDRATE_V) +#define GET_PORT(w) (((w) & SK_PORT) >> SK_PORT_V) +#define GET_UIREASON(w) (((w) & UI_REASON) >> UI_REASON_V) +#define GET_UIPORT(w) (((w) & UI_PORT) >> UI_PORT_V) + + +/* Multiplexer controller state variables */ + +typedef enum { /* execution state */ + idle, + cmd, + param, + exec + } STATE; + +STATE mpx_state = idle; /* controller state */ + +uint16 mpx_ibuf = 0; /* status/data in */ +uint16 mpx_obuf = 0; /* command/data out */ + +uint32 mpx_cmd = 0; /* current command */ +uint32 mpx_param = 0; /* current parameter */ +uint32 mpx_port = 0; /* current port number for R/W */ +uint32 mpx_portkey = 0; /* current port's key */ + int32 mpx_iolen = 0; /* length of current I/O xfer */ + +t_bool mpx_uien = FALSE; /* unsolicited interrupts enabled */ +uint32 mpx_uicode = 0; /* unsolicited interrupt reason and port */ + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } mpx = { CLEAR, CLEAR, CLEAR }; + +/* Multiplexer per-line state variables */ + +uint8 mpx_key [MPX_PORTS]; /* port keys */ +uint16 mpx_config [MPX_PORTS]; /* port configuration */ +uint16 mpx_rcvtype [MPX_PORTS]; /* receive type */ +uint16 mpx_charcnt [MPX_PORTS]; /* current character count */ +uint16 mpx_termcnt [MPX_PORTS]; /* termination character count */ +uint16 mpx_flowcntl [MPX_PORTS]; /* flow control */ +uint8 mpx_enq_cntr [MPX_PORTS]; /* ENQ character counter */ +uint16 mpx_ack_wait [MPX_PORTS]; /* ACK wait timer */ +uint16 mpx_flags [MPX_PORTS]; /* line state flags */ + +/* Multiplexer buffer selectors */ + +typedef enum { ioread, iowrite } IO_OPER; /* I/O operation */ +typedef enum { get, put } BUF_SELECT; /* buffer selector */ + +static const char *const io_op [] = { "read", /* operation names */ + "write" }; + +static const uint16 buf_size [] = { RD_BUF_SIZE, /* buffer sizes */ + WR_BUF_SIZE }; + +static uint32 emptying_flags [2]; /* buffer emptying flags [IO_OPER] */ +static uint32 filling_flags [2]; /* buffer filling flags [IO_OPER] */ + + +/* Multiplexer per-line buffer variables */ + +typedef uint16 BUF_INDEX [MPX_PORTS] [2]; /* buffer index (read and write) */ + +BUF_INDEX mpx_put; /* read/write buffer add index */ +BUF_INDEX mpx_sep; /* read/write buffer separator index */ +BUF_INDEX mpx_get; /* read/write buffer remove index */ + +uint8 mpx_rbuf [MPX_PORTS] [RD_BUF_SIZE]; /* read buffer */ +uint8 mpx_wbuf [MPX_PORTS] [WR_BUF_SIZE]; /* write buffer */ + + +/* Multiplexer local routines */ + +static t_bool exec_command (void); +static void poll_connection (void); +static void controller_reset (void); +static uint32 service_time (uint16 control_word); +static int32 key_to_port (uint32 key); + +static void buf_init (IO_OPER rw, uint32 port); +static uint8 buf_get (IO_OPER rw, uint32 port); +static void buf_put (IO_OPER rw, uint32 port, uint8 ch); +static void buf_remove (IO_OPER rw, uint32 port); +static void buf_term (IO_OPER rw, uint32 port, uint8 header); +static void buf_free (IO_OPER rw, uint32 port); +static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which); +static uint16 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which); +static uint32 buf_avail (IO_OPER rw, uint32 port); + + +/* Multiplexer global routines */ + +IOHANDLER mpx_io; + +t_stat mpx_line_svc (UNIT *uptr); +t_stat mpx_cntl_svc (UNIT *uptr); +t_stat mpx_poll_svc (UNIT *uptr); +t_stat mpx_reset (DEVICE *dptr); +t_stat mpx_attach (UNIT *uptr, CONST char *cptr); +t_stat mpx_detach (UNIT *uptr); +t_stat mpx_status (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat mpx_set_frev (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, CONST void *desc); + + +/* MPX data structures. + + mpx_order MPX line connection order table + mpx_ldsc MPX terminal multiplexer line descriptors + mpx_desc MPX terminal multiplexer device descriptor + mpx_dib MPX device information block + mpx_unit MPX unit list + mpx_reg MPX register list + mpx_mod MPX modifier list + mpx_deb MPX debug list + mpx_dev MPX device descriptor + + The first eight units correspond to the eight multiplexer line ports. These + handle character I/O via the Telnet library. A ninth unit acts as the card + controller, executing commands and transferring data to and from the I/O + buffers. A tenth unit is responsible for polling for connections and socket + I/O. It also holds the master socket. + + The character I/O service routines run only when there are characters to read + or write. They operate at the approximate baud rates of the terminals (in + CPU instructions per second) in order to be compatible with the OS drivers. + The controller service routine runs only when a command is executing or a + data transfer to or from the CPU is in progress. The Telnet poll must run + continuously, but it may operate much more slowly, as the only requirement is + that it must not present a perceptible lag to human input. To be compatible + with CPU idling, it is co-scheduled with the master poll timer, which uses a + ten millisecond period. + + The controller and poll units are hidden by disabling them, so as to present + a logical picture of the multiplexer to the user. +*/ + +DEVICE mpx_dev; + +int32 mpx_order [MPX_PORTS] = { -1 }; /* connection order */ +TMLN mpx_ldsc [MPX_PORTS] = { { 0 } }; /* line descriptors */ +TMXR mpx_desc = { MPX_PORTS, 0, 0, mpx_ldsc, mpx_order }; /* device descriptor */ + +DIB mpx_dib = { &mpx_io, MPX }; + +UNIT mpx_unit [] = { + { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 0 */ + { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 1 */ + { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 2 */ + { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 3 */ + { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 4 */ + { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 5 */ + { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 6 */ + { UDATA (&mpx_line_svc, UNIT_FASTTIME, 0) }, /* terminal I/O line 7 */ + { UDATA (&mpx_cntl_svc, UNIT_DIS, 0) }, /* controller unit */ + { UDATA (&mpx_poll_svc, UNIT_ATTABLE | UNIT_DIS, POLL_FIRST) } /* Telnet poll unit */ + }; + +REG mpx_reg [] = { + { DRDATA (STATE, mpx_state, 3) }, + { ORDATA (IBUF, mpx_ibuf, 16), REG_FIT }, + { ORDATA (OBUF, mpx_obuf, 16), REG_FIT }, + + { ORDATA (CMD, mpx_cmd, 8) }, + { ORDATA (PARAM, mpx_param, 16) }, + + { DRDATA (PORT, mpx_port, 8), PV_LEFT }, + { DRDATA (PORTKEY, mpx_portkey, 8), PV_LEFT }, + { DRDATA (IOLEN, mpx_iolen, 16), PV_LEFT }, + + { FLDATA (UIEN, mpx_uien, 0) }, + { GRDATA (UIPORT, mpx_uicode, 10, 3, 0) }, + { GRDATA (UICODE, mpx_uicode, 10, 3, UI_REASON_V) }, + + { BRDATA (KEYS, mpx_key, 10, 8, MPX_PORTS) }, + { BRDATA (PCONFIG, mpx_config, 8, 16, MPX_PORTS) }, + { BRDATA (RCVTYPE, mpx_rcvtype, 8, 16, MPX_PORTS) }, + { BRDATA (CHARCNT, mpx_charcnt, 8, 16, MPX_PORTS) }, + { BRDATA (TERMCNT, mpx_termcnt, 8, 16, MPX_PORTS) }, + { BRDATA (FLOWCNTL, mpx_flowcntl, 8, 16, MPX_PORTS) }, + + { BRDATA (ENQCNTR, mpx_enq_cntr, 10, 7, MPX_PORTS) }, + { BRDATA (ACKWAIT, mpx_ack_wait, 10, 10, MPX_PORTS) }, + { BRDATA (PFLAGS, mpx_flags, 2, 12, MPX_PORTS) }, + + { BRDATA (RBUF, mpx_rbuf, 8, 8, MPX_PORTS * RD_BUF_SIZE) }, + { BRDATA (WBUF, mpx_wbuf, 8, 8, MPX_PORTS * WR_BUF_SIZE) }, + + { BRDATA (GET, mpx_get, 10, 10, MPX_PORTS * 2) }, + { BRDATA (SEP, mpx_sep, 10, 10, MPX_PORTS * 2) }, + { BRDATA (PUT, mpx_put, 10, 10, MPX_PORTS * 2) }, + + { FLDATA (CTL, mpx.control, 0) }, + { FLDATA (FLG, mpx.flag, 0) }, + { FLDATA (FBF, mpx.flagbuf, 0) }, + { ORDATA (SC, mpx_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, mpx_dib.select_code, 6), REG_HRO }, + + { BRDATA (CONNORD, mpx_order, 10, 32, MPX_PORTS), REG_HRO }, + { NULL } + }; + +MTAB mpx_mod [] = { + { UNIT_FASTTIME, UNIT_FASTTIME, "fast timing", "FASTTIME", NULL, NULL, NULL }, + { UNIT_FASTTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL }, + + { UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL }, + { UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL }, + + { MTAB_XTD | MTAB_VDV, 0, "REV", NULL, &mpx_set_frev, &mpx_show_frev, NULL }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LINEORDER", "LINEORDER", &tmxr_set_lnorder, &tmxr_show_lnorder, &mpx_desc }, + + { MTAB_XTD | MTAB_VUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, &mpx_desc }, + { MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, &mpx_desc }, + + { MTAB_XTD | MTAB_VDV, 0, "", NULL, NULL, &mpx_status, &mpx_desc }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, &mpx_desc }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, &mpx_desc }, + { MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT", &tmxr_dscln, NULL, &mpx_desc }, + { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &mpx_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &mpx_dev }, + + { 0 } + }; + +DEBTAB mpx_deb [] = { + { "CMDS", DEB_CMDS }, + { "CPU", DEB_CPU }, + { "BUF", DEB_BUF }, + { "XFER", DEB_XFER }, + { NULL, 0 } + }; + +DEVICE mpx_dev = { + "MPX", /* device name */ + mpx_unit, /* unit array */ + mpx_reg, /* register array */ + mpx_mod, /* modifier array */ + MPX_PORTS + MPX_CNTLS, /* number of units */ + 10, /* address radix */ + 31, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 8, /* data width */ + &tmxr_ex, /* examine routine */ + &tmxr_dep, /* deposit routine */ + &mpx_reset, /* reset routine */ + NULL, /* boot routine */ + &mpx_attach, /* attach routine */ + &mpx_detach, /* detach routine */ + &mpx_dib, /* device information block */ + DEV_DEBUG | DEV_DISABLE | DEV_MUX, /* device flags */ + 0, /* debug control flags */ + mpx_deb, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL, /* logical device name */ + NULL, /* help routine */ + NULL, /* help attach routine*/ + (void *) &mpx_desc /* help context */ + }; + + +/* I/O signal handler. + + Commands are sent to the card via an OTA/B. Issuing an STC SC,C causes the + mux to accept the word (STC causes a NMI on the card). If the command uses + one word, command execution will commence, and the flag will set on + completion. If the command uses two words, the flag will be set, indicating + that the second word should be output via an OTA/B. Command execution will + commence upon receipt, and the flag will set on completion. + + When the flag sets for command completion, status or data may be read from + the card via an LIA/B. If additional status or data words are expected, the + flag will set when they are available. + + A command consists of an opcode in the high byte, and a port key or command + parameter in the low byte. Undefined commands are treated as NOPs. + + The card firmware executes commands as part of a twelve-event round-robin + scheduling poll. The card NMI service routine simply sets a flag that is + interrogated during polling. The poll sequence is advanced after each + command. This implies that successive commands incur a delay of at least one + poll-loop's execution time. On an otherwise quiescent card, this delay is + approximately 460 Z80 instructions, or about 950 usec. The average command + initiation time is half of that, or roughly 425 usec. + + If a detected command requires a second word, the card sits in a tight loop, + waiting for the OTx that indicates that the parameter is available. Command + initiation from parameter receipt is about 25 usec. + + For reads and writes to card buffers, the on-board DMA controller is used. + The CPU uses DCPC to handle the transfer, but the data transfer time is + limited by the Z80 DMA, which can process a word in about 1.25 usec. + + For most cards, the hardware POPIO signal sets the flag buffer and flag + flip-flops, while CRS clears the control flip-flop. For this card, the + control and flags are cleared together by CRS, and POPIO is not used. + + Implementation notes: + + 1. "Enable unsolicited input" is the only command that does not set the + device flag upon completion. Therefore, the CPU has no way of knowing + when the command has completed. Because the command in the input latch + is recorded in the NMI handler, but actual execution only begins when the + scheduler polls for the command indication, it is possible for another + command to be sent to the card before the "Enable unsolicited input" + command is recognized. In this case, the second command overwrites the + first and is executed by the scheduler poll. Under simulation, this + condition occurs when the OTx and STC processors are entered with + mpx_state = cmd. + + 2. The "Fast binary read" command inhibits all other commands until the card + is reset. +*/ + +uint32 mpx_io (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +static const char *output_state [] = { "Command", "Command override", "Parameter", "Data" }; +static const char *input_state [] = { "Status", "Invalid status", "Parameter", "Data" }; +const char *hold_or_clear = (signal_set & ioCLF ? ",C" : ""); +int32 delay; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + mpx.flag = mpx.flagbuf = CLEAR; /* clear flag and flag buffer */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fputs (">>MPX cmds: [CLF] Flag cleared\n", sim_deb); + break; + + + case ioSTF: /* set flag flip-flop */ + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fputs (">>MPX cmds: [STF] Flag set\n", sim_deb); + /* fall into ENF */ + + case ioENF: /* enable flag */ + mpx.flag = mpx.flagbuf = SET; /* set flag and flag buffer */ + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (mpx); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (mpx); + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, mpx_ibuf); /* return info */ + + if (DEBUG_PRI (mpx_dev, DEB_CPU)) + fprintf (sim_deb, ">>MPX cpu: [LIx%s] %s = %06o\n", + hold_or_clear, input_state [mpx_state], mpx_ibuf); + + if (mpx_state == exec) /* if this is input data word */ + sim_activate (&mpx_cntl, DATA_DELAY); /* continue transmission */ + break; + + + case ioIOO: /* I/O data output */ + mpx_obuf = IODATA (stat_data); /* save word */ + + if (DEBUG_PRI (mpx_dev, DEB_CPU)) + fprintf (sim_deb, ">>MPX cpu: [OTx%s] %s = %06o\n", + hold_or_clear, output_state [mpx_state], mpx_obuf); + + if (mpx_state == param) { /* if this is parameter word */ + sim_activate (&mpx_cntl, CMD_DELAY); /* do command now */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: [OTx%s] Command %03o parameter %06o scheduled, " + "time = %d\n", hold_or_clear, mpx_cmd, mpx_obuf, CMD_DELAY); + } + + else if (mpx_state == exec) /* else if this is output data word */ + sim_activate (&mpx_cntl, DATA_DELAY); /* then do transmission */ + break; + + + case ioCRS: /* control reset */ + controller_reset (); /* reset firmware to power-on defaults */ + mpx_obuf = 0; /* clear output buffer */ + + mpx.control = CLEAR; /* clear control */ + mpx.flagbuf = CLEAR; /* clear flag buffer */ + mpx.flag = CLEAR; /* clear flag */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fputs (">>MPX cmds: [CRS] Controller reset\n", sim_deb); + break; + + + case ioCLC: /* clear control flip-flop */ + mpx.control = CLEAR; /* clear control */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: [CLC%s] Control cleared\n", hold_or_clear); + break; + + + case ioSTC: /* set control flip-flop */ + mpx.control = SET; /* set control */ + + if (mpx_cmd == CMD_BINARY_READ) /* executing fast binary read? */ + break; /* further command execution inhibited */ + + mpx_cmd = GET_OPCODE (mpx_obuf); /* get command opcode */ + mpx_portkey = GET_KEY (mpx_obuf); /* get port key */ + + if (mpx_state == cmd) /* already scheduled? */ + sim_cancel (&mpx_cntl); /* cancel to get full delay */ + + mpx_state = cmd; /* set command state */ + + if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */ + delay = PARAM_DELAY; /* specify parameter wait */ + else /* one-word command */ + delay = CMD_DELAY; /* specify command wait */ + + sim_activate (&mpx_cntl, delay); /* schedule command */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: [STC%s] Command %03o key %d scheduled, " + "time = %d\n", hold_or_clear, mpx_cmd, mpx_portkey, delay); + break; + + + case ioEDT: /* end data transfer */ + if (DEBUG_PRI (mpx_dev, DEB_CPU)) + fputs (">>MPX cpu: [EDT] DCPC transfer ended\n", sim_deb); + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (mpx); /* set standard PRL signal */ + setstdIRQ (mpx); /* set standard IRQ signal */ + setstdSRQ (mpx); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + mpx.flagbuf = CLEAR; /* clear flag buffer */ + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Command executor. + + We are called by the controller service routine to process one- and two-word + commands. For two-word commands, the parameter word is present in mpx_param. + The return value indicates whether the card flag should be set upon + completion. + + Most commands execute and complete directly. The read and write commands, + however, transition to the execution state to simulate the DMA transfer, and + the "Download executable" command does the same to receive the download from + the CPU. + + Several commands were added for the B firmware revision, and the various + revisions of the RTE drivers sent some commands that were never implemented + in the mux firmware. The command protocol treated unknown commands as NOPs, + meaning that the command (and parameter, if it was a two-word command) was + absorbed and the card flag was set as though the command completed normally. + This allowed interoperability between firmware and driver revisions. + + Commands that refer to ports do so indirectly by passing a port key, rather + than a port number. The key-to-port translation is established by the "Set + port key" command. If a key is not found in the table, the command is not + executed, and the status return is ST_BAD_KEY, which in hex is "BAD0". + + Implementation notes: + + 1. The "Reset to power-on defaults" command causes the firmware to disable + interrupts and jump to the power-on initialization routine, exactly as + though the Z80 had received a hardware reset. + + 2. The "Abort DMA transfer" command works because STC causes NMI, so the + command is executed even in the middle of a DMA transfer. The OTx of the + command will be sent to the buffer if a "Write data to buffer" command is + in progress, but the STC will cause this routine to be called, which will + cancel the buffer and return the controller to the idle state. Note that + this command might be sent with no transfer in progress, in which case + nothing is done. + + 3. In response to an "Enable unsolicited interrupts" command, the controller + service is scheduled to check for a pending UI. If one is found, the + first UI status word is placed in the input buffer, and an interrupt is + generated by setting the flag. This causes entry to the driver, which + issues an "Acknowledge" command to obtain the second status word. + + It is possible, however, for the interrupt to be ignored. For example, + the driver may be waiting for a "write buffer available" UI when it is + called to begin a write to a different port. If the flag is set by + the UI after RTE has been entered, the interrupt will be held off, and + the STC sc,C instruction that begins the command sequence will clear the + flag, removing the interrupt entirely. In this case, the controller will + reissue the UI when the next "Enable unsolicited interrupts" command is + sent. + + Note that the firmware reissues the same UI, rather than recomputing UIs + and potentially selecting a different one of higher priority. + + 4. The "Fast binary read" command apparently was intended to facilitate + booting from a 264x tape drive, although no boot loader ROM for the + multiplexer was ever released. It sends the fast binary read escape + sequence (ESC e) to the terminal and then packs each pair of characters + received into a word and sends it to the CPU, accompanied by the device + flag. + + The multiplexer firmware disables interrupts and then manipulates the SIO + for port 0 directly. Significantly, it does no interpretation of the + incoming data and sits in an endless I/O loop, so the only way to exit + the command is to reset the card with a CRS (front panel PRESET or CLC 0 + instruction execution). Sending a command will not work; although the + NMI will interrupt the fast binary read, the NMI handler simply sets a + flag that is tested by the scheduler poll. Because the processor is in + an endless loop, control never returns to the scheduler, so the command + is never seen. + + 5. The "Terminate active receive buffer" behavior is a bit tricky. If the + read buffer has characters, the buffer is terminated as though a + "terminate on count" condition occurred. If the buffer is empty, + however, a "terminate on count = 1" condition is established. When a + character is received, the buffer is terminated, and the buffer + termination count is reset to 254. +*/ + +static t_bool exec_command (void) +{ +int32 port; +uint32 svc_time; +t_bool set_flag = TRUE; /* flag is normally set on completion */ +STATE next_state = idle; /* command normally executes to completion */ + +mpx_ibuf = ST_OK; /* return status is normally OK */ + +switch (mpx_cmd) { + + case CMD_NOP: /* no operation */ + break; /* just ignore */ + + + case CMD_RESET: /* reset firmware */ + controller_reset (); /* reset program variables */ + mpx_ibuf = ST_TEST_OK; /* return self-test OK code */ + break; + + + case CMD_ENABLE_UI: + mpx_uien = TRUE; /* enable unsolicited interrupts */ + sim_activate (&mpx_cntl, CMD_DELAY); /* and schedule controller for UI check */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: Controller status check scheduled, " + "time = %d\n", CMD_DELAY); + + set_flag = FALSE; /* do not set the flag at completion */ + break; + + + case CMD_DISABLE: + switch (mpx_portkey) { + case SUBCMD_UI: + mpx_uien = FALSE; /* disable unsolicited interrupts */ + break; + + case SUBCMD_DMA: + if (mpx_flags [mpx_port] & FL_WRFILL) /* write buffer xfer in progress? */ + buf_cancel (iowrite, mpx_port, put); /* cancel it */ + else if (mpx_flags [mpx_port] & FL_RDEMPT) /* read buffer xfer in progress? */ + buf_cancel (ioread, mpx_port, get); /* cancel it */ + break; + } + break; + + + case CMD_ACK: /* acknowledge unsolicited interrupt */ + switch (mpx_uicode & UI_REASON) { + + case UI_WRBUF_AVAIL: /* write buffer notification */ + mpx_flags [mpx_port] &= ~FL_WANTBUF; /* clear flag */ + mpx_ibuf = WR_BUF_LIMIT; /* report write buffer available */ + break; + + case UI_RDBUF_AVAIL: /* read buffer notification */ + mpx_flags [mpx_port] &= ~FL_HAVEBUF; /* clear flag */ + + mpx_ibuf = (uint16) (buf_get (ioread, mpx_port) << 8 | /* get header value and position */ + buf_len (ioread, mpx_port, get)); /* and include buffer length */ + + if (mpx_flags [mpx_port] & FL_RDOVFLOW) { /* did a buffer overflow? */ + mpx_ibuf = mpx_ibuf | RS_OVERFLOW; /* report it */ + mpx_flags [mpx_port] &= ~FL_RDOVFLOW; /* clear overflow flag */ + } + break; + + case UI_BRK_RECD: /* break received */ + mpx_flags [mpx_port] &= ~FL_BREAK; /* clear flag */ + mpx_ibuf = 0; /* 2nd word is zero */ + break; + } + + mpx_uicode = 0; /* clear notification code */ + break; + + + case CMD_CANCEL: /* cancel first read buffer */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) { /* port defined? */ + buf_cancel (ioread, port, get); /* cancel get buffer */ + + if (buf_avail (ioread, port) == 2) /* if all buffers are now clear */ + mpx_charcnt [port] = 0; /* then clear the current character count */ + + else if (!(mpx_flags [port] & FL_RDFILL)) /* otherwise if the other buffer is not filling */ + mpx_flags [port] |= FL_HAVEBUF; /* then indicate buffer availability */ + } + break; + + + case CMD_CANCEL_ALL: /* cancel all read buffers */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) { /* port defined? */ + buf_init (ioread, port); /* reinitialize read buffers */ + mpx_charcnt [port] = 0; /* and clear the current character count */ + } + break; + + + case CMD_BINARY_READ: /* fast binary read */ + for (port = 0; port < MPX_PORTS; port++) + sim_cancel (&mpx_unit [port]); /* cancel I/O on all lines */ + + mpx_flags [0] = 0; /* clear port 0 state flags */ + mpx_enq_cntr [0] = 0; /* clear port 0 ENQ counter */ + mpx_ack_wait [0] = 0; /* clear port 0 ACK wait timer */ + + tmxr_putc_ln (&mpx_ldsc [0], ESC); /* send fast binary read */ + tmxr_putc_ln (&mpx_ldsc [0], 'e'); /* escape sequence to port 0 */ + tmxr_poll_tx (&mpx_desc); /* flush output */ + + next_state = exec; /* set execution state */ + break; + + + case CMD_REQ_WRITE: /* request write buffer */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) /* port defined? */ + if (buf_avail (iowrite, port) > 0) /* is a buffer available? */ + mpx_ibuf = WR_BUF_LIMIT; /* report write buffer limit */ + + else { + mpx_ibuf = 0; /* report none available */ + mpx_flags [port] |= FL_WANTBUF; /* set buffer request */ + } + break; + + + case CMD_WRITE: /* write to buffer */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) { /* port defined? */ + mpx_port = port; /* save port number */ + mpx_iolen = mpx_param & WR_LENGTH; /* save request length */ + next_state = exec; /* set execution state */ + } + break; + + + case CMD_SET_KEY: /* set port key and configuration */ + port = GET_PORT (mpx_param); /* get target port number */ + mpx_key [port] = (uint8) mpx_portkey; /* set port key */ + mpx_config [port] = (uint16) mpx_param; /* set port configuration word */ + + svc_time = service_time (mpx_config [port]); /* get service time for baud rate */ + + if (svc_time) /* want to change? */ + mpx_unit [port].wait = svc_time; /* set service time */ + + mpx_ibuf = MPX_DATE_CODE; /* return firmware date code */ + break; + + + case CMD_SET_RCV: /* set receive type */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) /* port defined? */ + mpx_rcvtype [port] = (uint16) mpx_param; /* save port receive type */ + break; + + + case CMD_SET_COUNT: /* set character count */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) { /* port defined? */ + mpx_termcnt [port] = (uint16) mpx_param; /* save port termination character count */ + mpx_charcnt [port] = 0; /* and clear the current character count */ + } + break; + + + case CMD_SET_FLOW: /* set flow control */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) { /* port defined? */ + mpx_flowcntl [port] = mpx_param & FC_XONXOFF; /* save port flow control */ + + if (mpx_param & FC_FORCE_XON) /* force XON? */ + mpx_flags [port] &= ~FL_XOFF; /* resume transmission if suspended */ + } + break; + + + case CMD_READ: /* read from buffer */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) { /* port defined? */ + mpx_port = port; /* save port number */ + mpx_iolen = mpx_param; /* save request length */ + + sim_activate (&mpx_cntl, DATA_DELAY); /* schedule the transfer */ + next_state = exec; /* set execution state */ + set_flag = FALSE; /* no flag until word ready */ + } + break; + + + case CMD_DL_EXEC: /* Download executable */ + mpx_iolen = mpx_param; /* save request length */ + next_state = exec; /* set execution state */ + break; + + + case CMD_CN_LINE: /* connect modem line */ + case CMD_DC_LINE: /* disconnect modem line */ + case CMD_LOOPBACK: /* enable/disable modem loopback */ + mpx_ibuf = ST_NO_MODEM; /* report "no modem installed" */ + break; + + + case CMD_GET_STATUS: /* get modem status */ + mpx_ibuf = ST_NO_SYSMDM; /* report "no systems modem card" */ + break; + + + case CMD_TERM_BUF: /* terminate active receive buffer */ + port = key_to_port (mpx_portkey); /* get port */ + + if (port >= 0) /* port defined? */ + if (buf_len (ioread, port, put) > 0) { /* any chars in buffer? */ + buf_term (ioread, port, 0); /* terminate buffer and set header */ + mpx_charcnt [port] = 0; /* then clear the current character count */ + + if (buf_avail (ioread, port) == 1) /* first read buffer? */ + mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */ + } + + else { /* buffer is empty */ + mpx_termcnt [port] = 1; /* set to terminate on one char */ + mpx_flags [port] |= FL_ALERT; /* set alert flag */ + } + break; + + + case CMD_VCP_PUT: /* VCP put byte */ + case CMD_VCP_PUT_BUF: /* VCP put buffer */ + case CMD_VCP_GET: /* VCP get byte */ + case CMD_VCP_GET_BUF: /* VCP get buffer */ + case CMD_VCP_EXIT: /* Exit VCP mode */ + case CMD_VCP_ENTER: /* Enter VCP mode */ + + default: /* unknown command */ + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: Unknown command %03o ignored\n", mpx_cmd); + } + +mpx_state = next_state; +return set_flag; +} + + +/* Multiplexer controller service. + + The controller service handles commands and data transfers to and from the + CPU. The delay in scheduling the controller service represents the firmware + command or data execution time. The controller may be in one of four states + upon entry: idle, first word of command received (cmd), command parameter + received (param), or data transfer (exec). + + Entry in the command state causes execution of one-word commands and + solicitation of command parameters for two-word commands, which are executed + when entering in the parameter state. + + Entry in the data transfer state moves one word between the CPU and a read or + write buffer. For writes, the write buffer is filled with words from the + CPU. Once the indicated number of words have been transferred, the + appropriate line service is scheduled to send the characters. For reads, + characters are unloaded from the read buffer to the CPU; an odd-length + transfer is padded with a blank. A read of fewer characters than are present + in the buffer will return the remaining characters when the next read is + performed. + + Each read or write is terminated by the CPU sending one additional word (the + RTE drivers send -1). The command completes when this word is acknowledged + by the card setting the device flag. For zero-length writes, this additional + word will be the only word sent. + + Data transfer is also used by the "Download executable" command to absorb the + downloaded program. The firmware jumps to location 5100 hex in the + downloaded program upon completion of reception. It is the responsibility of + the program to return to the multiplexer firmware and to return to the CPU + whatever status is appropriate when it is done. Under simulation, we simply + "sink" the program and return status compatible with the multiplexer + diagnostic program to simulate a passing test. + + Entry in the idle state checks for unsolicited interrupts. UIs are sent to + the host when the controller is idle, UIs have been enabled, and a UI + condition exists. If a UI is not acknowledged, it will remain pending and + will be reissued the next time the controller is idle and UIs have been + enabled. + + UI conditions are kept in the per-port flags. The UI conditions are write + buffer available, read buffer available, break received, modem line + connected, and modem line disconnected. The latter two conditions are not + implemented in this simulation. If a break condition occurs at the same time + as a read buffer completion, the break has priority; the buffer UI will occur + after the break UI is acknowledged. + + The firmware checks for UI condition flags as part of the scheduler polling + loop. Under simulation, though, UIs can occur only in two places: the point + of origin (e.g., termination of a read buffer), or the "Enable unsolicited + input" command executor. UIs will be generated at the point of origin only + if the simulator is idle. If the simulator is not idle, it is assumed that + UIs have been disabled to execute the current command and will be reenabled + when the command sequence is complete. + + When the multiplexer is reset, and before the port keys are set, all ports + enter "echoplex" mode. In this mode, characters received are echoed back as + a functional test. Each port terminates buffers on CR reception. We detect + this condition, cancel the buffer, and discard the buffer termination UI. + + Implementation notes: + + 1. The firmware transfers the full amount requested by the CPU, even if the + transfer is longer than the buffer. Also, zero-length transfers program + the card DMA chip to transfer 0 bytes; this results in a transfer of 217 + bytes, per the Zilog databook. Under simulation, writes beyond the + buffer are accepted from the CPU but discarded, and reads beyond the + buffer return blanks. + + 2. We should never return from this routine in the "cmd" state, so debugging + will report "internal error!" if we do. +*/ + +t_stat mpx_cntl_svc (UNIT *uptr) +{ +uint8 ch; +uint32 i; +t_bool add_crlf; +t_bool set_flag = TRUE; +STATE last_state = mpx_state; + +static const char *cmd_state [] = { "complete", "internal error!", "waiting for parameter", "executing" }; + + +switch (mpx_state) { /* dispatch on current state */ + + case idle: /* controller idle */ + set_flag = FALSE; /* assume no UI */ + + if (mpx_uicode) { /* unacknowledged UI? */ + if (mpx_uien == TRUE) { /* interrupts enabled? */ + mpx_port = GET_UIPORT (mpx_uicode); /* get port number */ + mpx_portkey = mpx_key [mpx_port]; /* get port key */ + mpx_ibuf = (uint16) (mpx_uicode & UI_REASON | mpx_portkey); /* report UI reason and port key */ + set_flag = TRUE; /* reissue host interrupt */ + mpx_uien = FALSE; /* disable UI */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: Port %d key %d unsolicited interrupt reissued, " + "reason = %d\n", mpx_port, mpx_portkey, GET_UIREASON (mpx_uicode)); + } + } + + else { /* no unacknowledged UI */ + for (i = 0; i < MPX_PORTS; i++) { /* check all ports for UIs */ + if (mpx_flags [i] & FL_UI_PENDING) { /* pending UI? */ + mpx_portkey = mpx_key [i]; /* get port key */ + + if (mpx_portkey == KEY_DEFAULT) { /* key defined? */ + if (mpx_flags [i] & FL_HAVEBUF) /* no, is this read buffer avail? */ + buf_cancel (ioread, i, get); /* cancel buffer */ + + mpx_flags [i] &= ~FL_UI_PENDING; /* cancel pending UI */ + } + + else if (mpx_uien == TRUE) { /* interrupts enabled? */ + if ((mpx_flags [i] & FL_WANTBUF) && /* port wants a write buffer? */ + (buf_avail (iowrite, i) > 0)) /* and one is available? */ + mpx_uicode = UI_WRBUF_AVAIL; /* set UI reason */ + + else if (mpx_flags [i] & FL_BREAK) /* received a line BREAK? */ + mpx_uicode = UI_BRK_RECD; /* set UI reason */ + + else if (mpx_flags [i] & FL_HAVEBUF) /* have a read buffer ready? */ + mpx_uicode = UI_RDBUF_AVAIL; /* set UI reason */ + + if (mpx_uicode) { /* UI to send? */ + mpx_port = i; /* set port number for Acknowledge */ + mpx_ibuf = (uint16) (mpx_uicode | mpx_portkey); /* merge UI reason and port key */ + mpx_uicode = mpx_uicode | mpx_port; /* save UI reason and port */ + set_flag = TRUE; /* interrupt host */ + mpx_uien = FALSE; /* disable UI */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: Port %d key %d unsolicited interrupt generated, " + "reason = %d\n", i, mpx_portkey, GET_UIREASON (mpx_uicode)); + + break; /* quit after first UI */ + } + } + } + } + } + break; + + + case cmd: /* command state */ + if (mpx_cmd & CMD_TWO_WORDS) /* two-word command? */ + mpx_state = param; /* look for parameter before executing */ + else + set_flag = exec_command (); /* execute one-word command */ + break; + + + case param: /* parameter get state */ + mpx_param = mpx_obuf; /* save parameter */ + set_flag = exec_command (); /* execute two-word command */ + break; + + + case exec: /* execution state */ + switch (mpx_cmd) { + + case CMD_BINARY_READ: /* fast binary read */ + mpx_flags [0] &= ~FL_HAVEBUF; /* data word was picked up by CPU */ + set_flag = FALSE; /* suppress device flag */ + break; + + + case CMD_WRITE: /* transfer data to buffer */ + if (mpx_iolen <= 0) { /* last (or only) entry? */ + mpx_state = idle; /* idle controller */ + + if (mpx_iolen < 0) /* tie-off for buffer complete? */ + break; /* we're done */ + } + + add_crlf = ((mpx_param & /* CRLF should be added */ + (WR_ADD_CRLF | WR_PARTIAL)) == WR_ADD_CRLF); + + for (i = 0; i < 2; i++) /* output one or two chars */ + if (mpx_iolen > 0) { /* more to do? */ + if (i) /* high or low byte? */ + ch = (uint8) (mpx_obuf & 0377); /* low byte */ + else + ch = mpx_obuf >> 8; /* high byte */ + + if ((mpx_iolen == 1) && /* final char? */ + (ch == '_') && add_crlf) { /* underscore and asking for CRLF? */ + + add_crlf = FALSE; /* suppress CRLF */ + + if (DEBUG_PRI (mpx_dev, DEB_BUF)) + fprintf (sim_deb, ">>MPX buf: Port %d character '_' " + "suppressed CR/LF\n", mpx_port); + } + + else if (buf_len (iowrite, mpx_port, put) < WR_BUF_LIMIT) + buf_put (iowrite, mpx_port, ch); /* add char to buffer if space avail */ + + mpx_iolen = mpx_iolen - 1; /* drop remaining count */ + } + + if (mpx_iolen == 0) { /* buffer done? */ + if (add_crlf) { /* want CRLF? */ + buf_put (iowrite, mpx_port, CR); /* add CR to buffer */ + buf_put (iowrite, mpx_port, LF); /* add LF to buffer */ + } + + buf_term (iowrite, mpx_port, (uint8) (mpx_param >> 8)); /* terminate buffer */ + mpx_iolen = -1; /* mark as done */ + } + + if (DEBUG_PRI (mpx_dev, DEB_CMDS) && + (sim_is_active (&mpx_unit [mpx_port]) == 0)) + fprintf (sim_deb, ">>MPX cmds: Port %d service scheduled, " + "time = %d\n", mpx_port, mpx_unit [mpx_port].wait); + + sim_activate (&mpx_unit [mpx_port], /* start line service */ + mpx_unit [mpx_port].wait); + break; + + + case CMD_READ: /* transfer data from buffer */ + if (mpx_iolen < 0) { /* input complete? */ + if (mpx_obuf == 0177777) { /* "tie-off" word received? */ + if (buf_len (ioread, mpx_port, get) == 0) { /* buffer now empty? */ + buf_free (ioread, mpx_port); /* free buffer */ + + if ((buf_avail (ioread, mpx_port) == 1) && /* one buffer remaining? */ + !(mpx_flags [mpx_port] & FL_RDFILL)) /* and not filling it? */ + mpx_flags [mpx_port] |= FL_HAVEBUF; /* indicate buffer availability */ + } + + mpx_state = idle; /* idle controller */ + } + + else + set_flag = FALSE; /* ignore word */ + + break; + } + + for (i = 0; i < 2; i++) /* input one or two chars */ + if (mpx_iolen > 0) { /* more to transfer? */ + if (buf_len (ioread, mpx_port, get) > 0) /* more chars available? */ + ch = buf_get (ioread, mpx_port); /* get char from buffer */ + else /* buffer exhausted */ + ch = ' '; /* pad with blank */ + + if (i) /* high or low byte? */ + mpx_ibuf = mpx_ibuf | ch; /* low byte */ + else + mpx_ibuf = (uint16) (ch << 8); /* high byte */ + + mpx_iolen = mpx_iolen - 1; /* drop count */ + } + + else /* odd number of chars */ + mpx_ibuf = mpx_ibuf | ' '; /* pad last with blank */ + + if (mpx_iolen == 0) /* end of host xfer? */ + mpx_iolen = -1; /* mark as done */ + + break; + + + case CMD_DL_EXEC: /* sink data from host */ + if (mpx_iolen <= 0) { /* final entry? */ + mpx_state = idle; /* idle controller */ + mpx_ibuf = ST_DIAG_OK; /* return diag passed status */ + } + + else { + if (mpx_iolen > 0) /* more from host? */ + mpx_iolen = mpx_iolen - 2; /* sink two bytes */ + + if (mpx_iolen <= 0) /* finished download? */ + sim_activate (&mpx_cntl, CMD_DELAY); /* schedule completion */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: Download completion scheduled, " + "time = %d\n", CMD_DELAY); + } + + break; + + + default: /* no other entries allowed */ + return SCPE_IERR; /* simulator error! */ + } + break; + } + + +if (DEBUG_PRI (mpx_dev, DEB_CMDS) && /* debug print? */ + (last_state != mpx_state)) { /* and state change? */ + fprintf (sim_deb, ">>MPX cmds: Command %03o ", mpx_cmd); + + if ((mpx_cmd & CMD_TWO_WORDS) && (mpx_state != param)) + fprintf (sim_deb, "parameter %06o ", mpx_param); + + fputs (cmd_state [mpx_state], sim_deb); + fputc ('\n', sim_deb); + } + +if (set_flag) { + mpx_io (&mpx_dib, ioENF, 0); /* set device flag */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fputs (">>MPX cmds: Flag set\n", sim_deb); + } + +return SCPE_OK; +} + + +/* Multiplexer line service. + + The line service routine is used to transmit and receive characters. It is + started when a buffer is ready for output or when the Telnet poll routine + determines that there are characters ready for input, and it is stopped when + there are no more characters to output or input. When a line is quiescent, + this routine does not run. Service times are selected to approximate the + baud rate setting of the multiplexer port. + + "Fast timing" mode enables three optimizations. First, buffered characters + are transferred via Telnet in blocks, rather than a character at a time; this + reduces network traffic and decreases simulator overhead (there is only one + service routine entry per block, rather than one per character). Second, + ENQ/ACK handshaking is done locally, without involving the Telnet client. + Third, when editing and echo is enabled, entering BS echoes a backspace, a + space, and a backspace, and entering DEL echoes a backslash, a carriage + return, and a line feed, providing better compatibility with prior RTE + terminal drivers. + + Each read and write buffer begins with a reserved header byte that stores + per-buffer information, such as whether handshaking should be suppressed + during output, or the specific cause of termination for input. Buffer + termination sets the header byte with the appropriate flags. + + For output, a character counter is maintained and is incremented if ENQ/ACK + handshaking is enabled for the current port and request. If the counter + limit is reached, an ENQ is sent, and a flag is set to suspend transmission + until an ACK is received. If the last character of the buffer is sent, the + write buffer is freed, and a UI check is made if the controller is idle, in + case a write buffer request is pending. + + For input, the character is retrieved from the Telnet buffer. If a BREAK was + received, break status is set, and the character is discarded (the current + multiplexer library implementation always returns a NUL with a BREAK + indication). If the character is an XOFF, and XON/XOFF pacing is enabled, a + flag is set, and transmission is suspended until a corresponding XON is + received. If the character is an ACK and is in response to a previously sent + ENQ, it is discarded, and transmission is reenabled. + + If editing is enabled, a BS will delete the last character in the read + buffer, and a DEL will delete the entire buffer. Otherwise, buffer + termination conditions are checked (end on character, end on count, or + buffer full), and if observed, the read buffer is terminated, and a read + buffer available UI condition is signalled. + + + Implementation notes: + + 1. The firmware echoes an entered BS before checking the buffer count to see + if there are any characters to delete. Under simulation, we only echo if + the buffer is not empty. + + 2. The "Fast binary read" command inhibits the normal transmit and receive + processing. Instead, a pair of characters are sought on line 0 to fill + the input buffer. When they are received, the device flag is set. The + CPU will do a LIx sc,C to retrieve the data and reset the flag. + + 3. In fast timing mode, burst transfers are used only to fill the first of + the two receive buffers; the second is filled with one character per + service entry. This allows the CPU time to unload the first buffer + before the second fills up. Once the first buffer is freed, the routine + shifts back to burst mode to fill the remainder of the second buffer. +*/ + +t_stat mpx_line_svc (UNIT *uptr) +{ +const int32 port = uptr - mpx_unit; /* port number */ +const uint16 rt = mpx_rcvtype [port]; /* receive type for port */ +const uint32 data_bits = 5 + GET_BPC (mpx_config [port]); /* number of data bits */ +const uint32 data_mask = (1 << data_bits) - 1; /* mask for data bits */ +const t_bool fast_timing = (uptr->flags & UNIT_FASTTIME) != 0; /* port is set for fast timing */ +const t_bool fast_binary_read = (mpx_cmd == CMD_BINARY_READ); /* fast binary read in progress */ +uint8 ch; +int32 chx; +uint32 buffer_count, write_count; +t_stat status = SCPE_OK; +t_bool recv_loop = !fast_binary_read; /* bypass if fast binary read */ +t_bool xmit_loop = !(fast_binary_read || /* bypass if fast read or output suspended */ + (mpx_flags [port] & (FL_WAITACK | FL_XOFF))); + + +if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: Port %d service entered\n", port); + +/* Transmission service */ + +write_count = buf_len (iowrite, port, get); /* get the output buffer length */ + +while (xmit_loop && write_count > 0) { /* character available to output? */ + if ((mpx_flags [port] & FL_WREMPT) == 0) { /* if the buffer has not started emptying */ + chx = buf_get (iowrite, port) << 8; /* then get the header value and position it */ + + if (fast_timing || (chx & WR_NO_ENQACK) || /* do we want handshake? */ + !(mpx_config [port] & SK_ENQACK)) /* and configured for handshake? */ + mpx_flags [port] &= ~FL_DO_ENQACK; /* no, so clear flag */ + else + mpx_flags [port] |= FL_DO_ENQACK; /* yes, so set flag */ + + continue; /* continue with the first output character */ + } + + if (mpx_flags [port] & FL_DO_ENQACK) /* do handshake for this buffer? */ + mpx_enq_cntr [port] = mpx_enq_cntr [port] + 1; /* bump character counter */ + + if (mpx_enq_cntr [port] > ENQ_LIMIT) { /* ready for ENQ? */ + mpx_enq_cntr [port] = 0; /* clear ENQ counter */ + mpx_ack_wait [port] = 0; /* clear ACK wait timer */ + + mpx_flags [port] |= FL_WAITACK; /* set wait for ACK */ + ch = ENQ; + status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit ENQ */ + xmit_loop = FALSE; /* stop further transmission */ + } + + else { /* not ready for ENQ */ + ch = buf_get (iowrite, port) & data_mask; /* get char and mask to bit width */ + status = tmxr_putc_ln (&mpx_ldsc [port], ch); /* transmit the character */ + + write_count = write_count - 1; /* count the character */ + xmit_loop = (status == SCPE_OK) && fast_timing; /* and continue transmission if enabled */ + } + + if (status != SCPE_OK) /* if the transmission failed */ + xmit_loop = FALSE; /* then exit the loop */ + + else if (DEBUG_PRI (mpx_dev, DEB_XFER)) + fprintf (sim_deb, ">>MPX xfer: Port %d character %s transmitted\n", + port, fmt_char (ch)); + + if (write_count == 0) { /* buffer complete? */ + buf_free (iowrite, port); /* free buffer */ + + write_count = buf_len (iowrite, port, get); /* get the next output buffer length */ + + if (mpx_state == idle) /* controller idle? */ + mpx_cntl_svc (&mpx_cntl); /* check for UI */ + } + } + + +/* Reception service */ + +buffer_count = buf_avail (ioread, port); /* get the number of available read buffers */ + +if (mpx_flags [port] & FL_RDFILL) /* if filling the current buffer */ + buffer_count = buffer_count + 1; /* then include it in the count */ + +while (recv_loop) { /* OK to process? */ + chx = tmxr_getc_ln (&mpx_ldsc [port]); /* get a new character */ + + if (chx == 0) /* if there are no more characters available */ + break; /* then quit the reception loop */ + + if (chx & SCPE_BREAK) { /* break detected? */ + mpx_flags [port] |= FL_BREAK; /* set break status */ + + if (DEBUG_PRI (mpx_dev, DEB_XFER)) + fputs (">>MPX xfer: Break detected\n", sim_deb); + + if (mpx_state == idle) /* controller idle? */ + mpx_cntl_svc (&mpx_cntl); /* check for UI */ + + continue; /* discard NUL that accompanied BREAK */ + } + + ch = (uint8) (chx & data_mask); /* mask to bits per char */ + + if ((ch == XOFF) && /* XOFF? */ + (mpx_flowcntl [port] & FC_XONXOFF)) { /* and handshaking enabled? */ + mpx_flags [port] |= FL_XOFF; /* suspend transmission */ + + if (DEBUG_PRI (mpx_dev, DEB_XFER)) + fprintf (sim_deb, ">>MPX xfer: Port %d character XOFF " + "suspends transmission\n", port); + + recv_loop = fast_timing; /* set to loop if fast mode */ + continue; + } + + else if ((ch == XON) && /* XON? */ + (mpx_flags [port] & FL_XOFF)) { /* and currently suspended? */ + mpx_flags [port] &= ~FL_XOFF; /* resume transmission */ + + if (DEBUG_PRI (mpx_dev, DEB_XFER)) + fprintf (sim_deb, ">>MPX xfer: Port %d character XON " + "resumes transmission\n", port); + + recv_loop = fast_timing; /* set to loop if fast mode */ + continue; + } + + if (DEBUG_PRI (mpx_dev, DEB_XFER)) + fprintf (sim_deb, ">>MPX xfer: Port %d character %s received\n", + port, fmt_char (ch)); + + if ((ch == ACK) && (mpx_flags [port] & FL_WAITACK)) { /* ACK and waiting for it? */ + mpx_flags [port] = mpx_flags [port] & ~FL_WAITACK; /* clear wait flag */ + recv_loop = FALSE; /* absorb character */ + } + + else if (buffer_count == 0 && /* no free buffer available for char? */ + !(mpx_flags [port] & FL_RDFILL)) { /* and not filling last buffer? */ + mpx_flags [port] |= FL_RDOVFLOW; /* set buffer overflow flag */ + recv_loop = fast_timing; /* continue loop if fast mode */ + } + + else { /* buffer is available */ + if (rt & RT_ENAB_EDIT) /* editing enabled? */ + if (ch == BS) { /* backspace? */ + if (buf_len (ioread, port, put) > 0) /* at least one character in buffer? */ + buf_remove (ioread, port); /* remove last char */ + + if (rt & RT_ENAB_ECHO) { /* echo enabled? */ + tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */ + + if (fast_timing) { /* fast timing mode? */ + tmxr_putc_ln (&mpx_ldsc [port], ' '); /* echo space */ + tmxr_putc_ln (&mpx_ldsc [port], BS); /* echo BS */ + } + } + + continue; + } + + else if (ch == DEL) { /* delete line? */ + buf_cancel (ioread, port, put); /* cancel put buffer */ + + if (rt & RT_ENAB_ECHO) { /* echo enabled? */ + if (fast_timing) /* fast timing mode? */ + tmxr_putc_ln (&mpx_ldsc [port], '\\'); /* echo backslash */ + + tmxr_putc_ln (&mpx_ldsc [port], CR); /* echo CR */ + tmxr_putc_ln (&mpx_ldsc [port], LF); /* and LF */ + } + + continue; + } + + if (uptr->flags & UNIT_CAPSLOCK) /* caps lock mode? */ + ch = (uint8) toupper (ch); /* convert to upper case if lower */ + + if (rt & RT_ENAB_ECHO) /* echo enabled? */ + tmxr_putc_ln (&mpx_ldsc [port], ch); /* echo the char */ + + if (rt & RT_END_ON_CHAR) { /* end on character? */ + recv_loop = FALSE; /* assume termination */ + + if ((ch == CR) && (rt & RT_END_ON_CR)) { + if (rt & RT_ENAB_ECHO) /* echo enabled? */ + tmxr_putc_ln (&mpx_ldsc [port], LF); /* send LF */ + mpx_param = RS_ETC_CR; /* set termination condition */ + } + + else if ((ch == RS) && (rt & RT_END_ON_RS)) + mpx_param = RS_ETC_RS; /* set termination condition */ + + else if ((ch == EOT) && (rt & RT_END_ON_EOT)) + mpx_param = RS_ETC_EOT; /* set termination condition */ + + else if ((ch == DC2) && (rt & RT_END_ON_DC2)) + mpx_param = RS_ETC_DC2; /* set termination condition */ + + else + recv_loop = TRUE; /* no termination */ + } + + if (recv_loop) { /* no termination condition? */ + buf_put (ioread, port, ch); /* put character in buffer */ + mpx_charcnt [port]++; /* and count it */ + } + + if ((rt & RT_END_ON_CNT) && /* end on count */ + (mpx_charcnt [port] == mpx_termcnt [port])) { /* and termination count reached? */ + recv_loop = FALSE; /* set termination */ + mpx_param = 0; /* no extra termination info */ + mpx_charcnt [port] = 0; /* clear the current character count */ + + if (mpx_flags [port] & FL_ALERT) { /* was this alert for term rcv buffer? */ + mpx_flags [port] &= ~FL_ALERT; /* clear alert flag */ + mpx_termcnt [port] = RD_BUF_LIMIT; /* reset termination character count */ + } + } + + else if (buf_len (ioread, port, put) == RD_BUF_LIMIT) { /* buffer now full? */ + recv_loop = FALSE; /* set termination */ + mpx_param = mpx_param | RS_PARTIAL; /* and partial buffer flag */ + } + + if (recv_loop) /* if there is no termination condition */ + if (buffer_count == 2) /* then if we're filling the first buffer */ + recv_loop = fast_timing; /* then set to loop if in fast mode */ + else /* otherwise we're filling the second */ + recv_loop = FALSE; /* so give the CPU a chance to read the first */ + + else { /* otherwise a termination condition exists */ + if (DEBUG_PRI (mpx_dev, DEB_XFER)) { + fprintf (sim_deb, ">>MPX xfer: Port %d read terminated on ", port); + + if (mpx_param & RS_PARTIAL) + fputs ("buffer full\n", sim_deb); + else if (rt & RT_END_ON_CHAR) + fprintf (sim_deb, "character %s\n", fmt_char (ch)); + else + fprintf (sim_deb, "count = %d\n", mpx_termcnt [port]); + } + + if (buf_len (ioread, port, put) == 0) { /* zero-length read? */ + buf_put (ioread, port, 0); /* dummy put to reserve header */ + buf_remove (ioread, port); /* back out dummy char leaving header */ + } + + buf_term (ioread, port, (uint8) (mpx_param >> 8)); /* terminate buffer and set header */ + + if (buf_avail (ioread, port) == 1) /* first read buffer? */ + mpx_flags [port] |= FL_HAVEBUF; /* indicate availability */ + + if (mpx_state == idle) /* controller idle? */ + mpx_cntl_svc (&mpx_cntl); /* check for UI */ + } + } + } + + +/* Housekeeping */ + +if (fast_binary_read) { /* fast binary read in progress? */ + if (port == 0) { /* on port 0? */ + chx = tmxr_getc_ln (&mpx_ldsc [0]); /* see if a character is ready */ + + if (chx && !(mpx_flags [0] & FL_HAVEBUF)) { /* character ready and buffer empty? */ + if (mpx_flags [0] & FL_WANTBUF) { /* second character? */ + mpx_ibuf = mpx_ibuf | (chx & DMASK8); /* merge it into word */ + mpx_flags [0] |= FL_HAVEBUF; /* mark buffer as ready */ + + mpx_io (&mpx_dib, ioENF, 0); /* set device flag */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fputs (">>MPX cmds: Flag and SRQ set\n", sim_deb); + } + + else /* first character */ + mpx_ibuf = (uint16) ((chx & DMASK8) << 8); /* put in top half of word */ + + mpx_flags [0] ^= FL_WANTBUF; /* toggle byte flag */ + } + + sim_activate (uptr, uptr->wait); /* reschedule service for fast response */ + } + } + +else { /* normal service */ + tmxr_poll_tx (&mpx_desc); /* output any accumulated characters */ + + if (write_count > 0 /* if there are more characters to transmit */ + && !(mpx_flags [port] & (FL_WAITACK | FL_XOFF)) /* and transmission is not suspended */ + || tmxr_rqln (&mpx_ldsc [port])) { /* or there are more characters to receive */ + sim_activate (uptr, uptr->wait); /* then reschedule the service */ + + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: Port %d delay %d service rescheduled\n", port, uptr->wait); + } + + else + if (DEBUG_PRI (mpx_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MPX cmds: Port %d service stopped\n", port); + } + +return SCPE_OK; +} + + +/* Telnet poll service. + + This service routine is used to poll for Telnet connections and incoming + characters. It starts when the socket is attached and stops when the socket + is detached. + + Each line is then checked for a pending ENQ/ACK handshake. If one is + pending, the ACK counter is incremented, and if it times out, another ENQ is + sent to avoid stalls. Lines are also checked for available characters, and + the corresponding line I/O service routine is scheduled if needed. +*/ + +t_stat mpx_poll_svc (UNIT *uptr) +{ +uint32 i; +t_stat status = SCPE_OK; + +poll_connection (); /* check for new connection */ + +tmxr_poll_rx (&mpx_desc); /* poll for input */ + +for (i = 0; i < MPX_PORTS; i++) { /* check lines */ + if (mpx_flags [i] & FL_WAITACK) { /* waiting for ACK? */ + mpx_ack_wait [i] = mpx_ack_wait [i] + 1; /* increment ACK wait timer */ + + if (mpx_ack_wait [i] > ACK_LIMIT) { /* has wait timed out? */ + mpx_ack_wait [i] = 0; /* reset counter */ + status = tmxr_putc_ln (&mpx_ldsc [i], ENQ); /* send ENQ again */ + tmxr_poll_tx (&mpx_desc); /* transmit it */ + + if ((status == SCPE_OK) && /* transmitted OK? */ + DEBUG_PRI (mpx_dev, DEB_XFER)) + fprintf (sim_deb, ">>MPX xfer: Port %d character ENQ retransmitted\n", i); + } + } + + if (tmxr_rqln (&mpx_ldsc [i])) /* chars available? */ + sim_activate (&mpx_unit [i], mpx_unit [i].wait); /* activate I/O service */ + } + +if (uptr->wait == POLL_FIRST) /* first poll? */ + uptr->wait = sync_poll (INITIAL); /* initial synchronization */ +else /* not first */ + uptr->wait = sync_poll (SERVICE); /* continue synchronization */ + +sim_activate (uptr, uptr->wait); /* continue polling */ + +return SCPE_OK; +} + + +/* Simulator reset routine. + + The hardware CRS signal generates a reset signal to the Z80 and its + peripherals. This causes execution of the power up initialization code. + + The CRS signal also has these hardware effects: + - clears control + - clears flag + - clears flag buffer + - clears backplane ready + - clears the output buffer register + + Implementation notes: + + 1. Under simulation, we also clear the input buffer register, even though + the hardware doesn't. + + 2. We set up the first poll for Telnet connections to occur "immediately" + upon execution, so that clients will be connected before execution + begins. Otherwise, a fast program may access the multiplexer before the + poll service routine activates. + + 3. We must set the "emptying_flags" and "filling_flags" values here, because + they cannot be initialized statically, even though the values are + constant. +*/ + +t_stat mpx_reset (DEVICE *dptr) +{ +if (sim_switches & SWMASK ('P')) { /* power-on reset? */ + emptying_flags [ioread] = FL_RDEMPT; /* initialize buffer flags constants */ + emptying_flags [iowrite] = FL_WREMPT; + filling_flags [ioread] = FL_RDFILL; + filling_flags [iowrite] = FL_WRFILL; + } + +IOPRESET (&mpx_dib); /* PRESET device (does not use PON) */ + +mpx_ibuf = 0; /* clear input buffer */ + +if (mpx_poll.flags & UNIT_ATT) { /* network attached? */ + mpx_poll.wait = POLL_FIRST; /* set up poll */ + sim_activate (&mpx_poll, mpx_poll.wait); /* start Telnet poll immediately */ + } +else + sim_cancel (&mpx_poll); /* else stop Telnet poll */ + +return SCPE_OK; +} + + +/* Attach the multiplexer to a Telnet port. + + We are called by the ATTACH MPX command to attach the multiplexer to + the listening port indicated by . Logically, it is the multiplexer + device that is attached; however, SIMH only allows units to be attached. + This makes sense for devices such as tape drives, where the attached media is + a property of a specific drive. In our case, though, the listening port is a + property of the multiplexer card, not of any given serial line. As ATTACH + MPX is equivalent to ATTACH MPX0, the port would, by default, be attached to + the first serial line and be reported there in a SHOW MPX command. + + To preserve the logical picture, we attach the port to the Telnet poll unit, + which is normally disabled to inhibit its display. Attaching to a disabled + unit is not allowed, so we first enable the unit, then attach it, then + disable it again. Attachment is reported by the "mpx_status" routine below. + + A direct attach to the poll unit is only allowed when restoring a previously + saved session. + + The Telnet poll service routine is synchronized with the other input polling + devices in the simulator to facilitate idling. +*/ + +t_stat mpx_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat status = SCPE_OK; + +if (uptr != mpx_unit /* not unit 0? */ + && (uptr != &mpx_poll || !(sim_switches & SIM_SW_REST))) /* and not restoring the poll unit? */ + return SCPE_NOATT; /* can't attach */ + +mpx_poll.flags = mpx_poll.flags & ~UNIT_DIS; /* enable unit */ +status = tmxr_attach (&mpx_desc, &mpx_poll, cptr); /* attach to socket */ +mpx_poll.flags = mpx_poll.flags | UNIT_DIS; /* disable unit */ + +if (status == SCPE_OK) { + mpx_poll.wait = POLL_FIRST; /* set up poll */ + sim_activate (&mpx_poll, mpx_poll.wait); /* start poll immediately */ + } +return status; +} + + +/* Detach the multiplexer. + + Normally, we are called by the DETACH MPX command, which is equivalent to + DETACH MPX0. However, we may be called with other units in two cases. + + A DETACH ALL command will call us for unit 9 (the poll unit) if it is + attached. Also, during simulator shutdown, we will be called for units 0-8 + (detach_all in scp.c calls the detach routines of all units that do NOT have + UNIT_ATTABLE), as well as for unit 9 if it is attached. In both cases, it is + imperative that we return SCPE_OK, otherwise any remaining device detaches + will not be performed. +*/ + +t_stat mpx_detach (UNIT *uptr) +{ +t_stat status = SCPE_OK; +int32 i; + +if ((uptr == mpx_unit) || (uptr == &mpx_poll)) { /* base unit or poll unit? */ + status = tmxr_detach (&mpx_desc, &mpx_poll); /* detach socket */ + + for (i = 0; i < MPX_PORTS; i++) { + mpx_ldsc [i].rcve = 0; /* disable line reception */ + sim_cancel (&mpx_unit [i]); /* cancel any scheduled I/O */ + } + + sim_cancel (&mpx_poll); /* stop Telnet poll */ + } + +return status; +} + + +/* Show multiplexer status */ + +t_stat mpx_status (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +if (mpx_poll.flags & UNIT_ATT) /* attached to socket? */ + fprintf (st, "attached to port %s, ", mpx_poll.filename); +else + fprintf (st, "not attached, "); + +tmxr_show_summ (st, uptr, val, desc); /* report connection count */ +return SCPE_OK; +} + + +/* Set firmware revision. + + Currently, we support only revision C, so the MTAB entry does not have an + "mstring" entry. When we add revision D support, an "mstring" entry of "REV" + will enable changing the firmware revision. +*/ + +t_stat mpx_set_frev (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +if ((cptr == NULL) || /* no parameter? */ + (*cptr < 'C') || (*cptr > 'D') || /* or not C or D? */ + (*(cptr + 1) != '\0')) /* or not just one character? */ + return SCPE_ARG; /* bad argument */ + +else { + if (*cptr == 'C') /* setting revision C? */ + mpx_dev.flags = mpx_dev.flags & ~DEV_REV_D; /* clear 'D' flag */ + else if (*cptr == 'D') /* setting revision D? */ + mpx_dev.flags = mpx_dev.flags | DEV_REV_D; /* set 'D' flag */ + + return SCPE_OK; + } +} + + +/* Show firmware revision */ + +t_stat mpx_show_frev (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +if (mpx_dev.flags & DEV_REV_D) + fputs ("12792D", st); +else + fputs ("12792C", st); +return SCPE_OK; +} + + +/* Local routines */ + + +/* Poll for new Telnet connections */ + +static void poll_connection (void) +{ +int32 new_line; + +new_line = tmxr_poll_conn (&mpx_desc); /* check for new connection */ + +if (new_line >= 0) /* new connection established? */ + mpx_ldsc [new_line].rcve = 1; /* enable line to receive */ + +return; +} + + +/* Controller reset. + + This is the card microprocessor reset, not the simulator reset routine. It + simulates a power-on restart of the Z80 firmware. When it is called from the + simulator reset routine, that routine will take care of setting the card + flip-flops appropriately. +*/ + +static void controller_reset (void) +{ +uint32 i; + +mpx_state = idle; /* idle state */ + +mpx_cmd = 0; /* clear command */ +mpx_param = 0; /* clear parameter */ +mpx_uien = FALSE; /* disable interrupts */ + +for (i = 0; i < MPX_PORTS; i++) { /* clear per-line variables */ + buf_init (iowrite, i); /* initialize write buffers */ + buf_init (ioread, i); /* initialize read buffers */ + + mpx_key [i] = KEY_DEFAULT; /* clear port key to default */ + + if (i == 0) /* default port configurations */ + mpx_config [0] = SK_PWRUP_0; /* port 0 is separate from 1-7 */ + else + mpx_config [i] = (uint16) (SK_PWRUP_1 | i); + + mpx_rcvtype [i] = RT_PWRUP; /* power on config for echoplex */ + mpx_charcnt [i] = 0; /* clear character count */ + mpx_termcnt [i] = 0; /* default termination character count */ + mpx_flowcntl [i] = 0; /* default flow control */ + mpx_flags [i] = 0; /* clear state flags */ + mpx_enq_cntr [i] = 0; /* clear ENQ counter */ + mpx_ack_wait [i] = 0; /* clear ACK wait timer */ + mpx_unit [i].wait = service_time (mpx_config [i]); /* set terminal I/O time */ + + sim_cancel (&mpx_unit [i]); /* cancel line I/O */ + } + +sim_cancel (&mpx_cntl); /* cancel controller */ + +return; +} + + +/* Calculate service time from baud rate. + + Service times are based on 1580 instructions per millisecond, which is the + 1000 E-Series execution speed. Baud rate 0 means "don't change" and is + handled by the "Set port key" command executor. + + Baud rate settings of 13-15 are marked as "reserved" in the user manual, but + the firmware defines these as 38400, 9600, and 9600 baud, respectively. +*/ + +static uint32 service_time (uint16 control_word) +{ +/* Baud Rates 0- 7 : --, 50, 75, 110, 134.5, 150, 300, 1200, */ +/* Baud Rates 8-15 : 1800, 2400, 4800, 9600, 19200, 38400, 9600, 9600 */ +static const int32 ticks [] = { 0, 316000, 210667, 143636, 117472, 105333, 52667, 13167, + 8778, 6583, 3292, 1646, 823, 411, 1646, 1646 }; + +return ticks [GET_BAUDRATE (control_word)]; /* return service time for indicated rate */ +} + + +/* Translate port key to port number. + + Port keys are scanned in reverse port order, so if more than one port has the + same port key, commands specifying that key will affect the highest numbered + port. + + If a port key is the reserved value 255, then the port key has not been set. + In this case, set the input buffer to 0xBAD0 and return -1 to indicate + failure. +*/ + +static int32 key_to_port (uint32 key) +{ +int32 i; + +for (i = MPX_PORTS - 1; i >= 0; i--) /* scan in reverse order */ + if (mpx_key [i] == key) /* key found? */ + return i; /* return port number */ + +mpx_ibuf = ST_BAD_KEY; /* key not found: set status */ +return -1; /* return failure code */ +} + + +/* Buffer manipulation routines. + + The 12792 hardware provides 16K bytes of RAM to the microprocessor. From + this pool, the firmware allocates per-port read/write buffers and state + variables, global variables, and the system stack. Allocations are static + and differ between firmware revisions. + + The A/B/C revisions allocate two 254-byte read buffers and two 254-byte write + buffers per port. Assuming an idle condition, the first write to a port + transfers characters to the first write buffer. When the transfer completes, + the SIO begins transmitting. During transmission, a second write can be + initiated, which transfers characters to the second write buffer. If a third + write is attempted before the first buffer has been released, it will be + denied until the SIO completes transmission; then, if enabled, an unsolicited + interrupt will occur to announce buffer availability. The "active" (filling) + buffer alternates between the two. + + At idle, characters received will fill the first read buffer. When the read + completes according to the previously set termination criteria, an + unsolicited interrupt will occur (if enabled) to announce buffer + availability. If more characters are received before the first buffer has + been transferred to the CPU, they will fill the second buffer. If that read + also completes, additional characters will be discarded until the first + buffer has been emptied. The "active" (emptying) buffer alternates between + the two. + + With this configuration, two one-character writes or reads will allocate both + available buffers, even though each was essentially empty. + + The D revision allocates one 1024-byte FIFO read buffer and one 892-byte + write buffer per port. As with the A/B/C revisions, the first write to a + port transfers characters to the write buffer, and serial transmission begins + when the write completes. However, the write buffer is not a FIFO, so the + host is not permitted another write request until the entire buffer has been + transmitted. + + The read buffer is a FIFO. Characters received are placed into the FIFO as a + stream. Unlike the A/B/C revisions, character editing and termination + conditions are not evaluated until the buffer is read. Therefore, a full + 1024 characters may be received before additional characters would be + discarded. + + When the first character is received, an unsolicited interrupt occurs (if + enabled) to announce data reception. A host read may then be initiated. The + write buffer is used temporarily to process characters from the read buffer. + Characters are copied from the read to the write buffer while editing as + directed by the configuration accompanying the read request (e.g., deleting + the character preceding a BS, stripping CR/LF, etc.). When the termination + condition is found, the read command completes. Incoming characters may be + added to the FIFO while this is occurring. + + In summary, the revision differences in buffer handling are: + + Revisions A/B/C: + - two 254-byte receive buffers + - a buffer is "full" when the terminator character or count is received + - termination type must be established before the corresponding read + - data is echoed as it is received + + Revision D: + - one 1024-byte receive buffer + - buffer is "full" only when 1024 characters are received + - the concept of a buffer terminator does not apply, as the data is not + examined until a read is requested and characters are retrieved from the + FIFO. + - data is not echoed until it is read + + To implement the C revision behavior, while preserving the option of reusing + the buffer handlers for future D revision support, the dual 254-byte buffers + are implemented as a single 514-byte circular FIFO with capacity limited to + 254 bytes per buffer. This reserves space for a CR and LF and for a header + byte in each buffer. The header byte preserves per-buffer state information. + + In this implementation, the buffer "put" index points at the next free + location, and the buffer "get" index points at the next character to + retrieve. In addition to "put" and "get" indexes, a third "separator" index + is maintained to divide the FIFO into two areas corresponding to the two + buffers, and a "buffer filling" flag is maintained for each FIFO that is set + by the fill (put) routine and cleared by the terminate buffer routine. + + Graphically, the implementation is as follows for buffer "B[]", get "G", put + "P", and separator "S" indexes: + + 1. Initialize: 2. Fill first buffer: + G = S = P = 0 B[P] = char; Incr (P) + + |------------------------------| |---------|--------------------| + G G P --> + S S + P + + 3. Terminate first buffer: 4. Fill second buffer: + if S == G then S = P else nop B[P] = char; Incr (P) + + |------------|-----------------| |------------|------|----------| + G /---> S G S P --> + * ----/ P + + 5. Terminate second buffer: 6. Empty first buffer: + if S == G then S = P else nop char = B[G]; Incr (G) + + |------------|------------|----| |----|-------|------------|----| + G S P G --> S P + + 7. First buffer is empty: 8. Free first buffer: + G == S if !filling then S = P else nop + + |------------|------------|----| |------------|------------|----| + G P G /---> S + S * ----/ P + + 9. Empty second buffer: 10. Second buffer empty: + char = B[G]; Incr (G) G == S + + |----------------|--------|----| |-------------------------|----| + G --> S G + P S + P + 11. Free second buffer: + if !filling then S = P else nop + + |-------------------------|----| + G + S + P + + We also provide the following utility routines: + + - Remove Character: Decr (P) + + - Cancel Buffer: if S == G then P = G else G = S + + - Buffer Length: if S < G then return S + BUFSIZE - G else return S - G + + - Buffers Available: if G == P then return 2 else if G != S != P then return + 0 else return 1 + + The "buffer filling" flag is necessary for the "free" routine to decide + whether to advance the separator index. If the first buffer is to be freed, + then G == S and S != P. If the second buffer is already filled, then S = P. + However, if the buffer is still filling, then S must remain at G. This + cannot be determined from G, S, and P alone. + + A "buffer emptying" flag is also employed to record whether the per-buffer + header has been obtained. This allows the buffer length to exclude the + header and reflect only the characters present. +*/ + + +/* Increment a buffer index with wraparound */ + +static uint16 buf_incr (BUF_INDEX index, uint32 port, IO_OPER rw, int increment) +{ +index [port] [rw] = + (index [port] [rw] + buf_size [rw] + increment) % buf_size [rw]; + +return index [port] [rw]; +} + + +/* Initialize the buffer. + + Initialization sets the three indexes to zero and clears the buffer state + flags. +*/ + +static void buf_init (IO_OPER rw, uint32 port) +{ +mpx_get [port] [rw] = 0; /* clear indexes */ +mpx_sep [port] [rw] = 0; +mpx_put [port] [rw] = 0; + +if (rw == ioread) + mpx_flags [mpx_port] &= ~(FL_RDFLAGS); /* clear read buffer flags */ +else + mpx_flags [mpx_port] &= ~(FL_WRFLAGS); /* clear write buffer flags */ +return; +} + + +/* Get a character from the buffer. + + The character indicated by the "get" index is retrieved from the buffer, and + the index is incremented with wraparound. If the buffer is now empty, the + "buffer emptying" flag is cleared. Otherwise, it is set to indicate that + characters have been removed from the buffer. +*/ + +static uint8 buf_get (IO_OPER rw, uint32 port) +{ +uint8 ch; +uint32 index = mpx_get [port] [rw]; /* current get index */ + +if (rw == ioread) + ch = mpx_rbuf [port] [index]; /* get char from read buffer */ +else + ch = mpx_wbuf [port] [index]; /* get char from write buffer */ + +buf_incr (mpx_get, port, rw, +1); /* increment circular get index */ + +if (DEBUG_PRI (mpx_dev, DEB_BUF)) + if (mpx_flags [port] & emptying_flags [rw]) + fprintf (sim_deb, ">>MPX buf: Port %d character %s get from %s buffer " + "[%d]\n", port, fmt_char (ch), io_op [rw], index); + else + fprintf (sim_deb, ">>MPX buf: Port %d header %03o get from %s buffer " + "[%d]\n", port, ch, io_op [rw], index); + +if (mpx_get [port] [rw] == mpx_sep [port] [rw]) /* buffer now empty? */ + mpx_flags [port] &= ~emptying_flags [rw]; /* clear "buffer emptying" flag */ +else + mpx_flags [port] |= emptying_flags [rw]; /* set "buffer emptying" flag */ + +return ch; +} + + +/* Put a character to the buffer. + + The character is written to the buffer in the slot indicated by the "put" + index, and the index is incremented with wraparound. The first character put + to a new buffer reserves space for the header and sets the "buffer filling" + flag. +*/ + +static void buf_put (IO_OPER rw, uint32 port, uint8 ch) +{ +uint32 index; + +if ((mpx_flags [port] & filling_flags [rw]) == 0) { /* first put to this buffer? */ + mpx_flags [port] |= filling_flags [rw]; /* set buffer filling flag */ + index = mpx_put [port] [rw]; /* get current put index */ + buf_incr (mpx_put, port, rw, +1); /* reserve space for header */ + + if (DEBUG_PRI (mpx_dev, DEB_BUF)) + fprintf (sim_deb, ">>MPX buf: Port %d reserved header " + "for %s buffer [%d]\n", port, io_op [rw], index); + } + +index = mpx_put [port] [rw]; /* get current put index */ + +if (rw == ioread) + mpx_rbuf [port] [index] = ch; /* put char in read buffer */ +else + mpx_wbuf [port] [index] = ch; /* put char in write buffer */ + +buf_incr (mpx_put, port, rw, +1); /* increment circular put index */ + +if (DEBUG_PRI (mpx_dev, DEB_BUF)) + fprintf (sim_deb, ">>MPX buf: Port %d character %s put to %s buffer " + "[%d]\n", port, fmt_char (ch), io_op [rw], index); +return; +} + + +/* Remove the last character put to the buffer. + + The most-recent character put to the buffer is removed by decrementing the + "put" index with wraparound. +*/ + +static void buf_remove (IO_OPER rw, uint32 port) +{ +uint8 ch; +uint32 index; + +index = buf_incr (mpx_put, port, rw, -1); /* decrement circular put index */ + +if (DEBUG_PRI (mpx_dev, DEB_BUF)) { + if (rw == ioread) + ch = mpx_rbuf [port] [index]; /* pick up char from read buffer */ + else + ch = mpx_wbuf [port] [index]; /* pick up char from write buffer */ + + fprintf (sim_deb, ">>MPX buf: Port %d character %s removed from %s buffer " + "[%d]\n", port, fmt_char (ch), io_op [rw], index); + } +return; +} + + +/* Terminate the buffer. + + The buffer is marked to indicate that filling is complete and that the next + "put" operation should begin a new buffer. The header value is stored in + first byte of buffer, which is reserved, and the "buffer filling" flag is + cleared. +*/ + +static void buf_term (IO_OPER rw, uint32 port, uint8 header) +{ +uint32 index = mpx_sep [port] [rw]; /* separator index */ + +if (rw == ioread) + mpx_rbuf [port] [index] = header; /* put header in read buffer */ +else + mpx_wbuf [port] [index] = header; /* put header in write buffer */ + +mpx_flags [port] = mpx_flags [port] & ~filling_flags [rw]; /* clear filling flag */ + +if (mpx_get [port] [rw] == index) /* reached separator? */ + mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move sep to end of next buffer */ + +if (DEBUG_PRI (mpx_dev, DEB_BUF)) + fprintf (sim_deb, ">>MPX buf: Port %d header %03o terminated %s buffer\n", + port, header, io_op [rw]); +return; +} + + +/* Free the buffer. + + The buffer is marked to indicate that it is available for reuse, and the + "buffer emptying" flag is reset. +*/ + +static void buf_free (IO_OPER rw, uint32 port) +{ +if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling next buffer? */ + mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */ + /* else it will be moved when terminated */ +mpx_flags [port] = mpx_flags [port] & ~emptying_flags [rw]; /* clear emptying flag */ + +if (DEBUG_PRI (mpx_dev, DEB_BUF)) + fprintf (sim_deb, ">>MPX buf: Port %d released %s buffer\n", port, io_op [rw]); +return; +} + + +/* Cancel the selected buffer. + + The selected buffer is marked to indicate that it is empty. Either the "put" + buffer or the "get" buffer may be selected. +*/ + +static void buf_cancel (IO_OPER rw, uint32 port, BUF_SELECT which) +{ +if (which == put) { /* cancel put buffer? */ + mpx_put [port] [rw] = mpx_sep [port] [rw]; /* move put back to separator */ + mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */ + } + +else { /* cancel get buffer */ + if (mpx_sep [port] [rw] == mpx_get [port] [rw]) { /* filling first buffer? */ + mpx_put [port] [rw] = mpx_get [port] [rw]; /* cancel first buffer */ + mpx_flags [port] &= ~filling_flags [rw]; /* clear filling flag */ + } + + else { /* not filling first buffer */ + mpx_get [port] [rw] = mpx_sep [port] [rw]; /* cancel first buffer */ + + if ((mpx_flags [port] & filling_flags [rw]) == 0) /* not filling second buffer? */ + mpx_sep [port] [rw] = mpx_put [port] [rw]; /* move separator to end of next buffer */ + } + + mpx_flags [port] &= ~emptying_flags [rw]; /* clear emptying flag */ + } + +if (DEBUG_PRI (mpx_dev, DEB_BUF)) + fprintf (sim_deb, ">>MPX buf: Port %d cancelled %s buffer\n", port, io_op [rw]); +return; +} + + +/* Get the buffer length. + + The current length of the selected buffer (put or get) is returned. For ease + of use, the returned length does NOT include the header byte, i.e., it + reflects only the characters contained in the buffer. + + If the put buffer is selected, and the buffer is filling, or the get buffer + is selected, and the buffer is not emptying, then subtract one from the + length for the allocated header. +*/ + +static uint16 buf_len (IO_OPER rw, uint32 port, BUF_SELECT which) +{ +int16 length; + +if (which == put) + length = mpx_put [port] [rw] - mpx_sep [port] [rw] - /* calculate length */ + ((mpx_flags [port] & filling_flags [rw]) != 0); /* account for allocated header */ + +else { + length = mpx_sep [port] [rw] - mpx_get [port] [rw]; /* calculate length */ + + if (length && !(mpx_flags [port] & emptying_flags [rw])) /* not empty and not yet emptying? */ + length = length - 1; /* account for allocated header */ + } + +if (length < 0) /* is length negative? */ + return length + buf_size [rw]; /* account for wraparound */ +else + return length; +} + + +/* Return the number of free buffers available. + + Either 0, 1, or 2 free buffers will be available. A buffer is available if + it contains no characters (including the header byte). +*/ + +static uint32 buf_avail (IO_OPER rw, uint32 port) +{ +if (mpx_get [port] [rw] == mpx_put [port] [rw]) /* get and put indexes equal? */ + return 2; /* all buffers are free */ + +else if ((mpx_get [port] [rw] != mpx_sep [port] [rw]) && /* get, separator, and put */ + (mpx_sep [port] [rw] != mpx_put [port] [rw])) /* all different? */ + return 0; /* no buffers are free */ + +else + return 1; /* one buffer free */ +} diff --git a/HP2100/hp2100_ms.c b/HP2100/hp2100_ms.c index 916c0fc4..1db27ae2 100644 --- a/HP2100/hp2100_ms.c +++ b/HP2100/hp2100_ms.c @@ -1,1372 +1,1373 @@ -/* hp2100_ms.c: HP 2100 13181A/13183A magnetic tape simulator - - Copyright (c) 1993-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - MS 13181A 7970B 800bpi nine track magnetic tape - 13183A 7970E 1600bpi nine track magnetic tape - - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 24-Dec-14 JDB Use T_ADDR_FMT with t_addr values for 64-bit compatibility - Added casts for explicit downward conversions - 11-Dec-14 JDB Updated for new erase gap API, added CRCC/LRCC support - 10-Jan-13 MP Added DEV_TAPE to DEVICE flags - 09-May-12 JDB Separated assignments from conditional expressions - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - Added CNTLR_TYPE cast to ms_settype - 28-Mar-11 JDB Tidied up signal handling - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 11-Aug-08 JDB Revised to use AR instead of saved_AR in boot - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 18-Sep-06 JDB Fixed 2nd CLR after WC causing another write - Improve debug reporting, add debug flags - 14-Sep-06 JDB Removed local BOT flag, now uses sim_tape_bot - 30-Aug-06 JDB Added erase gap support, improved tape lib err reporting - 07-Jul-06 JDB Added CAPACITY as alternate for REEL - Fixed EOT test for unlimited reel size - 16-Feb-06 RMS Revised for new EOT test - 22-Jul-05 RMS Fixed compiler warning on Solaris (from Doug Glyn) - 01-Mar-05 JDB Added SET OFFLINE; rewind/offline now does not detach - 07-Oct-04 JDB Fixed enable/disable from either device - 14-Aug-04 JDB Fixed many functional and timing problems (from Dave Bryan) - - fixed erroneous execution of rejected command - - fixed erroneous execution of select-only command - - fixed erroneous execution of clear command - - fixed odd byte handling for read - - fixed spurious odd byte status on 13183A EOF - - modified handling of end of medium - - added detailed timing, with fast and realistic modes - - added reel sizes to simulate end of tape - - added debug printouts - 06-Jul-04 RMS Fixed spurious timing error after CLC (found by Dave Bryan) - 26-Apr-04 RMS Fixed SFS x,C and SFC x,C - Fixed SR setting in IBL - Revised IBL loader - Implemented DMA SRQ (follows FLG) - 25-Apr-03 RMS Revised for extended file support - 28-Mar-03 RMS Added multiformat support - 28-Feb-03 RMS Revised for magtape library - 18-Oct-02 RMS Added BOOT command, added 13183A support - 30-Sep-02 RMS Revamped error handling - 29-Aug-02 RMS Added end of medium support - 30-May-02 RMS Widened POS to 32b - 22-Apr-02 RMS Added maximum record length test - - References: - - 13181B Digital Magnetic Tape Unit Interface Kit Operating and Service Manual - (13181-90901, Nov-1982) - - 13183B Digital Magnetic Tape Unit Interface Kit Operating and Service Manual - (13183-90901, Nov-1983) - - SIMH Magtape Representation and Handling (Bob Supnik, 30-Aug-2006) -*/ - - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include "sim_tape.h" - -#define UNIT_V_OFFLINE (MTUF_V_UF + 0) /* unit offline */ -#define UNIT_OFFLINE (1 << UNIT_V_OFFLINE) - -#define MS_NUMDR 4 /* number of drives */ -#define DB_N_SIZE 16 /* max data buf */ -#define DBSIZE (1 << DB_N_SIZE) /* max data cmd */ -#define FNC u3 /* function */ -#define UST u4 /* unit status */ -#define REEL u5 /* tape reel size */ - -#define BPI_13181 MT_DENS_800 /* 800 bpi for 13181 cntlr */ -#define BPI_13183 MT_DENS_1600 /* 1600 bpi for 13183 cntlr */ -#define GAP_13181 48 /* gap is 4.8 inches for 13181 cntlr */ -#define GAP_13183 30 /* gap is 3.0 inches for 13183 cntlr */ -#define TCAP (300 * 12 * 800) /* 300 ft capacity at 800 bpi */ - -/* Debug flags */ - -#define DEB_CMDS (1 << 0) /* command init and compl */ -#define DEB_CPU (1 << 1) /* CPU I/O */ -#define DEB_RWS (1 << 2) /* tape reads, writes, status */ - -/* Command - msc_fnc */ - -#define FNC_CLR 00110 /* clear */ -#define FNC_GAP 00015 /* write gap */ -#define FNC_GFM 00215 /* gap+file mark */ -#define FNC_RC 00023 /* read */ -#define FNC_WC 00031 /* write */ -#define FNC_FSR 00003 /* forward space */ -#define FNC_BSR 00041 /* backward space */ -#define FNC_FSF 00203 /* forward file */ -#define FNC_BSF 00241 /* backward file */ -#define FNC_REW 00101 /* rewind */ -#define FNC_RWS 00105 /* rewind and offline */ -#define FNC_WFM 00211 /* write file mark */ -#define FNC_RFF 00223 /* read file fwd (diag) */ -#define FNC_RRR 00061 /* read record rev (diag) */ -#define FNC_CMPL 00400 /* completion state */ -#define FNC_V_SEL 9 /* select */ -#define FNC_M_SEL 017 -#define FNC_GETSEL(x) (((x) >> FNC_V_SEL) & FNC_M_SEL) - -#define FNF_MOT 00001 /* motion */ -#define FNF_OFL 00004 -#define FNF_WRT 00010 /* write */ -#define FNF_REV 00040 /* reverse */ -#define FNF_RWD 00100 /* rewind */ -#define FNF_CHS 00400 /* change select */ - -#define FNC_SEL ((FNC_M_SEL << FNC_V_SEL) | FNF_CHS) - -/* Status - stored in msc_sta, unit.UST (u), or dynamic (d) */ - -#define STA_PE 0100000 /* 1600 bpi (d) */ -#define STA_V_SEL 13 /* unit sel (d) */ -#define STA_M_SEL 03 -#define STA_SEL (STA_M_SEL << STA_V_SEL) -#define STA_ODD 0004000 /* odd bytes */ -#define STA_REW 0002000 /* rewinding (u) */ -#define STA_TBSY 0001000 /* transport busy (d) */ -#define STA_BUSY 0000400 /* ctrl busy */ -#define STA_EOF 0000200 /* end of file */ -#define STA_BOT 0000100 /* beg of tape (d) */ -#define STA_EOT 0000040 /* end of tape (d) */ -#define STA_TIM 0000020 /* timing error */ -#define STA_REJ 0000010 /* programming error */ -#define STA_WLK 0000004 /* write locked (d) */ -#define STA_PAR 0000002 /* parity error */ -#define STA_LOCAL 0000001 /* local (d) */ -#define STA_DYN (STA_PE | STA_SEL | STA_TBSY | STA_BOT | \ - STA_EOT | STA_WLK | STA_LOCAL) - -/* Controller types */ - -typedef enum { - A13181, - A13183 - } CNTLR_TYPE; - -CNTLR_TYPE ms_ctype = A13181; /* ctrl type */ -int32 ms_timing = 1; /* timing type */ - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } msc = { CLEAR, CLEAR, CLEAR }; - -int32 msc_sta = 0; /* status */ -int32 msc_buf = 0; /* buffer */ -int32 msc_usl = 0; /* unit select */ -int32 msc_1st = 0; /* first service */ -int32 msc_stopioe = 1; /* stop on error */ - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } msd = { CLEAR, CLEAR, CLEAR }; - -int32 msd_buf = 0; /* data buffer */ -uint8 msxb[DBSIZE] = { 0 }; /* data buffer */ -t_mtrlnt ms_ptr = 0, ms_max = 0; /* buffer ptrs */ -t_bool ms_crc = FALSE; /* buffer ready for CRC calc */ - - -/* Hardware timing at 45 IPS 13181 13183 - (based on 1580 instr/msec) instr msec SCP instr msec SCP - -------------------- -------------------- - - BOT start delay : btime = 161512 102.22 184 252800 160.00 288 - - motion cmd start delay : ctime = 14044 8.89 16 17556 11.11 20 - - GAP traversal time : gtime = 175553 111.11 200 105333 66.67 120 - - IRG traversal time : itime = 24885 15.75 - 27387 17.33 - - - rewind initiation time : rtime = 878 0.56 1 878 0.56 1 - - data xfer time / word : xtime = 88 55.56us - 44 27.78us - - - NOTE: The 13181-60001 Rev. 1629 tape diagnostic fails test 17B subtest 6 with - "E116 BYTE TIME SHORT" if the correct data transfer time is used for - 13181A interface. Set "xtime" to 115 (instructions) to pass that - diagnostic. Rev. 2040 of the tape diagnostic fixes this problem and - passes with the correct data transfer time. -*/ - -int32 msc_btime = 0; /* BOT start delay */ -int32 msc_ctime = 0; /* motion cmd start delay */ -int32 msc_gtime = 0; /* GAP traversal time */ -int32 msc_itime = 0; /* IRG traversal time */ -int32 msc_rtime = 0; /* rewind initiation time */ -int32 msc_xtime = 0; /* data xfer time / word */ - -typedef int32 TIMESET[6]; /* set of controller times */ - -int32 *const timers[] = { &msc_btime, &msc_ctime, &msc_gtime, - &msc_itime, &msc_rtime, &msc_xtime }; - -const TIMESET msc_times[3] = { - { 161512, 14044, 175553, 24885, 878, 88 }, /* 13181A */ - { 252800, 17556, 105333, 27387, 878, 44 }, /* 13183A */ - { 1, 1000, 1, 1, 100, 10 } /* FAST */ - }; - -DEVICE msd_dev, msc_dev; - -IOHANDLER msdio; -IOHANDLER mscio; - -t_stat msc_svc (UNIT *uptr); -t_stat msc_reset (DEVICE *dptr); -t_stat msc_attach (UNIT *uptr, char *cptr); -t_stat msc_detach (UNIT *uptr); -t_stat msc_online (UNIT *uptr, int32 value, char *cptr, void *desc); -t_stat msc_boot (int32 unitno, DEVICE *dptr); -t_stat ms_write_gap (UNIT *uptr); -t_stat ms_map_err (UNIT *uptr, t_stat st); -t_stat ms_settype (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat ms_showtype (FILE *st, UNIT *uptr, int32 val, void *desc); -t_stat ms_set_timing (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat ms_show_timing (FILE *st, UNIT *uptr, int32 val, void *desc); -t_stat ms_set_reelsize (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat ms_show_reelsize (FILE *st, UNIT *uptr, int32 val, void *desc); -void ms_config_timing (void); -char *ms_cmd_name (uint32 cmd); -t_stat ms_clear (void); -static uint32 calc_crc_lrc (uint8 *buffer, t_mtrlnt length); - - -/* MSD data structures - - msd_dev MSD device descriptor - msd_unit MSD unit list - msd_reg MSD register list -*/ - -DIB ms_dib[] = { - { &msdio, MSD }, - { &mscio, MSC } - }; - -#define msd_dib ms_dib[0] -#define msc_dib ms_dib[1] - -UNIT msd_unit = { UDATA (NULL, 0, 0) }; - -REG msd_reg[] = { - { ORDATA (BUF, msd_buf, 16) }, - { FLDATA (CTL, msd.control, 0) }, - { FLDATA (FLG, msd.flag, 0) }, - { FLDATA (FBF, msd.flagbuf, 0) }, - { BRDATA (DBUF, msxb, 8, 8, DBSIZE) }, - { DRDATA (BPTR, ms_ptr, DB_N_SIZE + 1) }, - { DRDATA (BMAX, ms_max, DB_N_SIZE + 1) }, - { ORDATA (SC, msd_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, msd_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB msd_mod[] = { - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &msd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &msd_dev }, - { 0 } - }; - -DEVICE msd_dev = { - "MSD", &msd_unit, msd_reg, msd_mod, - 1, 10, DB_N_SIZE, 1, 8, 8, - NULL, NULL, &msc_reset, - NULL, NULL, NULL, - &msd_dib, DEV_DISABLE - }; - -/* MSC data structures - - msc_dev MSC device descriptor - msc_unit MSC unit list - msc_reg MSC register list - msc_mod MSC modifier list - msc_deb MSC debug flags -*/ - -UNIT msc_unit[] = { - { UDATA (&msc_svc, UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_OFFLINE, 0) }, - { UDATA (&msc_svc, UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_OFFLINE, 0) }, - { UDATA (&msc_svc, UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_OFFLINE, 0) }, - { UDATA (&msc_svc, UNIT_ATTABLE | UNIT_ROABLE | - UNIT_DISABLE | UNIT_OFFLINE, 0) } - }; - -REG msc_reg[] = { - { ORDATA (STA, msc_sta, 12) }, - { ORDATA (BUF, msc_buf, 16) }, - { ORDATA (USEL, msc_usl, 2) }, - { FLDATA (FSVC, msc_1st, 0) }, - { FLDATA (CTL, msc.control, 0) }, - { FLDATA (FLG, msc.flag, 0) }, - { FLDATA (FBF, msc.flagbuf, 0) }, - { URDATA (POS, msc_unit[0].pos, 10, T_ADDR_W, 0, MS_NUMDR, PV_LEFT) }, - { URDATA (FNC, msc_unit[0].FNC, 8, 8, 0, MS_NUMDR, REG_HRO) }, - { URDATA (UST, msc_unit[0].UST, 8, 12, 0, MS_NUMDR, REG_HRO) }, - { URDATA (REEL, msc_unit[0].REEL, 10, 2, 0, MS_NUMDR, REG_HRO) }, - { DRDATA (BTIME, msc_btime, 24), REG_NZ + PV_LEFT }, - { DRDATA (CTIME, msc_ctime, 24), REG_NZ + PV_LEFT }, - { DRDATA (GTIME, msc_gtime, 24), REG_NZ + PV_LEFT }, - { DRDATA (ITIME, msc_itime, 24), REG_NZ + PV_LEFT }, - { DRDATA (RTIME, msc_rtime, 24), REG_NZ + PV_LEFT }, - { DRDATA (XTIME, msc_xtime, 24), REG_NZ + PV_LEFT }, - { FLDATA (TIMING, ms_timing, 0), REG_HRO }, - { FLDATA (STOP_IOE, msc_stopioe, 0) }, - { FLDATA (CTYPE, ms_ctype, 0), REG_HRO }, - { ORDATA (SC, msc_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, msc_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB msc_mod[] = { - { UNIT_OFFLINE, UNIT_OFFLINE, "offline", "OFFLINE", NULL }, - { UNIT_OFFLINE, 0, "online", "ONLINE", msc_online }, - { MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL }, - { MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL }, - { MTAB_XTD | MTAB_VUN, 0, "CAPACITY", "CAPACITY", - &ms_set_reelsize, &ms_show_reelsize, NULL }, - { MTAB_XTD | MTAB_VUN | MTAB_NMO, 1, "REEL", "REEL", - &ms_set_reelsize, &ms_show_reelsize, NULL }, - { MTAB_XTD | MTAB_VUN, 0, "FORMAT", "FORMAT", - &sim_tape_set_fmt, &sim_tape_show_fmt, NULL }, - { MTAB_XTD | MTAB_VDV, 0, NULL, "13181A", - &ms_settype, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 1, NULL, "13183A", - &ms_settype, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 0, "TYPE", NULL, - NULL, &ms_showtype, NULL }, - { MTAB_XTD | MTAB_VDV, 0, NULL, "REALTIME", - &ms_set_timing, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 1, NULL, "FASTTIME", - &ms_set_timing, NULL, NULL }, - { MTAB_XTD | MTAB_VDV, 0, "TIMING", NULL, - NULL, &ms_show_timing, NULL }, - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &msd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &msd_dev }, - { 0 } - }; - -DEBTAB msc_deb[] = { - { "CMDS", DEB_CMDS }, - { "CPU", DEB_CPU }, - { "RWS", DEB_RWS }, - { NULL, 0 } - }; - -DEVICE msc_dev = { - "MSC", msc_unit, msc_reg, msc_mod, - MS_NUMDR, 10, 31, 1, 8, 8, - NULL, NULL, &msc_reset, - &msc_boot, &msc_attach, &msc_detach, - &msc_dib, DEV_DISABLE | DEV_DEBUG | DEV_TAPE, - 0, msc_deb, NULL, NULL - }; - - -/* Data channel I/O signal handler */ - -uint32 msdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -uint32 check; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - case ioCLF: /* clear flag flip-flop */ - msd.flag = msd.flagbuf = CLEAR; - break; - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - msd.flag = msd.flagbuf = SET; - break; - - case ioSFC: /* skip if flag is clear */ - setstdSKF (msd); - break; - - case ioSFS: /* skip if flag is set */ - setstdSKF (msd); - break; - - case ioIOI: /* I/O data input */ - if (ms_crc) { /* ready for CRC? */ - check = calc_crc_lrc (msxb, ms_max); /* calculate CRCC and LRCC */ - msd_buf = check >> 8 & 0177400 /* position CRCC in upper byte */ - | check & 0377; /* and LRCC in lower byte */ - } - - stat_data = IORETURN (SCPE_OK, msd_buf); /* merge in return status */ - break; - - case ioIOO: /* I/O data output */ - msd_buf = IODATA (stat_data); /* store data */ - break; - - case ioPOPIO: /* power-on preset to I/O */ - ms_clear (); /* issue CLR to controller */ - break; - - case ioCRS: /* control reset */ - msd.flag = msd.flagbuf = SET; /* set flag and flag buffer */ - /* fall into CLC handler */ - case ioCLC: /* clear control flip-flop */ - msd.control = CLEAR; - break; - - case ioSTC: /* set control flip-flop */ - ms_crc = FALSE; /* reset CRC ready */ - msd.control = SET; - break; - - case ioEDT: /* end data transfer */ - msd.flag = msd.flagbuf = CLEAR; /* same as CLF */ - break; - - case ioSIR: /* set interrupt request */ - setstdPRL (msd); /* set standard PRL signal */ - setstdIRQ (msd); /* set standard IRQ signal */ - setstdSRQ (msd); /* set standard SRQ signal */ - break; - - case ioIAK: /* interrupt acknowledge */ - msd.flagbuf = CLEAR; - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Command channel I/O signal handler. - - Implementation notes: - - 1. Commands are usually initiated with an STC cc,C instruction. The CLR - command completes immediately and sets the flag. This requires that we - ignore the CLF part (but still process the SIR). - - 2. The command channel card clears its flag and flag buffer on EDT, but as - it never asserts SRQ, it will never get EDT. Under simulation, we omit - the EDT handler. - - 3. In hardware, the command channel card passes PRH to PRL. The data card - actually drives PRL with both channels' control and flag states. That - is, the priority chain is broken at the data card, even when the command - card is interrupting. This works in hardware, but we must break PRL at - the command card under simulation to allow the command card to interrupt. -*/ - -uint32 mscio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -static const uint8 map_sel[16] = { - 0, 0, 1, 1, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3 - }; -uint16 data; -int32 sched_time; -UNIT *uptr = msc_dev.units + msc_usl; - -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - msc.flag = msc.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - msc.flag = msc.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (msc); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (msc); - break; - - - case ioIOI: /* I/O data input */ - data = (uint16) (msc_sta & ~STA_DYN); /* get card status */ - - if ((uptr->flags & UNIT_OFFLINE) == 0) { /* online? */ - data = data | (uint16) uptr->UST; /* add unit status */ - - if (sim_tape_bot (uptr)) /* BOT? */ - data = data | STA_BOT; - - if (sim_is_active (uptr) && /* TBSY unless RWD at BOT */ - !((uptr->FNC & FNF_RWD) && sim_tape_bot (uptr))) - data = data | STA_TBSY; - - if (sim_tape_wrp (uptr)) /* write prot? */ - data = data | STA_WLK; - - if (sim_tape_eot (uptr)) /* EOT? */ - data = data | STA_EOT; - } - - else - data = data | STA_TBSY | STA_LOCAL; - - if (ms_ctype == A13183) /* 13183A? */ - data = data | STA_PE | (uint16) (msc_usl << STA_V_SEL); - - if (DEBUG_PRI (msc_dev, DEB_CPU)) - fprintf (sim_deb, ">>MSC LIx: Status = %06o\n", data); - - stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - msc_buf = IODATA (stat_data); /* clear supplied status */ - - if (DEBUG_PRI (msc_dev, DEB_CPU)) - fprintf (sim_deb, ">>MSC OTx: Command = %06o\n", msc_buf); - - msc_sta = msc_sta & ~STA_REJ; /* clear reject */ - - if ((msc_buf & 0377) == FNC_CLR) /* clear always ok */ - break; - - if (msc_sta & STA_BUSY) { /* busy? reject */ - msc_sta = msc_sta | STA_REJ; /* dont chg select */ - break; - } - - if (msc_buf & FNF_CHS) { /* select change */ - msc_usl = map_sel[FNC_GETSEL (msc_buf)]; /* is immediate */ - uptr = msc_dev.units + msc_usl; - if (DEBUG_PRI (msc_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MSC OTx: Unit %d selected\n", msc_usl); - } - - if (((msc_buf & FNF_MOT) && sim_is_active (uptr)) || - ((msc_buf & FNF_REV) && sim_tape_bot (uptr)) || - ((msc_buf & FNF_WRT) && sim_tape_wrp (uptr))) - msc_sta = msc_sta | STA_REJ; /* reject? */ - - break; - - - case ioCRS: /* control reset */ - msc.flag = msc.flagbuf = SET; /* set flag and flag buffer */ - /* fall into CLC handler */ - - case ioCLC: /* clear control flip-flop */ - msc.control = CLEAR; - break; - - - case ioSTC: /* set control flip-flop */ - if (!(msc_sta & STA_REJ)) { /* last cmd rejected? */ - if ((msc_buf & 0377) == FNC_CLR) { /* clear? */ - ms_clear (); /* issue CLR to controller */ - - msc.control = SET; /* set CTL for STC */ - msc.flag = msc.flagbuf = SET; /* set FLG for completion */ - - working_set = working_set & ~ioCLF; /* eliminate possible CLF */ - - if (DEBUG_PRI (msc_dev, DEB_CMDS)) - fputs (">>MSC STC: Controller cleared\n", sim_deb); - - break; /* command completes immediately */ - } - - uptr->FNC = msc_buf & 0377; /* save function */ - - if (uptr->FNC & FNF_RWD) { /* rewind? */ - if (!sim_tape_bot (uptr)) /* not at BOT? */ - uptr->UST = STA_REW; /* set rewinding */ - - sched_time = msc_rtime; /* set response time */ - } - - else { - if (sim_tape_bot (uptr)) /* at BOT? */ - sched_time = msc_btime; /* use BOT start time */ - - else if ((uptr->FNC == FNC_GAP) || (uptr->FNC == FNC_GFM)) - sched_time = msc_gtime; /* use gap traversal time */ - - else sched_time = 0; - - if (uptr->FNC != FNC_GAP) - sched_time += msc_ctime; /* add base command time */ - } - - if (msc_buf & ~FNC_SEL) { /* NOP for unit sel alone */ - sim_activate (uptr, sched_time); /* else schedule op */ - - if (DEBUG_PRI (msc_dev, DEB_CMDS)) - fprintf (sim_deb, - ">>MSC STC: Unit %d command %03o (%s) scheduled, " - "pos = %" T_ADDR_FMT "d, time = %d\n", - msc_usl, uptr->FNC, ms_cmd_name (uptr->FNC), - uptr->pos, sched_time); - } - - else if (DEBUG_PRI (msc_dev, DEB_CMDS)) - fputs (">>MSC STC: Unit select (NOP)\n", sim_deb); - - msc_sta = STA_BUSY; /* ctrl is busy */ - msc_1st = 1; - msc.control = SET; /* go */ - } - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (msc); /* set standard PRL signal */ - setstdIRQ (msc); /* set standard IRQ signal */ - setstdSRQ (msc); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - msc.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Unit service - - If rewind done, reposition to start of tape, set status - else, do operation, set done, interrupt. - - In addition to decreasing the timing intervals, the FASTTIME option enables - two additional optimizations: WFM for GFM substitution, and BOT gap - elimination. If FASTTIME is selected, gap and file mark (GFM) commands are - processed as WFM (write file mark) commands. That is, the preceding GAP is - not performed. Also, the initial gap that normally precedes the first data - record or EOF mark at the beginning of the tape is omitted. These omissions - result in smaller tape image files. If REALTIME is selected, the gaps are - included. Note that the gaps (and realistic timing) are necessary to pass - the 7970 diagnostics. -*/ - -t_stat msc_svc (UNIT *uptr) -{ -int32 unum; -t_mtrlnt tbc; -t_stat st, r = SCPE_OK; - -unum = uptr - msc_unit; /* get unit number */ - -if ((uptr->FNC != FNC_RWS) && (uptr->flags & UNIT_OFFLINE)) { /* offline? */ - msc_sta = (msc_sta | STA_REJ) & ~STA_BUSY; /* reject */ - mscio (&msc_dib, ioENF, 0); /* set flag */ - return IOERROR (msc_stopioe, SCPE_UNATT); - } - -switch (uptr->FNC) { /* case on function */ - - case FNC_RWS: /* rewind offline */ - sim_tape_rewind (uptr); /* rewind tape */ - uptr->flags = uptr->flags | UNIT_OFFLINE; /* set offline */ - uptr->UST = 0; /* clear REW status */ - break; /* we're done */ - - case FNC_REW: /* rewind */ - if (uptr->UST & STA_REW) { /* rewind in prog? */ - uptr->FNC |= FNC_CMPL; /* set compl state */ - sim_activate (uptr, msc_ctime); /* sched completion */ - } - break; /* anyway, ctrl done */ - - case FNC_REW | FNC_CMPL: /* complete rewind */ - sim_tape_rewind (uptr); /* rewind tape */ - uptr->UST = 0; /* clear REW status */ - return SCPE_OK; /* drive is free */ - - case FNC_GFM: /* gap + file mark */ - if (ms_timing == 1) /* fast timing? */ - goto DO_WFM; /* do plain file mark */ - /* else fall into GAP */ - case FNC_GAP: /* erase gap */ - if (DEBUG_PRI (msc_dev, DEB_RWS)) - fprintf (sim_deb, - ">>MSC svc: Unit %d wrote gap\n", - unum); - r = ms_write_gap (uptr); /* write tape gap*/ - - if (r || (uptr->FNC != FNC_GFM)) /* if error or not GFM */ - break; /* then bail out now */ - /* else drop into WFM */ - case FNC_WFM: /* write file mark */ - if ((ms_timing == 0) && sim_tape_bot (uptr)) { /* realistic timing + BOT? */ - if (DEBUG_PRI (msc_dev, DEB_RWS)) - fprintf (sim_deb, - ">>MSC svc: Unit %d wrote initial gap\n", - unum); - st = ms_write_gap (uptr); /* write initial gap*/ - if (st != MTSE_OK) { /* error? */ - r = ms_map_err (uptr, st); /* map error */ - break; /* terminate operation */ - } - } - DO_WFM: - if (DEBUG_PRI (msc_dev, DEB_RWS)) - fprintf (sim_deb, - ">>MSC svc: Unit %d wrote file mark\n", - unum); - st = sim_tape_wrtmk (uptr); /* write tmk */ - if (st != MTSE_OK) /* error? */ - r = ms_map_err (uptr, st); /* map error */ - msc_sta = STA_EOF; /* set EOF status */ - break; - - case FNC_FSR: /* space forward */ - st = sim_tape_sprecf (uptr, &tbc); /* space rec fwd */ - if (st != MTSE_OK) /* error? */ - r = ms_map_err (uptr, st); /* map error */ - if (tbc & 1) - msc_sta = msc_sta | STA_ODD; - else msc_sta = msc_sta & ~STA_ODD; - break; - - case FNC_BSR: /* space reverse */ - st = sim_tape_sprecr (uptr, &tbc); /* space rec rev*/ - if (st != MTSE_OK) /* error? */ - r = ms_map_err (uptr, st); /* map error */ - if (tbc & 1) - msc_sta = msc_sta | STA_ODD; - else msc_sta = msc_sta & ~STA_ODD; - break; - - case FNC_FSF: /* space fwd file */ - while ((st = sim_tape_sprecf (uptr, &tbc)) == MTSE_OK) { - if (sim_tape_eot (uptr)) break; /* EOT stops */ - } - r = ms_map_err (uptr, st); /* map error */ - break; - - case FNC_BSF: /* space rev file */ - while ((st = sim_tape_sprecr (uptr, &tbc)) == MTSE_OK) ; - r = ms_map_err (uptr, st); /* map error */ - break; - - case FNC_RFF: /* diagnostic read */ - case FNC_RC: /* read */ - if (msc_1st) { /* first svc? */ - msc_1st = ms_ptr = ms_max = 0; /* clr 1st flop */ - st = sim_tape_rdrecf (uptr, msxb, &ms_max, DBSIZE); /* read rec */ - if (DEBUG_PRI (msc_dev, DEB_RWS)) - fprintf (sim_deb, - ">>MSC svc: Unit %d read %d word record\n", - unum, ms_max / 2); - if (st == MTSE_RECE) msc_sta = msc_sta | STA_PAR; /* rec in err? */ - else if (st != MTSE_OK) { /* other error? */ - r = ms_map_err (uptr, st); /* map error */ - if (r == SCPE_OK) { /* recoverable? */ - sim_activate (uptr, msc_itime); /* sched IRG */ - uptr->FNC |= FNC_CMPL; /* set completion */ - return SCPE_OK; - } - break; /* err, done */ - } - if (ms_ctype == A13183) - msc_sta = msc_sta | STA_ODD; /* set ODD for 13183A */ - } - if (msd.control && (ms_ptr < ms_max)) { /* DCH on, more data? */ - if (msd.flag) msc_sta = msc_sta | STA_TIM | STA_PAR; - msd_buf = ((uint16) msxb[ms_ptr] << 8) | - ((ms_ptr + 1 == ms_max) ? 0 : msxb[ms_ptr + 1]); - ms_ptr = ms_ptr + 2; - msdio (&msd_dib, ioENF, 0); /* set flag */ - sim_activate (uptr, msc_xtime); /* re-activate */ - return SCPE_OK; - } - if (ms_max & 1) /* set ODD by rec len */ - msc_sta = msc_sta | STA_ODD; - else msc_sta = msc_sta & ~STA_ODD; - sim_activate (uptr, msc_itime); /* sched IRG */ - if (uptr->FNC == FNC_RFF) /* diagnostic? */ - msc_1st = 1; /* restart */ - else { - uptr->FNC |= FNC_CMPL; /* set completion */ - ms_crc = TRUE; /* and CRC ready */ - } - return SCPE_OK; - - case FNC_RFF | FNC_CMPL: /* diagnostic read completion */ - case FNC_RC | FNC_CMPL: /* read completion */ - break; - - case FNC_WC: /* write */ - if (msc_1st) { /* first service? */ - msc_1st = ms_ptr = 0; /* no data xfer on first svc */ - if ((ms_timing == 0) && sim_tape_bot (uptr)) { /* realistic timing + BOT? */ - if (DEBUG_PRI (msc_dev, DEB_RWS)) - fprintf (sim_deb, - ">>MSC svc: Unit %d wrote initial gap\n", - unum); - st = ms_write_gap (uptr); /* write initial gap */ - if (st != MTSE_OK) { /* error? */ - r = ms_map_err (uptr, st); /* map error */ - break; /* terminate operation */ - } - } - } - else { /* not 1st, next char */ - if (ms_ptr < DBSIZE) { /* room in buffer? */ - msxb[ms_ptr] = (uint8) (msd_buf >> 8); /* store 2 char */ - msxb[ms_ptr + 1] = msd_buf & 0377; - ms_ptr = ms_ptr + 2; - } - else msc_sta = msc_sta | STA_PAR; - } - if (msd.control) { /* xfer flop set? */ - msdio (&msd_dib, ioENF, 0); /* set flag */ - sim_activate (uptr, msc_xtime); /* re-activate */ - return SCPE_OK; - } - if (ms_ptr) { /* any data? write */ - if (DEBUG_PRI (msc_dev, DEB_RWS)) - fprintf (sim_deb, - ">>MSC svc: Unit %d wrote %d word record\n", - unum, ms_ptr / 2); - st = sim_tape_wrrecf (uptr, msxb, ms_ptr); /* write */ - if (st != MTSE_OK) { - r = ms_map_err (uptr, st); /* map error */ - break; - } - } - sim_activate (uptr, msc_itime); /* sched IRG */ - uptr->FNC |= FNC_CMPL; /* set completion */ - ms_max = ms_ptr; /* indicate buffer complete */ - ms_crc = TRUE; /* and CRC may be generated */ - return SCPE_OK; - - case FNC_WC | FNC_CMPL: /* write completion */ - break; - - case FNC_RRR: /* not supported */ - default: /* unknown command */ - if (DEBUG_PRI (msc_dev, DEB_CMDS)) - fprintf (sim_deb, - ">>MSC svc: Unit %d command %03o is unknown (NOP)\n", - unum, uptr->FNC); - break; - } - -mscio (&msc_dib, ioENF, 0); /* set flag */ -msc_sta = msc_sta & ~STA_BUSY; /* update status */ -if (DEBUG_PRI (msc_dev, DEB_CMDS)) - fprintf (sim_deb, - ">>MSC svc: Unit %d command %03o (%s) complete\n", - unum, uptr->FNC & 0377, ms_cmd_name (uptr->FNC)); -return r; -} - - -/* Write an erase gap */ - -t_stat ms_write_gap (UNIT *uptr) -{ -t_stat st; -uint32 gap_len = ms_ctype ? GAP_13183 : GAP_13181; /* establish gap length */ - -st = sim_tape_wrgap (uptr, gap_len); /* write gap */ - -if (st != MTSE_OK) - return ms_map_err (uptr, st); /* map error if failure */ -else - return SCPE_OK; -} - - -/* Map tape error status */ - -t_stat ms_map_err (UNIT *uptr, t_stat st) -{ -int32 unum = uptr - msc_unit; /* get unit number */ - -if (DEBUG_PRI (msc_dev, DEB_RWS)) - fprintf (sim_deb, - ">>MSC err: Unit %d tape library status = %d\n", - unum, st); - -switch (st) { - - case MTSE_FMT: /* illegal fmt */ - msc_sta = msc_sta | STA_REJ; /* reject cmd */ - return SCPE_FMT; /* format error */ - - case MTSE_UNATT: /* unattached */ - msc_detach (uptr); /* resync status (ignore rtn) */ - msc_sta = msc_sta | STA_REJ; /* reject cmd */ - return SCPE_UNATT; /* unit unattached */ - - case MTSE_OK: /* no error */ - return SCPE_IERR; /* never get here! */ - - case MTSE_EOM: /* end of medium */ - case MTSE_TMK: /* end of file */ - msc_sta = msc_sta | STA_EOF; - - if (ms_ctype == A13181) - msc_sta = msc_sta | STA_ODD; /* EOF also sets ODD for 13181A */ - break; - - case MTSE_INVRL: /* invalid rec lnt */ - msc_sta = msc_sta | STA_PAR; - return SCPE_MTRLNT; - - case MTSE_IOERR: /* IO error */ - msc_sta = msc_sta | STA_PAR; /* error */ - if (msc_stopioe) return SCPE_IOERR; - break; - - case MTSE_RECE: /* record in error */ - msc_sta = msc_sta | STA_PAR; /* error */ - break; - - case MTSE_WRP: /* write protect */ - msc_sta = msc_sta | STA_REJ; /* reject */ - break; - } - -return SCPE_OK; -} - - -/* Controller clear */ - -t_stat ms_clear (void) -{ -int32 i; -t_stat st; -UNIT *uptr; - -for (i = 0; i < MS_NUMDR; i++) { /* look for write in progr */ - uptr = &msc_unit [i]; /* get pointer to unit */ - - if (sim_is_active (uptr) && /* unit active? */ - (uptr->FNC == FNC_WC) && /* and last cmd write? */ - (ms_ptr > 0)) { /* and partial buffer? */ - if (DEBUG_PRI (msc_dev, DEB_RWS)) - fprintf (sim_deb, - ">>MSC rws: Unit %d wrote %d word partial record\n", i, ms_ptr / 2); - - st = sim_tape_wrrecf (uptr, msxb, ms_ptr | MTR_ERF); - - if (st != MTSE_OK) - ms_map_err (uptr, st); /* discard any error */ - - ms_ptr = 0; /* clear partial */ - } - - if ((uptr->UST & STA_REW) == 0) - sim_cancel (uptr); /* stop if not rew */ - } - -msc_sta = msc_1st = 0; /* clr ctlr status */ - -return SCPE_OK; -} - - -/* Reset routine */ - -t_stat msc_reset (DEVICE *dptr) -{ -int32 i; -UNIT *uptr; -DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ - -hp_enbdis_pair (dptr, /* make pair cons */ - (dptr == &msd_dev) ? &msc_dev : &msd_dev); - -if (sim_switches & SWMASK ('P')) /* initialization reset? */ - ms_config_timing (); - -IOPRESET (dibptr); /* PRESET device (does not use PON) */ - -msc_buf = msd_buf = 0; -msc_sta = msc_usl = 0; -msc_1st = 0; - -for (i = 0; i < MS_NUMDR; i++) { - uptr = msc_dev.units + i; - sim_tape_reset (uptr); - sim_cancel (uptr); - uptr->UST = 0; - - if (sim_switches & SWMASK ('P')) /* if this is an initialization reset */ - sim_tape_set_dens (uptr, /* then tell the tape library the density in use */ - ms_ctype ? BPI_13183 : BPI_13181, - NULL, NULL); - } - -return SCPE_OK; -} - - -/* Attach routine */ - -t_stat msc_attach (UNIT *uptr, char *cptr) -{ -t_stat r; - -r = sim_tape_attach (uptr, cptr); /* attach unit */ -if (r == SCPE_OK) - uptr->flags = uptr->flags & ~UNIT_OFFLINE; /* set online */ -return r; -} - -/* Detach routine */ - -t_stat msc_detach (UNIT* uptr) -{ -uptr->UST = 0; /* clear status */ -uptr->flags = uptr->flags | UNIT_OFFLINE; /* set offline */ -return sim_tape_detach (uptr); /* detach unit */ -} - -/* Online routine */ - -t_stat msc_online (UNIT *uptr, int32 value, char *cptr, void *desc) -{ -if (uptr->flags & UNIT_ATT) return SCPE_OK; -else return SCPE_UNATT; -} - -/* Configure timing */ - -void ms_config_timing (void) -{ -uint32 i, tset; - -tset = (ms_timing << 1) | (ms_timing ? 0 : ms_ctype); /* select timing set */ -for (i = 0; i < (sizeof (timers) / sizeof (timers[0])); i++) - *timers[i] = msc_times[tset][i]; /* assign times */ -} - -/* Set controller timing */ - -t_stat ms_set_timing (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -if ((val < 0) || (val > 1) || (cptr != NULL)) return SCPE_ARG; -ms_timing = val; -ms_config_timing (); -return SCPE_OK; -} - -/* Show controller timing */ - -t_stat ms_show_timing (FILE *st, UNIT *uptr, int32 val, void *desc) -{ -if (ms_timing) fputs ("fast timing", st); -else fputs ("realistic timing", st); -return SCPE_OK; -} - -/* Set controller type */ - -t_stat ms_settype (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -int32 i; - -if ((val < 0) || (val > 1) || (cptr != NULL)) return SCPE_ARG; -for (i = 0; i < MS_NUMDR; i++) { - if (msc_unit[i].flags & UNIT_ATT) return SCPE_ALATT; - } -ms_ctype = (CNTLR_TYPE) val; -ms_config_timing (); /* update for new type */ - -sim_tape_set_dens (uptr, ms_ctype ? BPI_13183 : BPI_13181, /* tell the tape library the density in use */ - NULL, NULL); - -return SCPE_OK; -} - -/* Show controller type */ - -t_stat ms_showtype (FILE *st, UNIT *uptr, int32 val, void *desc) -{ -if (ms_ctype == A13183) - fprintf (st, "13183A"); -else - fprintf (st, "13181A"); -return SCPE_OK; -} - -/* Set unit reel size - - val = 0 -> SET MSCn CAPACITY=n - val = 1 -> SET MSCn REEL=n */ - -t_stat ms_set_reelsize (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -int32 reel; -t_stat status; - -if (val == 0) { - status = sim_tape_set_capac (uptr, val, cptr, desc); - if (status == SCPE_OK) uptr->REEL = 0; - return status; - } - -if (cptr == NULL) return SCPE_ARG; -reel = (int32) get_uint (cptr, 10, 2400, &status); -if (status != SCPE_OK) return status; -else switch (reel) { - - case 0: - uptr->REEL = 0; /* type 0 = unlimited/custom */ - break; - - case 600: - uptr->REEL = 1; /* type 1 = 600 foot */ - break; - - case 1200: - uptr->REEL = 2; /* type 2 = 1200 foot */ - break; - - case 2400: - uptr->REEL = 3; /* type 3 = 2400 foot */ - break; - - default: - return SCPE_ARG; - } - -uptr->capac = uptr->REEL ? (TCAP << uptr->REEL) << ms_ctype : 0; -return SCPE_OK; -} - -/* Show unit reel size - - val = 0 -> SHOW MSC or SHOW MSCn or SHOW MSCn CAPACITY - val = 1 -> SHOW MSCn REEL */ - -t_stat ms_show_reelsize (FILE *st, UNIT *uptr, int32 val, void *desc) -{ -t_stat status = SCPE_OK; - -if (uptr->REEL == 0) status = sim_tape_show_capac (st, uptr, val, desc); -else fprintf (st, "%4d foot reel", 300 << uptr->REEL); -if (val == 1) fputc ('\n', st); /* MTAB_NMO omits \n */ -return status; -} - -/* Translate command to mnemonic for debug logging - - The command names and descriptions are taken from the 13181 interface - manual. */ - -char *ms_cmd_name (uint32 cmd) -{ - -switch (cmd & 0377) { - case FNC_WC: return "WCC"; /* Write command */ - case FNC_WFM: return "WFM"; /* Write file mark */ - case FNC_RC: return "RRF"; /* Read record forward */ - case FNC_FSR: return "FSR"; /* Forward space record */ - case FNC_FSF: return "FSF"; /* Forward space file */ - case FNC_GAP: return "GAP"; /* Write gap */ - case FNC_BSR: return "BSR"; /* Backspace record */ - case FNC_BSF: return "BSF"; /* Backspace file */ - case FNC_REW: return "REW"; /* Rewind */ - case FNC_RWS: return "RWO"; /* Rewind off-line */ - case FNC_CLR: return "CLR"; /* Clear controller */ - case FNC_GFM: return "GFM"; /* Gap file mark */ - case FNC_RFF: return "RFF"; /* Read forward until file mark (diag) */ - case FNC_RRR: return "RRR"; /* Read record in reverse (diag) */ - - default: return "???"; /* Unknown command */ - } -} - -/* 7970B/7970E bootstrap routine (HP 12992D ROM) */ - -const BOOT_ROM ms_rom = { - 0106501, /*ST LIB 1 ; read sw */ - 0006011, /* SLB,RSS ; bit 0 set? */ - 0027714, /* JMP RD ; no read */ - 0003004, /* CMA,INA ; A is ctr */ - 0073775, /* STA WC ; save */ - 0067772, /* LDA SL0RW ; sel 0, rew */ - 0017762, /*FF JSB CMD ; do cmd */ - 0102311, /* SFS CC ; done? */ - 0027707, /* JMP *-1 ; wait */ - 0067774, /* LDB FFC ; get file fwd */ - 0037775, /* ISZ WC ; done files? */ - 0027706, /* JMP FF ; no */ - 0067773, /*RD LDB RDCMD ; read cmd */ - 0017762, /* JSB CMD ; do cmd */ - 0103710, /* STC DC,C ; start dch */ - 0102211, /* SFC CC ; read done? */ - 0027752, /* JMP STAT ; no, get stat */ - 0102310, /* SFS DC ; any data? */ - 0027717, /* JMP *-3 ; wait */ - 0107510, /* LIB DC,C ; get rec cnt */ - 0005727, /* BLF,BLF ; move to lower */ - 0007000, /* CMB ; make neg */ - 0077775, /* STA WC ; save */ - 0102211, /* SFC CC ; read done? */ - 0027752, /* JMP STAT ; no, get stat */ - 0102310, /* SFS DC ; any data? */ - 0027727, /* JMP *-3 ; wait */ - 0107510, /* LIB DC,C ; get load addr */ - 0074000, /* STB 0 ; start csum */ - 0077762, /* STA CMD ; save address */ - 0027742, /* JMP *+4 */ - 0177762, /*NW STB CMD,I ; store data */ - 0040001, /* ADA 1 ; add to csum */ - 0037762, /* ISZ CMD ; adv addr ptr */ - 0102310, /* SFS DC ; any data? */ - 0027742, /* JMP *-1 ; wait */ - 0107510, /* LIB DC,C ; get word */ - 0037775, /* ISZ WC ; done? */ - 0027737, /* JMP NW ; no */ - 0054000, /* CPB 0 ; csum ok? */ - 0027717, /* JMP RD+3 ; yes, cont */ - 0102011, /* HLT 11 ; no, halt */ - 0102511, /*ST LIA CC ; get status */ - 0001727, /* ALF,ALF ; get eof bit */ - 0002020, /* SSA ; set? */ - 0102077, /* HLT 77 ; done */ - 0001727, /* ALF,ALF ; put status back */ - 0001310, /* RAR,SLA ; read ok? */ - 0102000, /* HLT 0 ; no */ - 0027714, /* JMP RD ; read next */ - 0000000, /*CMD 0 */ - 0106611, /* OTB CC ; output cmd */ - 0102511, /* LIA CC ; check for reject */ - 0001323, /* RAR,RAR */ - 0001310, /* RAR,SLA */ - 0027763, /* JMP CMD+1 ; try again */ - 0103711, /* STC CC,C ; start command */ - 0127762, /* JMP CMD,I ; exit */ - 0001501, /*SL0RW 001501 ; select 0, rewind */ - 0001423, /*RDCMD 001423 ; read record */ - 0000203, /*FFC 000203 ; space forward file */ - 0000000, /*WC 000000 */ - 0000000, - 0000000 - }; - -t_stat msc_boot (int32 unitno, DEVICE *dptr) -{ -const int32 dev = msd_dib.select_code; /* get data chan device no */ - -if (unitno != 0) /* boot supported on drive unit 0 only */ - return SCPE_NOFNC; /* report "Command not allowed" if attempted */ - -if (ibl_copy (ms_rom, dev, IBL_OPT, /* copy the boot ROM to memory and configure */ - IBL_MS | IBL_SET_SC (dev))) /* the S register accordingly */ - return SCPE_IERR; /* return an internal error if the copy failed */ - -if ((sim_switches & SWMASK ('S')) && AR) /* if -S is specified and the A register is non-zero */ - SR = SR | 1; /* then set to skip to the file number in A */ - -return SCPE_OK; -} - -/* Calculate tape record CRC and LRC characters */ - -#define E 0400 /* parity bit for odd parity */ -#define O 0000 /* parity bit for odd parity */ - -static const uint16 odd_parity [256] = { /* parity table */ - E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 000-017 */ - O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 020-037 */ - O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 040-067 */ - E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 060-077 */ - O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 100-117 */ - E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 120-137 */ - E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 140-157 */ - O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 160-177 */ - O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 200-217 */ - E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 220-237 */ - E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 240-267 */ - O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 260-277 */ - E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 300-317 */ - O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 320-337 */ - O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 340-357 */ - E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E /* 360-377 */ - }; - -static uint32 calc_crc_lrc (uint8 *buffer, t_mtrlnt length) -{ -uint32 i; -uint16 byte, crc, lrc; - -lrc = crc = 0; - -for (i = 0; i < length; i++) { - byte = odd_parity [buffer [i]] | buffer [i]; - - crc = crc ^ byte; - lrc = lrc ^ byte; - - if (crc & 1) - crc = crc >> 1 ^ 0474; - else - crc = crc >> 1; - } - -crc = crc ^ 0727; -lrc = lrc ^ crc; - -return (uint32) crc << 16 | lrc; -} +/* hp2100_ms.c: HP 2100 13181A/13183A magnetic tape simulator + + Copyright (c) 1993-2016, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + MS 13181A 7970B 800bpi nine track magnetic tape + 13183A 7970E 1600bpi nine track magnetic tape + + 13-May-16 JDB Modified for revised SCP API function parameter types + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 24-Dec-14 JDB Use T_ADDR_FMT with t_addr values for 64-bit compatibility + Added casts for explicit downward conversions + 11-Dec-14 JDB Updated for new erase gap API, added CRCC/LRCC support + 10-Jan-13 MP Added DEV_TAPE to DEVICE flags + 09-May-12 JDB Separated assignments from conditional expressions + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + Added CNTLR_TYPE cast to ms_settype + 28-Mar-11 JDB Tidied up signal handling + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 11-Aug-08 JDB Revised to use AR instead of saved_AR in boot + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 18-Sep-06 JDB Fixed 2nd CLR after WC causing another write + Improve debug reporting, add debug flags + 14-Sep-06 JDB Removed local BOT flag, now uses sim_tape_bot + 30-Aug-06 JDB Added erase gap support, improved tape lib err reporting + 07-Jul-06 JDB Added CAPACITY as alternate for REEL + Fixed EOT test for unlimited reel size + 16-Feb-06 RMS Revised for new EOT test + 22-Jul-05 RMS Fixed compiler warning on Solaris (from Doug Glyn) + 01-Mar-05 JDB Added SET OFFLINE; rewind/offline now does not detach + 07-Oct-04 JDB Fixed enable/disable from either device + 14-Aug-04 JDB Fixed many functional and timing problems (from Dave Bryan) + - fixed erroneous execution of rejected command + - fixed erroneous execution of select-only command + - fixed erroneous execution of clear command + - fixed odd byte handling for read + - fixed spurious odd byte status on 13183A EOF + - modified handling of end of medium + - added detailed timing, with fast and realistic modes + - added reel sizes to simulate end of tape + - added debug printouts + 06-Jul-04 RMS Fixed spurious timing error after CLC (found by Dave Bryan) + 26-Apr-04 RMS Fixed SFS x,C and SFC x,C + Fixed SR setting in IBL + Revised IBL loader + Implemented DMA SRQ (follows FLG) + 25-Apr-03 RMS Revised for extended file support + 28-Mar-03 RMS Added multiformat support + 28-Feb-03 RMS Revised for magtape library + 18-Oct-02 RMS Added BOOT command, added 13183A support + 30-Sep-02 RMS Revamped error handling + 29-Aug-02 RMS Added end of medium support + 30-May-02 RMS Widened POS to 32b + 22-Apr-02 RMS Added maximum record length test + + References: + - 13181B Digital Magnetic Tape Unit Interface Kit Operating and Service Manual + (13181-90901, Nov-1982) + - 13183B Digital Magnetic Tape Unit Interface Kit Operating and Service Manual + (13183-90901, Nov-1983) + - SIMH Magtape Representation and Handling (Bob Supnik, 30-Aug-2006) +*/ + + +#include "hp2100_defs.h" +#include "hp2100_cpu.h" +#include "sim_tape.h" + +#define UNIT_V_OFFLINE (MTUF_V_UF + 0) /* unit offline */ +#define UNIT_OFFLINE (1 << UNIT_V_OFFLINE) + +#define MS_NUMDR 4 /* number of drives */ +#define DB_N_SIZE 16 /* max data buf */ +#define DBSIZE (1 << DB_N_SIZE) /* max data cmd */ +#define FNC u3 /* function */ +#define UST u4 /* unit status */ +#define REEL u5 /* tape reel size */ + +#define BPI_13181 MT_DENS_800 /* 800 bpi for 13181 cntlr */ +#define BPI_13183 MT_DENS_1600 /* 1600 bpi for 13183 cntlr */ +#define GAP_13181 48 /* gap is 4.8 inches for 13181 cntlr */ +#define GAP_13183 30 /* gap is 3.0 inches for 13183 cntlr */ +#define TCAP (300 * 12 * 800) /* 300 ft capacity at 800 bpi */ + +/* Debug flags */ + +#define DEB_CMDS (1 << 0) /* command init and compl */ +#define DEB_CPU (1 << 1) /* CPU I/O */ +#define DEB_RWS (1 << 2) /* tape reads, writes, status */ + +/* Command - msc_fnc */ + +#define FNC_CLR 00110 /* clear */ +#define FNC_GAP 00015 /* write gap */ +#define FNC_GFM 00215 /* gap+file mark */ +#define FNC_RC 00023 /* read */ +#define FNC_WC 00031 /* write */ +#define FNC_FSR 00003 /* forward space */ +#define FNC_BSR 00041 /* backward space */ +#define FNC_FSF 00203 /* forward file */ +#define FNC_BSF 00241 /* backward file */ +#define FNC_REW 00101 /* rewind */ +#define FNC_RWS 00105 /* rewind and offline */ +#define FNC_WFM 00211 /* write file mark */ +#define FNC_RFF 00223 /* read file fwd (diag) */ +#define FNC_RRR 00061 /* read record rev (diag) */ +#define FNC_CMPL 00400 /* completion state */ +#define FNC_V_SEL 9 /* select */ +#define FNC_M_SEL 017 +#define FNC_GETSEL(x) (((x) >> FNC_V_SEL) & FNC_M_SEL) + +#define FNF_MOT 00001 /* motion */ +#define FNF_OFL 00004 +#define FNF_WRT 00010 /* write */ +#define FNF_REV 00040 /* reverse */ +#define FNF_RWD 00100 /* rewind */ +#define FNF_CHS 00400 /* change select */ + +#define FNC_SEL ((FNC_M_SEL << FNC_V_SEL) | FNF_CHS) + +/* Status - stored in msc_sta, unit.UST (u), or dynamic (d) */ + +#define STA_PE 0100000 /* 1600 bpi (d) */ +#define STA_V_SEL 13 /* unit sel (d) */ +#define STA_M_SEL 03 +#define STA_SEL (STA_M_SEL << STA_V_SEL) +#define STA_ODD 0004000 /* odd bytes */ +#define STA_REW 0002000 /* rewinding (u) */ +#define STA_TBSY 0001000 /* transport busy (d) */ +#define STA_BUSY 0000400 /* ctrl busy */ +#define STA_EOF 0000200 /* end of file */ +#define STA_BOT 0000100 /* beg of tape (d) */ +#define STA_EOT 0000040 /* end of tape (d) */ +#define STA_TIM 0000020 /* timing error */ +#define STA_REJ 0000010 /* programming error */ +#define STA_WLK 0000004 /* write locked (d) */ +#define STA_PAR 0000002 /* parity error */ +#define STA_LOCAL 0000001 /* local (d) */ +#define STA_DYN (STA_PE | STA_SEL | STA_TBSY | STA_BOT | \ + STA_EOT | STA_WLK | STA_LOCAL) + +/* Controller types */ + +typedef enum { + A13181, + A13183 + } CNTLR_TYPE; + +CNTLR_TYPE ms_ctype = A13181; /* ctrl type */ +int32 ms_timing = 1; /* timing type */ + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } msc = { CLEAR, CLEAR, CLEAR }; + +int32 msc_sta = 0; /* status */ +int32 msc_buf = 0; /* buffer */ +int32 msc_usl = 0; /* unit select */ +int32 msc_1st = 0; /* first service */ +int32 msc_stopioe = 1; /* stop on error */ + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } msd = { CLEAR, CLEAR, CLEAR }; + +int32 msd_buf = 0; /* data buffer */ +uint8 msxb[DBSIZE] = { 0 }; /* data buffer */ +t_mtrlnt ms_ptr = 0, ms_max = 0; /* buffer ptrs */ +t_bool ms_crc = FALSE; /* buffer ready for CRC calc */ + + +/* Hardware timing at 45 IPS 13181 13183 + (based on 1580 instr/msec) instr msec SCP instr msec SCP + -------------------- -------------------- + - BOT start delay : btime = 161512 102.22 184 252800 160.00 288 + - motion cmd start delay : ctime = 14044 8.89 16 17556 11.11 20 + - GAP traversal time : gtime = 175553 111.11 200 105333 66.67 120 + - IRG traversal time : itime = 24885 15.75 - 27387 17.33 - + - rewind initiation time : rtime = 878 0.56 1 878 0.56 1 + - data xfer time / word : xtime = 88 55.56us - 44 27.78us - + + NOTE: The 13181-60001 Rev. 1629 tape diagnostic fails test 17B subtest 6 with + "E116 BYTE TIME SHORT" if the correct data transfer time is used for + 13181A interface. Set "xtime" to 115 (instructions) to pass that + diagnostic. Rev. 2040 of the tape diagnostic fixes this problem and + passes with the correct data transfer time. +*/ + +int32 msc_btime = 0; /* BOT start delay */ +int32 msc_ctime = 0; /* motion cmd start delay */ +int32 msc_gtime = 0; /* GAP traversal time */ +int32 msc_itime = 0; /* IRG traversal time */ +int32 msc_rtime = 0; /* rewind initiation time */ +int32 msc_xtime = 0; /* data xfer time / word */ + +typedef int32 TIMESET[6]; /* set of controller times */ + +int32 *const timers[] = { &msc_btime, &msc_ctime, &msc_gtime, + &msc_itime, &msc_rtime, &msc_xtime }; + +const TIMESET msc_times[3] = { + { 161512, 14044, 175553, 24885, 878, 88 }, /* 13181A */ + { 252800, 17556, 105333, 27387, 878, 44 }, /* 13183A */ + { 1, 1000, 1, 1, 100, 10 } /* FAST */ + }; + +DEVICE msd_dev, msc_dev; + +IOHANDLER msdio; +IOHANDLER mscio; + +t_stat msc_svc (UNIT *uptr); +t_stat msc_reset (DEVICE *dptr); +t_stat msc_attach (UNIT *uptr, CONST char *cptr); +t_stat msc_detach (UNIT *uptr); +t_stat msc_online (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +t_stat msc_boot (int32 unitno, DEVICE *dptr); +t_stat ms_write_gap (UNIT *uptr); +t_stat ms_map_err (UNIT *uptr, t_stat st); +t_stat ms_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat ms_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat ms_set_timing (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat ms_show_timing (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +t_stat ms_set_reelsize (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat ms_show_reelsize (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +void ms_config_timing (void); +char *ms_cmd_name (uint32 cmd); +t_stat ms_clear (void); +static uint32 calc_crc_lrc (uint8 *buffer, t_mtrlnt length); + + +/* MSD data structures + + msd_dev MSD device descriptor + msd_unit MSD unit list + msd_reg MSD register list +*/ + +DIB ms_dib[] = { + { &msdio, MSD }, + { &mscio, MSC } + }; + +#define msd_dib ms_dib[0] +#define msc_dib ms_dib[1] + +UNIT msd_unit = { UDATA (NULL, 0, 0) }; + +REG msd_reg[] = { + { ORDATA (BUF, msd_buf, 16) }, + { FLDATA (CTL, msd.control, 0) }, + { FLDATA (FLG, msd.flag, 0) }, + { FLDATA (FBF, msd.flagbuf, 0) }, + { BRDATA (DBUF, msxb, 8, 8, DBSIZE) }, + { DRDATA (BPTR, ms_ptr, DB_N_SIZE + 1) }, + { DRDATA (BMAX, ms_max, DB_N_SIZE + 1) }, + { ORDATA (SC, msd_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, msd_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB msd_mod[] = { + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &msd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &msd_dev }, + { 0 } + }; + +DEVICE msd_dev = { + "MSD", &msd_unit, msd_reg, msd_mod, + 1, 10, DB_N_SIZE, 1, 8, 8, + NULL, NULL, &msc_reset, + NULL, NULL, NULL, + &msd_dib, DEV_DISABLE + }; + +/* MSC data structures + + msc_dev MSC device descriptor + msc_unit MSC unit list + msc_reg MSC register list + msc_mod MSC modifier list + msc_deb MSC debug flags +*/ + +UNIT msc_unit[] = { + { UDATA (&msc_svc, UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_OFFLINE, 0) }, + { UDATA (&msc_svc, UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_OFFLINE, 0) }, + { UDATA (&msc_svc, UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_OFFLINE, 0) }, + { UDATA (&msc_svc, UNIT_ATTABLE | UNIT_ROABLE | + UNIT_DISABLE | UNIT_OFFLINE, 0) } + }; + +REG msc_reg[] = { + { ORDATA (STA, msc_sta, 12) }, + { ORDATA (BUF, msc_buf, 16) }, + { ORDATA (USEL, msc_usl, 2) }, + { FLDATA (FSVC, msc_1st, 0) }, + { FLDATA (CTL, msc.control, 0) }, + { FLDATA (FLG, msc.flag, 0) }, + { FLDATA (FBF, msc.flagbuf, 0) }, + { URDATA (POS, msc_unit[0].pos, 10, T_ADDR_W, 0, MS_NUMDR, PV_LEFT) }, + { URDATA (FNC, msc_unit[0].FNC, 8, 8, 0, MS_NUMDR, REG_HRO) }, + { URDATA (UST, msc_unit[0].UST, 8, 12, 0, MS_NUMDR, REG_HRO) }, + { URDATA (REEL, msc_unit[0].REEL, 10, 2, 0, MS_NUMDR, REG_HRO) }, + { DRDATA (BTIME, msc_btime, 24), REG_NZ + PV_LEFT }, + { DRDATA (CTIME, msc_ctime, 24), REG_NZ + PV_LEFT }, + { DRDATA (GTIME, msc_gtime, 24), REG_NZ + PV_LEFT }, + { DRDATA (ITIME, msc_itime, 24), REG_NZ + PV_LEFT }, + { DRDATA (RTIME, msc_rtime, 24), REG_NZ + PV_LEFT }, + { DRDATA (XTIME, msc_xtime, 24), REG_NZ + PV_LEFT }, + { FLDATA (TIMING, ms_timing, 0), REG_HRO }, + { FLDATA (STOP_IOE, msc_stopioe, 0) }, + { FLDATA (CTYPE, ms_ctype, 0), REG_HRO }, + { ORDATA (SC, msc_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, msc_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB msc_mod[] = { + { UNIT_OFFLINE, UNIT_OFFLINE, "offline", "OFFLINE", NULL }, + { UNIT_OFFLINE, 0, "online", "ONLINE", msc_online }, + { MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL }, + { MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL }, + { MTAB_XTD | MTAB_VUN, 0, "CAPACITY", "CAPACITY", + &ms_set_reelsize, &ms_show_reelsize, NULL }, + { MTAB_XTD | MTAB_VUN | MTAB_NMO, 1, "REEL", "REEL", + &ms_set_reelsize, &ms_show_reelsize, NULL }, + { MTAB_XTD | MTAB_VUN, 0, "FORMAT", "FORMAT", + &sim_tape_set_fmt, &sim_tape_show_fmt, NULL }, + { MTAB_XTD | MTAB_VDV, 0, NULL, "13181A", + &ms_settype, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 1, NULL, "13183A", + &ms_settype, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 0, "TYPE", NULL, + NULL, &ms_showtype, NULL }, + { MTAB_XTD | MTAB_VDV, 0, NULL, "REALTIME", + &ms_set_timing, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 1, NULL, "FASTTIME", + &ms_set_timing, NULL, NULL }, + { MTAB_XTD | MTAB_VDV, 0, "TIMING", NULL, + NULL, &ms_show_timing, NULL }, + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &msd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &msd_dev }, + { 0 } + }; + +DEBTAB msc_deb[] = { + { "CMDS", DEB_CMDS }, + { "CPU", DEB_CPU }, + { "RWS", DEB_RWS }, + { NULL, 0 } + }; + +DEVICE msc_dev = { + "MSC", msc_unit, msc_reg, msc_mod, + MS_NUMDR, 10, 31, 1, 8, 8, + NULL, NULL, &msc_reset, + &msc_boot, &msc_attach, &msc_detach, + &msc_dib, DEV_DISABLE | DEV_DEBUG | DEV_TAPE, + 0, msc_deb, NULL, NULL + }; + + +/* Data channel I/O signal handler */ + +uint32 msdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +uint32 check; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + case ioCLF: /* clear flag flip-flop */ + msd.flag = msd.flagbuf = CLEAR; + break; + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + msd.flag = msd.flagbuf = SET; + break; + + case ioSFC: /* skip if flag is clear */ + setstdSKF (msd); + break; + + case ioSFS: /* skip if flag is set */ + setstdSKF (msd); + break; + + case ioIOI: /* I/O data input */ + if (ms_crc) { /* ready for CRC? */ + check = calc_crc_lrc (msxb, ms_max); /* calculate CRCC and LRCC */ + msd_buf = check >> 8 & 0177400 /* position CRCC in upper byte */ + | check & 0377; /* and LRCC in lower byte */ + } + + stat_data = IORETURN (SCPE_OK, msd_buf); /* merge in return status */ + break; + + case ioIOO: /* I/O data output */ + msd_buf = IODATA (stat_data); /* store data */ + break; + + case ioPOPIO: /* power-on preset to I/O */ + ms_clear (); /* issue CLR to controller */ + break; + + case ioCRS: /* control reset */ + msd.flag = msd.flagbuf = SET; /* set flag and flag buffer */ + /* fall into CLC handler */ + case ioCLC: /* clear control flip-flop */ + msd.control = CLEAR; + break; + + case ioSTC: /* set control flip-flop */ + ms_crc = FALSE; /* reset CRC ready */ + msd.control = SET; + break; + + case ioEDT: /* end data transfer */ + msd.flag = msd.flagbuf = CLEAR; /* same as CLF */ + break; + + case ioSIR: /* set interrupt request */ + setstdPRL (msd); /* set standard PRL signal */ + setstdIRQ (msd); /* set standard IRQ signal */ + setstdSRQ (msd); /* set standard SRQ signal */ + break; + + case ioIAK: /* interrupt acknowledge */ + msd.flagbuf = CLEAR; + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Command channel I/O signal handler. + + Implementation notes: + + 1. Commands are usually initiated with an STC cc,C instruction. The CLR + command completes immediately and sets the flag. This requires that we + ignore the CLF part (but still process the SIR). + + 2. The command channel card clears its flag and flag buffer on EDT, but as + it never asserts SRQ, it will never get EDT. Under simulation, we omit + the EDT handler. + + 3. In hardware, the command channel card passes PRH to PRL. The data card + actually drives PRL with both channels' control and flag states. That + is, the priority chain is broken at the data card, even when the command + card is interrupting. This works in hardware, but we must break PRL at + the command card under simulation to allow the command card to interrupt. +*/ + +uint32 mscio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +static const uint8 map_sel[16] = { + 0, 0, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3 + }; +uint16 data; +int32 sched_time; +UNIT *uptr = msc_dev.units + msc_usl; + +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + msc.flag = msc.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + msc.flag = msc.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (msc); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (msc); + break; + + + case ioIOI: /* I/O data input */ + data = (uint16) (msc_sta & ~STA_DYN); /* get card status */ + + if ((uptr->flags & UNIT_OFFLINE) == 0) { /* online? */ + data = data | (uint16) uptr->UST; /* add unit status */ + + if (sim_tape_bot (uptr)) /* BOT? */ + data = data | STA_BOT; + + if (sim_is_active (uptr) && /* TBSY unless RWD at BOT */ + !((uptr->FNC & FNF_RWD) && sim_tape_bot (uptr))) + data = data | STA_TBSY; + + if (sim_tape_wrp (uptr)) /* write prot? */ + data = data | STA_WLK; + + if (sim_tape_eot (uptr)) /* EOT? */ + data = data | STA_EOT; + } + + else + data = data | STA_TBSY | STA_LOCAL; + + if (ms_ctype == A13183) /* 13183A? */ + data = data | STA_PE | (uint16) (msc_usl << STA_V_SEL); + + if (DEBUG_PRI (msc_dev, DEB_CPU)) + fprintf (sim_deb, ">>MSC LIx: Status = %06o\n", data); + + stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + msc_buf = IODATA (stat_data); /* clear supplied status */ + + if (DEBUG_PRI (msc_dev, DEB_CPU)) + fprintf (sim_deb, ">>MSC OTx: Command = %06o\n", msc_buf); + + msc_sta = msc_sta & ~STA_REJ; /* clear reject */ + + if ((msc_buf & 0377) == FNC_CLR) /* clear always ok */ + break; + + if (msc_sta & STA_BUSY) { /* busy? reject */ + msc_sta = msc_sta | STA_REJ; /* dont chg select */ + break; + } + + if (msc_buf & FNF_CHS) { /* select change */ + msc_usl = map_sel[FNC_GETSEL (msc_buf)]; /* is immediate */ + uptr = msc_dev.units + msc_usl; + if (DEBUG_PRI (msc_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MSC OTx: Unit %d selected\n", msc_usl); + } + + if (((msc_buf & FNF_MOT) && sim_is_active (uptr)) || + ((msc_buf & FNF_REV) && sim_tape_bot (uptr)) || + ((msc_buf & FNF_WRT) && sim_tape_wrp (uptr))) + msc_sta = msc_sta | STA_REJ; /* reject? */ + + break; + + + case ioCRS: /* control reset */ + msc.flag = msc.flagbuf = SET; /* set flag and flag buffer */ + /* fall into CLC handler */ + + case ioCLC: /* clear control flip-flop */ + msc.control = CLEAR; + break; + + + case ioSTC: /* set control flip-flop */ + if (!(msc_sta & STA_REJ)) { /* last cmd rejected? */ + if ((msc_buf & 0377) == FNC_CLR) { /* clear? */ + ms_clear (); /* issue CLR to controller */ + + msc.control = SET; /* set CTL for STC */ + msc.flag = msc.flagbuf = SET; /* set FLG for completion */ + + working_set = working_set & ~ioCLF; /* eliminate possible CLF */ + + if (DEBUG_PRI (msc_dev, DEB_CMDS)) + fputs (">>MSC STC: Controller cleared\n", sim_deb); + + break; /* command completes immediately */ + } + + uptr->FNC = msc_buf & 0377; /* save function */ + + if (uptr->FNC & FNF_RWD) { /* rewind? */ + if (!sim_tape_bot (uptr)) /* not at BOT? */ + uptr->UST = STA_REW; /* set rewinding */ + + sched_time = msc_rtime; /* set response time */ + } + + else { + if (sim_tape_bot (uptr)) /* at BOT? */ + sched_time = msc_btime; /* use BOT start time */ + + else if ((uptr->FNC == FNC_GAP) || (uptr->FNC == FNC_GFM)) + sched_time = msc_gtime; /* use gap traversal time */ + + else sched_time = 0; + + if (uptr->FNC != FNC_GAP) + sched_time += msc_ctime; /* add base command time */ + } + + if (msc_buf & ~FNC_SEL) { /* NOP for unit sel alone */ + sim_activate (uptr, sched_time); /* else schedule op */ + + if (DEBUG_PRI (msc_dev, DEB_CMDS)) + fprintf (sim_deb, + ">>MSC STC: Unit %d command %03o (%s) scheduled, " + "pos = %" T_ADDR_FMT "d, time = %d\n", + msc_usl, uptr->FNC, ms_cmd_name (uptr->FNC), + uptr->pos, sched_time); + } + + else if (DEBUG_PRI (msc_dev, DEB_CMDS)) + fputs (">>MSC STC: Unit select (NOP)\n", sim_deb); + + msc_sta = STA_BUSY; /* ctrl is busy */ + msc_1st = 1; + msc.control = SET; /* go */ + } + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (msc); /* set standard PRL signal */ + setstdIRQ (msc); /* set standard IRQ signal */ + setstdSRQ (msc); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + msc.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Unit service + + If rewind done, reposition to start of tape, set status + else, do operation, set done, interrupt. + + In addition to decreasing the timing intervals, the FASTTIME option enables + two additional optimizations: WFM for GFM substitution, and BOT gap + elimination. If FASTTIME is selected, gap and file mark (GFM) commands are + processed as WFM (write file mark) commands. That is, the preceding GAP is + not performed. Also, the initial gap that normally precedes the first data + record or EOF mark at the beginning of the tape is omitted. These omissions + result in smaller tape image files. If REALTIME is selected, the gaps are + included. Note that the gaps (and realistic timing) are necessary to pass + the 7970 diagnostics. +*/ + +t_stat msc_svc (UNIT *uptr) +{ +int32 unum; +t_mtrlnt tbc; +t_stat st, r = SCPE_OK; + +unum = uptr - msc_unit; /* get unit number */ + +if ((uptr->FNC != FNC_RWS) && (uptr->flags & UNIT_OFFLINE)) { /* offline? */ + msc_sta = (msc_sta | STA_REJ) & ~STA_BUSY; /* reject */ + mscio (&msc_dib, ioENF, 0); /* set flag */ + return IOERROR (msc_stopioe, SCPE_UNATT); + } + +switch (uptr->FNC) { /* case on function */ + + case FNC_RWS: /* rewind offline */ + sim_tape_rewind (uptr); /* rewind tape */ + uptr->flags = uptr->flags | UNIT_OFFLINE; /* set offline */ + uptr->UST = 0; /* clear REW status */ + break; /* we're done */ + + case FNC_REW: /* rewind */ + if (uptr->UST & STA_REW) { /* rewind in prog? */ + uptr->FNC |= FNC_CMPL; /* set compl state */ + sim_activate (uptr, msc_ctime); /* sched completion */ + } + break; /* anyway, ctrl done */ + + case FNC_REW | FNC_CMPL: /* complete rewind */ + sim_tape_rewind (uptr); /* rewind tape */ + uptr->UST = 0; /* clear REW status */ + return SCPE_OK; /* drive is free */ + + case FNC_GFM: /* gap + file mark */ + if (ms_timing == 1) /* fast timing? */ + goto DO_WFM; /* do plain file mark */ + /* else fall into GAP */ + case FNC_GAP: /* erase gap */ + if (DEBUG_PRI (msc_dev, DEB_RWS)) + fprintf (sim_deb, + ">>MSC svc: Unit %d wrote gap\n", + unum); + r = ms_write_gap (uptr); /* write tape gap*/ + + if (r || (uptr->FNC != FNC_GFM)) /* if error or not GFM */ + break; /* then bail out now */ + /* else drop into WFM */ + case FNC_WFM: /* write file mark */ + if ((ms_timing == 0) && sim_tape_bot (uptr)) { /* realistic timing + BOT? */ + if (DEBUG_PRI (msc_dev, DEB_RWS)) + fprintf (sim_deb, + ">>MSC svc: Unit %d wrote initial gap\n", + unum); + st = ms_write_gap (uptr); /* write initial gap*/ + if (st != MTSE_OK) { /* error? */ + r = ms_map_err (uptr, st); /* map error */ + break; /* terminate operation */ + } + } + DO_WFM: + if (DEBUG_PRI (msc_dev, DEB_RWS)) + fprintf (sim_deb, + ">>MSC svc: Unit %d wrote file mark\n", + unum); + st = sim_tape_wrtmk (uptr); /* write tmk */ + if (st != MTSE_OK) /* error? */ + r = ms_map_err (uptr, st); /* map error */ + msc_sta = STA_EOF; /* set EOF status */ + break; + + case FNC_FSR: /* space forward */ + st = sim_tape_sprecf (uptr, &tbc); /* space rec fwd */ + if (st != MTSE_OK) /* error? */ + r = ms_map_err (uptr, st); /* map error */ + if (tbc & 1) + msc_sta = msc_sta | STA_ODD; + else msc_sta = msc_sta & ~STA_ODD; + break; + + case FNC_BSR: /* space reverse */ + st = sim_tape_sprecr (uptr, &tbc); /* space rec rev*/ + if (st != MTSE_OK) /* error? */ + r = ms_map_err (uptr, st); /* map error */ + if (tbc & 1) + msc_sta = msc_sta | STA_ODD; + else msc_sta = msc_sta & ~STA_ODD; + break; + + case FNC_FSF: /* space fwd file */ + while ((st = sim_tape_sprecf (uptr, &tbc)) == MTSE_OK) { + if (sim_tape_eot (uptr)) break; /* EOT stops */ + } + r = ms_map_err (uptr, st); /* map error */ + break; + + case FNC_BSF: /* space rev file */ + while ((st = sim_tape_sprecr (uptr, &tbc)) == MTSE_OK) ; + r = ms_map_err (uptr, st); /* map error */ + break; + + case FNC_RFF: /* diagnostic read */ + case FNC_RC: /* read */ + if (msc_1st) { /* first svc? */ + msc_1st = ms_ptr = ms_max = 0; /* clr 1st flop */ + st = sim_tape_rdrecf (uptr, msxb, &ms_max, DBSIZE); /* read rec */ + if (DEBUG_PRI (msc_dev, DEB_RWS)) + fprintf (sim_deb, + ">>MSC svc: Unit %d read %d word record\n", + unum, ms_max / 2); + if (st == MTSE_RECE) msc_sta = msc_sta | STA_PAR; /* rec in err? */ + else if (st != MTSE_OK) { /* other error? */ + r = ms_map_err (uptr, st); /* map error */ + if (r == SCPE_OK) { /* recoverable? */ + sim_activate (uptr, msc_itime); /* sched IRG */ + uptr->FNC |= FNC_CMPL; /* set completion */ + return SCPE_OK; + } + break; /* err, done */ + } + if (ms_ctype == A13183) + msc_sta = msc_sta | STA_ODD; /* set ODD for 13183A */ + } + if (msd.control && (ms_ptr < ms_max)) { /* DCH on, more data? */ + if (msd.flag) msc_sta = msc_sta | STA_TIM | STA_PAR; + msd_buf = ((uint16) msxb[ms_ptr] << 8) | + ((ms_ptr + 1 == ms_max) ? 0 : msxb[ms_ptr + 1]); + ms_ptr = ms_ptr + 2; + msdio (&msd_dib, ioENF, 0); /* set flag */ + sim_activate (uptr, msc_xtime); /* re-activate */ + return SCPE_OK; + } + if (ms_max & 1) /* set ODD by rec len */ + msc_sta = msc_sta | STA_ODD; + else msc_sta = msc_sta & ~STA_ODD; + sim_activate (uptr, msc_itime); /* sched IRG */ + if (uptr->FNC == FNC_RFF) /* diagnostic? */ + msc_1st = 1; /* restart */ + else { + uptr->FNC |= FNC_CMPL; /* set completion */ + ms_crc = TRUE; /* and CRC ready */ + } + return SCPE_OK; + + case FNC_RFF | FNC_CMPL: /* diagnostic read completion */ + case FNC_RC | FNC_CMPL: /* read completion */ + break; + + case FNC_WC: /* write */ + if (msc_1st) { /* first service? */ + msc_1st = ms_ptr = 0; /* no data xfer on first svc */ + if ((ms_timing == 0) && sim_tape_bot (uptr)) { /* realistic timing + BOT? */ + if (DEBUG_PRI (msc_dev, DEB_RWS)) + fprintf (sim_deb, + ">>MSC svc: Unit %d wrote initial gap\n", + unum); + st = ms_write_gap (uptr); /* write initial gap */ + if (st != MTSE_OK) { /* error? */ + r = ms_map_err (uptr, st); /* map error */ + break; /* terminate operation */ + } + } + } + else { /* not 1st, next char */ + if (ms_ptr < DBSIZE) { /* room in buffer? */ + msxb[ms_ptr] = (uint8) (msd_buf >> 8); /* store 2 char */ + msxb[ms_ptr + 1] = msd_buf & 0377; + ms_ptr = ms_ptr + 2; + } + else msc_sta = msc_sta | STA_PAR; + } + if (msd.control) { /* xfer flop set? */ + msdio (&msd_dib, ioENF, 0); /* set flag */ + sim_activate (uptr, msc_xtime); /* re-activate */ + return SCPE_OK; + } + if (ms_ptr) { /* any data? write */ + if (DEBUG_PRI (msc_dev, DEB_RWS)) + fprintf (sim_deb, + ">>MSC svc: Unit %d wrote %d word record\n", + unum, ms_ptr / 2); + st = sim_tape_wrrecf (uptr, msxb, ms_ptr); /* write */ + if (st != MTSE_OK) { + r = ms_map_err (uptr, st); /* map error */ + break; + } + } + sim_activate (uptr, msc_itime); /* sched IRG */ + uptr->FNC |= FNC_CMPL; /* set completion */ + ms_max = ms_ptr; /* indicate buffer complete */ + ms_crc = TRUE; /* and CRC may be generated */ + return SCPE_OK; + + case FNC_WC | FNC_CMPL: /* write completion */ + break; + + case FNC_RRR: /* not supported */ + default: /* unknown command */ + if (DEBUG_PRI (msc_dev, DEB_CMDS)) + fprintf (sim_deb, + ">>MSC svc: Unit %d command %03o is unknown (NOP)\n", + unum, uptr->FNC); + break; + } + +mscio (&msc_dib, ioENF, 0); /* set flag */ +msc_sta = msc_sta & ~STA_BUSY; /* update status */ +if (DEBUG_PRI (msc_dev, DEB_CMDS)) + fprintf (sim_deb, + ">>MSC svc: Unit %d command %03o (%s) complete\n", + unum, uptr->FNC & 0377, ms_cmd_name (uptr->FNC)); +return r; +} + + +/* Write an erase gap */ + +t_stat ms_write_gap (UNIT *uptr) +{ +t_stat st; +uint32 gap_len = ms_ctype ? GAP_13183 : GAP_13181; /* establish gap length */ + +st = sim_tape_wrgap (uptr, gap_len); /* write gap */ + +if (st != MTSE_OK) + return ms_map_err (uptr, st); /* map error if failure */ +else + return SCPE_OK; +} + + +/* Map tape error status */ + +t_stat ms_map_err (UNIT *uptr, t_stat st) +{ +int32 unum = uptr - msc_unit; /* get unit number */ + +if (DEBUG_PRI (msc_dev, DEB_RWS)) + fprintf (sim_deb, + ">>MSC err: Unit %d tape library status = %d\n", + unum, st); + +switch (st) { + + case MTSE_FMT: /* illegal fmt */ + msc_sta = msc_sta | STA_REJ; /* reject cmd */ + return SCPE_FMT; /* format error */ + + case MTSE_UNATT: /* unattached */ + msc_detach (uptr); /* resync status (ignore rtn) */ + msc_sta = msc_sta | STA_REJ; /* reject cmd */ + return SCPE_UNATT; /* unit unattached */ + + case MTSE_OK: /* no error */ + return SCPE_IERR; /* never get here! */ + + case MTSE_EOM: /* end of medium */ + case MTSE_TMK: /* end of file */ + msc_sta = msc_sta | STA_EOF; + + if (ms_ctype == A13181) + msc_sta = msc_sta | STA_ODD; /* EOF also sets ODD for 13181A */ + break; + + case MTSE_INVRL: /* invalid rec lnt */ + msc_sta = msc_sta | STA_PAR; + return SCPE_MTRLNT; + + case MTSE_IOERR: /* IO error */ + msc_sta = msc_sta | STA_PAR; /* error */ + if (msc_stopioe) return SCPE_IOERR; + break; + + case MTSE_RECE: /* record in error */ + msc_sta = msc_sta | STA_PAR; /* error */ + break; + + case MTSE_WRP: /* write protect */ + msc_sta = msc_sta | STA_REJ; /* reject */ + break; + } + +return SCPE_OK; +} + + +/* Controller clear */ + +t_stat ms_clear (void) +{ +int32 i; +t_stat st; +UNIT *uptr; + +for (i = 0; i < MS_NUMDR; i++) { /* look for write in progr */ + uptr = &msc_unit [i]; /* get pointer to unit */ + + if (sim_is_active (uptr) && /* unit active? */ + (uptr->FNC == FNC_WC) && /* and last cmd write? */ + (ms_ptr > 0)) { /* and partial buffer? */ + if (DEBUG_PRI (msc_dev, DEB_RWS)) + fprintf (sim_deb, + ">>MSC rws: Unit %d wrote %d word partial record\n", i, ms_ptr / 2); + + st = sim_tape_wrrecf (uptr, msxb, ms_ptr | MTR_ERF); + + if (st != MTSE_OK) + ms_map_err (uptr, st); /* discard any error */ + + ms_ptr = 0; /* clear partial */ + } + + if ((uptr->UST & STA_REW) == 0) + sim_cancel (uptr); /* stop if not rew */ + } + +msc_sta = msc_1st = 0; /* clr ctlr status */ + +return SCPE_OK; +} + + +/* Reset routine */ + +t_stat msc_reset (DEVICE *dptr) +{ +int32 i; +UNIT *uptr; +DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ + +hp_enbdis_pair (dptr, /* make pair cons */ + (dptr == &msd_dev) ? &msc_dev : &msd_dev); + +if (sim_switches & SWMASK ('P')) /* initialization reset? */ + ms_config_timing (); + +IOPRESET (dibptr); /* PRESET device (does not use PON) */ + +msc_buf = msd_buf = 0; +msc_sta = msc_usl = 0; +msc_1st = 0; + +for (i = 0; i < MS_NUMDR; i++) { + uptr = msc_dev.units + i; + sim_tape_reset (uptr); + sim_cancel (uptr); + uptr->UST = 0; + + if (sim_switches & SWMASK ('P')) /* if this is an initialization reset */ + sim_tape_set_dens (uptr, /* then tell the tape library the density in use */ + ms_ctype ? BPI_13183 : BPI_13181, + NULL, NULL); + } + +return SCPE_OK; +} + + +/* Attach routine */ + +t_stat msc_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat r; + +r = sim_tape_attach (uptr, cptr); /* attach unit */ +if (r == SCPE_OK) + uptr->flags = uptr->flags & ~UNIT_OFFLINE; /* set online */ +return r; +} + +/* Detach routine */ + +t_stat msc_detach (UNIT* uptr) +{ +uptr->UST = 0; /* clear status */ +uptr->flags = uptr->flags | UNIT_OFFLINE; /* set offline */ +return sim_tape_detach (uptr); /* detach unit */ +} + +/* Online routine */ + +t_stat msc_online (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +if (uptr->flags & UNIT_ATT) return SCPE_OK; +else return SCPE_UNATT; +} + +/* Configure timing */ + +void ms_config_timing (void) +{ +uint32 i, tset; + +tset = (ms_timing << 1) | (ms_timing ? 0 : ms_ctype); /* select timing set */ +for (i = 0; i < (sizeof (timers) / sizeof (timers[0])); i++) + *timers[i] = msc_times[tset][i]; /* assign times */ +} + +/* Set controller timing */ + +t_stat ms_set_timing (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +if ((val < 0) || (val > 1) || (cptr != NULL)) return SCPE_ARG; +ms_timing = val; +ms_config_timing (); +return SCPE_OK; +} + +/* Show controller timing */ + +t_stat ms_show_timing (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +if (ms_timing) fputs ("fast timing", st); +else fputs ("realistic timing", st); +return SCPE_OK; +} + +/* Set controller type */ + +t_stat ms_settype (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +int32 i; + +if ((val < 0) || (val > 1) || (cptr != NULL)) return SCPE_ARG; +for (i = 0; i < MS_NUMDR; i++) { + if (msc_unit[i].flags & UNIT_ATT) return SCPE_ALATT; + } +ms_ctype = (CNTLR_TYPE) val; +ms_config_timing (); /* update for new type */ + +sim_tape_set_dens (uptr, ms_ctype ? BPI_13183 : BPI_13181, /* tell the tape library the density in use */ + NULL, NULL); + +return SCPE_OK; +} + +/* Show controller type */ + +t_stat ms_showtype (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +if (ms_ctype == A13183) + fprintf (st, "13183A"); +else + fprintf (st, "13181A"); +return SCPE_OK; +} + +/* Set unit reel size + + val = 0 -> SET MSCn CAPACITY=n + val = 1 -> SET MSCn REEL=n */ + +t_stat ms_set_reelsize (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +int32 reel; +t_stat status; + +if (val == 0) { + status = sim_tape_set_capac (uptr, val, cptr, desc); + if (status == SCPE_OK) uptr->REEL = 0; + return status; + } + +if (cptr == NULL) return SCPE_ARG; +reel = (int32) get_uint (cptr, 10, 2400, &status); +if (status != SCPE_OK) return status; +else switch (reel) { + + case 0: + uptr->REEL = 0; /* type 0 = unlimited/custom */ + break; + + case 600: + uptr->REEL = 1; /* type 1 = 600 foot */ + break; + + case 1200: + uptr->REEL = 2; /* type 2 = 1200 foot */ + break; + + case 2400: + uptr->REEL = 3; /* type 3 = 2400 foot */ + break; + + default: + return SCPE_ARG; + } + +uptr->capac = uptr->REEL ? (TCAP << uptr->REEL) << ms_ctype : 0; +return SCPE_OK; +} + +/* Show unit reel size + + val = 0 -> SHOW MSC or SHOW MSCn or SHOW MSCn CAPACITY + val = 1 -> SHOW MSCn REEL */ + +t_stat ms_show_reelsize (FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ +t_stat status = SCPE_OK; + +if (uptr->REEL == 0) status = sim_tape_show_capac (st, uptr, val, desc); +else fprintf (st, "%4d foot reel", 300 << uptr->REEL); +if (val == 1) fputc ('\n', st); /* MTAB_NMO omits \n */ +return status; +} + +/* Translate command to mnemonic for debug logging + + The command names and descriptions are taken from the 13181 interface + manual. */ + +char *ms_cmd_name (uint32 cmd) +{ + +switch (cmd & 0377) { + case FNC_WC: return "WCC"; /* Write command */ + case FNC_WFM: return "WFM"; /* Write file mark */ + case FNC_RC: return "RRF"; /* Read record forward */ + case FNC_FSR: return "FSR"; /* Forward space record */ + case FNC_FSF: return "FSF"; /* Forward space file */ + case FNC_GAP: return "GAP"; /* Write gap */ + case FNC_BSR: return "BSR"; /* Backspace record */ + case FNC_BSF: return "BSF"; /* Backspace file */ + case FNC_REW: return "REW"; /* Rewind */ + case FNC_RWS: return "RWO"; /* Rewind off-line */ + case FNC_CLR: return "CLR"; /* Clear controller */ + case FNC_GFM: return "GFM"; /* Gap file mark */ + case FNC_RFF: return "RFF"; /* Read forward until file mark (diag) */ + case FNC_RRR: return "RRR"; /* Read record in reverse (diag) */ + + default: return "???"; /* Unknown command */ + } +} + +/* 7970B/7970E bootstrap routine (HP 12992D ROM) */ + +const BOOT_ROM ms_rom = { + 0106501, /*ST LIB 1 ; read sw */ + 0006011, /* SLB,RSS ; bit 0 set? */ + 0027714, /* JMP RD ; no read */ + 0003004, /* CMA,INA ; A is ctr */ + 0073775, /* STA WC ; save */ + 0067772, /* LDA SL0RW ; sel 0, rew */ + 0017762, /*FF JSB CMD ; do cmd */ + 0102311, /* SFS CC ; done? */ + 0027707, /* JMP *-1 ; wait */ + 0067774, /* LDB FFC ; get file fwd */ + 0037775, /* ISZ WC ; done files? */ + 0027706, /* JMP FF ; no */ + 0067773, /*RD LDB RDCMD ; read cmd */ + 0017762, /* JSB CMD ; do cmd */ + 0103710, /* STC DC,C ; start dch */ + 0102211, /* SFC CC ; read done? */ + 0027752, /* JMP STAT ; no, get stat */ + 0102310, /* SFS DC ; any data? */ + 0027717, /* JMP *-3 ; wait */ + 0107510, /* LIB DC,C ; get rec cnt */ + 0005727, /* BLF,BLF ; move to lower */ + 0007000, /* CMB ; make neg */ + 0077775, /* STA WC ; save */ + 0102211, /* SFC CC ; read done? */ + 0027752, /* JMP STAT ; no, get stat */ + 0102310, /* SFS DC ; any data? */ + 0027727, /* JMP *-3 ; wait */ + 0107510, /* LIB DC,C ; get load addr */ + 0074000, /* STB 0 ; start csum */ + 0077762, /* STA CMD ; save address */ + 0027742, /* JMP *+4 */ + 0177762, /*NW STB CMD,I ; store data */ + 0040001, /* ADA 1 ; add to csum */ + 0037762, /* ISZ CMD ; adv addr ptr */ + 0102310, /* SFS DC ; any data? */ + 0027742, /* JMP *-1 ; wait */ + 0107510, /* LIB DC,C ; get word */ + 0037775, /* ISZ WC ; done? */ + 0027737, /* JMP NW ; no */ + 0054000, /* CPB 0 ; csum ok? */ + 0027717, /* JMP RD+3 ; yes, cont */ + 0102011, /* HLT 11 ; no, halt */ + 0102511, /*ST LIA CC ; get status */ + 0001727, /* ALF,ALF ; get eof bit */ + 0002020, /* SSA ; set? */ + 0102077, /* HLT 77 ; done */ + 0001727, /* ALF,ALF ; put status back */ + 0001310, /* RAR,SLA ; read ok? */ + 0102000, /* HLT 0 ; no */ + 0027714, /* JMP RD ; read next */ + 0000000, /*CMD 0 */ + 0106611, /* OTB CC ; output cmd */ + 0102511, /* LIA CC ; check for reject */ + 0001323, /* RAR,RAR */ + 0001310, /* RAR,SLA */ + 0027763, /* JMP CMD+1 ; try again */ + 0103711, /* STC CC,C ; start command */ + 0127762, /* JMP CMD,I ; exit */ + 0001501, /*SL0RW 001501 ; select 0, rewind */ + 0001423, /*RDCMD 001423 ; read record */ + 0000203, /*FFC 000203 ; space forward file */ + 0000000, /*WC 000000 */ + 0000000, + 0000000 + }; + +t_stat msc_boot (int32 unitno, DEVICE *dptr) +{ +const int32 dev = msd_dib.select_code; /* get data chan device no */ + +if (unitno != 0) /* boot supported on drive unit 0 only */ + return SCPE_NOFNC; /* report "Command not allowed" if attempted */ + +if (ibl_copy (ms_rom, dev, IBL_OPT, /* copy the boot ROM to memory and configure */ + IBL_MS | IBL_SET_SC (dev))) /* the S register accordingly */ + return SCPE_IERR; /* return an internal error if the copy failed */ + +if ((sim_switches & SWMASK ('S')) && AR) /* if -S is specified and the A register is non-zero */ + SR = SR | 1; /* then set to skip to the file number in A */ + +return SCPE_OK; +} + +/* Calculate tape record CRC and LRC characters */ + +#define E 0400 /* parity bit for odd parity */ +#define O 0000 /* parity bit for odd parity */ + +static const uint16 odd_parity [256] = { /* parity table */ + E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 000-017 */ + O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 020-037 */ + O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 040-067 */ + E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 060-077 */ + O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 100-117 */ + E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 120-137 */ + E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 140-157 */ + O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 160-177 */ + O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 200-217 */ + E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 220-237 */ + E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 240-267 */ + O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 260-277 */ + E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E, /* 300-317 */ + O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 320-337 */ + O, E, E, O, E, O, O, E, E, O, O, E, O, E, E, O, /* 340-357 */ + E, O, O, E, O, E, E, O, O, E, E, O, E, O, O, E /* 360-377 */ + }; + +static uint32 calc_crc_lrc (uint8 *buffer, t_mtrlnt length) +{ +uint32 i; +uint16 byte, crc, lrc; + +lrc = crc = 0; + +for (i = 0; i < length; i++) { + byte = odd_parity [buffer [i]] | buffer [i]; + + crc = crc ^ byte; + lrc = lrc ^ byte; + + if (crc & 1) + crc = crc >> 1 ^ 0474; + else + crc = crc >> 1; + } + +crc = crc ^ 0727; +lrc = lrc ^ crc; + +return (uint32) crc << 16 | lrc; +} diff --git a/HP2100/hp2100_mt.c b/HP2100/hp2100_mt.c index c1c67061..3d9c3105 100644 --- a/HP2100/hp2100_mt.c +++ b/HP2100/hp2100_mt.c @@ -1,697 +1,698 @@ -/* hp2100_mt.c: HP 2100 12559A magnetic tape simulator - - Copyright (c) 1993-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - MT 12559A 3030 nine track magnetic tape - - 24-Dec-14 JDB Added casts for explicit downward conversions - 10-Jan-13 MP Added DEV_TAPE to DEVICE flags - 09-May-12 JDB Separated assignments from conditional expressions - 25-Mar-12 JDB Removed redundant MTAB_VUN from "format" MTAB entry - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - 28-Mar-11 JDB Tidied up signal handling - 29-Oct-10 JDB Fixed command scanning error in mtcio ioIOO handler - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 04-Sep-08 JDB Fixed missing flag after CLR command - 02-Sep-08 JDB Moved write enable and format commands from MTD to MTC - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 07-Oct-04 JDB Allow enable/disable from either device - 14-Aug-04 RMS Modified handling of end of medium (suggested by Dave Bryan) - 06-Jul-04 RMS Fixed spurious timing error after CLC (found by Dave Bryan) - 26-Apr-04 RMS Fixed SFS x,C and SFC x,C - Implemented DMA SRQ (follows FLG) - 21-Dec-03 RMS Adjusted msc_ctime for TSB (from Mike Gemeny) - 25-Apr-03 RMS Revised for extended file support - 28-Mar-03 RMS Added multiformat support - 28-Feb-03 RMS Revised for magtape library - 30-Sep-02 RMS Revamped error handling - 28-Aug-02 RMS Added end of medium support - 30-May-02 RMS Widened POS to 32b - 22-Apr-02 RMS Added maximum record length test - 20-Jan-02 RMS Fixed bug on last character write - 03-Dec-01 RMS Added read only unit, extended SET/SHOW support - 07-Sep-01 RMS Moved function prototypes - 30-Nov-00 RMS Made variable names unique - 04-Oct-98 RMS V2.4 magtape format - - References: - - 12559A 9-Track Magnetic Tape Unit Interface Kit Operating and Service Manual - (12559-9001, Jul-1970) - - SIMH Magtape Representation and Handling (Bob Supnik, 30-Aug-2006) - - - The 3030 was one of HP's earliest tape drives. The 12559A controller - supported a single 800 bpi, 9-track drive, operating at 75 inches per second. - It had two unusual characteristics: - - - The controller accepted only one byte per I/O word, rather than packing - two bytes per word. - - - The drive could not read or write fewer than 12 bytes per record. - - The first behavior meant that DMA operation required the byte-unpacking - feature of the 12578A DMA card for the 2116 computer. The second meant that - software drivers had to pad short records with blanks or nulls. - - Implementation notes: - - 1. The HP 3030 Magnetic Tape Subsystem diagnostic, part number 20433-60001, - has never been located, so this simulator has not been fully tested. It - does pass a functional test under DOS-III using driver DVR22. -*/ - - -#include "hp2100_defs.h" -#include "sim_tape.h" - -#define DB_V_SIZE 16 /* max data buf */ -#define DBSIZE (1 << DB_V_SIZE) /* max data cmd */ - -/* Command - mtc_fnc */ - -#define FNC_CLR 0300 /* clear */ -#define FNC_WC 0031 /* write */ -#define FNC_RC 0023 /* read */ -#define FNC_GAP 0011 /* write gap */ -#define FNC_FSR 0003 /* forward space */ -#define FNC_BSR 0041 /* backward space */ -#define FNC_REW 0201 /* rewind */ -#define FNC_RWS 0101 /* rewind and offline */ -#define FNC_WFM 0035 /* write file mark */ - -/* Status - stored in mtc_sta, (d) = dynamic */ - -#define STA_LOCAL 0400 /* local (d) */ -#define STA_EOF 0200 /* end of file */ -#define STA_BOT 0100 /* beginning of tape */ -#define STA_EOT 0040 /* end of tape */ -#define STA_TIM 0020 /* timing error */ -#define STA_REJ 0010 /* programming error */ -#define STA_WLK 0004 /* write locked (d) */ -#define STA_PAR 0002 /* parity error */ -#define STA_BUSY 0001 /* busy (d) */ - -struct { - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } mtd = { CLEAR, CLEAR }; - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } mtc = { CLEAR, CLEAR, CLEAR }; - -int32 mtc_fnc = 0; /* function */ -int32 mtc_sta = 0; /* status register */ -int32 mtc_dtf = 0; /* data xfer flop */ -int32 mtc_1st = 0; /* first svc flop */ -int32 mtc_ctime = 40; /* command wait */ -int32 mtc_gtime = 1000; /* gap stop time */ -int32 mtc_xtime = 15; /* data xfer time */ -int32 mtc_stopioe = 1; /* stop on error */ -uint8 mtxb[DBSIZE] = { 0 }; /* data buffer */ -t_mtrlnt mt_ptr = 0, mt_max = 0; /* buffer ptrs */ -static const uint32 mtc_cmd[] = { - FNC_WC, FNC_RC, FNC_GAP, FNC_FSR, FNC_BSR, FNC_REW, FNC_RWS, FNC_WFM }; -static const uint32 mtc_cmd_count = sizeof (mtc_cmd) / sizeof (mtc_cmd[0]); - -DEVICE mtd_dev, mtc_dev; - -IOHANDLER mtdio; -IOHANDLER mtcio; - -t_stat mtc_svc (UNIT *uptr); -t_stat mt_reset (DEVICE *dptr); -t_stat mtc_attach (UNIT *uptr, char *cptr); -t_stat mtc_detach (UNIT *uptr); -t_stat mt_map_err (UNIT *uptr, t_stat st); -t_stat mt_clear (void); - -/* MTD data structures - - mtd_dev MTD device descriptor - mtd_unit MTD unit list - mtd_reg MTD register list -*/ - -DIB mt_dib[] = { - { &mtdio, MTD }, - { &mtcio, MTC } - }; - -#define mtd_dib mt_dib[0] -#define mtc_dib mt_dib[1] - -UNIT mtd_unit = { UDATA (NULL, 0, 0) }; - -REG mtd_reg[] = { - { FLDATA (FLG, mtd.flag, 0) }, - { FLDATA (FBF, mtd.flagbuf, 0) }, - { BRDATA (DBUF, mtxb, 8, 8, DBSIZE) }, - { DRDATA (BPTR, mt_ptr, DB_V_SIZE + 1) }, - { DRDATA (BMAX, mt_max, DB_V_SIZE + 1) }, - { ORDATA (SC, mtd_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, mtd_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB mtd_mod[] = { - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &mtd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &mtd_dev }, - { 0 } - }; - -DEVICE mtd_dev = { - "MTD", &mtd_unit, mtd_reg, mtd_mod, - 1, 10, 16, 1, 8, 8, - NULL, NULL, &mt_reset, - NULL, NULL, NULL, - &mtd_dib, DEV_DISABLE | DEV_DIS - }; - -/* MTC data structures - - mtc_dev MTC device descriptor - mtc_unit MTC unit list - mtc_reg MTC register list - mtc_mod MTC modifier list -*/ - -UNIT mtc_unit = { UDATA (&mtc_svc, UNIT_ATTABLE + UNIT_ROABLE, 0) }; - -REG mtc_reg[] = { - { ORDATA (FNC, mtc_fnc, 8) }, - { ORDATA (STA, mtc_sta, 9) }, - { ORDATA (BUF, mtc_unit.buf, 8) }, - { FLDATA (CTL, mtc.control, 0) }, - { FLDATA (FLG, mtc.flag, 0) }, - { FLDATA (FBF, mtc.flagbuf, 0) }, - { FLDATA (DTF, mtc_dtf, 0) }, - { FLDATA (FSVC, mtc_1st, 0) }, - { DRDATA (POS, mtc_unit.pos, T_ADDR_W), PV_LEFT }, - { DRDATA (CTIME, mtc_ctime, 24), REG_NZ + PV_LEFT }, - { DRDATA (GTIME, mtc_gtime, 24), REG_NZ + PV_LEFT }, - { DRDATA (XTIME, mtc_xtime, 24), REG_NZ + PV_LEFT }, - { FLDATA (STOP_IOE, mtc_stopioe, 0) }, - { ORDATA (SC, mtc_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, mtc_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB mtc_mod[] = { - { MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL }, - { MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL }, - { MTAB_XTD | MTAB_VDV, 0, "FORMAT", "FORMAT", - &sim_tape_set_fmt, &sim_tape_show_fmt, NULL }, - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &mtd_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &mtd_dev }, - { 0 } - }; - -DEVICE mtc_dev = { - "MTC", &mtc_unit, mtc_reg, mtc_mod, - 1, 10, 31, 1, 8, 8, - NULL, NULL, &mt_reset, - NULL, &mtc_attach, &mtc_detach, - &mtc_dib, DEV_DISABLE | DEV_DIS | DEV_TAPE - }; - - -/* Data channel I/O signal handler - - The 12559A data channel interface has a number of non-standard features: - - - The card does not drive PRL or IRQ. - - The card does not respond to IAK. - - There is no control flip-flop; CLC resets the data transfer flip-flop. - - POPIO issues a CLR command and clears the flag and flag buffer flip-flops. - - CRS is not used. - - Implementation notes: - - 1. The data channel has a flag buffer flip-flop (necessary for the proper - timing of the flag flip-flop), but the data channel does not interrupt, - so the flag buffer serves no other purpose. -*/ - -uint32 mtdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - mtd.flag = mtd.flagbuf = CLEAR; - break; - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - mtd.flag = mtd.flagbuf = SET; - break; - - case ioSFC: /* skip if flag is clear */ - setstdSKF (mtd); - break; - - case ioSFS: /* skip if flag is set */ - setstdSKF (mtd); - break; - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, mtc_unit.buf); /* merge in return status */ - break; - - case ioIOO: /* I/O data output */ - mtc_unit.buf = IODATA (stat_data) & DMASK8; /* store data */ - break; - - case ioPOPIO: /* power-on preset to I/O */ - mt_clear (); /* issue CLR to controller */ - mtd.flag = mtd.flagbuf = CLEAR; /* clear flag and flag buffer */ - break; - - case ioCLC: /* clear control flip-flop */ - mtc_dtf = 0; /* clr xfer flop */ - mtd.flag = mtd.flagbuf = CLEAR; /* clear flag and flag buffer */ - break; - - case ioSIR: /* set interrupt request */ - setstdSRQ (mtd); /* set standard SRQ signal */ - break; - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Command channel I/O signal handler. - - The 12559A command interface is reasonably standard, although POPIO clears, - rather than sets, the flag and flag buffer flip-flops. One unusual feature - is that commands are initiated when they are output to the interface with - OTA/B, rather than waiting until control is set with STC. STC simply enables - command-channel interrupts. - - Implementation notes: - - 1. In hardware, the command channel card passes PRH to PRL. The data card - actually drives PRL with the command channel's control and flag states. - That is, the priority chain is broken at the data card, although the - command card is interrupting. This works in hardware, but we must break - PRL at the command card under simulation to allow the command card to - interrupt. - - 2. In hardware, the CLR command takes 5 milliseconds to complete. During - this time, the BUSY bit is set in the status word. Under simulation, we - complete immediately, and the BUSY bit never sets.. -*/ - -uint32 mtcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -uint16 data; -uint32 i; -int32 valid; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - mtc.flag = mtc.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - mtc.flag = mtc.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (mtc); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (mtc); - break; - - - case ioIOI: /* I/O data input */ - data = mtc_sta & ~(STA_LOCAL | STA_WLK | STA_BUSY); - - if (mtc_unit.flags & UNIT_ATT) { /* construct status */ - if (sim_is_active (&mtc_unit)) - data = data | STA_BUSY; - - if (sim_tape_wrp (&mtc_unit)) - data = data | STA_WLK; - } - else - data = data | STA_BUSY | STA_LOCAL; - - stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - data = IODATA (stat_data) & DMASK8; - mtc_sta = mtc_sta & ~STA_REJ; /* clear reject */ - - if (data == FNC_CLR) { /* clear? */ - mt_clear (); /* send CLR to controller */ - - mtd.flag = mtd.flagbuf = CLEAR; /* clear data flag and flag buffer */ - mtc.flag = mtc.flagbuf = SET; /* set command flag and flag buffer */ - break; /* command completes immediately */ - } - - for (i = valid = 0; i < mtc_cmd_count; i++) /* is fnc valid? */ - if (data == mtc_cmd[i]) { - valid = 1; - break; - } - - if (!valid || sim_is_active (&mtc_unit) || /* is cmd valid? */ - ((mtc_sta & STA_BOT) && (data == FNC_BSR)) || - (sim_tape_wrp (&mtc_unit) && - ((data == FNC_WC) || (data == FNC_GAP) || (data == FNC_WFM)))) - mtc_sta = mtc_sta | STA_REJ; - - else { - sim_activate (&mtc_unit, mtc_ctime); /* start tape */ - mtc_fnc = data; /* save function */ - mtc_sta = STA_BUSY; /* unit busy */ - mt_ptr = 0; /* init buffer ptr */ - - mtcio (&mtc_dib, ioCLF, 0); /* clear flags */ - mtcio (&mtd_dib, ioCLF, 0); - - mtc_1st = 1; /* set 1st flop */ - mtc_dtf = 1; /* set xfer flop */ - } - break; - - - case ioPOPIO: /* power-on preset to I/O */ - mtc.flag = mtc.flagbuf = CLEAR; /* clear flag and flag buffer */ - break; - - - case ioCRS: /* control reset */ - case ioCLC: /* clear control flip-flop */ - mtc.control = CLEAR; - break; - - - case ioSTC: /* set control flip-flop */ - mtc.control = SET; - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (mtc); /* set standard PRL signal */ - setstdIRQ (mtc); /* set standard IRQ signal */ - setstdSRQ (mtc); /* set standard SRQ signal */ - break; - - case ioIAK: /* interrupt acknowledge */ - mtc.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Unit service - - If rewind done, reposition to start of tape, set status - else, do operation, set done, interrupt - - Can't be write locked, can only write lock detached unit -*/ - -t_stat mtc_svc (UNIT *uptr) -{ -t_mtrlnt tbc; -t_stat st, r = SCPE_OK; - -if ((mtc_unit.flags & UNIT_ATT) == 0) { /* offline? */ - mtc_sta = STA_LOCAL | STA_REJ; /* rejected */ - mtcio (&mtc_dib, ioENF, 0); /* set cch flg */ - return IOERROR (mtc_stopioe, SCPE_UNATT); - } - -switch (mtc_fnc) { /* case on function */ - - case FNC_REW: /* rewind */ - sim_tape_rewind (uptr); /* BOT */ - mtc_sta = STA_BOT; /* update status */ - break; - - case FNC_RWS: /* rewind and offline */ - sim_tape_rewind (uptr); /* clear position */ - return sim_tape_detach (uptr); /* don't set cch flg */ - - case FNC_WFM: /* write file mark */ - st = sim_tape_wrtmk (uptr); /* write tmk */ - if (st != MTSE_OK) /* error? */ - r = mt_map_err (uptr, st); /* map error */ - mtc_sta = STA_EOF; /* set EOF status */ - break; - - case FNC_GAP: /* erase gap */ - break; - - case FNC_FSR: /* space forward */ - st = sim_tape_sprecf (uptr, &tbc); /* space rec fwd */ - if (st != MTSE_OK) /* error? */ - r = mt_map_err (uptr, st); /* map error */ - break; - - case FNC_BSR: /* space reverse */ - st = sim_tape_sprecr (uptr, &tbc); /* space rec rev */ - if (st != MTSE_OK) /* error? */ - r = mt_map_err (uptr, st); /* map error */ - break; - - case FNC_RC: /* read */ - if (mtc_1st) { /* first svc? */ - mtc_1st = mt_ptr = 0; /* clr 1st flop */ - st = sim_tape_rdrecf (uptr, mtxb, &mt_max, DBSIZE); /* read rec */ - if (st == MTSE_RECE) mtc_sta = mtc_sta | STA_PAR; /* rec in err? */ - else if (st != MTSE_OK) { /* other error? */ - r = mt_map_err (uptr, st); /* map error */ - if (r == SCPE_OK) { /* recoverable? */ - sim_activate (uptr, mtc_gtime); /* sched IRG */ - mtc_fnc = 0; /* NOP func */ - return SCPE_OK; - } - break; /* non-recov, done */ - } - if (mt_max < 12) { /* record too short? */ - mtc_sta = mtc_sta | STA_PAR; /* set flag */ - break; - } - } - if (mtc_dtf && (mt_ptr < mt_max)) { /* more chars? */ - if (mtd.flag) mtc_sta = mtc_sta | STA_TIM; - mtc_unit.buf = mtxb[mt_ptr++]; /* fetch next */ - mtdio (&mtd_dib, ioENF, 0); /* set dch flg */ - sim_activate (uptr, mtc_xtime); /* re-activate */ - return SCPE_OK; - } - sim_activate (uptr, mtc_gtime); /* schedule gap */ - mtc_fnc = 0; /* nop */ - return SCPE_OK; - - case FNC_WC: /* write */ - if (mtc_1st) mtc_1st = 0; /* no xfr on first */ - else { - if (mt_ptr < DBSIZE) { /* room in buffer? */ - mtxb[mt_ptr++] = (uint8) mtc_unit.buf; - mtc_sta = mtc_sta & ~STA_BOT; /* clear BOT */ - } - else mtc_sta = mtc_sta | STA_PAR; - } - if (mtc_dtf) { /* xfer flop set? */ - mtdio (&mtd_dib, ioENF, 0); /* set dch flg */ - sim_activate (uptr, mtc_xtime); /* re-activate */ - return SCPE_OK; - } - if (mt_ptr) { /* write buffer */ - st = sim_tape_wrrecf (uptr, mtxb, mt_ptr); /* write */ - if (st != MTSE_OK) { /* error? */ - r = mt_map_err (uptr, st); /* map error */ - break; /* done */ - } - } - sim_activate (uptr, mtc_gtime); /* schedule gap */ - mtc_fnc = 0; /* nop */ - return SCPE_OK; - - default: /* unknown */ - break; - } - -mtcio (&mtc_dib, ioENF, 0); /* set cch flg */ -mtc_sta = mtc_sta & ~STA_BUSY; /* not busy */ -return r; -} - -/* Map tape error status */ - -t_stat mt_map_err (UNIT *uptr, t_stat st) -{ -switch (st) { - - case MTSE_FMT: /* illegal fmt */ - case MTSE_UNATT: /* unattached */ - mtc_sta = mtc_sta | STA_REJ; /* reject */ - case MTSE_OK: /* no error */ - return SCPE_IERR; /* never get here! */ - - case MTSE_EOM: /* end of medium */ - case MTSE_TMK: /* end of file */ - mtc_sta = mtc_sta | STA_EOF; /* eof */ - break; - - case MTSE_IOERR: /* IO error */ - mtc_sta = mtc_sta | STA_PAR; /* error */ - if (mtc_stopioe) return SCPE_IOERR; - break; - - case MTSE_INVRL: /* invalid rec lnt */ - mtc_sta = mtc_sta | STA_PAR; - return SCPE_MTRLNT; - - case MTSE_RECE: /* record in error */ - mtc_sta = mtc_sta | STA_PAR; /* error */ - break; - - case MTSE_BOT: /* reverse into BOT */ - mtc_sta = mtc_sta | STA_BOT; /* set status */ - break; - - case MTSE_WRP: /* write protect */ - mtc_sta = mtc_sta | STA_REJ; /* reject */ - break; - } - -return SCPE_OK; -} - - -/* Controller clear */ - -t_stat mt_clear (void) -{ -t_stat st; - -if (sim_is_active (&mtc_unit) && /* write in prog? */ - (mtc_fnc == FNC_WC) && (mt_ptr > 0)) { /* yes, bad rec */ - st = sim_tape_wrrecf (&mtc_unit, mtxb, mt_ptr | MTR_ERF); - if (st != MTSE_OK) - mt_map_err (&mtc_unit, st); - } - -if (((mtc_fnc == FNC_REW) || (mtc_fnc == FNC_RWS)) && sim_is_active (&mtc_unit)) - sim_cancel (&mtc_unit); - -mtc_1st = mtc_dtf = 0; -mtc_sta = mtc_sta & STA_BOT; - -return SCPE_OK; -} - - -/* Reset routine */ - -t_stat mt_reset (DEVICE *dptr) -{ -DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ - -hp_enbdis_pair (dptr, /* make pair cons */ - (dptr == &mtd_dev) ? &mtc_dev : &mtd_dev); - -IOPRESET (dibptr); /* PRESET device (does not use PON) */ - -mtc_fnc = 0; -mtc_1st = mtc_dtf = 0; - -sim_cancel (&mtc_unit); /* cancel activity */ -sim_tape_reset (&mtc_unit); - -if (mtc_unit.flags & UNIT_ATT) - mtc_sta = (sim_tape_bot (&mtc_unit)? STA_BOT: 0) | - (sim_tape_wrp (&mtc_unit)? STA_WLK: 0); -else - mtc_sta = STA_LOCAL | STA_BUSY; - -return SCPE_OK; -} - - -/* Attach routine */ - -t_stat mtc_attach (UNIT *uptr, char *cptr) -{ -t_stat r; - -r = sim_tape_attach (uptr, cptr); /* attach unit */ -if (r != SCPE_OK) return r; /* update status */ -mtc_sta = STA_BOT; -return r; -} - -/* Detach routine */ - -t_stat mtc_detach (UNIT* uptr) -{ -mtc_sta = 0; /* update status */ -return sim_tape_detach (uptr); /* detach unit */ -} +/* hp2100_mt.c: HP 2100 12559A magnetic tape simulator + + Copyright (c) 1993-2016, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + MT 12559A 3030 nine track magnetic tape + + 13-May-16 JDB Modified for revised SCP API function parameter types + 24-Dec-14 JDB Added casts for explicit downward conversions + 10-Jan-13 MP Added DEV_TAPE to DEVICE flags + 09-May-12 JDB Separated assignments from conditional expressions + 25-Mar-12 JDB Removed redundant MTAB_VUN from "format" MTAB entry + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + 28-Mar-11 JDB Tidied up signal handling + 29-Oct-10 JDB Fixed command scanning error in mtcio ioIOO handler + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 04-Sep-08 JDB Fixed missing flag after CLR command + 02-Sep-08 JDB Moved write enable and format commands from MTD to MTC + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 07-Oct-04 JDB Allow enable/disable from either device + 14-Aug-04 RMS Modified handling of end of medium (suggested by Dave Bryan) + 06-Jul-04 RMS Fixed spurious timing error after CLC (found by Dave Bryan) + 26-Apr-04 RMS Fixed SFS x,C and SFC x,C + Implemented DMA SRQ (follows FLG) + 21-Dec-03 RMS Adjusted msc_ctime for TSB (from Mike Gemeny) + 25-Apr-03 RMS Revised for extended file support + 28-Mar-03 RMS Added multiformat support + 28-Feb-03 RMS Revised for magtape library + 30-Sep-02 RMS Revamped error handling + 28-Aug-02 RMS Added end of medium support + 30-May-02 RMS Widened POS to 32b + 22-Apr-02 RMS Added maximum record length test + 20-Jan-02 RMS Fixed bug on last character write + 03-Dec-01 RMS Added read only unit, extended SET/SHOW support + 07-Sep-01 RMS Moved function prototypes + 30-Nov-00 RMS Made variable names unique + 04-Oct-98 RMS V2.4 magtape format + + References: + - 12559A 9-Track Magnetic Tape Unit Interface Kit Operating and Service Manual + (12559-9001, Jul-1970) + - SIMH Magtape Representation and Handling (Bob Supnik, 30-Aug-2006) + + + The 3030 was one of HP's earliest tape drives. The 12559A controller + supported a single 800 bpi, 9-track drive, operating at 75 inches per second. + It had two unusual characteristics: + + - The controller accepted only one byte per I/O word, rather than packing + two bytes per word. + + - The drive could not read or write fewer than 12 bytes per record. + + The first behavior meant that DMA operation required the byte-unpacking + feature of the 12578A DMA card for the 2116 computer. The second meant that + software drivers had to pad short records with blanks or nulls. + + Implementation notes: + + 1. The HP 3030 Magnetic Tape Subsystem diagnostic, part number 20433-60001, + has never been located, so this simulator has not been fully tested. It + does pass a functional test under DOS-III using driver DVR22. +*/ + + +#include "hp2100_defs.h" +#include "sim_tape.h" + +#define DB_V_SIZE 16 /* max data buf */ +#define DBSIZE (1 << DB_V_SIZE) /* max data cmd */ + +/* Command - mtc_fnc */ + +#define FNC_CLR 0300 /* clear */ +#define FNC_WC 0031 /* write */ +#define FNC_RC 0023 /* read */ +#define FNC_GAP 0011 /* write gap */ +#define FNC_FSR 0003 /* forward space */ +#define FNC_BSR 0041 /* backward space */ +#define FNC_REW 0201 /* rewind */ +#define FNC_RWS 0101 /* rewind and offline */ +#define FNC_WFM 0035 /* write file mark */ + +/* Status - stored in mtc_sta, (d) = dynamic */ + +#define STA_LOCAL 0400 /* local (d) */ +#define STA_EOF 0200 /* end of file */ +#define STA_BOT 0100 /* beginning of tape */ +#define STA_EOT 0040 /* end of tape */ +#define STA_TIM 0020 /* timing error */ +#define STA_REJ 0010 /* programming error */ +#define STA_WLK 0004 /* write locked (d) */ +#define STA_PAR 0002 /* parity error */ +#define STA_BUSY 0001 /* busy (d) */ + +struct { + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } mtd = { CLEAR, CLEAR }; + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } mtc = { CLEAR, CLEAR, CLEAR }; + +int32 mtc_fnc = 0; /* function */ +int32 mtc_sta = 0; /* status register */ +int32 mtc_dtf = 0; /* data xfer flop */ +int32 mtc_1st = 0; /* first svc flop */ +int32 mtc_ctime = 40; /* command wait */ +int32 mtc_gtime = 1000; /* gap stop time */ +int32 mtc_xtime = 15; /* data xfer time */ +int32 mtc_stopioe = 1; /* stop on error */ +uint8 mtxb[DBSIZE] = { 0 }; /* data buffer */ +t_mtrlnt mt_ptr = 0, mt_max = 0; /* buffer ptrs */ +static const uint32 mtc_cmd[] = { + FNC_WC, FNC_RC, FNC_GAP, FNC_FSR, FNC_BSR, FNC_REW, FNC_RWS, FNC_WFM }; +static const uint32 mtc_cmd_count = sizeof (mtc_cmd) / sizeof (mtc_cmd[0]); + +DEVICE mtd_dev, mtc_dev; + +IOHANDLER mtdio; +IOHANDLER mtcio; + +t_stat mtc_svc (UNIT *uptr); +t_stat mt_reset (DEVICE *dptr); +t_stat mtc_attach (UNIT *uptr, CONST char *cptr); +t_stat mtc_detach (UNIT *uptr); +t_stat mt_map_err (UNIT *uptr, t_stat st); +t_stat mt_clear (void); + +/* MTD data structures + + mtd_dev MTD device descriptor + mtd_unit MTD unit list + mtd_reg MTD register list +*/ + +DIB mt_dib[] = { + { &mtdio, MTD }, + { &mtcio, MTC } + }; + +#define mtd_dib mt_dib[0] +#define mtc_dib mt_dib[1] + +UNIT mtd_unit = { UDATA (NULL, 0, 0) }; + +REG mtd_reg[] = { + { FLDATA (FLG, mtd.flag, 0) }, + { FLDATA (FBF, mtd.flagbuf, 0) }, + { BRDATA (DBUF, mtxb, 8, 8, DBSIZE) }, + { DRDATA (BPTR, mt_ptr, DB_V_SIZE + 1) }, + { DRDATA (BMAX, mt_max, DB_V_SIZE + 1) }, + { ORDATA (SC, mtd_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, mtd_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB mtd_mod[] = { + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &mtd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &mtd_dev }, + { 0 } + }; + +DEVICE mtd_dev = { + "MTD", &mtd_unit, mtd_reg, mtd_mod, + 1, 10, 16, 1, 8, 8, + NULL, NULL, &mt_reset, + NULL, NULL, NULL, + &mtd_dib, DEV_DISABLE | DEV_DIS + }; + +/* MTC data structures + + mtc_dev MTC device descriptor + mtc_unit MTC unit list + mtc_reg MTC register list + mtc_mod MTC modifier list +*/ + +UNIT mtc_unit = { UDATA (&mtc_svc, UNIT_ATTABLE + UNIT_ROABLE, 0) }; + +REG mtc_reg[] = { + { ORDATA (FNC, mtc_fnc, 8) }, + { ORDATA (STA, mtc_sta, 9) }, + { ORDATA (BUF, mtc_unit.buf, 8) }, + { FLDATA (CTL, mtc.control, 0) }, + { FLDATA (FLG, mtc.flag, 0) }, + { FLDATA (FBF, mtc.flagbuf, 0) }, + { FLDATA (DTF, mtc_dtf, 0) }, + { FLDATA (FSVC, mtc_1st, 0) }, + { DRDATA (POS, mtc_unit.pos, T_ADDR_W), PV_LEFT }, + { DRDATA (CTIME, mtc_ctime, 24), REG_NZ + PV_LEFT }, + { DRDATA (GTIME, mtc_gtime, 24), REG_NZ + PV_LEFT }, + { DRDATA (XTIME, mtc_xtime, 24), REG_NZ + PV_LEFT }, + { FLDATA (STOP_IOE, mtc_stopioe, 0) }, + { ORDATA (SC, mtc_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, mtc_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB mtc_mod[] = { + { MTUF_WLK, 0, "write enabled", "WRITEENABLED", NULL }, + { MTUF_WLK, MTUF_WLK, "write locked", "LOCKED", NULL }, + { MTAB_XTD | MTAB_VDV, 0, "FORMAT", "FORMAT", + &sim_tape_set_fmt, &sim_tape_show_fmt, NULL }, + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &mtd_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &mtd_dev }, + { 0 } + }; + +DEVICE mtc_dev = { + "MTC", &mtc_unit, mtc_reg, mtc_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &mt_reset, + NULL, &mtc_attach, &mtc_detach, + &mtc_dib, DEV_DISABLE | DEV_DIS | DEV_TAPE + }; + + +/* Data channel I/O signal handler + + The 12559A data channel interface has a number of non-standard features: + + - The card does not drive PRL or IRQ. + - The card does not respond to IAK. + - There is no control flip-flop; CLC resets the data transfer flip-flop. + - POPIO issues a CLR command and clears the flag and flag buffer flip-flops. + - CRS is not used. + + Implementation notes: + + 1. The data channel has a flag buffer flip-flop (necessary for the proper + timing of the flag flip-flop), but the data channel does not interrupt, + so the flag buffer serves no other purpose. +*/ + +uint32 mtdio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + mtd.flag = mtd.flagbuf = CLEAR; + break; + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + mtd.flag = mtd.flagbuf = SET; + break; + + case ioSFC: /* skip if flag is clear */ + setstdSKF (mtd); + break; + + case ioSFS: /* skip if flag is set */ + setstdSKF (mtd); + break; + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, mtc_unit.buf); /* merge in return status */ + break; + + case ioIOO: /* I/O data output */ + mtc_unit.buf = IODATA (stat_data) & DMASK8; /* store data */ + break; + + case ioPOPIO: /* power-on preset to I/O */ + mt_clear (); /* issue CLR to controller */ + mtd.flag = mtd.flagbuf = CLEAR; /* clear flag and flag buffer */ + break; + + case ioCLC: /* clear control flip-flop */ + mtc_dtf = 0; /* clr xfer flop */ + mtd.flag = mtd.flagbuf = CLEAR; /* clear flag and flag buffer */ + break; + + case ioSIR: /* set interrupt request */ + setstdSRQ (mtd); /* set standard SRQ signal */ + break; + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Command channel I/O signal handler. + + The 12559A command interface is reasonably standard, although POPIO clears, + rather than sets, the flag and flag buffer flip-flops. One unusual feature + is that commands are initiated when they are output to the interface with + OTA/B, rather than waiting until control is set with STC. STC simply enables + command-channel interrupts. + + Implementation notes: + + 1. In hardware, the command channel card passes PRH to PRL. The data card + actually drives PRL with the command channel's control and flag states. + That is, the priority chain is broken at the data card, although the + command card is interrupting. This works in hardware, but we must break + PRL at the command card under simulation to allow the command card to + interrupt. + + 2. In hardware, the CLR command takes 5 milliseconds to complete. During + this time, the BUSY bit is set in the status word. Under simulation, we + complete immediately, and the BUSY bit never sets.. +*/ + +uint32 mtcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +uint16 data; +uint32 i; +int32 valid; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + mtc.flag = mtc.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + mtc.flag = mtc.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (mtc); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (mtc); + break; + + + case ioIOI: /* I/O data input */ + data = mtc_sta & ~(STA_LOCAL | STA_WLK | STA_BUSY); + + if (mtc_unit.flags & UNIT_ATT) { /* construct status */ + if (sim_is_active (&mtc_unit)) + data = data | STA_BUSY; + + if (sim_tape_wrp (&mtc_unit)) + data = data | STA_WLK; + } + else + data = data | STA_BUSY | STA_LOCAL; + + stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + data = IODATA (stat_data) & DMASK8; + mtc_sta = mtc_sta & ~STA_REJ; /* clear reject */ + + if (data == FNC_CLR) { /* clear? */ + mt_clear (); /* send CLR to controller */ + + mtd.flag = mtd.flagbuf = CLEAR; /* clear data flag and flag buffer */ + mtc.flag = mtc.flagbuf = SET; /* set command flag and flag buffer */ + break; /* command completes immediately */ + } + + for (i = valid = 0; i < mtc_cmd_count; i++) /* is fnc valid? */ + if (data == mtc_cmd[i]) { + valid = 1; + break; + } + + if (!valid || sim_is_active (&mtc_unit) || /* is cmd valid? */ + ((mtc_sta & STA_BOT) && (data == FNC_BSR)) || + (sim_tape_wrp (&mtc_unit) && + ((data == FNC_WC) || (data == FNC_GAP) || (data == FNC_WFM)))) + mtc_sta = mtc_sta | STA_REJ; + + else { + sim_activate (&mtc_unit, mtc_ctime); /* start tape */ + mtc_fnc = data; /* save function */ + mtc_sta = STA_BUSY; /* unit busy */ + mt_ptr = 0; /* init buffer ptr */ + + mtcio (&mtc_dib, ioCLF, 0); /* clear flags */ + mtcio (&mtd_dib, ioCLF, 0); + + mtc_1st = 1; /* set 1st flop */ + mtc_dtf = 1; /* set xfer flop */ + } + break; + + + case ioPOPIO: /* power-on preset to I/O */ + mtc.flag = mtc.flagbuf = CLEAR; /* clear flag and flag buffer */ + break; + + + case ioCRS: /* control reset */ + case ioCLC: /* clear control flip-flop */ + mtc.control = CLEAR; + break; + + + case ioSTC: /* set control flip-flop */ + mtc.control = SET; + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (mtc); /* set standard PRL signal */ + setstdIRQ (mtc); /* set standard IRQ signal */ + setstdSRQ (mtc); /* set standard SRQ signal */ + break; + + case ioIAK: /* interrupt acknowledge */ + mtc.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Unit service + + If rewind done, reposition to start of tape, set status + else, do operation, set done, interrupt + + Can't be write locked, can only write lock detached unit +*/ + +t_stat mtc_svc (UNIT *uptr) +{ +t_mtrlnt tbc; +t_stat st, r = SCPE_OK; + +if ((mtc_unit.flags & UNIT_ATT) == 0) { /* offline? */ + mtc_sta = STA_LOCAL | STA_REJ; /* rejected */ + mtcio (&mtc_dib, ioENF, 0); /* set cch flg */ + return IOERROR (mtc_stopioe, SCPE_UNATT); + } + +switch (mtc_fnc) { /* case on function */ + + case FNC_REW: /* rewind */ + sim_tape_rewind (uptr); /* BOT */ + mtc_sta = STA_BOT; /* update status */ + break; + + case FNC_RWS: /* rewind and offline */ + sim_tape_rewind (uptr); /* clear position */ + return sim_tape_detach (uptr); /* don't set cch flg */ + + case FNC_WFM: /* write file mark */ + st = sim_tape_wrtmk (uptr); /* write tmk */ + if (st != MTSE_OK) /* error? */ + r = mt_map_err (uptr, st); /* map error */ + mtc_sta = STA_EOF; /* set EOF status */ + break; + + case FNC_GAP: /* erase gap */ + break; + + case FNC_FSR: /* space forward */ + st = sim_tape_sprecf (uptr, &tbc); /* space rec fwd */ + if (st != MTSE_OK) /* error? */ + r = mt_map_err (uptr, st); /* map error */ + break; + + case FNC_BSR: /* space reverse */ + st = sim_tape_sprecr (uptr, &tbc); /* space rec rev */ + if (st != MTSE_OK) /* error? */ + r = mt_map_err (uptr, st); /* map error */ + break; + + case FNC_RC: /* read */ + if (mtc_1st) { /* first svc? */ + mtc_1st = mt_ptr = 0; /* clr 1st flop */ + st = sim_tape_rdrecf (uptr, mtxb, &mt_max, DBSIZE); /* read rec */ + if (st == MTSE_RECE) mtc_sta = mtc_sta | STA_PAR; /* rec in err? */ + else if (st != MTSE_OK) { /* other error? */ + r = mt_map_err (uptr, st); /* map error */ + if (r == SCPE_OK) { /* recoverable? */ + sim_activate (uptr, mtc_gtime); /* sched IRG */ + mtc_fnc = 0; /* NOP func */ + return SCPE_OK; + } + break; /* non-recov, done */ + } + if (mt_max < 12) { /* record too short? */ + mtc_sta = mtc_sta | STA_PAR; /* set flag */ + break; + } + } + if (mtc_dtf && (mt_ptr < mt_max)) { /* more chars? */ + if (mtd.flag) mtc_sta = mtc_sta | STA_TIM; + mtc_unit.buf = mtxb[mt_ptr++]; /* fetch next */ + mtdio (&mtd_dib, ioENF, 0); /* set dch flg */ + sim_activate (uptr, mtc_xtime); /* re-activate */ + return SCPE_OK; + } + sim_activate (uptr, mtc_gtime); /* schedule gap */ + mtc_fnc = 0; /* nop */ + return SCPE_OK; + + case FNC_WC: /* write */ + if (mtc_1st) mtc_1st = 0; /* no xfr on first */ + else { + if (mt_ptr < DBSIZE) { /* room in buffer? */ + mtxb[mt_ptr++] = (uint8) mtc_unit.buf; + mtc_sta = mtc_sta & ~STA_BOT; /* clear BOT */ + } + else mtc_sta = mtc_sta | STA_PAR; + } + if (mtc_dtf) { /* xfer flop set? */ + mtdio (&mtd_dib, ioENF, 0); /* set dch flg */ + sim_activate (uptr, mtc_xtime); /* re-activate */ + return SCPE_OK; + } + if (mt_ptr) { /* write buffer */ + st = sim_tape_wrrecf (uptr, mtxb, mt_ptr); /* write */ + if (st != MTSE_OK) { /* error? */ + r = mt_map_err (uptr, st); /* map error */ + break; /* done */ + } + } + sim_activate (uptr, mtc_gtime); /* schedule gap */ + mtc_fnc = 0; /* nop */ + return SCPE_OK; + + default: /* unknown */ + break; + } + +mtcio (&mtc_dib, ioENF, 0); /* set cch flg */ +mtc_sta = mtc_sta & ~STA_BUSY; /* not busy */ +return r; +} + +/* Map tape error status */ + +t_stat mt_map_err (UNIT *uptr, t_stat st) +{ +switch (st) { + + case MTSE_FMT: /* illegal fmt */ + case MTSE_UNATT: /* unattached */ + mtc_sta = mtc_sta | STA_REJ; /* reject */ + case MTSE_OK: /* no error */ + return SCPE_IERR; /* never get here! */ + + case MTSE_EOM: /* end of medium */ + case MTSE_TMK: /* end of file */ + mtc_sta = mtc_sta | STA_EOF; /* eof */ + break; + + case MTSE_IOERR: /* IO error */ + mtc_sta = mtc_sta | STA_PAR; /* error */ + if (mtc_stopioe) return SCPE_IOERR; + break; + + case MTSE_INVRL: /* invalid rec lnt */ + mtc_sta = mtc_sta | STA_PAR; + return SCPE_MTRLNT; + + case MTSE_RECE: /* record in error */ + mtc_sta = mtc_sta | STA_PAR; /* error */ + break; + + case MTSE_BOT: /* reverse into BOT */ + mtc_sta = mtc_sta | STA_BOT; /* set status */ + break; + + case MTSE_WRP: /* write protect */ + mtc_sta = mtc_sta | STA_REJ; /* reject */ + break; + } + +return SCPE_OK; +} + + +/* Controller clear */ + +t_stat mt_clear (void) +{ +t_stat st; + +if (sim_is_active (&mtc_unit) && /* write in prog? */ + (mtc_fnc == FNC_WC) && (mt_ptr > 0)) { /* yes, bad rec */ + st = sim_tape_wrrecf (&mtc_unit, mtxb, mt_ptr | MTR_ERF); + if (st != MTSE_OK) + mt_map_err (&mtc_unit, st); + } + +if (((mtc_fnc == FNC_REW) || (mtc_fnc == FNC_RWS)) && sim_is_active (&mtc_unit)) + sim_cancel (&mtc_unit); + +mtc_1st = mtc_dtf = 0; +mtc_sta = mtc_sta & STA_BOT; + +return SCPE_OK; +} + + +/* Reset routine */ + +t_stat mt_reset (DEVICE *dptr) +{ +DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ + +hp_enbdis_pair (dptr, /* make pair cons */ + (dptr == &mtd_dev) ? &mtc_dev : &mtd_dev); + +IOPRESET (dibptr); /* PRESET device (does not use PON) */ + +mtc_fnc = 0; +mtc_1st = mtc_dtf = 0; + +sim_cancel (&mtc_unit); /* cancel activity */ +sim_tape_reset (&mtc_unit); + +if (mtc_unit.flags & UNIT_ATT) + mtc_sta = (sim_tape_bot (&mtc_unit)? STA_BOT: 0) | + (sim_tape_wrp (&mtc_unit)? STA_WLK: 0); +else + mtc_sta = STA_LOCAL | STA_BUSY; + +return SCPE_OK; +} + + +/* Attach routine */ + +t_stat mtc_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat r; + +r = sim_tape_attach (uptr, cptr); /* attach unit */ +if (r != SCPE_OK) return r; /* update status */ +mtc_sta = STA_BOT; +return r; +} + +/* Detach routine */ + +t_stat mtc_detach (UNIT* uptr) +{ +mtc_sta = 0; /* update status */ +return sim_tape_detach (uptr); /* detach unit */ +} diff --git a/HP2100/hp2100_mux.c b/HP2100/hp2100_mux.c index e098166b..9f3ecb5d 100644 --- a/HP2100/hp2100_mux.c +++ b/HP2100/hp2100_mux.c @@ -1,1406 +1,1408 @@ -/* hp2100_mux.c: HP 2100 12920A terminal multiplexor simulator - - Copyright (c) 2002-2014, Robert M Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - MUX,MUXL,MUXM 12920A terminal multiplexor - - 24-Dec-14 JDB Added casts for explicit downward conversions - 10-Jan-13 MP Added DEV_MUX and additional DEVICE field values - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - Removed DEV_NET to allow restoration of listening port - 28-Mar-11 JDB Tidied up signal handling - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 25-Nov-08 JDB Revised for new multiplexer library SHOW routines - 09-Oct-08 JDB "muxl_unit" defined one too many units (17 instead of 16) - 10-Sep-08 JDB SHOW MUX CONN/STAT with SET MUX DIAG is no longer disallowed - 07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach - 27-Aug-08 JDB Added LINEORDER support - 12-Aug-08 JDB Added BREAK deferral to allow RTE break-mode to work - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 16-Apr-08 JDB Sync mux poll with console poll for idle compatibility - 06-Mar-07 JDB Corrected "mux_sta" size from 16 to 21 elements - Fixed "muxc_reset" to clear lines 16-20 - 26-Feb-07 JDB Added debug printouts - Fixed control card OTx to set current channel number - Fixed to set "muxl_ibuf" in response to a transmit interrupt - Changed "mux_xbuf", "mux_rbuf" declarations from 8 to 16 bits - Fixed to set "mux_rchp" when a line break is received - Fixed incorrect "odd_par" table values - Reversed test in "RCV_PAR" to return "LIL_PAR" on odd parity - Fixed mux reset (ioCRS) to clear port parameters - Fixed to use PUT_DCH instead of PUT_CCH for data channel status - 10-Feb-07 JDB Added DIAG/TERM modifiers to implement diagnostic mode - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 02-Jun-06 JDB Fixed compiler warning for mux_ldsc init - 22-Nov-05 RMS Revised for new terminal processing routines - 29-Jun-05 RMS Added SET MUXLn DISCONNECT - 07-Oct-04 JDB Allow enable/disable from any device - 26-Apr-04 RMS Fixed SFS x,C and SFC x,C - Implemented DMA SRQ (follows FLG) - 05-Jan-04 RMS Revised for tmxr library changes - 21-Dec-03 RMS Added invalid character screening for TSB (from Mike Gemeny) - 09-May-03 RMS Added network device flag - 01-Nov-02 RMS Added 7B/8B support - 22-Aug-02 RMS Updated for changes to sim_tmxr - - Reference: - - 12920A Asynchronous Multiplexer Interface Kits Operating and Service Manual - (12920-90001, Oct-1972) - - - The 12920A was a 16-channel asynchronous terminal multiplexer. It supported - direct-connected terminals as well as modems at speeds up to 2400 baud. It - was the primary terminal multiplexer for the HP 2000 series of Time-Shared - BASIC systems. - - The multiplexer was implemented as a three-card set consisting of a lower - data card, an upper data card, and a modem control card. Under simulation, - these are implemented by three devices: - - MUXL lower data card (lines) - MUX upper data card (scanner) - MUXM control card (modem control) - - The lower and upper data cards must be in adjacent I/O slots. The control - card may be placed in any slot, although in practice it was placed in the - slot above the upper data card, so that all three cards were physically - together. - - The 12920A supported one or two control cards (two cards were used with - 801-type automatic dialers). Under simulation, only one control card is - supported. - - Implementation notes: - - 1. If a BREAK is detected during an input poll, and we are not in diagnostic - mode, we defer recognition until either a character is output or a second - successive input poll occurs. This is necessary for RTE break-mode - operation. Without this deferral, a BREAK during output would be ignored - by the RTE driver, making it impossible to stop a long listing. - - The problem is due to timing differences between simulated and real time. - The RTE multiplexer driver is a privileged driver. Privileged drivers - bypass RTE to provide rapid interrupt handling. To inform RTE that an - operation is complete, e.g., that a line has been written, the interrupt - section of the driver sets a device timeout of one clock tick (10 - milliseconds). When that timeout occurs, RTE is entered normally to - complete the I/O transaction. While the completion timeout is pending, - the driver ignores any further interrupts from the multiplexer line. - - The maximum communication rate for the multiplexer is 2400 baud, or - approximately 4.2 milliseconds per character transferred. A typical line - of 20 characters would therefore take ~85 milliseconds, plus the 10 - millisecond completion timeout, or about 95 milliseconds total. BREAK - recognition would be ignored for roughly 10% of that time. At lower baud - rates, recognition would be ignored for a correspondingly smaller - percentage of the time. - - However, SIMH uses an optimized timing of 500 instructions per character - transfer, rather than the ~6600 instructions that a character transfer - should take, and so a typical 20-character line will take about 11,000 - instructions. On the other hand, the clock tick is calibrated to real - time, and 10 milliseconds of real time takes about 420,000 instructions - on a 2.0 GHz PC. To be recognized, then, the BREAK key must be pressed - in a window that is open for about 2.5% of the time. Therefore, the - BREAK key will be ignored about 97.5% of the time, and RTE break-mode - effectively will not work. - - Deferring BREAK recognition until the next character is output ensures - that the BREAK interrupt will be accepted (the simulator delivers input - interrupts before output interrupts, so the BREAK interrupt arrives - before the output character transmit interrupt). If an output operation - is not in progress, then the BREAK will be recognized at the next input - poll. -*/ - - -#include - -#include "hp2100_defs.h" -#include "sim_tmxr.h" - - -/* Unit references */ - -#define MUX_LINES 16 /* number of user lines */ -#define MUX_ILINES 5 /* number of diag rcv only lines */ - - -/* Service times */ - -#define MUXL_WAIT 500 - - -/* Unit flags */ - -#define UNIT_V_MDM (TTUF_V_UF + 0) /* modem control */ -#define UNIT_V_DIAG (TTUF_V_UF + 1) /* loopback diagnostic */ -#define UNIT_MDM (1 << UNIT_V_MDM) -#define UNIT_DIAG (1 << UNIT_V_DIAG) - -/* Debug flags */ - -#define DEB_CMDS (1 << 0) /* Command initiation and completion */ -#define DEB_CPU (1 << 1) /* CPU I/O */ -#define DEB_XFER (1 << 2) /* Socket receive and transmit */ - -/* Channel number (OTA upper, LIA lower or upper) */ - -#define MUX_V_CHAN 10 /* channel num */ -#define MUX_M_CHAN 037 -#define MUX_CHAN(x) (((x) >> MUX_V_CHAN) & MUX_M_CHAN) - -/* OTA, lower = parameters or data */ - -#define OTL_P 0100000 /* parameter */ -#define OTL_TX 0040000 /* transmit */ -#define OTL_ENB 0020000 /* enable */ -#define OTL_TPAR 0010000 /* xmt parity */ -#define OTL_ECHO 0010000 /* rcv echo */ -#define OTL_DIAG 0004000 /* diagnose */ -#define OTL_SYNC 0004000 /* sync */ -#define OTL_V_LNT 8 /* char length */ -#define OTL_M_LNT 07 -#define OTL_LNT(x) (((x) >> OTL_V_LNT) & OTL_M_LNT) -#define OTL_V_BAUD 0 /* baud rate */ -#define OTL_M_BAUD 0377 -#define OTL_BAUD(x) (((x) >> OTL_V_BAUD) & OTL_M_BAUD) -#define OTL_CHAR 03777 /* char mask */ -#define OTL_PAR 0200 /* char parity */ - -/* LIA, lower = received data */ - -#define LIL_PAR 0100000 /* parity */ -#define PUT_DCH(x) (((x) & MUX_M_CHAN) << MUX_V_CHAN) -#define LIL_CHAR 01777 /* character */ - -/* LIA, upper = status */ - -#define LIU_SEEK 0100000 /* seeking NI */ -#define LIU_DG 0000010 /* diagnose */ -#define LIU_BRK 0000004 /* break */ -#define LIU_LOST 0000002 /* char lost */ -#define LIU_TR 0000001 /* trans/rcv */ - -/* OTA, control */ - -#define OTC_SCAN 0100000 /* scan */ -#define OTC_UPD 0040000 /* update */ -#define OTC_V_CHAN 10 /* channel */ -#define OTC_M_CHAN 017 -#define OTC_CHAN(x) (((x) >> OTC_V_CHAN) & OTC_M_CHAN) -#define OTC_EC2 0000200 /* enable Cn upd */ -#define OTC_EC1 0000100 -#define OTC_C2 0000040 /* Cn flops */ -#define OTC_C1 0000020 -#define OTC_V_C 4 /* S1 to C1 */ -#define OTC_ES2 0000010 /* enb comparison */ -#define OTC_ES1 0000004 -#define OTC_V_ES 2 -#define OTC_SS2 0000002 /* SSn flops */ -#define OTC_SS1 0000001 -#define OTC_RW (OTC_ES2|OTC_ES1|OTC_SS2|OTC_SS1) -#define RTS OCT_C2 /* C2 = rts */ -#define DTR OTC_C1 /* C1 = dtr */ - -/* LIA, control */ - -#define LIC_MBO 0140000 /* always set */ -#define LIC_V_CHAN 10 /* channel */ -#define LIC_M_CHAN 017 -#define PUT_CCH(x) (((x) & OTC_M_CHAN) << OTC_V_CHAN) -#define LIC_I2 0001000 /* change flags */ -#define LIC_I1 0000400 -#define LIC_S2 0000002 /* Sn flops */ -#define LIC_S1 0000001 -#define LIC_V_I 8 /* S1 to I1 */ -#define CDET LIC_S2 /* S2 = cdet */ -#define DSR LIC_S1 /* S1 = dsr */ - -#define LIC_TSTI(ch) (((muxc_lia[ch] ^ muxc_ota[ch]) & \ - ((muxc_ota[ch] & (OTC_ES2|OTC_ES1)) >> OTC_V_ES)) \ - << LIC_V_I) - - -/* Program constants */ - -static const uint8 odd_par [256] = { - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 000-017 */ - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 020-037 */ - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 040-067 */ - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 060-077 */ - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 100-117 */ - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 120-137 */ - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 140-157 */ - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 160-177 */ - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 200-217 */ - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 220-237 */ - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 240-267 */ - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 260-277 */ - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 300-317 */ - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 320-337 */ - 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 340-357 */ - 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 /* 360-377 */ - }; - -#define RCV_PAR(x) (odd_par[(x) & 0377] ? 0 : LIL_PAR) -#define XMT_PAR(x) (odd_par[(x) & 0377] ? 0 : OTL_PAR) - - -/* Multiplexer controller state variables */ - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } muxl = { CLEAR, CLEAR, CLEAR }; - -uint32 muxl_ibuf = 0; /* low in: rcv data */ -uint32 muxl_obuf = 0; /* low out: param */ - -uint32 muxu_ibuf = 0; /* upr in: status */ -uint32 muxu_obuf = 0; /* upr out: chan */ - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } muxc = { CLEAR, CLEAR, CLEAR }; - -uint32 muxc_chan = 0; /* ctrl chan */ -uint32 muxc_scan = 0; /* ctrl scan */ - - -/* Multiplexer per-line state variables */ - -uint16 mux_sta [MUX_LINES + MUX_ILINES]; /* line status */ -uint16 mux_rpar [MUX_LINES + MUX_ILINES]; /* rcv param */ -uint16 mux_xpar [MUX_LINES]; /* xmt param */ -uint8 mux_rchp [MUX_LINES + MUX_ILINES]; /* rcv chr pend */ -uint8 mux_xdon [MUX_LINES]; /* xmt done */ -uint8 muxc_ota [MUX_LINES]; /* ctrl: Cn,ESn,SSn */ -uint8 muxc_lia [MUX_LINES]; /* ctrl: Sn */ -uint8 mux_defer [MUX_LINES]; /* break deferred flags */ - - -/* Multiplexer per-line buffer variables */ - -uint16 mux_rbuf[MUX_LINES + MUX_ILINES]; /* rcv buf */ -uint16 mux_xbuf[MUX_LINES]; /* xmt buf */ - - -/* Multiplexer local routines */ - -void mux_receive (int32 ln, int32 c, t_bool diag); -void mux_data_int (void); -void mux_ctrl_int (void); -void mux_diag (int32 c); - - -/* Multiplexer global routines */ - -IOHANDLER muxlio; -IOHANDLER muxuio; -IOHANDLER muxcio; - -t_stat muxi_svc (UNIT *uptr); -t_stat muxo_svc (UNIT *uptr); -t_stat muxc_reset (DEVICE *dptr); -t_stat mux_attach (UNIT *uptr, char *cptr); -t_stat mux_detach (UNIT *uptr); -t_stat mux_setdiag (UNIT *uptr, int32 val, char *cptr, void *desc); - - -/* MUXL/MUXU device information block. - - The DIBs of adjacent cards must be contained in an array, so they are defined - here and referenced in the lower and upper card device structures. -*/ - -DIB mux_dib[] = { - { &muxlio, MUXL }, - { &muxuio, MUXU } - }; - -#define muxl_dib mux_dib[0] -#define muxu_dib mux_dib[1] - - -/* MUXL data structures. - - muxl_dib MUXL device information block - muxl_unit MUXL unit list - muxl_reg MUXL register list - muxl_mod MUXL modifier list - muxl_dev MUXL device descriptor -*/ - -TMXR mux_desc; - -DEVICE muxl_dev; - -UNIT muxl_unit[] = { - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, - { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT } - }; - -REG muxl_reg[] = { - { FLDATA (CTL, muxl.control, 0) }, - { FLDATA (FLG, muxl.flag, 0) }, - { FLDATA (FBF, muxl.flagbuf, 0) }, - { BRDATA (STA, mux_sta, 8, 16, MUX_LINES + MUX_ILINES) }, - { BRDATA (RPAR, mux_rpar, 8, 16, MUX_LINES + MUX_ILINES) }, - { BRDATA (XPAR, mux_xpar, 8, 16, MUX_LINES) }, - { BRDATA (RBUF, mux_rbuf, 8, 16, MUX_LINES + MUX_ILINES) }, - { BRDATA (XBUF, mux_xbuf, 8, 16, MUX_LINES) }, - { BRDATA (RCHP, mux_rchp, 8, 1, MUX_LINES + MUX_ILINES) }, - { BRDATA (XDON, mux_xdon, 8, 1, MUX_LINES) }, - { BRDATA (BDFR, mux_defer, 8, 1, MUX_LINES) }, - { URDATA (TIME, muxl_unit[0].wait, 10, 24, 0, - MUX_LINES, REG_NZ + PV_LEFT) }, - { ORDATA (SC, muxl_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, muxl_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB muxl_mod[] = { - { TT_MODE, TT_MODE_UC, "UC", "UC", NULL, NULL, NULL }, - { TT_MODE, TT_MODE_7B, "7b", "7B", NULL, NULL, NULL }, - { TT_MODE, TT_MODE_8B, "8b", "8B", NULL, NULL, NULL }, - { TT_MODE, TT_MODE_7P, "7p", "7P", NULL, NULL, NULL }, - - { UNIT_MDM, UNIT_MDM, "dataset", "DATASET", NULL, NULL, NULL }, - { UNIT_MDM, 0, "no dataset", "NODATASET", NULL, NULL, NULL }, - - { MTAB_XTD | MTAB_VUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, &mux_desc }, - { MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, &mux_desc }, - - { MTAB_XTD | MTAB_VUN, 0, NULL, "DISCONNECT", &tmxr_dscln, NULL, &mux_desc }, - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &muxl_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &muxl_dev }, - - { 0 } - }; - -DEVICE muxl_dev = { - "MUXL", /* device name */ - muxl_unit, /* unit array */ - muxl_reg, /* register array */ - muxl_mod, /* modifier array */ - MUX_LINES, /* number of units */ - 10, /* address radix */ - 31, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 8, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &muxc_reset, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - &muxl_dib, /* device information block */ - DEV_DISABLE, /* device flags */ - 0, /* debug control flags */ - NULL, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL, /* logical device name */ - NULL, /* help routine */ - NULL, /* help attach routine*/ - NULL /* help context */ - }; - - -/* MUXU data structures - - mux_order MUX line connection order table - mux_ldsc MUX terminal multiplexer line descriptors - mux_desc MUX terminal multiplexer device descriptor - - muxu_dib MUXU device information block - muxu_unit MUXU unit list - muxu_reg MUXU register list - muxu_mod MUXU modifier list - muxu_deb MUXU debug list - muxu_dev MUXU device descriptor -*/ - -DEVICE muxu_dev; - -int32 mux_order [MUX_LINES] = { -1 }; /* connection order */ -TMLN mux_ldsc [MUX_LINES] = { { 0 } }; /* line descriptors */ -TMXR mux_desc = { MUX_LINES, 0, 0, mux_ldsc, mux_order }; /* device descriptor */ - -UNIT muxu_unit = { UDATA (&muxi_svc, UNIT_ATTABLE, 0), POLL_FIRST }; - -REG muxu_reg[] = { - { ORDATA (IBUF, muxu_ibuf, 16) }, - { ORDATA (OBUF, muxu_obuf, 16) }, - { ORDATA (SC, muxu_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, muxu_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB muxu_mod[] = { - { UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAG", &mux_setdiag, NULL, NULL }, - { UNIT_DIAG, 0, "terminal mode", "TERM", &mux_setdiag, NULL, NULL }, - { UNIT_ATT, UNIT_ATT, "", NULL, NULL, &tmxr_show_summ, &mux_desc }, - - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LINEORDER", "LINEORDER", &tmxr_set_lnorder, &tmxr_show_lnorder, &mux_desc }, - - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, &mux_desc }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, &mux_desc }, - { MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT", &tmxr_dscln, NULL, &mux_desc }, - { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &muxl_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &muxl_dev }, - - { 0 } - }; - -DEBTAB muxu_deb [] = { - { "CMDS", DEB_CMDS }, - { "CPU", DEB_CPU }, - { "XFER", DEB_XFER }, - { NULL, 0 } - }; - -DEVICE muxu_dev = { - "MUX", /* device name */ - &muxu_unit, /* unit array */ - muxu_reg, /* register array */ - muxu_mod, /* modifier array */ - 1, /* number of units */ - 10, /* address radix */ - 31, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 8, /* data width */ - &tmxr_ex, /* examine routine */ - &tmxr_dep, /* deposit routine */ - &muxc_reset, /* reset routine */ - NULL, /* boot routine */ - &mux_attach, /* attach routine */ - &mux_detach, /* detach routine */ - &muxu_dib, /* device information block */ - DEV_DISABLE | DEV_DEBUG | DEV_MUX, /* device flags */ - 0, /* debug control flags */ - muxu_deb, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL, /* logical device name */ - NULL, /* help routine */ - NULL, /* help attach routine*/ - (void *) &mux_desc /* help context */ - }; - - -/* MUXC data structures. - - muxc_dib MUXC device information block - muxc_unit MUXC unit list - muxc_reg MUXC register list - muxc_mod MUXC modifier list - muxc_dev MUXC device descriptor -*/ - -DEVICE muxc_dev; - -DIB muxc_dib = { &muxcio, MUXC }; - -UNIT muxc_unit = { UDATA (NULL, 0, 0) }; - -REG muxc_reg[] = { - { FLDATA (CTL, muxc.control, 0) }, - { FLDATA (FLG, muxc.flag, 0) }, - { FLDATA (FBF, muxc.flagbuf, 0) }, - { FLDATA (SCAN, muxc_scan, 0) }, - { ORDATA (CHAN, muxc_chan, 4) }, - { BRDATA (DSO, muxc_ota, 8, 6, MUX_LINES) }, - { BRDATA (DSI, muxc_lia, 8, 2, MUX_LINES) }, - { ORDATA (SC, muxc_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, muxc_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB muxc_mod[] = { - { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &muxc_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &muxc_dev }, - { 0 } - }; - -DEVICE muxc_dev = { - "MUXM", /* device name */ - &muxc_unit, /* unit array */ - muxc_reg, /* register array */ - muxc_mod, /* modifier array */ - 1, /* number of units */ - 10, /* address radix */ - 31, /* address width */ - 1, /* address increment */ - 8, /* data radix */ - 8, /* data width */ - NULL, /* examine routine */ - NULL, /* deposit routine */ - &muxc_reset, /* reset routine */ - NULL, /* boot routine */ - NULL, /* attach routine */ - NULL, /* detach routine */ - &muxc_dib, /* device information block */ - DEV_DISABLE, /* device flags */ - 0, /* debug control flags */ - NULL, /* debug flag name table */ - NULL, /* memory size change routine */ - NULL, /* logical device name */ - NULL, /* help routine */ - NULL, /* help attach routine*/ - NULL /* help context */ - }; - - -/* Lower data card I/O signal handler. - - Implementation notes: - - 1. The operating manual says that "at least 100 milliseconds of CLC 0s must - be programmed" by systems employing the multiplexer to ensure that the - multiplexer resets. In practice, such systems issue 128K CLC 0 - instructions. As we provide debug logging of multiplexer resets, a CRS - counter is used to ensure that only one debug line is printed in response - to these 128K CRS invocations. -*/ - -uint32 muxlio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -int32 ln; -const char *hold_or_clear = (signal_set & ioCLF ? ",C" : ""); -static uint32 crs_count = 0; /* cntr for ioCRS repeat */ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -if (crs_count && !(signal_set & ioCRS)) { /* counting CRSes and not present? */ - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) /* report reset count */ - fprintf (sim_deb, ">>MUXl cmds: [CRS] Multiplexer reset %d times\n", - crs_count); - - crs_count = 0; /* clear counter */ - } - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - muxl.flag = muxl.flagbuf = CLEAR; - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fputs (">>MUXl cmds: [CLF] Flag cleared\n", sim_deb); - - mux_data_int (); /* look for new int */ - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - muxl.flag = muxl.flagbuf = SET; - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fputs (">>MUXl cmds: [STF] Flag set\n", sim_deb); - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (muxl); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (muxl); - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, muxl_ibuf); /* merge in return status */ - - if (DEBUG_PRI (muxu_dev, DEB_CPU)) - fprintf (sim_deb, ">>MUXl cpu: [LIx%s] Data = %06o\n", hold_or_clear, muxl_ibuf); - break; - - - case ioIOO: /* I/O data output */ - muxl_obuf = IODATA (stat_data); /* store data */ - - if (DEBUG_PRI (muxu_dev, DEB_CPU)) - if (muxl_obuf & OTL_P) - fprintf (sim_deb, ">>MUXl cpu: [OTx%s] Parameter = %06o\n", hold_or_clear, muxl_obuf); - else - fprintf (sim_deb, ">>MUXl cpu: [OTx%s] Data = %06o\n", hold_or_clear, muxl_obuf); - break; - - - case ioPOPIO: /* power-on preset to I/O */ - muxl.flag = muxl.flagbuf = SET; /* set flag andflag buffer */ - break; - - - case ioCRS: /* control reset */ - if (crs_count == 0) { /* first reset? */ - muxl.control = CLEAR; /* clear control flip-flop */ - - for (ln = 0; ln < MUX_LINES; ln++) { /* clear transmit info */ - mux_xbuf[ln] = mux_xpar[ln] = 0; - muxc_ota[ln] = muxc_lia[ln] = mux_xdon[ln] = 0; - } - - for (ln = 0; ln < (MUX_LINES + MUX_ILINES); ln++) { - mux_rbuf[ln] = mux_rpar[ln] = 0; /* clear receive info */ - mux_sta[ln] = mux_rchp[ln] = 0; - } - } - - crs_count = crs_count + 1; /* increment count */ - break; - - - case ioCLC: /* clear control flip-flop */ - muxl.control = CLEAR; - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MUXl cmds: [CLC%s] Data interrupt inhibited\n", hold_or_clear); - break; - - - case ioSTC: /* set control flip-flop */ - muxl.control = SET; /* set control */ - - ln = MUX_CHAN (muxu_obuf); /* get chan # */ - - if (muxl_obuf & OTL_TX) { /* transmit? */ - if (ln < MUX_LINES) { /* line valid? */ - if (muxl_obuf & OTL_P) { /* parameter? */ - mux_xpar[ln] = (uint16) muxl_obuf; /* store param value */ - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, - ">>MUXl cmds: [STC%s] Transmit channel %d parameter %06o stored\n", - hold_or_clear, ln, muxl_obuf); - } - - else { /* data */ - if (mux_xpar[ln] & OTL_TPAR) /* parity requested? */ - muxl_obuf = /* add parity bit */ - muxl_obuf & ~OTL_PAR | - XMT_PAR(muxl_obuf); - mux_xbuf[ln] = (uint16) muxl_obuf; /* load buffer */ - - if (sim_is_active (&muxl_unit[ln])) { /* still working? */ - mux_sta[ln] = mux_sta[ln] | LIU_LOST; /* char lost */ - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MUXl cmds: [STC%s] Transmit channel %d data overrun\n", - hold_or_clear, ln); - } - else { - if (muxu_unit.flags & UNIT_DIAG) /* loopback? */ - mux_ldsc[ln].conn = 1; /* connect this line */ - sim_activate (&muxl_unit[ln], muxl_unit[ln].wait); - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MUXl cmds: [STC%s] Transmit channel %d data %06o scheduled\n", - hold_or_clear, ln, muxl_obuf); - } - } - } - else if (DEBUG_PRI (muxu_dev, DEB_CMDS)) /* line invalid */ - fprintf (sim_deb, ">>MUXl cmds: [STC%s] Transmit channel %d invalid\n", hold_or_clear, ln); - } - - else /* receive */ - if (ln < (MUX_LINES + MUX_ILINES)) { /* line valid? */ - if (muxl_obuf & OTL_P) { /* parameter? */ - mux_rpar[ln] = (uint16) muxl_obuf; /* store param value */ - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, - ">>MUXl cmds: [STC%s] Receive channel %d parameter %06o stored\n", - hold_or_clear, ln, muxl_obuf); - } - - else if (DEBUG_PRI (muxu_dev, DEB_CMDS)) /* data (invalid action) */ - fprintf (sim_deb, - ">>MUXl cmds: [STC%s] Receive channel %d parameter %06o invalid action\n", - hold_or_clear, ln, muxl_obuf); - } - - else if (DEBUG_PRI (muxu_dev, DEB_CMDS)) /* line invalid */ - fprintf (sim_deb, ">>MUXl cmds: [STC%s] Receive channel %d invalid\n", hold_or_clear, ln); - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (muxl); /* set standard PRL signal */ - setstdIRQ (muxl); /* set standard IRQ signal */ - setstdSRQ (muxl); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - muxl.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Upper data card I/O signal handler. - - The upper data card does not have a control, flag, or flag buffer flip-flop. - It does not drive the IRQ or SRQ lines, so the I/O dispatcher does not handle - the ioSIR signal. - - Implementation notes: - - 1. The upper and lower data card hardware takes a number of actions in - response to the CRS signal. Under simulation, these actions are taken by - the lower data card CRS handler. -*/ - -uint32 muxuio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, muxu_ibuf); /* merge in return status */ - - if (DEBUG_PRI (muxu_dev, DEB_CPU)) - fprintf (sim_deb, ">>MUXu cpu: [LIx] Status = %06o, channel = %d\n", - muxu_ibuf, MUX_CHAN(muxu_ibuf)); - break; - - - case ioIOO: /* I/O data output */ - muxu_obuf = IODATA (stat_data); /* store data */ - - if (DEBUG_PRI (muxu_dev, DEB_CPU)) - fprintf (sim_deb, ">>MUXu cpu: [OTx] Data channel = %d\n", MUX_CHAN(muxu_obuf)); - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Control card I/O signal handler. - - In diagnostic mode, the control signals C1 and C2 are looped back to status - signals S1 and S2. Changing the control signals may cause an interrupt, so a - test is performed after IOO processing. -*/ - -uint32 muxcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -const char *hold_or_clear = (signal_set & ioCLF ? ",C" : ""); -uint16 data; -int32 ln, old; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - muxc.flag = muxc.flagbuf = CLEAR; - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fputs (">>MUXc cmds: [CLF] Flag cleared\n", sim_deb); - - mux_ctrl_int (); /* look for new int */ - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - muxc.flag = muxc.flagbuf = SET; - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fputs (">>MUXc cmds: [STF] Flag set\n", sim_deb); - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (muxc); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (muxc); - break; - - - case ioIOI: /* I/O data input */ - data = (uint16) (LIC_MBO | PUT_CCH (muxc_chan) | /* mbo, chan num */ - LIC_TSTI (muxc_chan) | /* I2, I1 */ - (muxc_ota[muxc_chan] & (OTC_ES2 | OTC_ES1)) | /* ES2, ES1 */ - (muxc_lia[muxc_chan] & (LIC_S2 | LIC_S1))); /* S2, S1 */ - - if (DEBUG_PRI (muxu_dev, DEB_CPU)) - fprintf (sim_deb, ">>MUXc cpu: [LIx%s] Status = %06o, channel = %d\n", - hold_or_clear, data, muxc_chan); - - muxc_chan = (muxc_chan + 1) & LIC_M_CHAN; /* incr channel */ - stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - data = IODATA (stat_data); /* clear supplied status */ - ln = muxc_chan = OTC_CHAN (data); /* set channel */ - - if (data & OTC_SCAN) muxc_scan = 1; /* set scan flag */ - else muxc_scan = 0; - - if (data & OTC_UPD) { /* update? */ - old = muxc_ota[ln]; /* save prior val */ - muxc_ota[ln] = /* save ESn,SSn */ - (muxc_ota[ln] & ~OTC_RW) | (data & OTC_RW); - - if (data & OTC_EC2) /* if EC2, upd C2 */ - muxc_ota[ln] = - (muxc_ota[ln] & ~OTC_C2) | (data & OTC_C2); - - if (data & OTC_EC1) /* if EC1, upd C1 */ - muxc_ota[ln] = - (muxc_ota[ln] & ~OTC_C1) | (data & OTC_C1); - - if (muxu_unit.flags & UNIT_DIAG) /* loopback? */ - muxc_lia[ln ^ 1] = /* set S1, S2 to C1, C2 */ - (muxc_lia[ln ^ 1] & ~(LIC_S2 | LIC_S1)) | - (muxc_ota[ln] & (OTC_C1 | OTC_C2)) >> OTC_V_C; - - else if ((muxl_unit[ln].flags & UNIT_MDM) && /* modem ctrl? */ - (old & DTR) && /* DTR drop? */ - !(muxc_ota[ln] & DTR)) { - tmxr_linemsg (&mux_ldsc[ln], "\r\nLine hangup\r\n"); - tmxr_reset_ln (&mux_ldsc[ln]); /* reset line */ - muxc_lia[ln] = 0; /* dataset off */ - } - } /* end update */ - - if (DEBUG_PRI (muxu_dev, DEB_CPU)) - fprintf (sim_deb, ">>MUXc cpu: [OTx%s] Parameter = %06o, channel = %d\n", - hold_or_clear, data, ln); - - if ((muxu_unit.flags & UNIT_DIAG) && (!muxc.flag)) /* loopback and flag clear? */ - mux_ctrl_int (); /* status chg may interrupt */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - muxc.flag = muxc.flagbuf = SET; /* set flag and flag buffer */ - break; - - - case ioCRS: /* control reset */ - case ioCLC: /* clear control flip-flop */ - muxc.control = CLEAR; - break; - - - case ioSTC: /* set control flip-flop */ - muxc.control = SET; - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (muxc); /* set standard PRL signal */ - setstdIRQ (muxc); /* set standard IRQ signal */ - setstdSRQ (muxc); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - muxc.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Unit service - receive side - - Poll for new connections - Poll all active lines for input -*/ - -t_stat muxi_svc (UNIT *uptr) -{ -int32 ln, c; -t_bool loopback; - -loopback = ((muxu_unit.flags & UNIT_DIAG) != 0); /* diagnostic mode? */ - -if (!loopback) { /* terminal mode? */ - if (uptr->wait == POLL_FIRST) /* first poll? */ - uptr->wait = sync_poll (INITIAL); /* initial synchronization */ - else /* not first */ - uptr->wait = sync_poll (SERVICE); /* continue synchronization */ - - sim_activate (uptr, uptr->wait); /* continue polling */ - - ln = tmxr_poll_conn (&mux_desc); /* look for connect */ - - if (ln >= 0) { /* got one? */ - if ((muxl_unit[ln].flags & UNIT_MDM) && /* modem ctrl? */ - (muxc_ota[ln] & DTR)) /* DTR? */ - muxc_lia[ln] = muxc_lia[ln] | CDET; /* set cdet */ - muxc_lia[ln] = muxc_lia[ln] | DSR; /* set dsr */ - mux_ldsc[ln].rcve = 1; /* rcv enabled */ - } - tmxr_poll_rx (&mux_desc); /* poll for input */ - } - -for (ln = 0; ln < MUX_LINES; ln++) { /* loop thru lines */ - if (mux_ldsc[ln].conn) { /* connected? */ - if (loopback) { /* diagnostic mode? */ - c = mux_xbuf[ln ^ 1] & OTL_CHAR; /* get char from xmit line */ - if (c == 0) /* all char bits = 0? */ - c = c | SCPE_BREAK; /* set break flag */ - mux_ldsc[ln].conn = 0; /* clear connection */ - } - - else if (mux_defer[ln]) /* break deferred? */ - c = SCPE_BREAK; /* supply it now */ - - else - c = tmxr_getc_ln (&mux_ldsc[ln]); /* get char from Telnet */ - - if (c) /* valid char? */ - mux_receive (ln, c, loopback); /* process it */ - } - - else /* not connected */ - if (!loopback) /* terminal mode? */ - muxc_lia[ln] = 0; /* line disconnected */ - } - -if (!muxl.flag) mux_data_int (); /* scan for data int */ -if (!muxc.flag) mux_ctrl_int (); /* scan modem */ -return SCPE_OK; -} - - -/* Unit service - transmit side */ - -t_stat muxo_svc (UNIT *uptr) -{ -int32 c, fc, ln, altln; -t_bool loopback; - -ln = uptr - muxl_unit; /* line # */ -altln = ln ^ 1; /* alt. line for diag mode */ - -fc = mux_xbuf[ln] & OTL_CHAR; /* full character data */ -c = fc & 0377; /* Telnet character data */ - -loopback = ((muxu_unit.flags & UNIT_DIAG) != 0); /* diagnostic mode? */ - -if (mux_ldsc[ln].conn) { /* connected? */ - if (mux_ldsc[ln].xmte) { /* xmt enabled? */ - if (loopback) /* diagnostic mode? */ - mux_ldsc[ln].conn = 0; /* clear connection */ - - else if (mux_defer[ln]) /* break deferred? */ - mux_receive (ln, SCPE_BREAK, loopback); /* process it now */ - - if ((mux_xbuf[ln] & OTL_SYNC) == 0) { /* start bit 0? */ - TMLN *lp = &mux_ldsc[ln]; /* get line */ - c = sim_tt_outcvt (c, TT_GET_MODE (muxl_unit[ln].flags)); - - if (mux_xpar[ln] & OTL_DIAG) /* xmt diagnose? */ - mux_diag (fc); /* before munge */ - - if (loopback) { /* diagnostic mode? */ - mux_ldsc[altln].conn = 1; /* set recv connection */ - sim_activate (&muxu_unit, 1); /* schedule receive */ - } - - else { /* no loopback */ - if (c >= 0) /* valid? */ - tmxr_putc_ln (lp, c); /* output char */ - tmxr_poll_tx (&mux_desc); /* poll xmt */ - } - } - - mux_xdon[ln] = 1; /* set for xmit irq */ - - if (DEBUG_PRI (muxu_dev, DEB_XFER) && (loopback | (c >= 0))) - fprintf (sim_deb, ">>MUXl xfer: Line %d character %s sent\n", - ln, fmt_char ((uint8) (loopback ? fc : c))); - } - - else { /* buf full */ - tmxr_poll_tx (&mux_desc); /* poll xmt */ - sim_activate (uptr, muxl_unit[ln].wait); /* wait */ - return SCPE_OK; - } - } - -if (!muxl.flag) mux_data_int (); /* scan for int */ -return SCPE_OK; -} - - -/* Process a character received from a multiplexer port */ - -void mux_receive (int32 ln, int32 c, t_bool diag) -{ -if (c & SCPE_BREAK) { /* break? */ - if (mux_defer[ln] || diag) { /* break deferred or diagnostic mode? */ - mux_defer[ln] = 0; /* process now */ - mux_rbuf[ln] = 0; /* break returns NUL */ - mux_sta[ln] = mux_sta[ln] | LIU_BRK; /* set break status */ - - if (DEBUG_PRI (muxu_dev, DEB_XFER)) - if (diag) - fputs (">>MUXl xfer: Break detected\n", sim_deb); - else - fputs (">>MUXl xfer: Deferred break processed\n", sim_deb); - } - - else { - mux_defer[ln] = 1; /* defer break */ - - if (DEBUG_PRI (muxu_dev, DEB_XFER)) - fputs (">>MUXl xfer: Break detected and deferred\n", sim_deb); - - return; - } - } -else { /* normal */ - if (mux_rchp[ln]) /* char already pending? */ - mux_sta[ln] = mux_sta[ln] | LIU_LOST; - - if (!diag) { /* terminal mode? */ - c = sim_tt_inpcvt (c, TT_GET_MODE (muxl_unit[ln].flags)); - if (mux_rpar[ln] & OTL_ECHO) { /* echo? */ - TMLN *lp = &mux_ldsc[ln]; /* get line */ - tmxr_putc_ln (lp, c); /* output char */ - tmxr_poll_tx (&mux_desc); /* poll xmt */ - } - } - mux_rbuf[ln] = (uint16) c; /* save char */ - } - -mux_rchp[ln] = 1; /* char pending */ - -if (DEBUG_PRI (muxu_dev, DEB_XFER)) - fprintf (sim_deb, ">>MUXl xfer: Line %d character %s received\n", - ln, fmt_char ((uint8) c)); - -if (mux_rpar[ln] & OTL_DIAG) /* diagnose this line? */ - mux_diag (c); /* do diagnosis */ - -return; -} - - -/* Look for data interrupt */ - -void mux_data_int (void) -{ -int32 i; - -for (i = 0; i < MUX_LINES; i++) { /* rcv lines */ - if ((mux_rpar[i] & OTL_ENB) && mux_rchp[i]) { /* enabled, char? */ - muxl_ibuf = PUT_DCH (i) | /* lo buf = char */ - mux_rbuf[i] & LIL_CHAR | - RCV_PAR (mux_rbuf[i]); - muxu_ibuf = PUT_DCH (i) | mux_sta[i]; /* hi buf = stat */ - mux_rchp[i] = 0; /* clr char, stat */ - mux_sta[i] = 0; - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MUXl cmds: Receive channel %d interrupt requested\n", i); - - muxlio (&muxl_dib, ioENF, 0); /* interrupt */ - return; - } - } -for (i = 0; i < MUX_LINES; i++) { /* xmt lines */ - if ((mux_xpar[i] & OTL_ENB) && mux_xdon[i]) { /* enabled, done? */ - muxl_ibuf = PUT_DCH (i) | /* lo buf = last rcv char */ - mux_rbuf[i] & LIL_CHAR | - RCV_PAR (mux_rbuf[i]); - muxu_ibuf = PUT_DCH (i) | mux_sta[i] | LIU_TR; /* hi buf = stat */ - mux_xdon[i] = 0; /* clr done, stat */ - mux_sta[i] = 0; - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MUXl cmds: Transmit channel %d interrupt requested\n", i); - - muxlio (&muxl_dib, ioENF, 0); /* interrupt */ - return; - } - } -for (i = MUX_LINES; i < (MUX_LINES + MUX_ILINES); i++) { /* diag lines */ - if ((mux_rpar[i] & OTL_ENB) && mux_rchp[i]) { /* enabled, char? */ - muxl_ibuf = PUT_DCH (i) | /* lo buf = char */ - mux_rbuf[i] & LIL_CHAR | - RCV_PAR (mux_rbuf[i]); - muxu_ibuf = PUT_DCH (i) | mux_sta[i] | LIU_DG; /* hi buf = stat */ - mux_rchp[i] = 0; /* clr char, stat */ - mux_sta[i] = 0; - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, ">>MUXl cmds: Receive channel %d interrupt requested\n", i); - - muxlio (&muxl_dib, ioENF, 0); /* interrupt */ - return; - } - } -return; -} - - -/* Look for control interrupt - - If either of the incoming status bits does not match the stored status, and - the corresponding mismatch is enabled, a control interrupt request is - generated. Depending on the scan flag, we check either all 16 lines or just - the current line. If an interrupt is requested, the channel counter - indicates the interrupting channel. -*/ - -void mux_ctrl_int (void) -{ -int32 i, line_count; - -line_count = (muxc_scan ? MUX_LINES : 1); /* check one or all lines */ - -for (i = 0; i < line_count; i++) { - if (muxc_scan) /* scanning? */ - muxc_chan = (muxc_chan + 1) & LIC_M_CHAN; /* step channel */ - if (LIC_TSTI (muxc_chan)) { /* status change? */ - - if (DEBUG_PRI (muxu_dev, DEB_CMDS)) - fprintf (sim_deb, - ">>MUXc cmds: Control channel %d interrupt requested (poll = %d)\n", - muxc_chan, i + 1); - - muxcio (&muxc_dib, ioENF, 0); /* set flag */ - break; - } - } -return; -} - - -/* Set diagnostic lines for given character */ - -void mux_diag (int32 c) -{ -int32 i; - -for (i = MUX_LINES; i < (MUX_LINES + MUX_ILINES); i++) { - if (c & SCPE_BREAK) { /* break? */ - mux_sta[i] = mux_sta[i] | LIU_BRK; - mux_rbuf[i] = 0; /* no char */ - } - else { - if (mux_rchp[i]) mux_sta[i] = mux_sta[i] | LIU_LOST; - mux_rchp[i] = 1; - mux_rbuf[i] = (uint16) c; - } - } -return; -} - - -/* Reset an individual line */ - -static void mux_reset_ln (int32 i) -{ -mux_rbuf[i] = mux_xbuf[i] = 0; /* clear state */ -mux_rpar[i] = mux_xpar[i] = 0; -mux_rchp[i] = mux_xdon[i] = 0; -mux_sta[i] = mux_defer[i] = 0; -muxc_ota[i] = muxc_lia[i] = 0; /* clear modem */ -if (mux_ldsc[i].conn && /* connected? */ - ((muxu_unit.flags & UNIT_DIAG) == 0)) /* term mode? */ - muxc_lia[i] = muxc_lia[i] | DSR | /* cdet, dsr */ - (muxl_unit[i].flags & UNIT_MDM? CDET: 0); -sim_cancel (&muxl_unit[i]); -return; -} - - -/* Reset routine for lower data, upper data, and control cards */ - -t_stat muxc_reset (DEVICE *dptr) -{ -int32 i; -DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ - -if (dptr == &muxc_dev) { /* make all consistent */ - hp_enbdis_pair (dptr, &muxl_dev); - hp_enbdis_pair (dptr, &muxu_dev); - } -else if (dptr == &muxl_dev) { - hp_enbdis_pair (dptr, &muxc_dev); - hp_enbdis_pair (dptr, &muxu_dev); - } -else { - hp_enbdis_pair (dptr, &muxc_dev); - hp_enbdis_pair (dptr, &muxl_dev); - } - -IOPRESET (dibptr); /* PRESET device (does not use PON) */ - -muxc_chan = muxc_scan = 0; /* init modem scan */ - -if (muxu_unit.flags & UNIT_ATT) { /* master att? */ - muxu_unit.wait = POLL_FIRST; /* set up poll */ - sim_activate (&muxu_unit, muxu_unit.wait); /* start Telnet poll immediately */ - } -else - sim_cancel (&muxu_unit); /* else stop */ - -for (i = 0; i < MUX_LINES; i++) - mux_reset_ln (i); /* reset lines 0-15 */ - -for (i = MUX_LINES; i < (MUX_LINES + MUX_ILINES); i++) /* reset lines 16-20 */ - mux_rbuf[i] = mux_rpar[i] = mux_sta[i] = mux_rchp[i] = 0; - -return SCPE_OK; -} - - -/* Attach master unit */ - -t_stat mux_attach (UNIT *uptr, char *cptr) -{ -t_stat status = SCPE_OK; - -if (muxu_unit.flags & UNIT_DIAG) /* diag mode? */ - return SCPE_NOFNC; /* command not allowed */ - -status = tmxr_attach (&mux_desc, uptr, cptr); /* attach */ - -if (status == SCPE_OK) { - muxu_unit.wait = POLL_FIRST; /* set up poll */ - sim_activate (&muxu_unit, muxu_unit.wait); /* start Telnet poll immediately */ - } - -return status; -} - - -/* Detach master unit */ - -t_stat mux_detach (UNIT *uptr) -{ -int32 i; -t_stat r; - -r = tmxr_detach (&mux_desc, uptr); /* detach */ -for (i = 0; i < MUX_LINES; i++) mux_ldsc[i].rcve = 0; /* disable rcv */ -sim_cancel (uptr); /* stop poll */ -return r; -} - - -/* Diagnostic/normal mode routine, - - Diagnostic testing wants to exercise as much of the regular simulation code - as possible to ensure good test coverage. Normally, input polling and output - transmission only occurs on connected lines. In diagnostic mode, line - connection flags are set selectively to enable processing on the lines under - test. The alternative to this would require duplicating the send/receive - code; the diagnostic would then test the copy but not the actual code used - for normal character transfers, which is undesirable. - - Therefore, to enable diagnostic mode, we must force a disconnect of the - master socket and any connected Telnet lines, which clears the connection - flags on all lines. Then we set the "transmission enabled" flags on all - lines to enable output character processing for the diagnostic. (Normally, - all of the flags are set when the multiplexer is first attached. Until then, - the enable flags default to "not enabled," so we enable them explicitly - here.) -*/ - -t_stat mux_setdiag (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -int32 ln; - -if (val) { /* set diag? */ - mux_detach (uptr); /* detach lines */ - for (ln = 0; ln < MUX_LINES; ln++) /* enable transmission */ - mux_ldsc[ln].xmte = 1; /* on all lines */ - } -else { /* set term */ - for (ln = 0; ln < MUX_LINES; ln++) /* clear connections */ - mux_ldsc[ln].conn = 0; /* on all lines */ - } -return SCPE_OK; -} +/* hp2100_mux.c: HP 2100 12920A terminal multiplexor simulator + + Copyright (c) 2002-2016, Robert M Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + MUX,MUXL,MUXM 12920A terminal multiplexor + + 13-May-16 JDB Modified for revised SCP API function parameter types + 29-Jun-15 JDB Corrected typo in RTS macro definition + 24-Dec-14 JDB Added casts for explicit downward conversions + 10-Jan-13 MP Added DEV_MUX and additional DEVICE field values + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + Removed DEV_NET to allow restoration of listening port + 28-Mar-11 JDB Tidied up signal handling + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 25-Nov-08 JDB Revised for new multiplexer library SHOW routines + 09-Oct-08 JDB "muxl_unit" defined one too many units (17 instead of 16) + 10-Sep-08 JDB SHOW MUX CONN/STAT with SET MUX DIAG is no longer disallowed + 07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach + 27-Aug-08 JDB Added LINEORDER support + 12-Aug-08 JDB Added BREAK deferral to allow RTE break-mode to work + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 16-Apr-08 JDB Sync mux poll with console poll for idle compatibility + 06-Mar-07 JDB Corrected "mux_sta" size from 16 to 21 elements + Fixed "muxc_reset" to clear lines 16-20 + 26-Feb-07 JDB Added debug printouts + Fixed control card OTx to set current channel number + Fixed to set "muxl_ibuf" in response to a transmit interrupt + Changed "mux_xbuf", "mux_rbuf" declarations from 8 to 16 bits + Fixed to set "mux_rchp" when a line break is received + Fixed incorrect "odd_par" table values + Reversed test in "RCV_PAR" to return "LIL_PAR" on odd parity + Fixed mux reset (ioCRS) to clear port parameters + Fixed to use PUT_DCH instead of PUT_CCH for data channel status + 10-Feb-07 JDB Added DIAG/TERM modifiers to implement diagnostic mode + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 02-Jun-06 JDB Fixed compiler warning for mux_ldsc init + 22-Nov-05 RMS Revised for new terminal processing routines + 29-Jun-05 RMS Added SET MUXLn DISCONNECT + 07-Oct-04 JDB Allow enable/disable from any device + 26-Apr-04 RMS Fixed SFS x,C and SFC x,C + Implemented DMA SRQ (follows FLG) + 05-Jan-04 RMS Revised for tmxr library changes + 21-Dec-03 RMS Added invalid character screening for TSB (from Mike Gemeny) + 09-May-03 RMS Added network device flag + 01-Nov-02 RMS Added 7B/8B support + 22-Aug-02 RMS Updated for changes to sim_tmxr + + Reference: + - 12920A Asynchronous Multiplexer Interface Kits Operating and Service Manual + (12920-90001, Oct-1972) + + + The 12920A was a 16-channel asynchronous terminal multiplexer. It supported + direct-connected terminals as well as modems at speeds up to 2400 baud. It + was the primary terminal multiplexer for the HP 2000 series of Time-Shared + BASIC systems. + + The multiplexer was implemented as a three-card set consisting of a lower + data card, an upper data card, and a modem control card. Under simulation, + these are implemented by three devices: + + MUXL lower data card (lines) + MUX upper data card (scanner) + MUXM control card (modem control) + + The lower and upper data cards must be in adjacent I/O slots. The control + card may be placed in any slot, although in practice it was placed in the + slot above the upper data card, so that all three cards were physically + together. + + The 12920A supported one or two control cards (two cards were used with + 801-type automatic dialers). Under simulation, only one control card is + supported. + + Implementation notes: + + 1. If a BREAK is detected during an input poll, and we are not in diagnostic + mode, we defer recognition until either a character is output or a second + successive input poll occurs. This is necessary for RTE break-mode + operation. Without this deferral, a BREAK during output would be ignored + by the RTE driver, making it impossible to stop a long listing. + + The problem is due to timing differences between simulated and real time. + The RTE multiplexer driver is a privileged driver. Privileged drivers + bypass RTE to provide rapid interrupt handling. To inform RTE that an + operation is complete, e.g., that a line has been written, the interrupt + section of the driver sets a device timeout of one clock tick (10 + milliseconds). When that timeout occurs, RTE is entered normally to + complete the I/O transaction. While the completion timeout is pending, + the driver ignores any further interrupts from the multiplexer line. + + The maximum communication rate for the multiplexer is 2400 baud, or + approximately 4.2 milliseconds per character transferred. A typical line + of 20 characters would therefore take ~85 milliseconds, plus the 10 + millisecond completion timeout, or about 95 milliseconds total. BREAK + recognition would be ignored for roughly 10% of that time. At lower baud + rates, recognition would be ignored for a correspondingly smaller + percentage of the time. + + However, SIMH uses an optimized timing of 500 instructions per character + transfer, rather than the ~6600 instructions that a character transfer + should take, and so a typical 20-character line will take about 11,000 + instructions. On the other hand, the clock tick is calibrated to real + time, and 10 milliseconds of real time takes about 420,000 instructions + on a 2.0 GHz PC. To be recognized, then, the BREAK key must be pressed + in a window that is open for about 2.5% of the time. Therefore, the + BREAK key will be ignored about 97.5% of the time, and RTE break-mode + effectively will not work. + + Deferring BREAK recognition until the next character is output ensures + that the BREAK interrupt will be accepted (the simulator delivers input + interrupts before output interrupts, so the BREAK interrupt arrives + before the output character transmit interrupt). If an output operation + is not in progress, then the BREAK will be recognized at the next input + poll. +*/ + + +#include + +#include "hp2100_defs.h" +#include "sim_tmxr.h" + + +/* Unit references */ + +#define MUX_LINES 16 /* number of user lines */ +#define MUX_ILINES 5 /* number of diag rcv only lines */ + + +/* Service times */ + +#define MUXL_WAIT 500 + + +/* Unit flags */ + +#define UNIT_V_MDM (TTUF_V_UF + 0) /* modem control */ +#define UNIT_V_DIAG (TTUF_V_UF + 1) /* loopback diagnostic */ +#define UNIT_MDM (1 << UNIT_V_MDM) +#define UNIT_DIAG (1 << UNIT_V_DIAG) + +/* Debug flags */ + +#define DEB_CMDS (1 << 0) /* Command initiation and completion */ +#define DEB_CPU (1 << 1) /* CPU I/O */ +#define DEB_XFER (1 << 2) /* Socket receive and transmit */ + +/* Channel number (OTA upper, LIA lower or upper) */ + +#define MUX_V_CHAN 10 /* channel num */ +#define MUX_M_CHAN 037 +#define MUX_CHAN(x) (((x) >> MUX_V_CHAN) & MUX_M_CHAN) + +/* OTA, lower = parameters or data */ + +#define OTL_P 0100000 /* parameter */ +#define OTL_TX 0040000 /* transmit */ +#define OTL_ENB 0020000 /* enable */ +#define OTL_TPAR 0010000 /* xmt parity */ +#define OTL_ECHO 0010000 /* rcv echo */ +#define OTL_DIAG 0004000 /* diagnose */ +#define OTL_SYNC 0004000 /* sync */ +#define OTL_V_LNT 8 /* char length */ +#define OTL_M_LNT 07 +#define OTL_LNT(x) (((x) >> OTL_V_LNT) & OTL_M_LNT) +#define OTL_V_BAUD 0 /* baud rate */ +#define OTL_M_BAUD 0377 +#define OTL_BAUD(x) (((x) >> OTL_V_BAUD) & OTL_M_BAUD) +#define OTL_CHAR 03777 /* char mask */ +#define OTL_PAR 0200 /* char parity */ + +/* LIA, lower = received data */ + +#define LIL_PAR 0100000 /* parity */ +#define PUT_DCH(x) (((x) & MUX_M_CHAN) << MUX_V_CHAN) +#define LIL_CHAR 01777 /* character */ + +/* LIA, upper = status */ + +#define LIU_SEEK 0100000 /* seeking NI */ +#define LIU_DG 0000010 /* diagnose */ +#define LIU_BRK 0000004 /* break */ +#define LIU_LOST 0000002 /* char lost */ +#define LIU_TR 0000001 /* trans/rcv */ + +/* OTA, control */ + +#define OTC_SCAN 0100000 /* scan */ +#define OTC_UPD 0040000 /* update */ +#define OTC_V_CHAN 10 /* channel */ +#define OTC_M_CHAN 017 +#define OTC_CHAN(x) (((x) >> OTC_V_CHAN) & OTC_M_CHAN) +#define OTC_EC2 0000200 /* enable Cn upd */ +#define OTC_EC1 0000100 +#define OTC_C2 0000040 /* Cn flops */ +#define OTC_C1 0000020 +#define OTC_V_C 4 /* S1 to C1 */ +#define OTC_ES2 0000010 /* enb comparison */ +#define OTC_ES1 0000004 +#define OTC_V_ES 2 +#define OTC_SS2 0000002 /* SSn flops */ +#define OTC_SS1 0000001 +#define OTC_RW (OTC_ES2|OTC_ES1|OTC_SS2|OTC_SS1) +#define RTS OTC_C2 /* C2 = rts */ +#define DTR OTC_C1 /* C1 = dtr */ + +/* LIA, control */ + +#define LIC_MBO 0140000 /* always set */ +#define LIC_V_CHAN 10 /* channel */ +#define LIC_M_CHAN 017 +#define PUT_CCH(x) (((x) & OTC_M_CHAN) << OTC_V_CHAN) +#define LIC_I2 0001000 /* change flags */ +#define LIC_I1 0000400 +#define LIC_S2 0000002 /* Sn flops */ +#define LIC_S1 0000001 +#define LIC_V_I 8 /* S1 to I1 */ +#define CDET LIC_S2 /* S2 = cdet */ +#define DSR LIC_S1 /* S1 = dsr */ + +#define LIC_TSTI(ch) (((muxc_lia[ch] ^ muxc_ota[ch]) & \ + ((muxc_ota[ch] & (OTC_ES2|OTC_ES1)) >> OTC_V_ES)) \ + << LIC_V_I) + + +/* Program constants */ + +static const uint8 odd_par [256] = { + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 000-017 */ + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 020-037 */ + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 040-067 */ + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 060-077 */ + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 100-117 */ + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 120-137 */ + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 140-157 */ + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 160-177 */ + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 200-217 */ + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 220-237 */ + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 240-267 */ + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 260-277 */ + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, /* 300-317 */ + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 320-337 */ + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, /* 340-357 */ + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 /* 360-377 */ + }; + +#define RCV_PAR(x) (odd_par[(x) & 0377] ? 0 : LIL_PAR) +#define XMT_PAR(x) (odd_par[(x) & 0377] ? 0 : OTL_PAR) + + +/* Multiplexer controller state variables */ + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } muxl = { CLEAR, CLEAR, CLEAR }; + +uint32 muxl_ibuf = 0; /* low in: rcv data */ +uint32 muxl_obuf = 0; /* low out: param */ + +uint32 muxu_ibuf = 0; /* upr in: status */ +uint32 muxu_obuf = 0; /* upr out: chan */ + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } muxc = { CLEAR, CLEAR, CLEAR }; + +uint32 muxc_chan = 0; /* ctrl chan */ +uint32 muxc_scan = 0; /* ctrl scan */ + + +/* Multiplexer per-line state variables */ + +uint16 mux_sta [MUX_LINES + MUX_ILINES]; /* line status */ +uint16 mux_rpar [MUX_LINES + MUX_ILINES]; /* rcv param */ +uint16 mux_xpar [MUX_LINES]; /* xmt param */ +uint8 mux_rchp [MUX_LINES + MUX_ILINES]; /* rcv chr pend */ +uint8 mux_xdon [MUX_LINES]; /* xmt done */ +uint8 muxc_ota [MUX_LINES]; /* ctrl: Cn,ESn,SSn */ +uint8 muxc_lia [MUX_LINES]; /* ctrl: Sn */ +uint8 mux_defer [MUX_LINES]; /* break deferred flags */ + + +/* Multiplexer per-line buffer variables */ + +uint16 mux_rbuf[MUX_LINES + MUX_ILINES]; /* rcv buf */ +uint16 mux_xbuf[MUX_LINES]; /* xmt buf */ + + +/* Multiplexer local routines */ + +void mux_receive (int32 ln, int32 c, t_bool diag); +void mux_data_int (void); +void mux_ctrl_int (void); +void mux_diag (int32 c); + + +/* Multiplexer global routines */ + +IOHANDLER muxlio; +IOHANDLER muxuio; +IOHANDLER muxcio; + +t_stat muxi_svc (UNIT *uptr); +t_stat muxo_svc (UNIT *uptr); +t_stat muxc_reset (DEVICE *dptr); +t_stat mux_attach (UNIT *uptr, CONST char *cptr); +t_stat mux_detach (UNIT *uptr); +t_stat mux_setdiag (UNIT *uptr, int32 val, CONST char *cptr, void *desc); + + +/* MUXL/MUXU device information block. + + The DIBs of adjacent cards must be contained in an array, so they are defined + here and referenced in the lower and upper card device structures. +*/ + +DIB mux_dib[] = { + { &muxlio, MUXL }, + { &muxuio, MUXU } + }; + +#define muxl_dib mux_dib[0] +#define muxu_dib mux_dib[1] + + +/* MUXL data structures. + + muxl_dib MUXL device information block + muxl_unit MUXL unit list + muxl_reg MUXL register list + muxl_mod MUXL modifier list + muxl_dev MUXL device descriptor +*/ + +TMXR mux_desc; + +DEVICE muxl_dev; + +UNIT muxl_unit[] = { + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT }, + { UDATA (&muxo_svc, TT_MODE_UC, 0), MUXL_WAIT } + }; + +REG muxl_reg[] = { + { FLDATA (CTL, muxl.control, 0) }, + { FLDATA (FLG, muxl.flag, 0) }, + { FLDATA (FBF, muxl.flagbuf, 0) }, + { BRDATA (STA, mux_sta, 8, 16, MUX_LINES + MUX_ILINES) }, + { BRDATA (RPAR, mux_rpar, 8, 16, MUX_LINES + MUX_ILINES) }, + { BRDATA (XPAR, mux_xpar, 8, 16, MUX_LINES) }, + { BRDATA (RBUF, mux_rbuf, 8, 16, MUX_LINES + MUX_ILINES) }, + { BRDATA (XBUF, mux_xbuf, 8, 16, MUX_LINES) }, + { BRDATA (RCHP, mux_rchp, 8, 1, MUX_LINES + MUX_ILINES) }, + { BRDATA (XDON, mux_xdon, 8, 1, MUX_LINES) }, + { BRDATA (BDFR, mux_defer, 8, 1, MUX_LINES) }, + { URDATA (TIME, muxl_unit[0].wait, 10, 24, 0, + MUX_LINES, REG_NZ + PV_LEFT) }, + { ORDATA (SC, muxl_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, muxl_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB muxl_mod[] = { + { TT_MODE, TT_MODE_UC, "UC", "UC", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_7B, "7b", "7B", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_8B, "8b", "8B", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_7P, "7p", "7P", NULL, NULL, NULL }, + + { UNIT_MDM, UNIT_MDM, "dataset", "DATASET", NULL, NULL, NULL }, + { UNIT_MDM, 0, "no dataset", "NODATASET", NULL, NULL, NULL }, + + { MTAB_XTD | MTAB_VUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, &mux_desc }, + { MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, &mux_desc }, + + { MTAB_XTD | MTAB_VUN, 0, NULL, "DISCONNECT", &tmxr_dscln, NULL, &mux_desc }, + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &muxl_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &muxl_dev }, + + { 0 } + }; + +DEVICE muxl_dev = { + "MUXL", /* device name */ + muxl_unit, /* unit array */ + muxl_reg, /* register array */ + muxl_mod, /* modifier array */ + MUX_LINES, /* number of units */ + 10, /* address radix */ + 31, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &muxc_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &muxl_dib, /* device information block */ + DEV_DISABLE, /* device flags */ + 0, /* debug control flags */ + NULL, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL, /* logical device name */ + NULL, /* help routine */ + NULL, /* help attach routine*/ + NULL /* help context */ + }; + + +/* MUXU data structures + + mux_order MUX line connection order table + mux_ldsc MUX terminal multiplexer line descriptors + mux_desc MUX terminal multiplexer device descriptor + + muxu_dib MUXU device information block + muxu_unit MUXU unit list + muxu_reg MUXU register list + muxu_mod MUXU modifier list + muxu_deb MUXU debug list + muxu_dev MUXU device descriptor +*/ + +DEVICE muxu_dev; + +int32 mux_order [MUX_LINES] = { -1 }; /* connection order */ +TMLN mux_ldsc [MUX_LINES] = { { 0 } }; /* line descriptors */ +TMXR mux_desc = { MUX_LINES, 0, 0, mux_ldsc, mux_order }; /* device descriptor */ + +UNIT muxu_unit = { UDATA (&muxi_svc, UNIT_ATTABLE, 0), POLL_FIRST }; + +REG muxu_reg[] = { + { ORDATA (IBUF, muxu_ibuf, 16) }, + { ORDATA (OBUF, muxu_obuf, 16) }, + { ORDATA (SC, muxu_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, muxu_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB muxu_mod[] = { + { UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAG", &mux_setdiag, NULL, NULL }, + { UNIT_DIAG, 0, "terminal mode", "TERM", &mux_setdiag, NULL, NULL }, + { UNIT_ATT, UNIT_ATT, "", NULL, NULL, &tmxr_show_summ, &mux_desc }, + + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LINEORDER", "LINEORDER", &tmxr_set_lnorder, &tmxr_show_lnorder, &mux_desc }, + + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, &mux_desc }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, &mux_desc }, + { MTAB_XTD | MTAB_VDV, 1, NULL, "DISCONNECT", &tmxr_dscln, NULL, &mux_desc }, + { MTAB_XTD | MTAB_VDV, 1, "SC", "SC", &hp_setsc, &hp_showsc, &muxl_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &muxl_dev }, + + { 0 } + }; + +DEBTAB muxu_deb [] = { + { "CMDS", DEB_CMDS }, + { "CPU", DEB_CPU }, + { "XFER", DEB_XFER }, + { NULL, 0 } + }; + +DEVICE muxu_dev = { + "MUX", /* device name */ + &muxu_unit, /* unit array */ + muxu_reg, /* register array */ + muxu_mod, /* modifier array */ + 1, /* number of units */ + 10, /* address radix */ + 31, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 8, /* data width */ + &tmxr_ex, /* examine routine */ + &tmxr_dep, /* deposit routine */ + &muxc_reset, /* reset routine */ + NULL, /* boot routine */ + &mux_attach, /* attach routine */ + &mux_detach, /* detach routine */ + &muxu_dib, /* device information block */ + DEV_DISABLE | DEV_DEBUG | DEV_MUX, /* device flags */ + 0, /* debug control flags */ + muxu_deb, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL, /* logical device name */ + NULL, /* help routine */ + NULL, /* help attach routine*/ + (void *) &mux_desc /* help context */ + }; + + +/* MUXC data structures. + + muxc_dib MUXC device information block + muxc_unit MUXC unit list + muxc_reg MUXC register list + muxc_mod MUXC modifier list + muxc_dev MUXC device descriptor +*/ + +DEVICE muxc_dev; + +DIB muxc_dib = { &muxcio, MUXC }; + +UNIT muxc_unit = { UDATA (NULL, 0, 0) }; + +REG muxc_reg[] = { + { FLDATA (CTL, muxc.control, 0) }, + { FLDATA (FLG, muxc.flag, 0) }, + { FLDATA (FBF, muxc.flagbuf, 0) }, + { FLDATA (SCAN, muxc_scan, 0) }, + { ORDATA (CHAN, muxc_chan, 4) }, + { BRDATA (DSO, muxc_ota, 8, 6, MUX_LINES) }, + { BRDATA (DSI, muxc_lia, 8, 2, MUX_LINES) }, + { ORDATA (SC, muxc_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, muxc_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB muxc_mod[] = { + { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &muxc_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &muxc_dev }, + { 0 } + }; + +DEVICE muxc_dev = { + "MUXM", /* device name */ + &muxc_unit, /* unit array */ + muxc_reg, /* register array */ + muxc_mod, /* modifier array */ + 1, /* number of units */ + 10, /* address radix */ + 31, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &muxc_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &muxc_dib, /* device information block */ + DEV_DISABLE, /* device flags */ + 0, /* debug control flags */ + NULL, /* debug flag name table */ + NULL, /* memory size change routine */ + NULL, /* logical device name */ + NULL, /* help routine */ + NULL, /* help attach routine*/ + NULL /* help context */ + }; + + +/* Lower data card I/O signal handler. + + Implementation notes: + + 1. The operating manual says that "at least 100 milliseconds of CLC 0s must + be programmed" by systems employing the multiplexer to ensure that the + multiplexer resets. In practice, such systems issue 128K CLC 0 + instructions. As we provide debug logging of multiplexer resets, a CRS + counter is used to ensure that only one debug line is printed in response + to these 128K CRS invocations. +*/ + +uint32 muxlio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +int32 ln; +const char *hold_or_clear = (signal_set & ioCLF ? ",C" : ""); +static uint32 crs_count = 0; /* cntr for ioCRS repeat */ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +if (crs_count && !(signal_set & ioCRS)) { /* counting CRSes and not present? */ + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) /* report reset count */ + fprintf (sim_deb, ">>MUXl cmds: [CRS] Multiplexer reset %d times\n", + crs_count); + + crs_count = 0; /* clear counter */ + } + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + muxl.flag = muxl.flagbuf = CLEAR; + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fputs (">>MUXl cmds: [CLF] Flag cleared\n", sim_deb); + + mux_data_int (); /* look for new int */ + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + muxl.flag = muxl.flagbuf = SET; + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fputs (">>MUXl cmds: [STF] Flag set\n", sim_deb); + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (muxl); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (muxl); + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, muxl_ibuf); /* merge in return status */ + + if (DEBUG_PRI (muxu_dev, DEB_CPU)) + fprintf (sim_deb, ">>MUXl cpu: [LIx%s] Data = %06o\n", hold_or_clear, muxl_ibuf); + break; + + + case ioIOO: /* I/O data output */ + muxl_obuf = IODATA (stat_data); /* store data */ + + if (DEBUG_PRI (muxu_dev, DEB_CPU)) + if (muxl_obuf & OTL_P) + fprintf (sim_deb, ">>MUXl cpu: [OTx%s] Parameter = %06o\n", hold_or_clear, muxl_obuf); + else + fprintf (sim_deb, ">>MUXl cpu: [OTx%s] Data = %06o\n", hold_or_clear, muxl_obuf); + break; + + + case ioPOPIO: /* power-on preset to I/O */ + muxl.flag = muxl.flagbuf = SET; /* set flag andflag buffer */ + break; + + + case ioCRS: /* control reset */ + if (crs_count == 0) { /* first reset? */ + muxl.control = CLEAR; /* clear control flip-flop */ + + for (ln = 0; ln < MUX_LINES; ln++) { /* clear transmit info */ + mux_xbuf[ln] = mux_xpar[ln] = 0; + muxc_ota[ln] = muxc_lia[ln] = mux_xdon[ln] = 0; + } + + for (ln = 0; ln < (MUX_LINES + MUX_ILINES); ln++) { + mux_rbuf[ln] = mux_rpar[ln] = 0; /* clear receive info */ + mux_sta[ln] = mux_rchp[ln] = 0; + } + } + + crs_count = crs_count + 1; /* increment count */ + break; + + + case ioCLC: /* clear control flip-flop */ + muxl.control = CLEAR; + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MUXl cmds: [CLC%s] Data interrupt inhibited\n", hold_or_clear); + break; + + + case ioSTC: /* set control flip-flop */ + muxl.control = SET; /* set control */ + + ln = MUX_CHAN (muxu_obuf); /* get chan # */ + + if (muxl_obuf & OTL_TX) { /* transmit? */ + if (ln < MUX_LINES) { /* line valid? */ + if (muxl_obuf & OTL_P) { /* parameter? */ + mux_xpar[ln] = (uint16) muxl_obuf; /* store param value */ + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, + ">>MUXl cmds: [STC%s] Transmit channel %d parameter %06o stored\n", + hold_or_clear, ln, muxl_obuf); + } + + else { /* data */ + if (mux_xpar[ln] & OTL_TPAR) /* parity requested? */ + muxl_obuf = /* add parity bit */ + muxl_obuf & ~OTL_PAR | + XMT_PAR(muxl_obuf); + mux_xbuf[ln] = (uint16) muxl_obuf; /* load buffer */ + + if (sim_is_active (&muxl_unit[ln])) { /* still working? */ + mux_sta[ln] = mux_sta[ln] | LIU_LOST; /* char lost */ + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MUXl cmds: [STC%s] Transmit channel %d data overrun\n", + hold_or_clear, ln); + } + else { + if (muxu_unit.flags & UNIT_DIAG) /* loopback? */ + mux_ldsc[ln].conn = 1; /* connect this line */ + sim_activate (&muxl_unit[ln], muxl_unit[ln].wait); + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MUXl cmds: [STC%s] Transmit channel %d data %06o scheduled\n", + hold_or_clear, ln, muxl_obuf); + } + } + } + else if (DEBUG_PRI (muxu_dev, DEB_CMDS)) /* line invalid */ + fprintf (sim_deb, ">>MUXl cmds: [STC%s] Transmit channel %d invalid\n", hold_or_clear, ln); + } + + else /* receive */ + if (ln < (MUX_LINES + MUX_ILINES)) { /* line valid? */ + if (muxl_obuf & OTL_P) { /* parameter? */ + mux_rpar[ln] = (uint16) muxl_obuf; /* store param value */ + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, + ">>MUXl cmds: [STC%s] Receive channel %d parameter %06o stored\n", + hold_or_clear, ln, muxl_obuf); + } + + else if (DEBUG_PRI (muxu_dev, DEB_CMDS)) /* data (invalid action) */ + fprintf (sim_deb, + ">>MUXl cmds: [STC%s] Receive channel %d parameter %06o invalid action\n", + hold_or_clear, ln, muxl_obuf); + } + + else if (DEBUG_PRI (muxu_dev, DEB_CMDS)) /* line invalid */ + fprintf (sim_deb, ">>MUXl cmds: [STC%s] Receive channel %d invalid\n", hold_or_clear, ln); + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (muxl); /* set standard PRL signal */ + setstdIRQ (muxl); /* set standard IRQ signal */ + setstdSRQ (muxl); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + muxl.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Upper data card I/O signal handler. + + The upper data card does not have a control, flag, or flag buffer flip-flop. + It does not drive the IRQ or SRQ lines, so the I/O dispatcher does not handle + the ioSIR signal. + + Implementation notes: + + 1. The upper and lower data card hardware takes a number of actions in + response to the CRS signal. Under simulation, these actions are taken by + the lower data card CRS handler. +*/ + +uint32 muxuio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, muxu_ibuf); /* merge in return status */ + + if (DEBUG_PRI (muxu_dev, DEB_CPU)) + fprintf (sim_deb, ">>MUXu cpu: [LIx] Status = %06o, channel = %d\n", + muxu_ibuf, MUX_CHAN(muxu_ibuf)); + break; + + + case ioIOO: /* I/O data output */ + muxu_obuf = IODATA (stat_data); /* store data */ + + if (DEBUG_PRI (muxu_dev, DEB_CPU)) + fprintf (sim_deb, ">>MUXu cpu: [OTx] Data channel = %d\n", MUX_CHAN(muxu_obuf)); + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Control card I/O signal handler. + + In diagnostic mode, the control signals C1 and C2 are looped back to status + signals S1 and S2. Changing the control signals may cause an interrupt, so a + test is performed after IOO processing. +*/ + +uint32 muxcio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +const char *hold_or_clear = (signal_set & ioCLF ? ",C" : ""); +uint16 data; +int32 ln, old; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + muxc.flag = muxc.flagbuf = CLEAR; + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fputs (">>MUXc cmds: [CLF] Flag cleared\n", sim_deb); + + mux_ctrl_int (); /* look for new int */ + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + muxc.flag = muxc.flagbuf = SET; + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fputs (">>MUXc cmds: [STF] Flag set\n", sim_deb); + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (muxc); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (muxc); + break; + + + case ioIOI: /* I/O data input */ + data = (uint16) (LIC_MBO | PUT_CCH (muxc_chan) | /* mbo, chan num */ + LIC_TSTI (muxc_chan) | /* I2, I1 */ + (muxc_ota[muxc_chan] & (OTC_ES2 | OTC_ES1)) | /* ES2, ES1 */ + (muxc_lia[muxc_chan] & (LIC_S2 | LIC_S1))); /* S2, S1 */ + + if (DEBUG_PRI (muxu_dev, DEB_CPU)) + fprintf (sim_deb, ">>MUXc cpu: [LIx%s] Status = %06o, channel = %d\n", + hold_or_clear, data, muxc_chan); + + muxc_chan = (muxc_chan + 1) & LIC_M_CHAN; /* incr channel */ + stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + data = IODATA (stat_data); /* clear supplied status */ + ln = muxc_chan = OTC_CHAN (data); /* set channel */ + + if (data & OTC_SCAN) muxc_scan = 1; /* set scan flag */ + else muxc_scan = 0; + + if (data & OTC_UPD) { /* update? */ + old = muxc_ota[ln]; /* save prior val */ + muxc_ota[ln] = /* save ESn,SSn */ + (muxc_ota[ln] & ~OTC_RW) | (data & OTC_RW); + + if (data & OTC_EC2) /* if EC2, upd C2 */ + muxc_ota[ln] = + (muxc_ota[ln] & ~OTC_C2) | (data & OTC_C2); + + if (data & OTC_EC1) /* if EC1, upd C1 */ + muxc_ota[ln] = + (muxc_ota[ln] & ~OTC_C1) | (data & OTC_C1); + + if (muxu_unit.flags & UNIT_DIAG) /* loopback? */ + muxc_lia[ln ^ 1] = /* set S1, S2 to C1, C2 */ + (muxc_lia[ln ^ 1] & ~(LIC_S2 | LIC_S1)) | + (muxc_ota[ln] & (OTC_C1 | OTC_C2)) >> OTC_V_C; + + else if ((muxl_unit[ln].flags & UNIT_MDM) && /* modem ctrl? */ + (old & DTR) && /* DTR drop? */ + !(muxc_ota[ln] & DTR)) { + tmxr_linemsg (&mux_ldsc[ln], "\r\nLine hangup\r\n"); + tmxr_reset_ln (&mux_ldsc[ln]); /* reset line */ + muxc_lia[ln] = 0; /* dataset off */ + } + } /* end update */ + + if (DEBUG_PRI (muxu_dev, DEB_CPU)) + fprintf (sim_deb, ">>MUXc cpu: [OTx%s] Parameter = %06o, channel = %d\n", + hold_or_clear, data, ln); + + if ((muxu_unit.flags & UNIT_DIAG) && (!muxc.flag)) /* loopback and flag clear? */ + mux_ctrl_int (); /* status chg may interrupt */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + muxc.flag = muxc.flagbuf = SET; /* set flag and flag buffer */ + break; + + + case ioCRS: /* control reset */ + case ioCLC: /* clear control flip-flop */ + muxc.control = CLEAR; + break; + + + case ioSTC: /* set control flip-flop */ + muxc.control = SET; + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (muxc); /* set standard PRL signal */ + setstdIRQ (muxc); /* set standard IRQ signal */ + setstdSRQ (muxc); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + muxc.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Unit service - receive side + + Poll for new connections + Poll all active lines for input +*/ + +t_stat muxi_svc (UNIT *uptr) +{ +int32 ln, c; +t_bool loopback; + +loopback = ((muxu_unit.flags & UNIT_DIAG) != 0); /* diagnostic mode? */ + +if (!loopback) { /* terminal mode? */ + if (uptr->wait == POLL_FIRST) /* first poll? */ + uptr->wait = sync_poll (INITIAL); /* initial synchronization */ + else /* not first */ + uptr->wait = sync_poll (SERVICE); /* continue synchronization */ + + sim_activate (uptr, uptr->wait); /* continue polling */ + + ln = tmxr_poll_conn (&mux_desc); /* look for connect */ + + if (ln >= 0) { /* got one? */ + if ((muxl_unit[ln].flags & UNIT_MDM) && /* modem ctrl? */ + (muxc_ota[ln] & DTR)) /* DTR? */ + muxc_lia[ln] = muxc_lia[ln] | CDET; /* set cdet */ + muxc_lia[ln] = muxc_lia[ln] | DSR; /* set dsr */ + mux_ldsc[ln].rcve = 1; /* rcv enabled */ + } + tmxr_poll_rx (&mux_desc); /* poll for input */ + } + +for (ln = 0; ln < MUX_LINES; ln++) { /* loop thru lines */ + if (mux_ldsc[ln].conn) { /* connected? */ + if (loopback) { /* diagnostic mode? */ + c = mux_xbuf[ln ^ 1] & OTL_CHAR; /* get char from xmit line */ + if (c == 0) /* all char bits = 0? */ + c = c | SCPE_BREAK; /* set break flag */ + mux_ldsc[ln].conn = 0; /* clear connection */ + } + + else if (mux_defer[ln]) /* break deferred? */ + c = SCPE_BREAK; /* supply it now */ + + else + c = tmxr_getc_ln (&mux_ldsc[ln]); /* get char from Telnet */ + + if (c) /* valid char? */ + mux_receive (ln, c, loopback); /* process it */ + } + + else /* not connected */ + if (!loopback) /* terminal mode? */ + muxc_lia[ln] = 0; /* line disconnected */ + } + +if (!muxl.flag) mux_data_int (); /* scan for data int */ +if (!muxc.flag) mux_ctrl_int (); /* scan modem */ +return SCPE_OK; +} + + +/* Unit service - transmit side */ + +t_stat muxo_svc (UNIT *uptr) +{ +int32 c, fc, ln, altln; +t_bool loopback; + +ln = uptr - muxl_unit; /* line # */ +altln = ln ^ 1; /* alt. line for diag mode */ + +fc = mux_xbuf[ln] & OTL_CHAR; /* full character data */ +c = fc & 0377; /* Telnet character data */ + +loopback = ((muxu_unit.flags & UNIT_DIAG) != 0); /* diagnostic mode? */ + +if (mux_ldsc[ln].conn) { /* connected? */ + if (mux_ldsc[ln].xmte) { /* xmt enabled? */ + if (loopback) /* diagnostic mode? */ + mux_ldsc[ln].conn = 0; /* clear connection */ + + else if (mux_defer[ln]) /* break deferred? */ + mux_receive (ln, SCPE_BREAK, loopback); /* process it now */ + + if ((mux_xbuf[ln] & OTL_SYNC) == 0) { /* start bit 0? */ + TMLN *lp = &mux_ldsc[ln]; /* get line */ + c = sim_tt_outcvt (c, TT_GET_MODE (muxl_unit[ln].flags)); + + if (mux_xpar[ln] & OTL_DIAG) /* xmt diagnose? */ + mux_diag (fc); /* before munge */ + + if (loopback) { /* diagnostic mode? */ + mux_ldsc[altln].conn = 1; /* set recv connection */ + sim_activate (&muxu_unit, 1); /* schedule receive */ + } + + else { /* no loopback */ + if (c >= 0) /* valid? */ + tmxr_putc_ln (lp, c); /* output char */ + tmxr_poll_tx (&mux_desc); /* poll xmt */ + } + } + + mux_xdon[ln] = 1; /* set for xmit irq */ + + if (DEBUG_PRI (muxu_dev, DEB_XFER) && (loopback | (c >= 0))) + fprintf (sim_deb, ">>MUXl xfer: Line %d character %s sent\n", + ln, fmt_char ((uint8) (loopback ? fc : c))); + } + + else { /* buf full */ + tmxr_poll_tx (&mux_desc); /* poll xmt */ + sim_activate (uptr, muxl_unit[ln].wait); /* wait */ + return SCPE_OK; + } + } + +if (!muxl.flag) mux_data_int (); /* scan for int */ +return SCPE_OK; +} + + +/* Process a character received from a multiplexer port */ + +void mux_receive (int32 ln, int32 c, t_bool diag) +{ +if (c & SCPE_BREAK) { /* break? */ + if (mux_defer[ln] || diag) { /* break deferred or diagnostic mode? */ + mux_defer[ln] = 0; /* process now */ + mux_rbuf[ln] = 0; /* break returns NUL */ + mux_sta[ln] = mux_sta[ln] | LIU_BRK; /* set break status */ + + if (DEBUG_PRI (muxu_dev, DEB_XFER)) + if (diag) + fputs (">>MUXl xfer: Break detected\n", sim_deb); + else + fputs (">>MUXl xfer: Deferred break processed\n", sim_deb); + } + + else { + mux_defer[ln] = 1; /* defer break */ + + if (DEBUG_PRI (muxu_dev, DEB_XFER)) + fputs (">>MUXl xfer: Break detected and deferred\n", sim_deb); + + return; + } + } +else { /* normal */ + if (mux_rchp[ln]) /* char already pending? */ + mux_sta[ln] = mux_sta[ln] | LIU_LOST; + + if (!diag) { /* terminal mode? */ + c = sim_tt_inpcvt (c, TT_GET_MODE (muxl_unit[ln].flags)); + if (mux_rpar[ln] & OTL_ECHO) { /* echo? */ + TMLN *lp = &mux_ldsc[ln]; /* get line */ + tmxr_putc_ln (lp, c); /* output char */ + tmxr_poll_tx (&mux_desc); /* poll xmt */ + } + } + mux_rbuf[ln] = (uint16) c; /* save char */ + } + +mux_rchp[ln] = 1; /* char pending */ + +if (DEBUG_PRI (muxu_dev, DEB_XFER)) + fprintf (sim_deb, ">>MUXl xfer: Line %d character %s received\n", + ln, fmt_char ((uint8) c)); + +if (mux_rpar[ln] & OTL_DIAG) /* diagnose this line? */ + mux_diag (c); /* do diagnosis */ + +return; +} + + +/* Look for data interrupt */ + +void mux_data_int (void) +{ +int32 i; + +for (i = 0; i < MUX_LINES; i++) { /* rcv lines */ + if ((mux_rpar[i] & OTL_ENB) && mux_rchp[i]) { /* enabled, char? */ + muxl_ibuf = PUT_DCH (i) | /* lo buf = char */ + mux_rbuf[i] & LIL_CHAR | + RCV_PAR (mux_rbuf[i]); + muxu_ibuf = PUT_DCH (i) | mux_sta[i]; /* hi buf = stat */ + mux_rchp[i] = 0; /* clr char, stat */ + mux_sta[i] = 0; + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MUXl cmds: Receive channel %d interrupt requested\n", i); + + muxlio (&muxl_dib, ioENF, 0); /* interrupt */ + return; + } + } +for (i = 0; i < MUX_LINES; i++) { /* xmt lines */ + if ((mux_xpar[i] & OTL_ENB) && mux_xdon[i]) { /* enabled, done? */ + muxl_ibuf = PUT_DCH (i) | /* lo buf = last rcv char */ + mux_rbuf[i] & LIL_CHAR | + RCV_PAR (mux_rbuf[i]); + muxu_ibuf = PUT_DCH (i) | mux_sta[i] | LIU_TR; /* hi buf = stat */ + mux_xdon[i] = 0; /* clr done, stat */ + mux_sta[i] = 0; + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MUXl cmds: Transmit channel %d interrupt requested\n", i); + + muxlio (&muxl_dib, ioENF, 0); /* interrupt */ + return; + } + } +for (i = MUX_LINES; i < (MUX_LINES + MUX_ILINES); i++) { /* diag lines */ + if ((mux_rpar[i] & OTL_ENB) && mux_rchp[i]) { /* enabled, char? */ + muxl_ibuf = PUT_DCH (i) | /* lo buf = char */ + mux_rbuf[i] & LIL_CHAR | + RCV_PAR (mux_rbuf[i]); + muxu_ibuf = PUT_DCH (i) | mux_sta[i] | LIU_DG; /* hi buf = stat */ + mux_rchp[i] = 0; /* clr char, stat */ + mux_sta[i] = 0; + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, ">>MUXl cmds: Receive channel %d interrupt requested\n", i); + + muxlio (&muxl_dib, ioENF, 0); /* interrupt */ + return; + } + } +return; +} + + +/* Look for control interrupt + + If either of the incoming status bits does not match the stored status, and + the corresponding mismatch is enabled, a control interrupt request is + generated. Depending on the scan flag, we check either all 16 lines or just + the current line. If an interrupt is requested, the channel counter + indicates the interrupting channel. +*/ + +void mux_ctrl_int (void) +{ +int32 i, line_count; + +line_count = (muxc_scan ? MUX_LINES : 1); /* check one or all lines */ + +for (i = 0; i < line_count; i++) { + if (muxc_scan) /* scanning? */ + muxc_chan = (muxc_chan + 1) & LIC_M_CHAN; /* step channel */ + if (LIC_TSTI (muxc_chan)) { /* status change? */ + + if (DEBUG_PRI (muxu_dev, DEB_CMDS)) + fprintf (sim_deb, + ">>MUXc cmds: Control channel %d interrupt requested (poll = %d)\n", + muxc_chan, i + 1); + + muxcio (&muxc_dib, ioENF, 0); /* set flag */ + break; + } + } +return; +} + + +/* Set diagnostic lines for given character */ + +void mux_diag (int32 c) +{ +int32 i; + +for (i = MUX_LINES; i < (MUX_LINES + MUX_ILINES); i++) { + if (c & SCPE_BREAK) { /* break? */ + mux_sta[i] = mux_sta[i] | LIU_BRK; + mux_rbuf[i] = 0; /* no char */ + } + else { + if (mux_rchp[i]) mux_sta[i] = mux_sta[i] | LIU_LOST; + mux_rchp[i] = 1; + mux_rbuf[i] = (uint16) c; + } + } +return; +} + + +/* Reset an individual line */ + +static void mux_reset_ln (int32 i) +{ +mux_rbuf[i] = mux_xbuf[i] = 0; /* clear state */ +mux_rpar[i] = mux_xpar[i] = 0; +mux_rchp[i] = mux_xdon[i] = 0; +mux_sta[i] = mux_defer[i] = 0; +muxc_ota[i] = muxc_lia[i] = 0; /* clear modem */ +if (mux_ldsc[i].conn && /* connected? */ + ((muxu_unit.flags & UNIT_DIAG) == 0)) /* term mode? */ + muxc_lia[i] = muxc_lia[i] | DSR | /* cdet, dsr */ + (muxl_unit[i].flags & UNIT_MDM? CDET: 0); +sim_cancel (&muxl_unit[i]); +return; +} + + +/* Reset routine for lower data, upper data, and control cards */ + +t_stat muxc_reset (DEVICE *dptr) +{ +int32 i; +DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */ + +if (dptr == &muxc_dev) { /* make all consistent */ + hp_enbdis_pair (dptr, &muxl_dev); + hp_enbdis_pair (dptr, &muxu_dev); + } +else if (dptr == &muxl_dev) { + hp_enbdis_pair (dptr, &muxc_dev); + hp_enbdis_pair (dptr, &muxu_dev); + } +else { + hp_enbdis_pair (dptr, &muxc_dev); + hp_enbdis_pair (dptr, &muxl_dev); + } + +IOPRESET (dibptr); /* PRESET device (does not use PON) */ + +muxc_chan = muxc_scan = 0; /* init modem scan */ + +if (muxu_unit.flags & UNIT_ATT) { /* master att? */ + muxu_unit.wait = POLL_FIRST; /* set up poll */ + sim_activate (&muxu_unit, muxu_unit.wait); /* start Telnet poll immediately */ + } +else + sim_cancel (&muxu_unit); /* else stop */ + +for (i = 0; i < MUX_LINES; i++) + mux_reset_ln (i); /* reset lines 0-15 */ + +for (i = MUX_LINES; i < (MUX_LINES + MUX_ILINES); i++) /* reset lines 16-20 */ + mux_rbuf[i] = mux_rpar[i] = mux_sta[i] = mux_rchp[i] = 0; + +return SCPE_OK; +} + + +/* Attach master unit */ + +t_stat mux_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat status = SCPE_OK; + +if (muxu_unit.flags & UNIT_DIAG) /* diag mode? */ + return SCPE_NOFNC; /* command not allowed */ + +status = tmxr_attach (&mux_desc, uptr, cptr); /* attach */ + +if (status == SCPE_OK) { + muxu_unit.wait = POLL_FIRST; /* set up poll */ + sim_activate (&muxu_unit, muxu_unit.wait); /* start Telnet poll immediately */ + } + +return status; +} + + +/* Detach master unit */ + +t_stat mux_detach (UNIT *uptr) +{ +int32 i; +t_stat r; + +r = tmxr_detach (&mux_desc, uptr); /* detach */ +for (i = 0; i < MUX_LINES; i++) mux_ldsc[i].rcve = 0; /* disable rcv */ +sim_cancel (uptr); /* stop poll */ +return r; +} + + +/* Diagnostic/normal mode routine, + + Diagnostic testing wants to exercise as much of the regular simulation code + as possible to ensure good test coverage. Normally, input polling and output + transmission only occurs on connected lines. In diagnostic mode, line + connection flags are set selectively to enable processing on the lines under + test. The alternative to this would require duplicating the send/receive + code; the diagnostic would then test the copy but not the actual code used + for normal character transfers, which is undesirable. + + Therefore, to enable diagnostic mode, we must force a disconnect of the + master socket and any connected Telnet lines, which clears the connection + flags on all lines. Then we set the "transmission enabled" flags on all + lines to enable output character processing for the diagnostic. (Normally, + all of the flags are set when the multiplexer is first attached. Until then, + the enable flags default to "not enabled," so we enable them explicitly + here.) +*/ + +t_stat mux_setdiag (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +int32 ln; + +if (val) { /* set diag? */ + mux_detach (uptr); /* detach lines */ + for (ln = 0; ln < MUX_LINES; ln++) /* enable transmission */ + mux_ldsc[ln].xmte = 1; /* on all lines */ + } +else { /* set term */ + for (ln = 0; ln < MUX_LINES; ln++) /* clear connections */ + mux_ldsc[ln].conn = 0; /* on all lines */ + } +return SCPE_OK; +} diff --git a/HP2100/hp2100_pif.c b/HP2100/hp2100_pif.c index ceaaeb8c..4d91eb04 100644 --- a/HP2100/hp2100_pif.c +++ b/HP2100/hp2100_pif.c @@ -1,6 +1,6 @@ /* hp2100_pif.c: HP 12620A/12936A privileged interrupt fence simulator - Copyright (c) 2008-2012, J. David Bryan + Copyright (c) 2008-2016, J. David Bryan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -25,6 +25,7 @@ PIF 12620A/12936A privileged interrupt fence + 13-May-16 JDB Modified for revised SCP API function parameter types 10-Feb-12 JDB Deprecated DEVNO in favor of SC 28-Mar-11 JDB Tidied up signal handling 26-Oct-10 JDB Changed I/O signal handler for revised signal model @@ -118,8 +119,8 @@ struct { IOHANDLER pif_io; t_stat pif_reset (DEVICE *dptr); -t_stat pif_set_card (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat pif_show_card (FILE *st, UNIT *uptr, int32 val, void *desc); +t_stat pif_set_card (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat pif_show_card (FILE *st, UNIT *uptr, int32 val, CONST void *desc); /* PIF data structures. @@ -347,7 +348,7 @@ return SCPE_OK; val == 1 --> set to 12620A (RTE PIF) */ -t_stat pif_set_card (UNIT *uptr, int32 val, char *cptr, void *desc) +t_stat pif_set_card (UNIT *uptr, int32 val, CONST char *cptr, void *desc) { if ((val < 0) || (val > 1) || (cptr != NULL)) /* sanity check */ return SCPE_ARG; /* bad argument */ @@ -363,7 +364,7 @@ return SCPE_OK; /* Show card type */ -t_stat pif_show_card (FILE *st, UNIT *uptr, int32 val, void *desc) +t_stat pif_show_card (FILE *st, UNIT *uptr, int32 val, CONST void *desc) { if (pif_dev.flags & DEV_12936) fputs ("12936A", st); diff --git a/HP2100/hp2100_release.txt b/HP2100/hp2100_release.txt new file mode 100644 index 00000000..5a7fa9ed --- /dev/null +++ b/HP2100/hp2100_release.txt @@ -0,0 +1,496 @@ + SIMH/HP 2100 RELEASE NOTES + ========================== + Last update: 2017-05-01 + + +This file documents the release history of the simulator for the Hewlett-Packard +2114, 2115, 2116, 2100, 1000-M, 1000-E, and 1000-F machines. + +The SIMH project does not issue discrete releases. Instead, the current +simulator code base is available at: + + https://github.com/simh/simh + +...and may be downloaded at any time. A code snapshot is identified by the "git +commit ID" that is displayed in the simulator welcome banner. + +Therefore, HP 2100 releases are simply documentation checkpoints that describe +the changes that have occurred since the last checkpoint. Generally, a release +is written when one or more major changes have been incorporated. Minor bug +fixes will be available immediately from the repository but only noted as part +of the next release document. + + + +=============================== +Reporting Bugs in the Simulator +=============================== + +If you find a bug in the HP 2100 simulator, please report it either to the SIMH +issue tracker on github at: + + https://github.com/simh/simh/issues + +...or to the SIMH mailing list; see: + + http://mailman.trailing-edge.com/mailman/listinfo/simh + +...for subscribing information. In either case, please include a console log +that contains a reproducible test case that illustrates the problem. See the +"Recording Simulator Activities" section of the "SIMH User's Guide" for details. + + + +=================== +General Information +=================== + +The simulator passes the HP 24396 offline diagnostic suite with some expected +failures due to unimplemented features. For example, the disc diagnostic +error-correction logic tests and the tape diagnostic CRCC and LRCC tests fail, +as these features are not supported. However, all features that are required +for operation of the supported HP operating systems pass their respective +diagnostic tests. See the accompanying "hp2100_diag.txt" file for details. + +The simulator has been tested with the following operating systems: + + - SIO, BCS, and MTS. + + - 2000E, 2000F, and 2000/Access Time-Shared BASIC. + + - DOS, DOS-M, and DOS-III. + + - RTE, RTE-B, RTE-C, RTE-II, RTE-III, RTE-IV, RTE-IVB, and RTE-6/VM. + +The user's manual for the simulator is provided in Microsoft Word format in the +"doc" subdirectory of the code base snapshot downloaded from the github site. A +PDF version of the same manual is available at: + + http://alum.mit.edu/www/jdbryan/hp2100_doc.pdf + + +------------------ +Available Software +------------------ + +A preconfigured RTE-6/VM disc image containing the operating system, the MACRO +assembler and Fortran 77 compiler, a selection of tape backup and restore +programs (READT/WRITT, READR/SAVER, FC, TF, and FST), an example programs +(/KENC/HELLO.FTN), and the Mystery Mansion game (MMM) is available from +Bitsavers at: + + http://www.bitsavers.org/bits/HP/tapes/rte-6vm/rte6200/ + +The archive contains instructions and simulator command files that allow +ready-to-run operation. + +The Computer History Museum has graciously arranged with HP to offer the HP 1000 +Software Collection with a sublicense for non-commercial use by private +individuals. The Collection is hosted by Bitsavers at: + + http://www.bitsavers.org/bits/HP/HP_1000_software_collection/ + +A preconfigured RTE-IVB disc image containing the operating system, the ASMB +assembler and FORTRAN IV compiler, and the READR/SAVER and FC tape backup and +restore programs is available from the HP Computer Museum at: + + http://www.hpmuseum.net/display_item.php?sw=565 + +The archive contains instructions and a simulator command file. + +QCTerm, an HP 700 terminal emulator for Microsoft Windows, is available from the +HP Computer Museum at: + + http://www.hpmuseum.net/display_item.php?sw=585 + +Use of an HP terminal via a serial port or terminal emulator via Telnet enables +more advanced screen editing features of the RTE operating systems. + +Manuals describing the operation of HP software are available from Bitsavers at: + + http://www.bitsavers.org/pdf/hp/1000/ + http://www.bitsavers.org/pdf/hp/2000TSB/ + http://www.bitsavers.org/pdf/hp/21xx/ + +...and from the HP Computer Museum at: + + http://www.hpmuseum.net/collection_document.php + + +---------------- +Year 2000 Issues +---------------- + +RTE-6/VM Revision 6200 is Y2K compliant, except for the READR and SAVER +programs. The errors are cosmetic only. + +RTE-IVB Revision 5010 is not Y2K compliant. All of the failures are in +subsystems; the operating itself (time-of-day clock) accommodates dates through +2059. All of the errors are cosmetic. Typically, punctuation characters appear +in the years, e.g., "19:0" for 2000. The RTE-IVB software kit from the HP +Computer Museum includes fixes for the affected modules to bring the system into +compliance. + +All other HP operating systems are not Y2K compliant. + + +----------------------------- +Bugs in RTE-IVB Revision 5010 +----------------------------- + +Testing during simulator development revealed the presence of a bug in RTE-IVB +Revision 5010: + + - The $BALC module in the system library has a bug that causes memory + corruption. This module is used by the ACCTS program and manifests itself by + printing gibberish after the "PLEASE LOG ON:" prompt. + + Specifically, the internal MXEV routine performs a cross-store indirect via a + location in Table Area II (XSA $MAXI+0,I). This fails because the indirect + chain is resolved in the user map, but TA II is not in the user map of + large-background programs, such as ACCTS. Therefore, the location in the + user map corresponding to $MAXI in the system map is used as the pointer to + the location to store. + + A corrected version of $BALC is present on cartridge GF of the disc image + supplied with the RTE-IVB software kit at the HP Computer Museum. + + + +====================== +Release 26, 2017-05-01 +====================== + +This release of the HP 2100 simulator does not add any new features. + + +-------------------- +Implementation Notes +-------------------- + + - Starting with the next release, the LOAD command will be rewritten to load + files containing absolute binary loaders into the protected address space of + the 21xx machines and configure the I/O instructions. The LOAD command is + not designed for general loading of absolute binary files, as it does not + initialize the A and B registers as some HP software expects. It is intended + only to install bootstrap loaders. The BOOT PTR command is the proper + simulation of the hardware absolute paper tape loader. + + +---------- +Bugs Fixed +---------- + + 1. PROBLEM: The RWCS debug option shown in the user's guide does not exist. + + VERSION: Release 25. + + OBSERVATION: The "HP2100 Simulator User's Guide" says that the RWCS debug + option may be specified for the DS and DA devices to trace "disk read/ + write/control/status commands." However, entering a SET DS DEBUG=RWCS + command gives an "Invalid argument" error. + + CAUSE: The option name is misspelled; the correct option is RWSC. + + RESOLUTION: Modify "hp2100_doc.doc" to list the correct option name. + + STATUS: Fixed in Release 26. + + + 2. PROBLEM: Halt opcodes 1060xx and 1070xx do not display in mnemonic form. + + VERSION: Release 25. + + OBSERVATION: Halt instructions 106000-106077 and 107000-107077 are not + displayed in mnemonic form, either directly with an EXAMINE -M command + or in the message displayed for a programmed halt. These instruction + ranges are displayed in octal only. + + CAUSE: Section 3.20, "Input/Output Instructions," of the "HP 1000 + M/E/F-Series Computers Technical Reference Handbook" (HP 5955-0282, March + 1980) says, in part, "Bit 11, where relevant, specifies the A- or + B-register or distinguishes between set control and clear control; + otherwise, bit 11 may be a logic 0 or a logic 1 without affecting the + instruction (although the assembler will assign zeros in this case)." The + HLT instruction does not specify the A/B register, so the valid opcodes are + 102000-102077, 103000-103777, 106000-106077, and 107000-107077. However, + the latter two ranges are omitted from the "opcode" and "opc_val" tables + used for decoding. + + RESOLUTION: Add the 1060xx/107xx range to the "opc_val" table and a second + "HLT" string to the "opcode" table (hp2100_sys.c) to permit mnemonic + display of this instruction range. + + STATUS: Fixed in Release 26. + + + 3. PROBLEM: The SFB (scan for byte) opcode displays as SBT (store byte). + + VERSION: Release 25. + + OBSERVATION: Entering the "EVAL -M 105767" command should display the + "SFB" mnemonic. Instead, it displays "SBT". + + CAUSE: The entry in the opcode mnemonic table corresponding to the 105767 + value is "SBT", which is incorrect. It should be "SFB" (SBT is 105764). + + RESOLUTION: Modify the "opcode" table (hp2100_sys.c) to use the correct + mnemonic for the SFB instruction. + + STATUS: Fixed in Release 26. + + + 4. PROBLEM: Host file system seek errors are not caught. + + VERSION: Release 25. + + OBSERVATION: The MAC/ICD disc library checks for host file system read or + write errors and returns Uncorrectable Data Error status if an error is + indicated. However, host file system seeks are simply assumed to succeed; + no indication of an error is given if a call fails. A failed seek should + be detected, and a Drive Fault (positioner error) should be returned. + + CAUSE: Oversight. + + RESOLUTION: Modify "position_sector" (hp2100_disclib.c) to test the + "sim_fseek" call for error status and to simulate a Drive Fault (AGC error) + if the call fails. + + STATUS: Fixed in Release 26. + + + 5. PROBLEM: Set Flow Control and Cancel commands fail if port key is not set. + + VERSION: Release 25. + + OBSERVATION: HP 8-channel multiplexer commands that refer to ports do so + indirectly by passing a port key, rather than a port number. The + key-to-port translation is established by the "Set Port Key" command, which + must be executed before any port-specific commands. If a port key has not + been established, then all port-specific commands should be ignored. + However, the "Cancel first receive buffer" and "Set flow control" commands + cause program corruption if the key has not been set. + + CAUSE: The test for key validity is improperly applied for these commands. + + RESOLUTION: Modify "exec_command" (hp2100_mpx.c) to ignore these commands + if the port key has not been set. + + STATUS: Fixed in Release 26. + + + 6. ENHANCEMENT: Improve the EAU shift and rotate instruction simulations. + + VERSION: Release 25. + + OBSERVATION: The shift and rotate instructions (ASL, ASR, LSL, LSR, RRL, + and RRR) perform 32-bit operations on the combined B and A registers. The + original implementation treated the 16-bit registers independently. + However, it is faster and smaller to form a 32-bit operand, apply the + operation, and then split the operand back into the B and A registers. + Modern compilers, such as gcc, recognize the shifting and masking patterns + necessary for a rotation and generate a single rotate-left or rotate-right + machine instruction. + + RESOLUTION: Modify "cpu_eau" (hp2100_cpu1.c) to reimplement the shift and + rotate instructions as 32-bit operations. + + STATUS: Fixed in Release 26. + + + +====================== +Release 25, 2017-01-11 +====================== + +This is the initial checkpoint release of the HP 2100 simulator, corresponding +to the 25th set of changes to the 4.0 code base. The following devices are +currently simulated: + + - 2114C CPU with up to 16 KW of memory + - 2115A CPU with up to 8 KW of memory + - 2116C CPU with up to 32 KW of memory + - 2100A CPU with up to 32 KW of memory + - 1000 M/E/F-Series CPU with up to 1024 KW of memory + - EAU, FP, IOP, DMS, FFP, DBI, VIS, and SIGNAL microcode extensions + - RTE-IV EMA or RTE-6/VM OS and VMA microcode extensions + - 12531C Buffered Teleprinter Interface with one 2752 Teleprinter + - 12539C Time Base Generator + - 12557A Disc Controller with four 2870 drives + - 12559C Magnetic Tape Controller with one 3030 drive + - 12565A Disc Controller with two 2883 drives + - 12566B Microcircuit Interface with a loopback connector + - 12578A Direct Memory Access Controller + - 12581A Memory Protect + - 12597A Duplex Register Interface with one 2748 Paper Tape Reader + - 12597A Duplex Register Interface with one 2895 Paper Tape Punch + - 12606B Fixed Head Disc Controller with one 2770/2771 drive + - 12607B Direct Memory Access Controller + - 12610B Drum Controller with one 2773/2774/2775 drive + - 12620A Privileged Interrupt Fence + - 12653A Printer Controller with one 2767 Line Printer + - 12792C 8-Channel Asynchronous Multiplexer + - 12821A Disc Interface with four 7906H/7920H/7925H drives + - 12845B Printer Controller with one 2607 Line Printer + - 12875A Interprocessor Link + - 12892B Memory Protect + - 12895A Direct Memory Access Controller + - 12897B Dual-Channel Port Controller + - 12920A 16-Channel Terminal Multiplexer + - 12936A Privileged Interrupt Fence + - 12966A Buffered Asynchronous Communications Interface + - 13037D Disc Controller with eight 7905/7906/7920/7925 drives + - 13181A Magnetic Tape Controller with four 7970B drives + - 13183A Magnetic Tape Controller with four 7970E drives + - 13210A Disc Controller with four 7900 drives + +The "HP 2100 Simulator User's Guide" manual describes the configuration and +operation of each of these devices in detail. + + +-------------------- +Implementation Notes +-------------------- + + - New bug fixes will now be listed in this file under the associated release + rather than in their previous location (hp2100_bugfixes.txt). + + - Starting with the next release, the LOAD command will restrict its operation + to the addresses occupied by the bootstrap loaders, i.e., the last 64 + locations in memory (up to 32K). The LOAD command is not designed for + general loading of absolute binary files, as it does not initialize the A and + B registers as some HP software expects. It is intended only to install + bootstrap loaders. The BOOT PTR command is the proper simulation of the + hardware absolute paper tape loader. + + +---------- +Bugs Fixed +---------- + + 1. PROBLEM: DPC device documentation uses the wrong disc drive model number. + + VERSION: Release 24. + + OBSERVATION: The comments in the hp2100_dpc.c source file and Section 2 of + the "HP2100 Simulator User's Guide" say that the DPC device supports the + 2871 disc drive, while Section 2.6.1 of the User's Guide says that the + support is for the 2781 disc drive. Neither of these model numbers is + correct. + + Contemporaneous literature (e.g., the "2116B Computer Price List," dated + June 1970) states that the disc memory subsystem consists of the HP 2870A + Moving Head Disc, HP 2871A Disc Controller, HP 2881A Power Supply, and HP + 2882A Cabinet. + + CAUSE: The controller model number is used instead of the drive model + number, while the "2781" number is a transposition of "2871." + + RESOLUTION: Modify the initial comments in the DPC device source file + (hp2100_dpc.c) and modify the sections of the HP2100 Simulator User's Guide + to use the correct disc drive model number (2870). + + STATUS: Fixed in Release 25. + + + 2. PROBLEM: The BOOT DRC command does not execute correctly. + + VERSION: Release 24. + + OBSERVATION: Attempting to boot DOS from a fixed-head disc or drum does + not work. The CPU sits in a loop waiting for DMA to finish, but it never + does. + + CAUSE: The DMA control word in the DR device bootstrap is not configured + during BOOT DRC processing, so DMA is communicating with the wrong device. + + RESOLUTION: Modify "drc_boot" (hp2100_dr.c) to set the fixed disc/drum + select code into the DMA control word before returning. + + STATUS: Fixed in Release 25. + + + 3. PROBLEM: The valid command "DEPOSIT 2000 JMP 2001" is rejected. + + VERSION: Release 24. + + OBSERVATION: Regarding symbolic input, the HP2100 User's Manual says that + the "C" and "Z" flags, signifying a current-page or zero-page reference, + respectively, are not needed "...when entering [memory reference] + instructions into CPU memory; the simulator figures out from the target + address what mode to use." While the valid command "DEPOSIT 1000 JMP 1001" + correctly enters a zero-page jump into memory, the valid command "DEPOSIT + 2000 JMP 2001" does not enter a current-page jump. Instead, an "Invalid + argument" error occurs. + + CAUSE: The "parse_sym" routine looks for the optional "C" or "Z" flag when + parsing memory reference instructions and sets a flag if either is + specified. The test for a current-page reference is performed only if the + reference type was not explicitly specified. However, the sense of the + test is reversed. + + RESOLUTION: Modify "parse_sym" (hp2100_sys.c) to correct the test for C/Z + option specification. + + STATUS: Fixed in Release 25. + + + 4. PROBLEM: The invalid command "DEPOSIT 2000 JMP C 2001" is accepted. + + VERSION: Release 24. + + OBSERVATION: Regarding symbolic input, the HP2100 User's Manual says that + "The address is an octal number in the range 0 - 77777; if C or Z is + specified, the address is a page offset in the range 0 - 1777." However, + specifying a page offset > 1777 is accepted without complaint if it is + within the current page range. + + CAUSE: Error checking for memory reference instruction entry is + incomplete. + + RESOLUTION: Modify "parse_sym" (hp2100_sys.c) to ensure that the range + check is enforced if either C or Z is specified. + + STATUS: Fixed in Release 25. + + + 5. PROBLEM: Punched output does not appear on TTY devices lacking a paper + tape punch. + + VERSION: Release 24. + + OBSERVATION: Running the HP contributed library program "HP 2000F BASIC + for DOS-M/DOS III" does not produce any console output when using terminal + driver DVR00 as required by the program. When using alternate terminal + driver DVR05, console output appears but console input does not work + properly. + + CAUSE: DOS-M and DOS-III support two modes of console I/O: ASCII mode and + binary mode. ASCII mode appends carriage-return/line-feed characters on + output and strips them on input. Binary mode sends and receives bytes + exactly as supplied. + + DVR00 is required because DVR05 does not support the binary I/O mode + required by the program. However, DVR00 assumes that a binary write is to + be directed to the console's paper tape punch rather than the console + printer and therefore sets the TTY interface's "punch flip-flop" instead of + the "print flip-flop" to accompany the text output. The simulation of the + HP 12531 interface card associated with the TTY device discards output if + the punch flip-flop is set and the punch unit (TTY2) is not attached. + + The problem occurs because only a connected HP 2754 teleprinter (a modified + Teletype ASR35) reacts to the print and punch flip-flop signals. All other + supported terminal devices ignore the signals and print whatever output is + supplied (an HP 2752 -- a rebadged ASR33 -- has a manual control for the + punch, but the punch and printer operate together when the punch is on). + The 2000F BASIC program apparently was designed for use with one of these + other terminals, which print normally even though only the punch flip-flop + is set. + + RESOLUTION: Modify "tto_out" (hp2100_stddev.c) to honor the print and + punch flip-flop settings and separate the output as directed only if the + console punch unit is attached (simulating an HP 2754). When the unit is + detached, all output is delivered to the console printer, regardless of the + flip-flop settings (simulating all other console devices). + + STATUS: Fixed in Release 25. diff --git a/HP2100/hp2100_stddev.c b/HP2100/hp2100_stddev.c index dbd8cb41..b9ad5ea2 100644 --- a/HP2100/hp2100_stddev.c +++ b/HP2100/hp2100_stddev.c @@ -1,1218 +1,1299 @@ -/* hp2100_stddev.c: HP2100 standard devices simulator - - Copyright (c) 1993-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - PTR 12597A-002 paper tape reader interface - PTP 12597A-005 paper tape punch interface - TTY 12531C buffered teleprinter interface - TBG 12539C time base generator - - 30-Dec-14 JDB Added S-register parameters to ibl_copy - 24-Dec-14 JDB Added casts for explicit downward conversions - 28-Dec-12 JDB Allocate the TBG logical name during power-on reset - 18-Dec-12 MP Now calls sim_activate_time to get remaining poll time - 09-May-12 JDB Separated assignments from conditional expressions - 12-Feb-12 JDB Add TBG as a logical name for the CLK device - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - 28-Mar-11 JDB Tidied up signal handling - 26-Oct-10 JDB Changed I/O signal handler for revised signal model - 26-Jun-08 JDB Rewrote device I/O to model backplane signals - 25-Apr-08 JDB Changed TTY output wait from 100 to 200 for MSU BASIC - 18-Apr-08 JDB Removed redundant control char handling definitions - 14-Apr-08 JDB Changed TTY console poll to 10 msec. real time - Synchronized CLK with TTY if set for 10 msec. - Added UNIT_IDLE to TTY and CLK - 09-Jan-08 JDB Fixed PTR trailing null counter for tape re-read - 31-Dec-07 JDB Added IPTICK register to CLK to display CPU instr/tick - Corrected and verified ioCRS actions - 28-Dec-06 JDB Added ioCRS state to I/O decoders - 22-Nov-05 RMS Revised for new terminal processing routines - 13-Sep-04 JDB Added paper tape loop mode, DIAG/READER modifiers to PTR - Added PV_LEFT to PTR TRLLIM register - Modified CLK to permit disable - 15-Aug-04 RMS Added tab to control char set (from Dave Bryan) - 14-Jul-04 RMS Generalized handling of control char echoing - (from Dave Bryan) - 26-Apr-04 RMS Fixed SFS x,C and SFC x,C - Fixed SR setting in IBL - Fixed input behavior during typeout for RTE-IV - Suppressed nulls on TTY output for RTE-IV - Implemented DMA SRQ (follows FLG) - 29-Mar-03 RMS Added support for console backpressure - 25-Apr-03 RMS Added extended file support - 22-Dec-02 RMS Added break support - 01-Nov-02 RMS Revised BOOT command for IBL ROMs - Fixed bug in TTY reset, TTY starts in input mode - Fixed bug in TTY mode OTA, stores data as well - Fixed clock to add calibration, proper start/stop - Added UC option to TTY output - 30-May-02 RMS Widened POS to 32b - 22-Mar-02 RMS Revised for dynamically allocated memory - 03-Nov-01 RMS Changed DEVNO to use extended SET/SHOW - 29-Nov-01 RMS Added read only unit support - 24-Nov-01 RMS Changed TIME to an array - 07-Sep-01 RMS Moved function prototypes - 21-Nov-00 RMS Fixed flag, buffer power up state - Added status input for ptp, tty - 15-Oct-00 RMS Added dynamic device number support - - References: - - 2748B Tape Reader Operating and Service Manual (02748-90041, Oct-1977) - - 12597A 8-Bit Duplex Register Interface Kit Operating and Service Manual - (12597-9002, Sep-1974) - - 12531C Buffered Teleprinter Interface Kit Operating and Service Manual - (12531-90033, Nov-1972) - - 12539C Time Base Generator Interface Kit Operating and Service Manual - (12539-90008, Jan-1975) - - - The reader and punch, like most HP devices, have a command flop. The - teleprinter and clock do not. - - Reader diagnostic mode simulates a tape loop by rewinding the tape image file - upon EOF. Normal mode EOF action is to supply TRLLIM nulls and then either - return SCPE_IOERR or SCPE_OK without setting the device flag. - - To support CPU idling, the teleprinter interface (which doubles as the - simulator console) polls for input using a calibrated timer with a ten - millisecond period. Other polled-keyboard input devices (multiplexers and - the BACI card) synchronize with the console poll to ensure maximum available - idle time. The console poll is guaranteed to run, as the TTY device cannot - be disabled. - - The clock (time base generator) autocalibrates. If the TBG is set to a ten - millisecond period (e.g., as under RTE), it is synchronized to the console - poll. Otherwise (e.g., as under DOS or TSB, which use 100 millisecond - periods), it runs asynchronously. If the specified clock frequency is below - 10Hz, the clock service routine runs at 10Hz and counts down a repeat counter - before generating an interrupt. Autocalibration will not work if the clock - is running at 1Hz or less. - - Clock diagnostic mode corresponds to inserting jumper W2 on the 12539C. - This turns off autocalibration and divides the longest time intervals down - by 10**3. The clk_time values were chosen to allow the diagnostic to - pass its clock calibration test. -*/ - -#include "hp2100_defs.h" - -#define TTY_OUT_WAIT 200 /* TTY output wait */ - -#define UNIT_V_DIAG (TTUF_V_UF + 0) /* diag mode */ -#define UNIT_V_AUTOLF (TTUF_V_UF + 1) /* auto linefeed */ -#define UNIT_DIAG (1 << UNIT_V_DIAG) -#define UNIT_AUTOLF (1 << UNIT_V_AUTOLF) - -#define PTP_LOW 0000040 /* low tape */ -#define TM_MODE 0100000 /* mode change */ -#define TM_KBD 0040000 /* enable keyboard */ -#define TM_PRI 0020000 /* enable printer */ -#define TM_PUN 0010000 /* enable punch */ -#define TP_BUSY 0100000 /* busy */ - -#define CLK_V_ERROR 4 /* clock overrun */ -#define CLK_ERROR (1 << CLK_V_ERROR) - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } ptr = { CLEAR, CLEAR, CLEAR }; - -int32 ptr_stopioe = 0; /* stop on error */ -int32 ptr_trlcnt = 0; /* trailer counter */ -int32 ptr_trllim = 40; /* trailer to add */ - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } ptp = { CLEAR, CLEAR, CLEAR }; - -int32 ptp_stopioe = 0; - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } tty = { CLEAR, CLEAR, CLEAR }; - -int32 ttp_stopioe = 0; -int32 tty_buf = 0; /* tty buffer */ -int32 tty_mode = 0; /* tty mode */ -int32 tty_shin = 0377; /* tty shift in */ -int32 tty_lf = 0; /* lf flag */ - -struct { - FLIP_FLOP control; /* control flip-flop */ - FLIP_FLOP flag; /* flag flip-flop */ - FLIP_FLOP flagbuf; /* flag buffer flip-flop */ - } clk = { CLEAR, CLEAR, CLEAR }; - -int32 clk_select = 0; /* clock time select */ -int32 clk_error = 0; /* clock error */ -int32 clk_ctr = 0; /* clock counter */ -int32 clk_time[8] = { /* clock intervals */ - 155, 1550, 15500, 155000, 155000, 155000, 155000, 155000 - }; -int32 clk_tps[8] = { /* clock tps */ - 10000, 1000, 100, 10, 10, 10, 10, 10 - }; -int32 clk_rpt[8] = { /* number of repeats */ - 1, 1, 1, 1, 10, 100, 1000, 10000 - }; -uint32 clk_tick = 0; /* instructions per tick */ - -DEVICE ptr_dev, ptp_dev, tty_dev, clk_dev; - -IOHANDLER ptrio; -t_stat ptr_svc (UNIT *uptr); -t_stat ptr_attach (UNIT *uptr, char *cptr); -t_stat ptr_reset (DEVICE *dptr); -t_stat ptr_boot (int32 unitno, DEVICE *dptr); - -IOHANDLER ptpio; -t_stat ptp_svc (UNIT *uptr); -t_stat ptp_reset (DEVICE *dptr); - -IOHANDLER ttyio; -t_stat tti_svc (UNIT *uptr); -t_stat tto_svc (UNIT *uptr); -t_stat tty_reset (DEVICE *dptr); -t_stat tty_set_opt (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat tty_set_alf (UNIT *uptr, int32 val, char *cptr, void *desc); -t_stat tto_out (int32 c); -t_stat ttp_out (int32 c); - -IOHANDLER clkio; -t_stat clk_svc (UNIT *uptr); -t_stat clk_reset (DEVICE *dptr); -int32 clk_delay (int32 flg); - -/* PTR data structures - - ptr_dev PTR device descriptor - ptr_unit PTR unit descriptor - ptr_mod PTR modifiers - ptr_reg PTR register list -*/ - -DIB ptr_dib = { &ptrio, PTR }; - -UNIT ptr_unit = { - UDATA (&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), - SERIAL_IN_WAIT - }; - -REG ptr_reg[] = { - { ORDATA (BUF, ptr_unit.buf, 8) }, - { FLDATA (CTL, ptr.control, 0) }, - { FLDATA (FLG, ptr.flag, 0) }, - { FLDATA (FBF, ptr.flagbuf, 0) }, - { DRDATA (TRLCTR, ptr_trlcnt, 8), REG_HRO }, - { DRDATA (TRLLIM, ptr_trllim, 8), PV_LEFT }, - { DRDATA (POS, ptr_unit.pos, T_ADDR_W), PV_LEFT }, - { DRDATA (TIME, ptr_unit.wait, 24), PV_LEFT }, - { FLDATA (STOP_IOE, ptr_stopioe, 0) }, - { ORDATA (SC, ptr_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, ptr_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB ptr_mod[] = { - { UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAG", NULL }, - { UNIT_DIAG, 0, "reader mode", "READER", NULL }, - { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &ptr_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &ptr_dev }, - { 0 } - }; - -DEVICE ptr_dev = { - "PTR", &ptr_unit, ptr_reg, ptr_mod, - 1, 10, 31, 1, 8, 8, - NULL, NULL, &ptr_reset, - &ptr_boot, &ptr_attach, NULL, - &ptr_dib, DEV_DISABLE - }; - -/* PTP data structures - - ptp_dev PTP device descriptor - ptp_unit PTP unit descriptor - ptp_mod PTP modifiers - ptp_reg PTP register list -*/ - -DIB ptp_dib = { &ptpio, PTP }; - -UNIT ptp_unit = { - UDATA (&ptp_svc, UNIT_SEQ+UNIT_ATTABLE, 0), SERIAL_OUT_WAIT - }; - -REG ptp_reg[] = { - { ORDATA (BUF, ptp_unit.buf, 8) }, - { FLDATA (CTL, ptp.control, 0) }, - { FLDATA (FLG, ptp.flag, 0) }, - { FLDATA (FBF, ptp.flagbuf, 0) }, - { DRDATA (POS, ptp_unit.pos, T_ADDR_W), PV_LEFT }, - { DRDATA (TIME, ptp_unit.wait, 24), PV_LEFT }, - { FLDATA (STOP_IOE, ptp_stopioe, 0) }, - { ORDATA (SC, ptp_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, ptp_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB ptp_mod[] = { - { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &ptp_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &ptp_dev }, - { 0 } - }; - -DEVICE ptp_dev = { - "PTP", &ptp_unit, ptp_reg, ptp_mod, - 1, 10, 31, 1, 8, 8, - NULL, NULL, &ptp_reset, - NULL, NULL, NULL, - &ptp_dib, DEV_DISABLE - }; - -/* TTY data structures - - tty_dev TTY device descriptor - tty_unit TTY unit descriptor - tty_reg TTY register list - tty_mod TTy modifiers list -*/ - -#define TTI 0 -#define TTO 1 -#define TTP 2 - -DIB tty_dib = { &ttyio, TTY }; - -UNIT tty_unit[] = { - { UDATA (&tti_svc, UNIT_IDLE | TT_MODE_UC, 0), POLL_WAIT }, - { UDATA (&tto_svc, TT_MODE_UC, 0), TTY_OUT_WAIT }, - { UDATA (&tto_svc, UNIT_SEQ | UNIT_ATTABLE | TT_MODE_8B, 0), SERIAL_OUT_WAIT } - }; - -REG tty_reg[] = { - { ORDATA (BUF, tty_buf, 8) }, - { ORDATA (MODE, tty_mode, 16) }, - { ORDATA (SHIN, tty_shin, 8), REG_HRO }, - { FLDATA (CTL, tty.control, 0) }, - { FLDATA (FLG, tty.flag, 0) }, - { FLDATA (FBF, tty.flagbuf, 0) }, - { FLDATA (KLFP, tty_lf, 0), REG_HRO }, - { DRDATA (KPOS, tty_unit[TTI].pos, T_ADDR_W), PV_LEFT }, - { DRDATA (KTIME, tty_unit[TTI].wait, 24), REG_NZ + PV_LEFT }, - { DRDATA (TPOS, tty_unit[TTO].pos, T_ADDR_W), PV_LEFT }, - { DRDATA (TTIME, tty_unit[TTO].wait, 24), REG_NZ + PV_LEFT }, - { DRDATA (PPOS, tty_unit[TTP].pos, T_ADDR_W), PV_LEFT }, - { FLDATA (STOP_IOE, ttp_stopioe, 0) }, - { ORDATA (SC, tty_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, tty_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB tty_mod[] = { - { TT_MODE, TT_MODE_UC, "UC", "UC", &tty_set_opt }, - { TT_MODE, TT_MODE_7B, "7b", "7B", &tty_set_opt }, - { TT_MODE, TT_MODE_8B, "8b", "8B", &tty_set_opt }, - { TT_MODE, TT_MODE_7P, "7p", "7P", &tty_set_opt }, - { UNIT_AUTOLF, UNIT_AUTOLF, "autolf", "AUTOLF", &tty_set_alf }, - { UNIT_AUTOLF, 0 , NULL, "NOAUTOLF", &tty_set_alf }, - { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &tty_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &tty_dev }, - { 0 } - }; - -DEVICE tty_dev = { - "TTY", tty_unit, tty_reg, tty_mod, - 3, 10, 31, 1, 8, 8, - NULL, NULL, &tty_reset, - NULL, NULL, NULL, - &tty_dib, 0 - }; - -/* CLK data structures - - clk_dev CLK device descriptor - clk_unit CLK unit descriptor - clk_mod CLK modifiers - clk_reg CLK register list -*/ - -DIB clk_dib = { &clkio, CLK }; - -UNIT clk_unit = { UDATA (&clk_svc, UNIT_IDLE, 0) }; - -REG clk_reg[] = { - { ORDATA (SEL, clk_select, 3) }, - { DRDATA (CTR, clk_ctr, 14) }, - { FLDATA (CTL, clk.control, 0) }, - { FLDATA (FLG, clk.flag, 0) }, - { FLDATA (FBF, clk.flagbuf, 0) }, - { FLDATA (ERR, clk_error, CLK_V_ERROR) }, - { BRDATA (TIME, clk_time, 10, 24, 8) }, - { DRDATA (IPTICK, clk_tick, 24), PV_RSPC | REG_RO }, - { ORDATA (SC, clk_dib.select_code, 6), REG_HRO }, - { ORDATA (DEVNO, clk_dib.select_code, 6), REG_HRO }, - { NULL } - }; - -MTAB clk_mod[] = { - { UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAG", NULL }, - { UNIT_DIAG, 0, "calibrated", "CALIBRATED", NULL }, - { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &clk_dev }, - { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &clk_dev }, - { 0 } - }; - -DEVICE clk_dev = { - "CLK", &clk_unit, clk_reg, clk_mod, - 1, 0, 0, 0, 0, 0, - NULL, NULL, &clk_reset, - NULL, NULL, NULL, - &clk_dib, DEV_DISABLE, - 0, NULL, NULL, NULL - }; - - -/* Paper tape reader I/O signal handler. - - Implementation notes: - - 1. The 12597A duplex register card is used to interface the paper tape - reader to the computer. This card has a device command flip-flop, which - supplies the READ signal to the tape reader. Under simulation, this - state is implied by the activation of the PTR unit. - - 2. The POPIO signal clears the output buffer of the duplex card. However, - the buffer outputs are not used by the paper tape reader. Under - simulation, we omit the buffer clear. -*/ - -uint32 ptrio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - ptr.flag = ptr.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - ptr.flag = ptr.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (ptr); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (ptr); - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, ptr_unit.buf); /* merge in return status */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - ptr.flag = ptr.flagbuf = SET; /* set flag and flag buffer */ - break; - - - case ioCRS: /* control reset */ - case ioCLC: /* clear control flip-flop */ - ptr.control = CLEAR; - break; - - - case ioSTC: /* set control flip-flop */ - ptr.control = SET; - sim_activate (&ptr_unit, ptr_unit.wait); - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (ptr); /* set standard PRL signal */ - setstdIRQ (ptr); /* set standard IRQ signal */ - setstdSRQ (ptr); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - ptr.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Unit service */ - -t_stat ptr_svc (UNIT *uptr) -{ -int32 temp; - -if ((ptr_unit.flags & UNIT_ATT) == 0) /* attached? */ - return IOERROR (ptr_stopioe, SCPE_UNATT); -while ((temp = getc (ptr_unit.fileref)) == EOF) { /* read byte, error? */ - if (feof (ptr_unit.fileref)) { /* end of file? */ - if ((ptr_unit.flags & UNIT_DIAG) && (ptr_unit.pos > 0)) { - rewind (ptr_unit.fileref); /* rewind if loop mode */ - ptr_unit.pos = 0; - } - else { - if (ptr_trlcnt >= ptr_trllim) { /* added all trailer? */ - if (ptr_stopioe) { /* stop on error? */ - printf ("PTR end of file\n"); - return SCPE_IOERR; - } - else return SCPE_OK; /* no, just hang */ - } - ptr_trlcnt++; /* count trailer */ - temp = 0; /* read a zero */ - break; - } - } - else { /* no, real error */ - perror ("PTR I/O error"); - clearerr (ptr_unit.fileref); - return SCPE_IOERR; - } - } - -ptrio (&ptr_dib, ioENF, 0); /* set flag */ - -ptr_unit.buf = temp & 0377; /* put byte in buf */ -ptr_unit.pos = ftell (ptr_unit.fileref); - -if (temp) /* character non-null? */ - ptr_trlcnt = 0; /* clear trailing null counter */ - -return SCPE_OK; -} - - -/* Attach routine - clear the trailer counter */ - -t_stat ptr_attach (UNIT *uptr, char *cptr) -{ -ptr_trlcnt = 0; -return attach_unit (uptr, cptr); -} - - -/* Reset routine - called from SCP */ - -t_stat ptr_reset (DEVICE *dptr) -{ -IOPRESET (&ptr_dib); /* PRESET device (does not use PON) */ -sim_cancel (&ptr_unit); /* deactivate unit */ -return SCPE_OK; -} - - -/* Paper tape reader bootstrap routine (HP 12992K ROM) */ - -const BOOT_ROM ptr_rom = { - 0107700, /*ST CLC 0,C ; intr off */ - 0002401, /* CLA,RSS ; skip in */ - 0063756, /*CN LDA M11 ; feed frame */ - 0006700, /* CLB,CCE ; set E to rd byte */ - 0017742, /* JSB READ ; get #char */ - 0007306, /* CMB,CCE,INB,SZB ; 2's comp */ - 0027713, /* JMP *+5 ; non-zero byte */ - 0002006, /* INA,SZA ; feed frame ctr */ - 0027703, /* JMP *-3 */ - 0102077, /* HLT 77B ; stop */ - 0027700, /* JMP ST ; next */ - 0077754, /* STA WC ; word in rec */ - 0017742, /* JSB READ ; get feed frame */ - 0017742, /* JSB READ ; get address */ - 0074000, /* STB 0 ; init csum */ - 0077755, /* STB AD ; save addr */ - 0067755, /*CK LDB AD ; check addr */ - 0047777, /* ADB MAXAD ; below loader */ - 0002040, /* SEZ ; E =0 => OK */ - 0027740, /* JMP H55 */ - 0017742, /* JSB READ ; get word */ - 0040001, /* ADA 1 ; cont checksum */ - 0177755, /* STA AD,I ; store word */ - 0037755, /* ISZ AD */ - 0000040, /* CLE ; force wd read */ - 0037754, /* ISZ WC ; block done? */ - 0027720, /* JMP CK ; no */ - 0017742, /* JSB READ ; get checksum */ - 0054000, /* CPB 0 ; ok? */ - 0027702, /* JMP CN ; next block */ - 0102011, /* HLT 11 ; bad csum */ - 0027700, /* JMP ST ; next */ - 0102055, /*H55 HALT 55 ; bad address */ - 0027700, /* JMP ST ; next */ - 0000000, /*RD 0 */ - 0006600, /* CLB,CME ; E reg byte ptr */ - 0103710, /* STC RDR,C ; start reader */ - 0102310, /* SFS RDR ; wait */ - 0027745, /* JMP *-1 */ - 0106410, /* MIB RDR ; get byte */ - 0002041, /* SEZ,RSS ; E set? */ - 0127742, /* JMP RD,I ; no, done */ - 0005767, /* BLF,CLE,BLF ; shift byte */ - 0027744, /* JMP RD+2 ; again */ - 0000000, /*WC 000000 ; word count */ - 0000000, /*AD 000000 ; address */ - 0177765, /*M11 -11 ; feed count */ - 0, 0, 0, 0, 0, 0, 0, 0, /* unused */ - 0, 0, 0, 0, 0, 0, 0, /* unused */ - 0000000 /*MAXAD -ST ; max addr */ - }; - -t_stat ptr_boot (int32 unitno, DEVICE *dptr) -{ -const int32 dev = ptr_dib.select_code; /* get device no */ - -if (ibl_copy (ptr_rom, dev, IBL_OPT, /* copy the boot ROM to memory and configure */ - IBL_PTR | IBL_SET_SC (dev))) /* the S register accordingly */ - return SCPE_IERR; /* return an internal error if the copy failed */ -else - return SCPE_OK; -} - - -/* Paper tape punch I/O signal handler. - - Implementation notes: - - 1. The 12597A duplex register card is used to interface the paper tape - punch to the computer. This card has a device command flip-flop, which - supplies the PUNCH signal to the tape reader. Under simulation, this - state is implied by the activation of the PTP unit. -*/ - -uint32 ptpio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - ptp.flag = ptp.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - ptp.flag = ptp.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (ptp); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (ptp); - break; - - - case ioIOI: /* I/O data input */ - if ((ptp_unit.flags & UNIT_ATT) == 0) /* not attached? */ - stat_data = IORETURN (SCPE_OK, PTP_LOW); /* report as out of tape */ - else - stat_data = IORETURN (SCPE_OK, 0); - break; - - - case ioIOO: /* I/O data output */ - ptp_unit.buf = IODATA (stat_data); /* clear supplied status */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - ptp.flag = ptp.flagbuf = SET; /* set flag and flag buffer */ - ptp_unit.buf = 0; /* clear output buffer */ - break; - - - case ioCRS: /* control reset */ - case ioCLC: /* clear control flip-flop */ - ptp.control = CLEAR; - break; - - - case ioSTC: /* set control flip-flop */ - ptp.control = SET; - sim_activate (&ptp_unit, ptp_unit.wait); - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (ptp); /* set standard PRL signal */ - setstdIRQ (ptp); /* set standard IRQ signal */ - setstdSRQ (ptp); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - ptp.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* Unit service */ - -t_stat ptp_svc (UNIT *uptr) -{ -ptpio (&ptp_dib, ioENF, 0); /* set flag */ - -if ((ptp_unit.flags & UNIT_ATT) == 0) /* attached? */ - return IOERROR (ptp_stopioe, SCPE_UNATT); -if (putc (ptp_unit.buf, ptp_unit.fileref) == EOF) { /* output byte */ - perror ("PTP I/O error"); - clearerr (ptp_unit.fileref); - return SCPE_IOERR; - } -ptp_unit.pos = ftell (ptp_unit.fileref); /* update position */ -return SCPE_OK; -} - - -/* Reset routine */ - -t_stat ptp_reset (DEVICE *dptr) -{ -IOPRESET (&ptp_dib); /* PRESET device (does not use PON) */ -sim_cancel (&ptp_unit); /* deactivate unit */ -return SCPE_OK; -} - - -/* Terminal I/O signal handler */ - -uint32 ttyio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -uint16 data; -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - tty.flag = tty.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - tty.flag = tty.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (tty); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (tty); - break; - - - case ioIOI: /* I/O data input */ - data = (uint16) tty_buf; - - if (!(tty_mode & TM_KBD) && sim_is_active (&tty_unit[TTO])) - data = data | TP_BUSY; - - stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - data = IODATA (stat_data); /* clear supplied status */ - - if (data & TM_MODE) - tty_mode = data & (TM_KBD|TM_PRI|TM_PUN); - - tty_buf = data & 0377; - break; - - - case ioCRS: /* control reset */ - tty.control = CLEAR; /* clear control */ - tty.flag = tty.flagbuf = SET; /* set flag and flag buffer */ - tty_mode = TM_KBD; /* set tty, clear print/punch */ - tty_shin = 0377; /* input inactive */ - tty_lf = 0; /* no lf pending */ - break; - - - case ioCLC: /* clear control flip-flop */ - tty.control = CLEAR; - break; - - - case ioSTC: /* set control flip-flop */ - tty.control = SET; - - if (!(tty_mode & TM_KBD)) /* output? */ - sim_activate (&tty_unit[TTO], tty_unit[TTO].wait); - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (tty); /* set standard PRL signal */ - setstdIRQ (tty); /* set standard IRQ signal */ - setstdSRQ (tty); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - tty.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* TTY input service routine. - - The console input poll routine is scheduled with a ten millisecond period - using a calibrated timer, which is the source of event timing for all of the - keyboard polling routines. Synchronizing other keyboard polls with the - console poll ensures maximum idle time. - - Several HP operating systems require a CR and LF sequence for line - termination. This is awkward on a PC, as there is no LF key (CTRL+J is - needed instead). We provide an AUTOLF mode to add a LF automatically to each - CR input. When this mode is set, entering CR will set a flag, which will - cause a LF to be supplied automatically at the next input poll. - - The 12531C teleprinter interface and the later 12880A CRT interface provide a - clever mechanism to detect a keypress during output. This is used by DOS and - RTE to allow the user to interrupt lengthy output operations to enter system - commands. - - Referring to the 12531C schematic, the terminal input enters on pin X - ("DATA FROM EIA COMPATIBLE DEVICE"). The signal passes through four - transistor inversions (Q8, Q1, Q2, and Q3) to appear on pin 12 of NAND gate - U104C. If the flag flip-flop is not set, the terminal input passes to the - (inverted) output of U104C and thence to the D input of the first of the - flip-flops forming the data register. - - In the idle condition (no key pressed), the terminal input line is marking - (voltage negative), so in passing through a total of five inversions, a - logic one is presented at the serial input of the data register. During an - output operation, the register is parallel loaded and serially shifted, - sending the output data through the register to the device and -- this is - the crux -- filling the register with logic ones from U104C. - - At the end of the output operation, the card flag is set, an interrupt - occurs, and the RTE driver is entered. The driver then does an LIA SC to - read the contents of the data register. If no key has been pressed during - the output operation, the register will read as all ones (octal 377). If, - however, any key was struck, at least one zero bit will be present. If the - register value doesn't equal 377, the driver sets the system "operator - attention" flag, which will cause DOS or RTE to output an asterisk prompt and - initiate a terminal read when the current output line is completed. -*/ - -t_stat tti_svc (UNIT *uptr) -{ -int32 c; - -uptr->wait = sim_rtcn_calb (POLL_RATE, TMR_POLL); /* calibrate poll timer */ -sim_activate (uptr, uptr->wait); /* continue poll */ - -tty_shin = 0377; /* assume inactive */ -if (tty_lf) { /* auto lf pending? */ - c = 012; /* force lf */ - tty_lf = 0; - } -else { - if ((c = sim_poll_kbd ()) < SCPE_KFLAG) return c; /* no char or error? */ - if (c & SCPE_BREAK) c = 0; /* break? */ - else c = sim_tt_inpcvt (c, TT_GET_MODE (uptr->flags)); - tty_lf = ((c & 0177) == 015) && (uptr->flags & UNIT_AUTOLF); - } -if (tty_mode & TM_KBD) { /* keyboard enabled? */ - tty_buf = c; /* put char in buf */ - uptr->pos = uptr->pos + 1; - - ttyio (&tty_dib, ioENF, 0); /* set flag */ - - if (c) { - tto_out (c); /* echo? */ - return ttp_out (c); /* punch? */ - } - } -else tty_shin = c; /* no, char shifts in */ -return SCPE_OK; -} - - -/* TTY output service routine */ - -t_stat tto_svc (UNIT *uptr) -{ -int32 c; -t_stat r; - -c = tty_buf; /* get char */ -tty_buf = tty_shin; /* shift in */ -tty_shin = 0377; /* line inactive */ -if ((r = tto_out (c)) != SCPE_OK) { /* output; error? */ - sim_activate (uptr, uptr->wait); /* retry */ - return ((r == SCPE_STALL)? SCPE_OK: r); /* !stall? report */ - } - -ttyio (&tty_dib, ioENF, 0); /* set flag */ - -return ttp_out (c); /* punch if enabled */ -} - - -t_stat tto_out (int32 c) -{ -t_stat r; - -if (tty_mode & TM_PRI) { /* printing? */ - c = sim_tt_outcvt (c, TT_GET_MODE (tty_unit[TTO].flags)); - if (c >= 0) { /* valid? */ - r = sim_putchar_s (c); /* output char */ - if (r != SCPE_OK) - return r; - tty_unit[TTO].pos = tty_unit[TTO].pos + 1; - } - } -return SCPE_OK; -} - - -t_stat ttp_out (int32 c) -{ -if (tty_mode & TM_PUN) { /* punching? */ - if ((tty_unit[TTP].flags & UNIT_ATT) == 0) /* attached? */ - return IOERROR (ttp_stopioe, SCPE_UNATT); - if (putc (c, tty_unit[TTP].fileref) == EOF) { /* output char */ - perror ("TTP I/O error"); - clearerr (tty_unit[TTP].fileref); - return SCPE_IOERR; - } - tty_unit[TTP].pos = ftell (tty_unit[TTP].fileref); - } -return SCPE_OK; -} - - -/* TTY reset routine */ - -t_stat tty_reset (DEVICE *dptr) -{ -if (sim_switches & SWMASK ('P')) /* initialization reset? */ - tty_buf = 0; /* clear buffer */ - -IOPRESET (&tty_dib); /* PRESET device (does not use PON) */ - -tty_unit[TTI].wait = POLL_WAIT; /* reset initial poll */ -sim_rtcn_init (tty_unit[TTI].wait, TMR_POLL); /* init poll timer */ -sim_activate (&tty_unit[TTI], tty_unit[TTI].wait); /* activate poll */ -sim_cancel (&tty_unit[TTO]); /* cancel output */ -return SCPE_OK; -} - - -t_stat tty_set_opt (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -int32 u = uptr - tty_unit; - -if (u > TTO) return SCPE_NOFNC; -if ((u == TTI) && (val == TT_MODE_7P)) - val = TT_MODE_7B; -tty_unit[u].flags = (tty_unit[u].flags & ~TT_MODE) | val; -return SCPE_OK; -} - - -t_stat tty_set_alf (UNIT *uptr, int32 val, char *cptr, void *desc) -{ -int32 u = uptr - tty_unit; - -if (u != TTI) return SCPE_NOFNC; -return SCPE_OK; -} - - -/* Synchronize polling. - - Return an event time corresponding either with the amount of time remaining - in the current poll (mode = INITIAL) or the amount of time in a full poll - period (mode = SERVICE). If the former call is made when the device service - routine is started, then making the latter call during unit service will - ensure that the polls remain synchronized. - */ - -int32 sync_poll (POLLMODE poll_mode) -{ -int32 poll_time; - - if (poll_mode == INITIAL) { - poll_time = sim_activate_time (&tty_unit[TTI]); - - if (poll_time) - return poll_time; - else - return POLL_WAIT; - } - else - return tty_unit[TTI].wait; -} - - -/* Clock I/O signal handler. - - The time base generator (CLK) provides periodic interrupts from 100 - microseconds to 1000 seconds. The CLK uses a calibrated timer to provide the - time base. For periods ranging from 1 to 1000 seconds, a 100 millisecond - timer is used, and 10 to 10000 ticks are counted before setting the device - flag to indicate that the period has expired. - - If the period is set to ten milliseconds, the console poll timer is used - instead of an independent timer. This is to maximize the idle period. - - In diagnostic mode, the clock period is set to the expected number of CPU - instructions, rather than wall-clock time, so that the diagnostic executes as - expected. -*/ - -uint32 clkio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) -{ -IOSIGNAL signal; -IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ - -while (working_set) { - signal = IONEXT (working_set); /* isolate next signal */ - - switch (signal) { /* dispatch I/O signal */ - - case ioCLF: /* clear flag flip-flop */ - clk.flag = clk.flagbuf = CLEAR; - break; - - - case ioSTF: /* set flag flip-flop */ - case ioENF: /* enable flag */ - clk.flag = clk.flagbuf = SET; - break; - - - case ioSFC: /* skip if flag is clear */ - setstdSKF (clk); - break; - - - case ioSFS: /* skip if flag is set */ - setstdSKF (clk); - break; - - - case ioIOI: /* I/O data input */ - stat_data = IORETURN (SCPE_OK, clk_error); /* merge in return status */ - break; - - - case ioIOO: /* I/O data output */ - clk_select = IODATA (stat_data) & 07; /* save select */ - sim_cancel (&clk_unit); /* stop the clock */ - clk.control = CLEAR; /* clear control */ - working_set = working_set | ioSIR; /* set interrupt request (IOO normally doesn't) */ - break; - - - case ioPOPIO: /* power-on preset to I/O */ - clk.flag = clk.flagbuf = SET; /* set flag and flag buffer */ - break; - - - case ioCRS: /* control reset */ - case ioCLC: /* clear control flip-flop */ - clk.control = CLEAR; - sim_cancel (&clk_unit); /* deactivate unit */ - break; - - - case ioSTC: /* set control flip-flop */ - clk.control = SET; - if (clk_unit.flags & UNIT_DIAG) /* diag mode? */ - clk_unit.flags = clk_unit.flags & ~UNIT_IDLE; /* not calibrated */ - else - clk_unit.flags = clk_unit.flags | UNIT_IDLE; /* is calibrated */ - - if (!sim_is_active (&clk_unit)) { /* clock running? */ - clk_tick = clk_delay (0); /* get tick count */ - - if ((clk_unit.flags & UNIT_DIAG) == 0) /* calibrated? */ - if (clk_select == 2) /* 10 msec. interval? */ - clk_tick = sync_poll (INITIAL); /* sync poll */ - else - sim_rtcn_init (clk_tick, TMR_CLK); /* initialize timer */ - - sim_activate (&clk_unit, clk_tick); /* start clock */ - clk_ctr = clk_delay (1); /* set repeat ctr */ - } - clk_error = 0; /* clear error */ - break; - - - case ioSIR: /* set interrupt request */ - setstdPRL (clk); /* set standard PRL signal */ - setstdIRQ (clk); /* set standard IRQ signal */ - setstdSRQ (clk); /* set standard SRQ signal */ - break; - - - case ioIAK: /* interrupt acknowledge */ - clk.flagbuf = CLEAR; - break; - - - default: /* all other signals */ - break; /* are ignored */ - } - - working_set = working_set & ~signal; /* remove current signal from set */ - } - -return stat_data; -} - - -/* CLK unit service. - - As with the I/O handler, if the time base period is set to ten milliseconds, - the console poll timer is used instead of an independent timer. -*/ - -t_stat clk_svc (UNIT *uptr) -{ -if (!clk.control) /* control clear? */ - return SCPE_OK; /* done */ - -if (clk_unit.flags & UNIT_DIAG) /* diag mode? */ - clk_tick = clk_delay (0); /* get fixed delay */ -else if (clk_select == 2) /* 10 msec period? */ - clk_tick = sync_poll (SERVICE); /* sync poll */ -else - clk_tick = sim_rtcn_calb (clk_tps[clk_select], TMR_CLK); /* calibrate delay */ - -sim_activate (uptr, clk_tick); /* reactivate */ -clk_ctr = clk_ctr - 1; /* decrement counter */ -if (clk_ctr <= 0) { /* end of interval? */ - if (clk.flag) - clk_error = CLK_ERROR; /* overrun? error */ - else - clkio (&clk_dib, ioENF, 0); /* set flag */ - clk_ctr = clk_delay (1); /* reset counter */ - } -return SCPE_OK; -} - - -/* Reset routine */ - -t_stat clk_reset (DEVICE *dptr) -{ -if (sim_switches & SWMASK ('P')) { /* initialization reset? */ - clk_error = 0; /* clear error */ - clk_select = 0; /* clear select */ - clk_ctr = 0; /* clear counter */ - - if (clk_dev.lname == NULL) /* logical name unassigned? */ - clk_dev.lname = strdup ("TBG"); /* allocate and initialize the name */ - } - -IOPRESET (&clk_dib); /* PRESET device (does not use PON) */ - -return SCPE_OK; -} - - -/* Clock delay routine */ - -int32 clk_delay (int32 flg) -{ -int32 sel = clk_select; - -if ((clk_unit.flags & UNIT_DIAG) && (sel >= 4)) sel = sel - 3; -if (flg) return clk_rpt[sel]; -else return clk_time[sel]; -} +/* hp2100_stddev.c: HP2100 standard devices simulator + + Copyright (c) 1993-2016, Robert M. Supnik + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + PTR 12597A-002 paper tape reader interface + PTP 12597A-005 paper tape punch interface + TTY 12531C buffered teleprinter interface + TBG 12539C time base generator + + 30-Dec-16 JDB Modified the TTY to print if the punch is not attached + 13-May-16 JDB Modified for revised SCP API function parameter types + 30-Dec-14 JDB Added S-register parameters to ibl_copy + 24-Dec-14 JDB Added casts for explicit downward conversions + 28-Dec-12 JDB Allocate the TBG logical name during power-on reset + 18-Dec-12 MP Now calls sim_activate_time to get remaining poll time + 09-May-12 JDB Separated assignments from conditional expressions + 12-Feb-12 JDB Add TBG as a logical name for the CLK device + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + 28-Mar-11 JDB Tidied up signal handling + 26-Oct-10 JDB Changed I/O signal handler for revised signal model + 26-Jun-08 JDB Rewrote device I/O to model backplane signals + 25-Apr-08 JDB Changed TTY output wait from 100 to 200 for MSU BASIC + 18-Apr-08 JDB Removed redundant control char handling definitions + 14-Apr-08 JDB Changed TTY console poll to 10 msec. real time + Synchronized CLK with TTY if set for 10 msec. + Added UNIT_IDLE to TTY and CLK + 09-Jan-08 JDB Fixed PTR trailing null counter for tape re-read + 31-Dec-07 JDB Added IPTICK register to CLK to display CPU instr/tick + Corrected and verified ioCRS actions + 28-Dec-06 JDB Added ioCRS state to I/O decoders + 22-Nov-05 RMS Revised for new terminal processing routines + 13-Sep-04 JDB Added paper tape loop mode, DIAG/READER modifiers to PTR + Added PV_LEFT to PTR TRLLIM register + Modified CLK to permit disable + 15-Aug-04 RMS Added tab to control char set (from Dave Bryan) + 14-Jul-04 RMS Generalized handling of control char echoing + (from Dave Bryan) + 26-Apr-04 RMS Fixed SFS x,C and SFC x,C + Fixed SR setting in IBL + Fixed input behavior during typeout for RTE-IV + Suppressed nulls on TTY output for RTE-IV + Implemented DMA SRQ (follows FLG) + 29-Mar-03 RMS Added support for console backpressure + 25-Apr-03 RMS Added extended file support + 22-Dec-02 RMS Added break support + 01-Nov-02 RMS Revised BOOT command for IBL ROMs + Fixed bug in TTY reset, TTY starts in input mode + Fixed bug in TTY mode OTA, stores data as well + Fixed clock to add calibration, proper start/stop + Added UC option to TTY output + 30-May-02 RMS Widened POS to 32b + 22-Mar-02 RMS Revised for dynamically allocated memory + 03-Nov-01 RMS Changed DEVNO to use extended SET/SHOW + 29-Nov-01 RMS Added read only unit support + 24-Nov-01 RMS Changed TIME to an array + 07-Sep-01 RMS Moved function prototypes + 21-Nov-00 RMS Fixed flag, buffer power up state + Added status input for ptp, tty + 15-Oct-00 RMS Added dynamic device number support + + References: + - 2748B Tape Reader Operating and Service Manual (02748-90041, Oct-1977) + - 12597A 8-Bit Duplex Register Interface Kit Operating and Service Manual + (12597-9002, Sep-1974) + - 12531C Buffered Teleprinter Interface Kit Operating and Service Manual + (12531-90033, Nov-1972) + - 12539C Time Base Generator Interface Kit Operating and Service Manual + (12539-90008, Jan-1975) + + + The reader and punch, like most HP devices, have a command flop. The + teleprinter and clock do not. + + Reader diagnostic mode simulates a tape loop by rewinding the tape image file + upon EOF. Normal mode EOF action is to supply TRLLIM nulls and then either + return SCPE_IOERR or SCPE_OK without setting the device flag. + + To support CPU idling, the teleprinter interface (which doubles as the + simulator console) polls for input using a calibrated timer with a ten + millisecond period. Other polled-keyboard input devices (multiplexers and + the BACI card) synchronize with the console poll to ensure maximum available + idle time. The console poll is guaranteed to run, as the TTY device cannot + be disabled. + + The clock (time base generator) autocalibrates. If the TBG is set to a ten + millisecond period (e.g., as under RTE), it is synchronized to the console + poll. Otherwise (e.g., as under DOS or TSB, which use 100 millisecond + periods), it runs asynchronously. If the specified clock frequency is below + 10Hz, the clock service routine runs at 10Hz and counts down a repeat counter + before generating an interrupt. Autocalibration will not work if the clock + is running at 1Hz or less. + + Clock diagnostic mode corresponds to inserting jumper W2 on the 12539C. + This turns off autocalibration and divides the longest time intervals down + by 10**3. The clk_time values were chosen to allow the diagnostic to + pass its clock calibration test. +*/ + + +#include "hp2100_defs.h" + +#define TTY_OUT_WAIT 200 /* TTY output wait */ + +#define UNIT_V_DIAG (TTUF_V_UF + 0) /* diag mode */ +#define UNIT_V_AUTOLF (TTUF_V_UF + 1) /* auto linefeed */ +#define UNIT_DIAG (1 << UNIT_V_DIAG) +#define UNIT_AUTOLF (1 << UNIT_V_AUTOLF) + +#define PTP_LOW 0000040 /* low tape */ +#define TM_MODE 0100000 /* mode change */ +#define TM_KBD 0040000 /* enable keyboard */ +#define TM_PRI 0020000 /* enable printer */ +#define TM_PUN 0010000 /* enable punch */ +#define TP_BUSY 0100000 /* busy */ + +#define CLK_V_ERROR 4 /* clock overrun */ +#define CLK_ERROR (1 << CLK_V_ERROR) + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } ptr = { CLEAR, CLEAR, CLEAR }; + +int32 ptr_stopioe = 0; /* stop on error */ +int32 ptr_trlcnt = 0; /* trailer counter */ +int32 ptr_trllim = 40; /* trailer to add */ + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } ptp = { CLEAR, CLEAR, CLEAR }; + +int32 ptp_stopioe = 0; + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } tty = { CLEAR, CLEAR, CLEAR }; + +int32 ttp_stopioe = 0; +int32 tty_buf = 0; /* tty buffer */ +int32 tty_mode = 0; /* tty mode */ +int32 tty_shin = 0377; /* tty shift in */ +int32 tty_lf = 0; /* lf flag */ + +struct { + FLIP_FLOP control; /* control flip-flop */ + FLIP_FLOP flag; /* flag flip-flop */ + FLIP_FLOP flagbuf; /* flag buffer flip-flop */ + } clk = { CLEAR, CLEAR, CLEAR }; + +int32 clk_select = 0; /* clock time select */ +int32 clk_error = 0; /* clock error */ +int32 clk_ctr = 0; /* clock counter */ +int32 clk_time[8] = { /* clock intervals */ + 155, 1550, 15500, 155000, 155000, 155000, 155000, 155000 + }; +int32 clk_tps[8] = { /* clock tps */ + 10000, 1000, 100, 10, 10, 10, 10, 10 + }; +int32 clk_rpt[8] = { /* number of repeats */ + 1, 1, 1, 1, 10, 100, 1000, 10000 + }; +uint32 clk_tick = 0; /* instructions per tick */ + +DEVICE ptr_dev, ptp_dev, tty_dev, clk_dev; + +IOHANDLER ptrio; +t_stat ptr_svc (UNIT *uptr); +t_stat ptr_attach (UNIT *uptr, CONST char *cptr); +t_stat ptr_reset (DEVICE *dptr); +t_stat ptr_boot (int32 unitno, DEVICE *dptr); + +IOHANDLER ptpio; +t_stat ptp_svc (UNIT *uptr); +t_stat ptp_reset (DEVICE *dptr); + +IOHANDLER ttyio; +t_stat tti_svc (UNIT *uptr); +t_stat tto_svc (UNIT *uptr); +t_stat tty_reset (DEVICE *dptr); +t_stat tty_set_opt (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat tty_set_alf (UNIT *uptr, int32 val, CONST char *cptr, void *desc); +t_stat tto_out (int32 c); + +IOHANDLER clkio; +t_stat clk_svc (UNIT *uptr); +t_stat clk_reset (DEVICE *dptr); +int32 clk_delay (int32 flg); + +/* PTR data structures + + ptr_dev PTR device descriptor + ptr_unit PTR unit descriptor + ptr_mod PTR modifiers + ptr_reg PTR register list +*/ + +DIB ptr_dib = { &ptrio, PTR }; + +UNIT ptr_unit = { + UDATA (&ptr_svc, UNIT_SEQ+UNIT_ATTABLE+UNIT_ROABLE, 0), + SERIAL_IN_WAIT + }; + +REG ptr_reg[] = { + { ORDATA (BUF, ptr_unit.buf, 8) }, + { FLDATA (CTL, ptr.control, 0) }, + { FLDATA (FLG, ptr.flag, 0) }, + { FLDATA (FBF, ptr.flagbuf, 0) }, + { DRDATA (TRLCTR, ptr_trlcnt, 8), REG_HRO }, + { DRDATA (TRLLIM, ptr_trllim, 8), PV_LEFT }, + { DRDATA (POS, ptr_unit.pos, T_ADDR_W), PV_LEFT }, + { DRDATA (TIME, ptr_unit.wait, 24), PV_LEFT }, + { FLDATA (STOP_IOE, ptr_stopioe, 0) }, + { ORDATA (SC, ptr_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, ptr_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB ptr_mod[] = { + { UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAG", NULL }, + { UNIT_DIAG, 0, "reader mode", "READER", NULL }, + { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &ptr_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &ptr_dev }, + { 0 } + }; + +DEVICE ptr_dev = { + "PTR", &ptr_unit, ptr_reg, ptr_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &ptr_reset, + &ptr_boot, &ptr_attach, NULL, + &ptr_dib, DEV_DISABLE + }; + +/* PTP data structures + + ptp_dev PTP device descriptor + ptp_unit PTP unit descriptor + ptp_mod PTP modifiers + ptp_reg PTP register list +*/ + +DIB ptp_dib = { &ptpio, PTP }; + +UNIT ptp_unit = { + UDATA (&ptp_svc, UNIT_SEQ+UNIT_ATTABLE, 0), SERIAL_OUT_WAIT + }; + +REG ptp_reg[] = { + { ORDATA (BUF, ptp_unit.buf, 8) }, + { FLDATA (CTL, ptp.control, 0) }, + { FLDATA (FLG, ptp.flag, 0) }, + { FLDATA (FBF, ptp.flagbuf, 0) }, + { DRDATA (POS, ptp_unit.pos, T_ADDR_W), PV_LEFT }, + { DRDATA (TIME, ptp_unit.wait, 24), PV_LEFT }, + { FLDATA (STOP_IOE, ptp_stopioe, 0) }, + { ORDATA (SC, ptp_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, ptp_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB ptp_mod[] = { + { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &ptp_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &ptp_dev }, + { 0 } + }; + +DEVICE ptp_dev = { + "PTP", &ptp_unit, ptp_reg, ptp_mod, + 1, 10, 31, 1, 8, 8, + NULL, NULL, &ptp_reset, + NULL, NULL, NULL, + &ptp_dib, DEV_DISABLE + }; + +/* TTY data structures + + tty_dev TTY device descriptor + tty_unit TTY unit descriptor + tty_reg TTY register list + tty_mod TTy modifiers list +*/ + +#define TTI 0 +#define TTO 1 +#define TTP 2 + +DIB tty_dib = { &ttyio, TTY }; + +UNIT tty_unit[] = { + { UDATA (&tti_svc, UNIT_IDLE | TT_MODE_UC, 0), POLL_WAIT }, + { UDATA (&tto_svc, TT_MODE_UC, 0), TTY_OUT_WAIT }, + { UDATA (&tto_svc, UNIT_SEQ | UNIT_ATTABLE | TT_MODE_8B, 0), SERIAL_OUT_WAIT } + }; + +REG tty_reg[] = { + { ORDATA (BUF, tty_buf, 8) }, + { ORDATA (MODE, tty_mode, 16) }, + { ORDATA (SHIN, tty_shin, 8), REG_HRO }, + { FLDATA (CTL, tty.control, 0) }, + { FLDATA (FLG, tty.flag, 0) }, + { FLDATA (FBF, tty.flagbuf, 0) }, + { FLDATA (KLFP, tty_lf, 0), REG_HRO }, + { DRDATA (KPOS, tty_unit[TTI].pos, T_ADDR_W), PV_LEFT }, + { DRDATA (KTIME, tty_unit[TTI].wait, 24), REG_NZ + PV_LEFT }, + { DRDATA (TPOS, tty_unit[TTO].pos, T_ADDR_W), PV_LEFT }, + { DRDATA (TTIME, tty_unit[TTO].wait, 24), REG_NZ + PV_LEFT }, + { DRDATA (PPOS, tty_unit[TTP].pos, T_ADDR_W), PV_LEFT }, + { FLDATA (STOP_IOE, ttp_stopioe, 0) }, + { ORDATA (SC, tty_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, tty_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB tty_mod[] = { + { TT_MODE, TT_MODE_UC, "UC", "UC", &tty_set_opt }, + { TT_MODE, TT_MODE_7B, "7b", "7B", &tty_set_opt }, + { TT_MODE, TT_MODE_8B, "8b", "8B", &tty_set_opt }, + { TT_MODE, TT_MODE_7P, "7p", "7P", &tty_set_opt }, + { UNIT_AUTOLF, UNIT_AUTOLF, "autolf", "AUTOLF", &tty_set_alf }, + { UNIT_AUTOLF, 0 , NULL, "NOAUTOLF", &tty_set_alf }, + { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &tty_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &tty_dev }, + { 0 } + }; + +DEVICE tty_dev = { + "TTY", tty_unit, tty_reg, tty_mod, + 3, 10, 31, 1, 8, 8, + NULL, NULL, &tty_reset, + NULL, NULL, NULL, + &tty_dib, 0 + }; + +/* CLK data structures + + clk_dev CLK device descriptor + clk_unit CLK unit descriptor + clk_mod CLK modifiers + clk_reg CLK register list +*/ + +DIB clk_dib = { &clkio, CLK }; + +UNIT clk_unit = { UDATA (&clk_svc, UNIT_IDLE, 0) }; + +REG clk_reg[] = { + { ORDATA (SEL, clk_select, 3) }, + { DRDATA (CTR, clk_ctr, 14) }, + { FLDATA (CTL, clk.control, 0) }, + { FLDATA (FLG, clk.flag, 0) }, + { FLDATA (FBF, clk.flagbuf, 0) }, + { FLDATA (ERR, clk_error, CLK_V_ERROR) }, + { BRDATA (TIME, clk_time, 10, 24, 8) }, + { DRDATA (IPTICK, clk_tick, 24), PV_RSPC | REG_RO }, + { ORDATA (SC, clk_dib.select_code, 6), REG_HRO }, + { ORDATA (DEVNO, clk_dib.select_code, 6), REG_HRO }, + { NULL } + }; + +MTAB clk_mod[] = { + { UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAG", NULL }, + { UNIT_DIAG, 0, "calibrated", "CALIBRATED", NULL }, + { MTAB_XTD | MTAB_VDV, 0, "SC", "SC", &hp_setsc, &hp_showsc, &clk_dev }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "DEVNO", "DEVNO", &hp_setdev, &hp_showdev, &clk_dev }, + { 0 } + }; + +DEVICE clk_dev = { + "CLK", &clk_unit, clk_reg, clk_mod, + 1, 0, 0, 0, 0, 0, + NULL, NULL, &clk_reset, + NULL, NULL, NULL, + &clk_dib, DEV_DISABLE, + 0, NULL, NULL, NULL + }; + + +/* Paper tape reader I/O signal handler. + + Implementation notes: + + 1. The 12597A duplex register card is used to interface the paper tape + reader to the computer. This card has a device command flip-flop, which + supplies the READ signal to the tape reader. Under simulation, this + state is implied by the activation of the PTR unit. + + 2. The POPIO signal clears the output buffer of the duplex card. However, + the buffer outputs are not used by the paper tape reader. Under + simulation, we omit the buffer clear. +*/ + +uint32 ptrio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + ptr.flag = ptr.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + ptr.flag = ptr.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (ptr); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (ptr); + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, ptr_unit.buf); /* merge in return status */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + ptr.flag = ptr.flagbuf = SET; /* set flag and flag buffer */ + break; + + + case ioCRS: /* control reset */ + case ioCLC: /* clear control flip-flop */ + ptr.control = CLEAR; + break; + + + case ioSTC: /* set control flip-flop */ + ptr.control = SET; + sim_activate (&ptr_unit, ptr_unit.wait); + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (ptr); /* set standard PRL signal */ + setstdIRQ (ptr); /* set standard IRQ signal */ + setstdSRQ (ptr); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + ptr.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Unit service */ + +t_stat ptr_svc (UNIT *uptr) +{ +int32 temp; + +if ((ptr_unit.flags & UNIT_ATT) == 0) /* attached? */ + return IOERROR (ptr_stopioe, SCPE_UNATT); +while ((temp = getc (ptr_unit.fileref)) == EOF) { /* read byte, error? */ + if (feof (ptr_unit.fileref)) { /* end of file? */ + if ((ptr_unit.flags & UNIT_DIAG) && (ptr_unit.pos > 0)) { + rewind (ptr_unit.fileref); /* rewind if loop mode */ + ptr_unit.pos = 0; + } + else { + if (ptr_trlcnt >= ptr_trllim) { /* added all trailer? */ + if (ptr_stopioe) { /* stop on error? */ + printf ("PTR end of file\n"); + return SCPE_IOERR; + } + else return SCPE_OK; /* no, just hang */ + } + ptr_trlcnt++; /* count trailer */ + temp = 0; /* read a zero */ + break; + } + } + else { /* no, real error */ + perror ("PTR I/O error"); + clearerr (ptr_unit.fileref); + return SCPE_IOERR; + } + } + +ptrio (&ptr_dib, ioENF, 0); /* set flag */ + +ptr_unit.buf = temp & 0377; /* put byte in buf */ +ptr_unit.pos = ftell (ptr_unit.fileref); + +if (temp) /* character non-null? */ + ptr_trlcnt = 0; /* clear trailing null counter */ + +return SCPE_OK; +} + + +/* Attach routine - clear the trailer counter */ + +t_stat ptr_attach (UNIT *uptr, CONST char *cptr) +{ +ptr_trlcnt = 0; +return attach_unit (uptr, cptr); +} + + +/* Reset routine - called from SCP */ + +t_stat ptr_reset (DEVICE *dptr) +{ +IOPRESET (&ptr_dib); /* PRESET device (does not use PON) */ +sim_cancel (&ptr_unit); /* deactivate unit */ +return SCPE_OK; +} + + +/* Paper tape reader bootstrap routine (HP 12992K ROM) */ + +const BOOT_ROM ptr_rom = { + 0107700, /*ST CLC 0,C ; intr off */ + 0002401, /* CLA,RSS ; skip in */ + 0063756, /*CN LDA M11 ; feed frame */ + 0006700, /* CLB,CCE ; set E to rd byte */ + 0017742, /* JSB READ ; get #char */ + 0007306, /* CMB,CCE,INB,SZB ; 2's comp */ + 0027713, /* JMP *+5 ; non-zero byte */ + 0002006, /* INA,SZA ; feed frame ctr */ + 0027703, /* JMP *-3 */ + 0102077, /* HLT 77B ; stop */ + 0027700, /* JMP ST ; next */ + 0077754, /* STA WC ; word in rec */ + 0017742, /* JSB READ ; get feed frame */ + 0017742, /* JSB READ ; get address */ + 0074000, /* STB 0 ; init csum */ + 0077755, /* STB AD ; save addr */ + 0067755, /*CK LDB AD ; check addr */ + 0047777, /* ADB MAXAD ; below loader */ + 0002040, /* SEZ ; E =0 => OK */ + 0027740, /* JMP H55 */ + 0017742, /* JSB READ ; get word */ + 0040001, /* ADA 1 ; cont checksum */ + 0177755, /* STA AD,I ; store word */ + 0037755, /* ISZ AD */ + 0000040, /* CLE ; force wd read */ + 0037754, /* ISZ WC ; block done? */ + 0027720, /* JMP CK ; no */ + 0017742, /* JSB READ ; get checksum */ + 0054000, /* CPB 0 ; ok? */ + 0027702, /* JMP CN ; next block */ + 0102011, /* HLT 11 ; bad csum */ + 0027700, /* JMP ST ; next */ + 0102055, /*H55 HALT 55 ; bad address */ + 0027700, /* JMP ST ; next */ + 0000000, /*RD 0 */ + 0006600, /* CLB,CME ; E reg byte ptr */ + 0103710, /* STC RDR,C ; start reader */ + 0102310, /* SFS RDR ; wait */ + 0027745, /* JMP *-1 */ + 0106410, /* MIB RDR ; get byte */ + 0002041, /* SEZ,RSS ; E set? */ + 0127742, /* JMP RD,I ; no, done */ + 0005767, /* BLF,CLE,BLF ; shift byte */ + 0027744, /* JMP RD+2 ; again */ + 0000000, /*WC 000000 ; word count */ + 0000000, /*AD 000000 ; address */ + 0177765, /*M11 -11 ; feed count */ + 0, 0, 0, 0, 0, 0, 0, 0, /* unused */ + 0, 0, 0, 0, 0, 0, 0, /* unused */ + 0000000 /*MAXAD -ST ; max addr */ + }; + +t_stat ptr_boot (int32 unitno, DEVICE *dptr) +{ +const int32 dev = ptr_dib.select_code; /* get device no */ + +if (ibl_copy (ptr_rom, dev, IBL_OPT, /* copy the boot ROM to memory and configure */ + IBL_PTR | IBL_SET_SC (dev))) /* the S register accordingly */ + return SCPE_IERR; /* return an internal error if the copy failed */ +else + return SCPE_OK; +} + + +/* Paper tape punch I/O signal handler. + + Implementation notes: + + 1. The 12597A duplex register card is used to interface the paper tape + punch to the computer. This card has a device command flip-flop, which + supplies the PUNCH signal to the tape reader. Under simulation, this + state is implied by the activation of the PTP unit. +*/ + +uint32 ptpio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + ptp.flag = ptp.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + ptp.flag = ptp.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (ptp); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (ptp); + break; + + + case ioIOI: /* I/O data input */ + if ((ptp_unit.flags & UNIT_ATT) == 0) /* not attached? */ + stat_data = IORETURN (SCPE_OK, PTP_LOW); /* report as out of tape */ + else + stat_data = IORETURN (SCPE_OK, 0); + break; + + + case ioIOO: /* I/O data output */ + ptp_unit.buf = IODATA (stat_data); /* clear supplied status */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + ptp.flag = ptp.flagbuf = SET; /* set flag and flag buffer */ + ptp_unit.buf = 0; /* clear output buffer */ + break; + + + case ioCRS: /* control reset */ + case ioCLC: /* clear control flip-flop */ + ptp.control = CLEAR; + break; + + + case ioSTC: /* set control flip-flop */ + ptp.control = SET; + sim_activate (&ptp_unit, ptp_unit.wait); + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (ptp); /* set standard PRL signal */ + setstdIRQ (ptp); /* set standard IRQ signal */ + setstdSRQ (ptp); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + ptp.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* Unit service */ + +t_stat ptp_svc (UNIT *uptr) +{ +ptpio (&ptp_dib, ioENF, 0); /* set flag */ + +if ((ptp_unit.flags & UNIT_ATT) == 0) /* attached? */ + return IOERROR (ptp_stopioe, SCPE_UNATT); +if (putc (ptp_unit.buf, ptp_unit.fileref) == EOF) { /* output byte */ + perror ("PTP I/O error"); + clearerr (ptp_unit.fileref); + return SCPE_IOERR; + } +ptp_unit.pos = ftell (ptp_unit.fileref); /* update position */ +return SCPE_OK; +} + + +/* Reset routine */ + +t_stat ptp_reset (DEVICE *dptr) +{ +IOPRESET (&ptp_dib); /* PRESET device (does not use PON) */ +sim_cancel (&ptp_unit); /* deactivate unit */ +return SCPE_OK; +} + + +/* Terminal I/O signal handler. + + Output Word Format: + + 15 |14 13 12 |11 10 9 | 8 7 6 | 5 4 3 | 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 1 | I | P | N | - - - - - - - - - - - - | control + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | - - - - - - - | output character | data + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + I = set the interface to output/input mode (0/1) + P = enable the printer for output + N = enable the punch for output + + + Input Word Format: + + 15 |14 13 12 |11 10 9 | 8 7 6 | 5 4 3 | 2 1 0 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | B | - - - - - - - | input character | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + B = interface is idle/busy (0/1) + +*/ + +uint32 ttyio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +uint16 data; +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + tty.flag = tty.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + tty.flag = tty.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (tty); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (tty); + break; + + + case ioIOI: /* I/O data input */ + data = (uint16) tty_buf; + + if (!(tty_mode & TM_KBD) && sim_is_active (&tty_unit[TTO])) + data = data | TP_BUSY; + + stat_data = IORETURN (SCPE_OK, data); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + data = IODATA (stat_data); /* clear supplied status */ + + if (data & TM_MODE) + tty_mode = data & (TM_KBD|TM_PRI|TM_PUN); + + tty_buf = data & 0377; + break; + + + case ioCRS: /* control reset */ + tty.control = CLEAR; /* clear control */ + tty.flag = tty.flagbuf = SET; /* set flag and flag buffer */ + tty_mode = TM_KBD; /* set tty, clear print/punch */ + tty_shin = 0377; /* input inactive */ + tty_lf = 0; /* no lf pending */ + break; + + + case ioCLC: /* clear control flip-flop */ + tty.control = CLEAR; + break; + + + case ioSTC: /* set control flip-flop */ + tty.control = SET; + + if (!(tty_mode & TM_KBD)) /* output? */ + sim_activate (&tty_unit[TTO], tty_unit[TTO].wait); + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (tty); /* set standard PRL signal */ + setstdIRQ (tty); /* set standard IRQ signal */ + setstdSRQ (tty); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + tty.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* TTY input service routine. + + The console input poll routine is scheduled with a ten millisecond period + using a calibrated timer, which is the source of event timing for all of the + keyboard polling routines. Synchronizing other keyboard polls with the + console poll ensures maximum idle time. + + Several HP operating systems require a CR and LF sequence for line + termination. This is awkward on a PC, as there is no LF key (CTRL+J is + needed instead). We provide an AUTOLF mode to add a LF automatically to each + CR input. When this mode is set, entering CR will set a flag, which will + cause a LF to be supplied automatically at the next input poll. + + The 12531C teleprinter interface and the later 12880A CRT interface provide a + clever mechanism to detect a keypress during output. This is used by DOS and + RTE to allow the user to interrupt lengthy output operations to enter system + commands. + + Referring to the 12531C schematic, the terminal input enters on pin X + ("DATA FROM EIA COMPATIBLE DEVICE"). The signal passes through four + transistor inversions (Q8, Q1, Q2, and Q3) to appear on pin 12 of NAND gate + U104C. If the flag flip-flop is not set, the terminal input passes to the + (inverted) output of U104C and thence to the D input of the first of the + flip-flops forming the data register. + + In the idle condition (no key pressed), the terminal input line is marking + (voltage negative), so in passing through a total of five inversions, a + logic one is presented at the serial input of the data register. During an + output operation, the register is parallel loaded and serially shifted, + sending the output data through the register to the device and -- this is + the crux -- filling the register with logic ones from U104C. + + At the end of the output operation, the card flag is set, an interrupt + occurs, and the RTE driver is entered. The driver then does an LIA SC to + read the contents of the data register. If no key has been pressed during + the output operation, the register will read as all ones (octal 377). If, + however, any key was struck, at least one zero bit will be present. If the + register value doesn't equal 377, the driver sets the system "operator + attention" flag, which will cause DOS or RTE to output an asterisk prompt and + initiate a terminal read when the current output line is completed. +*/ + +t_stat tti_svc (UNIT *uptr) +{ +int32 c; + +uptr->wait = sim_rtcn_calb (POLL_RATE, TMR_POLL); /* calibrate poll timer */ +sim_activate (uptr, uptr->wait); /* continue poll */ + +tty_shin = 0377; /* assume inactive */ + +if (tty_lf) { /* auto lf pending? */ + c = 012; /* force lf */ + tty_lf = 0; + } + +else { + c = sim_poll_kbd (); + + if (c < SCPE_KFLAG) /* no char or error? */ + return c; + + if (c & SCPE_BREAK) /* break? */ + c = 0; + else + c = sim_tt_inpcvt (c, TT_GET_MODE (uptr->flags)); + + tty_lf = ((c & 0177) == 015) && (uptr->flags & UNIT_AUTOLF); + } + +if (tty_mode & TM_KBD) { /* keyboard enabled? */ + tty_buf = c; /* put char in buf */ + uptr->pos = uptr->pos + 1; + + ttyio (&tty_dib, ioENF, 0); /* set flag */ + + if (c) + tto_out (c); /* echo? */ + } + +else /* no, char shifts in */ + tty_shin = c; + +return SCPE_OK; +} + + +/* TTY output service routine */ + +t_stat tto_svc (UNIT *uptr) +{ +int32 c; +t_stat r; + +c = tty_buf; /* get char */ +tty_buf = tty_shin; /* shift in */ +tty_shin = 0377; /* line inactive */ + +r = tto_out (c); /* output the character */ + +if (r != SCPE_OK) { /* if an error occurred */ + sim_activate (uptr, uptr->wait); /* then schedule a retry */ + return (r == SCPE_STALL ? SCPE_OK : r); /* report a stall as success */ + } + +ttyio (&tty_dib, ioENF, 0); /* set flag */ + +return SCPE_OK; +} + + +/* TTY output routine. + + The 12531C Buffered Teleprinter Interface connects current-loop devices, such + as the HP 2752A (ASR33) and 2754A (ASR35) teleprinters, as well as EIA RS-232 + devices, such as the HP 2749A (ASR33) teleprinter and HP 2600 terminal. For + output, the control word sent to the interface may set the print flip-flop, + the punch flip-flop, or both flip-flops. These flip-flops generate the PRINT + COMMAND and PUNCH COMMAND output signals, respectively. Setting either one + enables data transmission. + + Only the 2754A responds to the PRINT and PUNCH COMMAND signals. All of the + other devices ignore these signals and respond only to the serial data out + signal. (The paper tape punches on the 2749A and 2752A teleprinters must be + enabled manually at the console and operate concurrently with the printers.) + + This routine simulates a 2754A if the punch unit (TTY unit 2) is attached and + a generic terminal when the unit is detached. With the punch unit attached, + the punch flip-flop must be set to punch, and the print flip-flop must be set + to print. These flip-flops, and therefore their respective operations, are + independent. When the punch unit is detached, printing will occur if either + the print or punch flip-flop is set. If neither flip-flop is set, no output + occurs. Therefore, the logic is: + + if punch-flip-flop and punch-attached + then punch character + + if print-flip-flop or punch-flip-flop and not punch-attached + then print character + + Certain HP programs, e.g., HP 2000F BASIC FOR DOS-M/DOS III, depend on the + 2752A et. al. behavior. The DOS and RTE teleprinter drivers support text and + binary output modes. Text mode sets the print flip-flop, and binary mode + sets the punch flip-flop. These programs use binary mode to write single + characters to the teleprinter and expect that they will be printed. The + simulator follows this behavior. +*/ + +t_stat tto_out (int32 c) +{ +t_stat r = SCPE_OK; + +if ((tty_mode & TM_PUN) /* if punching is enabled */ + && tty_unit [TTP].flags & UNIT_ATT) /* and the punch is attached */ + if (putc (c, tty_unit [TTP].fileref) == EOF) { /* then output the character; if it fails */ + perror ("TTP I/O error"); /* then report an error */ + clearerr (tty_unit [TTP].fileref); /* clear the error */ + r = SCPE_IOERR; /* and stop the simulator */ + } + + else /* otherwise the output succeeded */ + tty_unit [TTP].pos = ftell (tty_unit [TTP].fileref); /* so update the file position */ + +if ((tty_mode & TM_PRI) /* if printing is enabled */ + || (tty_mode & TM_PUN) /* or punching is enabled */ + && (tty_unit [TTP].flags & UNIT_ATT) == 0) { /* and the punch is not attached */ + c = sim_tt_outcvt (c, TT_GET_MODE (tty_unit [TTO].flags)); /* then convert the character */ + + if (c >= 0) { /* if the character is valid */ + r = sim_putchar_s (c); /* then output it to the console */ + + if (r == SCPE_OK) /* if the output succeeded */ + tty_unit [TTO].pos = tty_unit [TTO].pos + 1; /* then update the file position */ + } + } + +return r; /* return the result */ +} + + +/* TTY reset routine */ + +t_stat tty_reset (DEVICE *dptr) +{ +if (sim_switches & SWMASK ('P')) /* initialization reset? */ + tty_buf = 0; /* clear buffer */ + +IOPRESET (&tty_dib); /* PRESET device (does not use PON) */ + +tty_unit[TTI].wait = POLL_WAIT; /* reset initial poll */ +sim_rtcn_init (tty_unit[TTI].wait, TMR_POLL); /* init poll timer */ +sim_activate (&tty_unit[TTI], tty_unit[TTI].wait); /* activate poll */ +sim_cancel (&tty_unit[TTO]); /* cancel output */ +return SCPE_OK; +} + + +t_stat tty_set_opt (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +int32 u = uptr - tty_unit; + +if (u > TTO) return SCPE_NOFNC; +if ((u == TTI) && (val == TT_MODE_7P)) + val = TT_MODE_7B; +tty_unit[u].flags = (tty_unit[u].flags & ~TT_MODE) | val; +return SCPE_OK; +} + + +t_stat tty_set_alf (UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ +int32 u = uptr - tty_unit; + +if (u != TTI) return SCPE_NOFNC; +return SCPE_OK; +} + + +/* Synchronize polling. + + Return an event time corresponding either with the amount of time remaining + in the current poll (mode = INITIAL) or the amount of time in a full poll + period (mode = SERVICE). If the former call is made when the device service + routine is started, then making the latter call during unit service will + ensure that the polls remain synchronized. + */ + +int32 sync_poll (POLLMODE poll_mode) +{ +int32 poll_time; + + if (poll_mode == INITIAL) { + poll_time = sim_activate_time (&tty_unit[TTI]); + + if (poll_time) + return poll_time; + else + return POLL_WAIT; + } + else + return tty_unit[TTI].wait; +} + + +/* Clock I/O signal handler. + + The time base generator (CLK) provides periodic interrupts from 100 + microseconds to 1000 seconds. The CLK uses a calibrated timer to provide the + time base. For periods ranging from 1 to 1000 seconds, a 100 millisecond + timer is used, and 10 to 10000 ticks are counted before setting the device + flag to indicate that the period has expired. + + If the period is set to ten milliseconds, the console poll timer is used + instead of an independent timer. This is to maximize the idle period. + + In diagnostic mode, the clock period is set to the expected number of CPU + instructions, rather than wall-clock time, so that the diagnostic executes as + expected. +*/ + +uint32 clkio (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data) +{ +IOSIGNAL signal; +IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */ + +while (working_set) { + signal = IONEXT (working_set); /* isolate next signal */ + + switch (signal) { /* dispatch I/O signal */ + + case ioCLF: /* clear flag flip-flop */ + clk.flag = clk.flagbuf = CLEAR; + break; + + + case ioSTF: /* set flag flip-flop */ + case ioENF: /* enable flag */ + clk.flag = clk.flagbuf = SET; + break; + + + case ioSFC: /* skip if flag is clear */ + setstdSKF (clk); + break; + + + case ioSFS: /* skip if flag is set */ + setstdSKF (clk); + break; + + + case ioIOI: /* I/O data input */ + stat_data = IORETURN (SCPE_OK, clk_error); /* merge in return status */ + break; + + + case ioIOO: /* I/O data output */ + clk_select = IODATA (stat_data) & 07; /* save select */ + sim_cancel (&clk_unit); /* stop the clock */ + clk.control = CLEAR; /* clear control */ + working_set = working_set | ioSIR; /* set interrupt request (IOO normally doesn't) */ + break; + + + case ioPOPIO: /* power-on preset to I/O */ + clk.flag = clk.flagbuf = SET; /* set flag and flag buffer */ + break; + + + case ioCRS: /* control reset */ + case ioCLC: /* clear control flip-flop */ + clk.control = CLEAR; + sim_cancel (&clk_unit); /* deactivate unit */ + break; + + + case ioSTC: /* set control flip-flop */ + clk.control = SET; + if (clk_unit.flags & UNIT_DIAG) /* diag mode? */ + clk_unit.flags = clk_unit.flags & ~UNIT_IDLE; /* not calibrated */ + else + clk_unit.flags = clk_unit.flags | UNIT_IDLE; /* is calibrated */ + + if (!sim_is_active (&clk_unit)) { /* clock running? */ + clk_tick = clk_delay (0); /* get tick count */ + + if ((clk_unit.flags & UNIT_DIAG) == 0) /* calibrated? */ + if (clk_select == 2) /* 10 msec. interval? */ + clk_tick = sync_poll (INITIAL); /* sync poll */ + else + sim_rtcn_init (clk_tick, TMR_CLK); /* initialize timer */ + + sim_activate (&clk_unit, clk_tick); /* start clock */ + clk_ctr = clk_delay (1); /* set repeat ctr */ + } + clk_error = 0; /* clear error */ + break; + + + case ioSIR: /* set interrupt request */ + setstdPRL (clk); /* set standard PRL signal */ + setstdIRQ (clk); /* set standard IRQ signal */ + setstdSRQ (clk); /* set standard SRQ signal */ + break; + + + case ioIAK: /* interrupt acknowledge */ + clk.flagbuf = CLEAR; + break; + + + default: /* all other signals */ + break; /* are ignored */ + } + + working_set = working_set & ~signal; /* remove current signal from set */ + } + +return stat_data; +} + + +/* CLK unit service. + + As with the I/O handler, if the time base period is set to ten milliseconds, + the console poll timer is used instead of an independent timer. +*/ + +t_stat clk_svc (UNIT *uptr) +{ +if (!clk.control) /* control clear? */ + return SCPE_OK; /* done */ + +if (clk_unit.flags & UNIT_DIAG) /* diag mode? */ + clk_tick = clk_delay (0); /* get fixed delay */ +else if (clk_select == 2) /* 10 msec period? */ + clk_tick = sync_poll (SERVICE); /* sync poll */ +else + clk_tick = sim_rtcn_calb (clk_tps[clk_select], TMR_CLK); /* calibrate delay */ + +sim_activate (uptr, clk_tick); /* reactivate */ +clk_ctr = clk_ctr - 1; /* decrement counter */ +if (clk_ctr <= 0) { /* end of interval? */ + if (clk.flag) + clk_error = CLK_ERROR; /* overrun? error */ + else + clkio (&clk_dib, ioENF, 0); /* set flag */ + clk_ctr = clk_delay (1); /* reset counter */ + } +return SCPE_OK; +} + + +/* Reset routine */ + +t_stat clk_reset (DEVICE *dptr) +{ +if (sim_switches & SWMASK ('P')) { /* initialization reset? */ + clk_error = 0; /* clear error */ + clk_select = 0; /* clear select */ + clk_ctr = 0; /* clear counter */ + + if (clk_dev.lname == NULL) /* logical name unassigned? */ + clk_dev.lname = strdup ("TBG"); /* allocate and initialize the name */ + } + +IOPRESET (&clk_dib); /* PRESET device (does not use PON) */ + +return SCPE_OK; +} + + +/* Clock delay routine */ + +int32 clk_delay (int32 flg) +{ +int32 sel = clk_select; + +if ((clk_unit.flags & UNIT_DIAG) && (sel >= 4)) sel = sel - 3; +if (flg) return clk_rpt[sel]; +else return clk_time[sel]; +} diff --git a/HP2100/hp2100_sys.c b/HP2100/hp2100_sys.c index 4f0ff61e..c57dab9d 100644 --- a/HP2100/hp2100_sys.c +++ b/HP2100/hp2100_sys.c @@ -1,895 +1,919 @@ -/* hp2100_sys.c: HP 2100 simulator interface - - Copyright (c) 1993-2014, Robert M. Supnik - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Except as contained in this notice, the name of Robert M Supnik shall not be - used in advertising or otherwise to promote the sale, use or other dealings - in this Software without prior written authorization from Robert M Supnik. - - 24-Dec-14 JDB Added casts to t_addr and t_value for 64-bit compatibility - Made local routines static - 05-Feb-13 JDB Added hp_fprint_stopped to handle HLT instruction message - 18-Mar-13 JDB Moved CPU state variable declarations to hp2100_cpu.h - 09-May-12 JDB Quieted warnings for assignments in conditional expressions - 10-Feb-12 JDB Deprecated DEVNO in favor of SC - Added hp_setsc, hp_showsc functions to support SC modifier - 15-Dec-11 JDB Added DA and dummy DC devices - 29-Oct-10 JDB DMA channels renamed from 0,1 to 1,2 to match documentation - 26-Oct-10 JDB Changed DIB access for revised signal model - 03-Sep-08 JDB Fixed IAK instruction dual-use mnemonic display - 07-Aug-08 JDB Moved hp_setdev, hp_showdev from hp2100_cpu.c - Changed sim_load to use WritePW instead of direct M[] access - 18-Jun-08 JDB Added PIF device - 17-Jun-08 JDB Moved fmt_char() function from hp2100_baci.c - 26-May-08 JDB Added MPX device - 24-Apr-08 JDB Changed fprint_sym to handle step with irq pending - 07-Dec-07 JDB Added BACI device - 27-Nov-07 JDB Added RTE OS/VMA/EMA mnemonics - 21-Dec-06 JDB Added "fwanxm" external for sim_load check - 19-Nov-04 JDB Added STOP_OFFLINE, STOP_PWROFF messages - 25-Sep-04 JDB Added memory protect device - Fixed display of CCA/CCB/CCE instructions - 01-Jun-04 RMS Added latent 13037 support - 19-Apr-04 RMS Recognize SFS x,C and SFC x,C - 22-Mar-02 RMS Revised for dynamically allocated memory - 14-Feb-02 RMS Added DMS instructions - 04-Feb-02 RMS Fixed bugs in alter/skip display and parsing - 01-Feb-02 RMS Added terminal multiplexor support - 16-Jan-02 RMS Added additional device support - 17-Sep-01 RMS Removed multiconsole support - 27-May-01 RMS Added multiconsole support - 14-Mar-01 RMS Revised load/dump interface (again) - 30-Oct-00 RMS Added examine to file support - 15-Oct-00 RMS Added dynamic device number support - 27-Oct-98 RMS V2.4 load interface -*/ - -#include "hp2100_defs.h" -#include "hp2100_cpu.h" -#include - - -extern DEVICE mp_dev; -extern DEVICE dma1_dev, dma2_dev; -extern DEVICE ptr_dev, ptp_dev; -extern DEVICE tty_dev, clk_dev; -extern DEVICE lps_dev; -extern DEVICE lpt_dev; -extern DEVICE baci_dev; -extern DEVICE mpx_dev; -extern DEVICE mtd_dev, mtc_dev; -extern DEVICE msd_dev, msc_dev; -extern DEVICE dpd_dev, dpc_dev; -extern DEVICE dqd_dev, dqc_dev; -extern DEVICE drd_dev, drc_dev; -extern DEVICE ds_dev; -extern DEVICE muxl_dev, muxu_dev, muxc_dev; -extern DEVICE ipli_dev, iplo_dev; -extern DEVICE pif_dev; -extern DEVICE da_dev, dc_dev; - -/* SCP data structures and interface routines - - sim_name simulator name string - sim_PC pointer to saved PC register descriptor - sim_emax maximum number of words for examine/deposit - sim_devices array of pointers to simulated devices - sim_stop_messages array of pointers to stop messages - sim_load binary loader -*/ - -char sim_name[] = "HP 2100"; - -REG *sim_PC = &cpu_reg[0]; - -int32 sim_emax = 3; - -DEVICE *sim_devices[] = { - &cpu_dev, - &mp_dev, - &dma1_dev, &dma2_dev, - &ptr_dev, - &ptp_dev, - &tty_dev, - &clk_dev, - &lps_dev, - &lpt_dev, - &baci_dev, - &mpx_dev, - &dpd_dev, &dpc_dev, - &dqd_dev, &dqc_dev, - &drd_dev, &drc_dev, - &ds_dev, - &mtd_dev, &mtc_dev, - &msd_dev, &msc_dev, - &muxl_dev, &muxu_dev, &muxc_dev, - &ipli_dev, &iplo_dev, - &pif_dev, - &da_dev, &dc_dev, - NULL - }; - -const char *sim_stop_messages[] = { - "Unknown error", - "Unimplemented instruction", - "Non-existent I/O device", - "HALT instruction", - "Breakpoint", - "Indirect address loop", - "Indirect address interrupt (should not happen!)", - "No connection on interprocessor link", - "Device/unit offline", - "Device/unit powered off" - }; - - -/* Print additional information for simulator stops. - - The HP 21xx/1000 halt instruction ("HLT") opcode includes select code and - device flag hold/clear bit fields. In practice, these are not used to affect - the device interface; rather, they communicate to the operator the - significance of the particular halt encountered. - - Under simulation, the halt opcode must be communicated to the user as part of - the stop message. To so do, we define a sim_vm_fprint_stopped handler that - is called for all VM stops. When called for a STOP_HALT, the halt message - has been printed, and we add the opcode value in the T register before - returning TRUE, so that SCP will add the program counter value. For example: - - HALT instruction 102077, P: 00101 (NOP) - - Reasons other than STOP_HALT need no additional information. - - Implementation notes: - - 1. The octal halt instruction will always be of the form 10x0xx. We take - advantage of this to request 19 bits printed with leading spaces. This - adds a leading space to separate the value from the message. -*/ - -t_bool hp_fprint_stopped (FILE *st, t_stat reason) -{ -if (reason == STOP_HALT) - fprint_val (st, TR, 8, 19, PV_RSPC); - -return TRUE; -} - - -/* Binary loader - - The binary loader consists of blocks preceded and trailed by zero frames. - A block consists of 16b words (punched big endian), as follows: - - count'xxx - origin - word 0 - : - word count-1 - checksum - - The checksum includes the origin but not the count. -*/ - -static int32 fgetw (FILE *fileref) -{ -int c1, c2; - -if ((c1 = fgetc (fileref)) == EOF) return -1; -if ((c2 = fgetc (fileref)) == EOF) return -1; -return ((c1 & 0377) << 8) | (c2 & 0377); -} - -t_stat sim_load (FILE *fileref, char *cptr, char *fnam, int flag) -{ -int32 origin, csum, zerocnt, count, word, i; - -if ((*cptr != 0) || (flag != 0)) return SCPE_ARG; -for (zerocnt = 1;; zerocnt = -10) { /* block loop */ - for (;; zerocnt++) { /* skip 0's */ - if ((count = fgetc (fileref)) == EOF) return SCPE_OK; - else if (count) break; - else if (zerocnt == 0) return SCPE_OK; - } - if (fgetc (fileref) == EOF) return SCPE_FMT; - if ((origin = fgetw (fileref)) < 0) return SCPE_FMT; - csum = origin; /* seed checksum */ - for (i = 0; i < count; i++) { /* get data words */ - if ((word = fgetw (fileref)) < 0) return SCPE_FMT; - WritePW (origin, word); - origin = origin + 1; - csum = csum + word; - } - if ((word = fgetw (fileref)) < 0) return SCPE_FMT; - if ((word ^ csum) & DMASK) return SCPE_CSUM; - } -} - -/* Symbol tables */ - -#define I_V_FL 16 /* flag start */ -#define I_M_FL 017 /* flag mask */ -#define I_V_NPN 0 /* no operand */ -#define I_V_NPC 1 /* no operand + C */ -#define I_V_MRF 2 /* mem ref */ -#define I_V_ASH 3 /* alter/skip, shift */ -#define I_V_ESH 4 /* extended shift */ -#define I_V_EMR 5 /* extended mem ref */ -#define I_V_IO1 6 /* I/O + HC */ -#define I_V_IO2 7 /* I/O only */ -#define I_V_EGZ 010 /* ext grp, 1 op + 0 */ -#define I_V_EG2 011 /* ext grp, 2 op */ -#define I_V_ALT 012 /* alternate use instr */ -#define I_NPN (I_V_NPN << I_V_FL) -#define I_NPC (I_V_NPC << I_V_FL) -#define I_MRF (I_V_MRF << I_V_FL) -#define I_ASH (I_V_ASH << I_V_FL) -#define I_ESH (I_V_ESH << I_V_FL) -#define I_EMR (I_V_EMR << I_V_FL) -#define I_IO1 (I_V_IO1 << I_V_FL) -#define I_IO2 (I_V_IO2 << I_V_FL) -#define I_EGZ (I_V_EGZ << I_V_FL) -#define I_EG2 (I_V_EG2 << I_V_FL) -#define I_ALT (I_V_ALT << I_V_FL) - -static const int32 masks[] = { - 0177777, 0176777, 0074000, 0170000, - 0177760, 0177777, 0176700, 0177700, - 0177777, 0177777, 0177777 - }; - -static const char *opcode[] = { - -/* These mnemonics are used by debug printouts, so put them first. */ - - "$LIBR", "$LIBX", ".TICK", ".TNAM", /* RTE-6/VM OS firmware */ - ".STIO", ".FNW", ".IRT", ".LLS", - ".SIP", ".YLD", ".CPM", ".ETEQ", - ".ENTN", "$OTST", ".ENTC", ".DSPI", - "$DCPC", "$MPV", "$DEV", "$TBG", /* alternates for dual-use */ - - ".PMAP", "$LOC", "$VTST",/* --- */ /* RTE-6/VM VMA firmware */ -/* --- --- --- --- */ - ".IMAP", ".IMAR", ".JMAP", ".JMAR", - ".LPXR", ".LPX", ".LBPR", ".LBP", - - ".EMIO", "MMAP", "$ETST",/* --- */ /* RTE-IV EMA firmware */ -/* --- --- --- --- */ -/* --- --- --- --- */ -/* --- --- --- */ ".EMAP", - -/* Regular mnemonics. */ - - "NOP", "NOP", "AND", "JSB", - "XOR", "JMP", "IOR", "ISZ", - "ADA", "ADB" ,"CPA", "CPB", - "LDA", "LDB", "STA", "STB", - "DIAG", "ASL", "LSL", "TIMER", - "RRL", "ASR", "LSR", "RRR", - "MPY", "DIV", "DLD", "DST", - "FAD", "FSB", "FMP", "FDV", - "FIX", "FLT", - "STO", "CLO", "SOC", "SOS", - "HLT", "STF", "CLF", - "SFC", "SFS", "MIA", "MIB", - "LIA", "LIB", "OTA", "OTB", - "STC", "CLC", - "SYA", "USA", "PAA", "PBA", - "XMA", - "XLA", "XSA", "XCA", "LFA", - "RSA", "RVA", - "MBI", "MBF", - "MBW", "MWI", "MWF", "MWW", - "SYB", "USB", "PAB", "PBB", - "SSM", "JRS", - "XMM", "XMS", "XMB", - "XLB", "XSB", "XCB", "LFB", - "RSB", "RVB", "DJP", "DJS", - "SJP", "SJS", "UJP", "UJS", - "SAX", "SBX", "CAX", "CBX", - "LAX", "LBX", "STX", - "CXA", "CXB", "LDX", - "ADX", "XAX", "XBX", - "SAY", "SBY", "CAY", "CBY", - "LAY", "LBY", "STY", - "CYA", "CYB", "LDY", - "ADY", "XAY", "XBY", - "ISX", "DSX", "JLY", "LBT", - "SBT", "MBT", "CBT", "SBT", - "ISY", "DSY", "JPY", "SBS", - "CBS", "TBS", "CMW", "MVW", - NULL, /* decode only */ - NULL - }; - -static const int32 opc_val[] = { - 0105340+I_NPN, 0105341+I_NPN, 0105342+I_NPN, 0105343+I_NPN, /* RTE-6/VM OS */ - 0105344+I_NPN, 0105345+I_NPN, 0105346+I_NPN, 0105347+I_NPN, - 0105350+I_NPN, 0105351+I_NPN, 0105352+I_NPN, 0105353+I_NPN, - 0105354+I_ALT, 0105355+I_ALT, 0105356+I_ALT, 0105357+I_ALT, - 0105354+I_NPN, 0105355+I_NPN, 0105356+I_NPN, 0105357+I_NPN, /* alternates */ - - 0105240+I_ALT, 0105241+I_ALT, 0105242+I_ALT, /* --- */ /* RTE-6/VM VMA */ -/* --- --- --- --- */ - 0105250+I_NPN, 0105251+I_NPN, 0105252+I_NPN, 0105253+I_NPN, - 0105254+I_NPN, 0105255+I_NPN, 0105256+I_NPN, 0105257+I_ALT, - - 0105240+I_NPN, 0105241+I_NPN, 0105242+I_NPN, /* RTE-IV EMA */ -/* --- --- --- --- */ -/* --- --- --- --- */ -/* --- --- --- */ 0105257+I_NPN, - - 0000000+I_NPN, 0002000+I_NPN, 0010000+I_MRF, 0014000+I_MRF, - 0020000+I_MRF, 0024000+I_MRF, 0030000+I_MRF, 0034000+I_MRF, - 0040000+I_MRF, 0044000+I_MRF, 0050000+I_MRF, 0054000+I_MRF, - 0060000+I_MRF, 0064000+I_MRF, 0070000+I_MRF, 0074000+I_MRF, - 0100000+I_NPN, 0100020+I_ESH, 0100040+I_ESH, 0100060+I_NPN, - 0100100+I_ESH, 0101020+I_ESH, 0101040+I_ESH, 0101100+I_ESH, - 0100200+I_EMR, 0100400+I_EMR, 0104200+I_EMR, 0104400+I_EMR, - 0105000+I_EMR, 0105020+I_EMR, 0105040+I_EMR, 0105060+I_EMR, - 0105100+I_NPN, 0105120+I_NPN, - 0102101+I_NPN, 0103101+I_NPN, 0102201+I_NPC, 0102301+I_NPC, - 0102000+I_IO1, 0102100+I_IO2, 0103100+I_IO2, - 0102200+I_IO1, 0102300+I_IO1, 0102400+I_IO1, 0106400+I_IO1, - 0102500+I_IO1, 0106500+I_IO1, 0102600+I_IO1, 0106600+I_IO1, - 0102700+I_IO1, 0106700+I_IO1, - 0101710+I_NPN, 0101711+I_NPN, 0101712+I_NPN, 0101713+I_NPN, - 0101722+I_NPN, - 0101724+I_EMR, 0101725+I_EMR, 0101726+I_EMR, 0101727+I_NPN, - 0101730+I_NPN, 0101731+I_NPN, - 0105702+I_NPN, 0105703+I_NPN, - 0105704+I_NPN, 0105705+I_NPN, 0105706+I_NPN, 0105707+I_NPN, - 0105710+I_NPN, 0105711+I_NPN, 0105712+I_NPN, 0105713+I_NPN, - 0105714+I_EMR, 0105715+I_EG2, - 0105720+I_NPN, 0105721+I_NPN, 0105722+I_NPN, - 0105724+I_EMR, 0105725+I_EMR, 0105726+I_EMR, 0105727+I_NPN, - 0105730+I_NPN, 0105731+I_NPN, 0105732+I_EMR, 0105733+I_EMR, - 0105734+I_EMR, 0105735+I_EMR, 0105736+I_EMR, 0105737+I_EMR, - 0101740+I_EMR, 0105740+I_EMR, 0101741+I_NPN, 0105741+I_NPN, - 0101742+I_EMR, 0105742+I_EMR, 0105743+I_EMR, - 0101744+I_NPN, 0105744+I_NPN, 0105745+I_EMR, - 0105746+I_EMR, 0101747+I_NPN, 0105747+I_NPN, - 0101750+I_EMR, 0105750+I_EMR, 0101751+I_NPN, 0105751+I_NPN, - 0101752+I_EMR, 0105752+I_EMR, 0105753+I_EMR, - 0101754+I_NPN, 0105754+I_NPN, 0105755+I_EMR, - 0105756+I_EMR, 0101757+I_NPN, 0105757+I_NPN, - 0105760+I_NPN, 0105761+I_NPN, 0105762+I_EMR, 0105763+I_NPN, - 0105764+I_NPN, 0105765+I_EGZ, 0105766+I_EGZ, 0105767+I_NPN, - 0105770+I_NPN, 0105771+I_NPN, 0105772+I_EMR, 0105773+I_EG2, - 0105774+I_EG2, 0105775+I_EG2, 0105776+I_EGZ, 0105777+I_EGZ, - 0000000+I_ASH, /* decode only */ - -1 - }; - -/* Decode tables for shift and alter/skip groups */ - -static const char *stab[] = { - "ALS", "ARS", "RAL", "RAR", "ALR", "ERA", "ELA", "ALF", - "BLS", "BRS", "RBL", "RBR", "BLR", "ERB", "ELB", "BLF", - "CLA", "CMA", "CCA", "CLB", "CMB", "CCB", - "SEZ", "CLE", "CLE", "CME", "CCE", - "SSA", "SSB", "SLA", "SLB", - "ALS", "ARS", "RAL", "RAR", "ALR", "ERA", "ELA", "ALF", - "BLS", "BRS", "RBL", "RBR", "BLR", "ERB", "ELB", "BLF", - "INA", "INB", "SZA", "SZB", "RSS", - NULL - }; - -static const int32 mtab[] = { - 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, - 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, - 0007400, 0007400, 0007400, 0007400, 0007400, 0007400, - 0002040, 0002040, 0002300, 0002300, 0002300, - 0006020, 0006020, 0004010, 0004010, - 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, - 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, - 0006004, 0006004, 0006002, 0006002, 0002001, - 0 - }; - -static const int32 vtab[] = { - 0001000, 0001100, 0001200, 0001300, 0001400, 0001500, 0001600, 0001700, - 0005000, 0005100, 0005200, 0005300, 0005400, 0005500, 0005600, 0005700, - 0002400, 0003000, 0003400, 0006400, 0007000, 0007400, - 0002040, 0000040, 0002100, 0002200, 0002300, - 0002020, 0006020, 0000010, 0004010, - 0000020, 0000021, 0000022, 0000023, 0000024, 0000025, 0000026, 0000027, - 0004020, 0004021, 0004022, 0004023, 0004024, 0004025, 0004026, 0004027, - 0002004, 0006004, 0002002, 0006002, 0002001, - -1 - }; - -/* Symbolic decode - - Inputs: - *of = output stream - addr = current PC - *val = pointer to data - *uptr = pointer to unit - sw = switches - Outputs: - return = status code -*/ - -#define FMTASC(x) ((x) < 040)? "<%03o>": "%c", (x) - -t_stat fprint_sym (FILE *of, t_addr addr, t_value *val, - UNIT *uptr, int32 sw) -{ -int32 cflag, cm, i, j, inst, disp; -uint32 irq; - -cflag = (uptr == NULL) || (uptr == &cpu_unit); -inst = (int32) val[0]; -if (sw & SWMASK ('A')) { /* ASCII? */ - if (inst > 0377) return SCPE_ARG; - fprintf (of, FMTASC (inst & 0177)); - return SCPE_OK; - } -if (sw & SWMASK ('C')) { /* characters? */ - fprintf (of, FMTASC ((inst >> 8) & 0177)); - fprintf (of, FMTASC (inst & 0177)); - return SCPE_OK; - } -if (!(sw & SWMASK ('M'))) return SCPE_ARG; - -/* If we are being called as a result of a VM stop to display the next - instruction to be executed, check to see if an interrupt is pending and not - deferred. If so, then display the interrupt source and the trap cell - instruction as the instruction to be executed, rather than the instruction at - the current PC. -*/ - -if (sw & SIM_SW_STOP) { /* simulator stop? */ - irq = calc_int (); /* check interrupt */ - - if (irq && (!ion_defer || !calc_defer())) { /* pending interrupt and not deferred? */ - addr = irq; /* set display address to trap cell */ - val[0] = inst = ReadIO (irq, SMAP); /* load trap cell instruction */ - val[1] = ReadIO (irq + 1, SMAP); /* might be multi-word */ - val[2] = ReadIO (irq + 2, SMAP); /* although it's unlikely */ - fprintf (of, "IAK %2o: ", irq); /* report acknowledged interrupt */ - } - } - -for (i = 0; opc_val[i] >= 0; i++) { /* loop thru ops */ - j = (opc_val[i] >> I_V_FL) & I_M_FL; /* get class */ - if ((opc_val[i] & DMASK) == (inst & masks[j])) { /* match? */ - switch (j) { /* case on class */ - - case I_V_NPN: /* no operands */ - fprintf (of, "%s", opcode[i]); /* opcode */ - break; - - case I_V_NPC: /* no operands + C */ - fprintf (of, "%s", opcode[i]); - if (inst & I_HC) fprintf (of, " C"); - break; - - case I_V_MRF: /* mem ref */ - disp = inst & I_DISP; /* displacement */ - fprintf (of, "%s ", opcode[i]); /* opcode */ - if (inst & I_CP) { /* current page? */ - if (cflag) - fprintf (of, "%-o", ((uint32) addr & I_PAGENO) | disp); - else - fprintf (of, "C %-o", disp); - } - else fprintf (of, "%-o", disp); /* page zero */ - if (inst & I_IA) fprintf (of, ",I"); - break; - - case I_V_ASH: /* shift, alter-skip */ - cm = FALSE; - for (i = 0; mtab[i] != 0; i++) { - if ((inst & mtab[i]) == vtab[i]) { - inst = inst & ~(vtab[i] & 01777); - if (cm) fprintf (of, ","); - cm = TRUE; - fprintf (of, "%s", stab[i]); - } - } - if (!cm) return SCPE_ARG; /* nothing decoded? */ - break; - - case I_V_ESH: /* extended shift */ - disp = inst & 017; /* shift count */ - if (disp == 0) disp = 16; - fprintf (of, "%s %d", opcode[i], disp); - break; - - case I_V_EMR: /* extended mem ref */ - fprintf (of, "%s %-o", opcode[i], (uint32) val[1] & VAMASK); - if (val[1] & I_IA) fprintf (of, ",I"); - return -1; /* extra word */ - - case I_V_IO1: /* IOT with H/C */ - fprintf (of, "%s %-o", opcode[i], inst & I_DEVMASK); - if (inst & I_HC) fprintf (of, ",C"); - break; - - case I_V_IO2: /* IOT */ - fprintf (of, "%s %-o", opcode[i], inst & I_DEVMASK); - break; - - case I_V_EGZ: /* ext grp 1 op + 0 */ - fprintf (of, "%s %-o", opcode[i], (uint32) val[1] & VAMASK); - if (val[1] & I_IA) fprintf (of, ",I"); - return -2; /* extra words */ - - case I_V_EG2: /* ext grp 2 op */ - fprintf (of, "%s %-o", opcode[i], (uint32) val[1] & VAMASK); - if (val[1] & I_IA) fprintf (of, ",I"); - fprintf (of, " %-o", (uint32) val[2] & VAMASK); - if (val[2] & I_IA) fprintf (of, ",I"); - return -2; /* extra words */ - - case I_V_ALT: /* alternate use instr */ - if ((inst >= 0105354) && - (inst <= 0105357) && /* RTE-6/VM OS range? */ - (addr >= 2) && - (addr <= 077)) /* in trap cell? */ - continue; /* use alternate mnemonic */ - - else if ((inst >= 0105240) && /* RTE-6/VM VMA range? */ - (inst <= 0105257) && - (cpu_unit.flags & UNIT_EMA)) /* EMA enabled? */ - continue; /* use EMA mnemonics */ - - else - fprintf (of, "%s", opcode[i]); /* print opcode */ - break; - } - - return SCPE_OK; - } /* end if */ - } /* end for */ -return SCPE_ARG; -} - -/* Get address with indirection - - Inputs: - *cptr = pointer to input string - Outputs: - val = address - -1 if error -*/ - -static int32 get_addr (char *cptr) -{ -int32 d; -t_stat r; -char gbuf[CBUFSIZE]; - -cptr = get_glyph (cptr, gbuf, ','); /* get next field */ -d = (int32) get_uint (gbuf, 8, VAMASK, &r); /* construe as addr */ -if (r != SCPE_OK) return -1; -if (*cptr != 0) { /* more? */ - cptr = get_glyph (cptr, gbuf, 0); /* look for indirect */ - if (*cptr != 0) return -1; /* should be done */ - if (strcmp (gbuf, "I")) return -1; /* I? */ - d = d | I_IA; - } -return d; -} - -/* Symbolic input - - Inputs: - *iptr = pointer to input string - addr = current PC - *uptr = pointer to unit - *val = pointer to output values - sw = switches - Outputs: - status = error status -*/ - -t_stat parse_sym (char *iptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw) -{ -int32 cflag, d, i, j, k, clef, tbits; -t_stat r, ret; -char *cptr, gbuf[CBUFSIZE]; - -cflag = (uptr == NULL) || (uptr == &cpu_unit); -while (isspace (*iptr)) iptr++; /* absorb spaces */ -if ((sw & SWMASK ('A')) || ((*iptr == '\'') && iptr++)) { /* ASCII char? */ - if (iptr[0] == 0) return SCPE_ARG; /* must have 1 char */ - val[0] = (t_value) iptr[0] & 0177; - return SCPE_OK; - } -if ((sw & SWMASK ('C')) || ((*iptr == '"') && iptr++)) { /* char string? */ - if (iptr[0] == 0) return SCPE_ARG; /* must have 1 char */ - val[0] = (((t_value) iptr[0] & 0177) << 8) | - ((t_value) iptr[1] & 0177); - return SCPE_OK; - } - -ret = SCPE_OK; -cptr = get_glyph (iptr, gbuf, 0); /* get opcode */ -for (i = 0; (opcode[i] != NULL) && (strcmp (opcode[i], gbuf) != 0) ; i++) ; -if (opcode[i]) { /* found opcode? */ - val[0] = opc_val[i] & DMASK; /* get value */ - j = (opc_val[i] >> I_V_FL) & I_M_FL; /* get class */ - switch (j) { /* case on class */ - - case I_V_NPN: /* no operand */ - break; - - case I_V_NPC: /* no operand + C */ - if (*cptr != 0) { - cptr = get_glyph (cptr, gbuf, 0); - if (strcmp (gbuf, "C")) return SCPE_ARG; - val[0] = val[0] | I_HC; - } - break; - - case I_V_MRF: /* mem ref */ - cptr = get_glyph (cptr, gbuf, 0); /* get next field */ - k = strcmp (gbuf, "C"); - if (k == 0) { /* C specified? */ - val[0] = val[0] | I_CP; - cptr = get_glyph (cptr, gbuf, 0); - } - else { - k = strcmp (gbuf, "Z"); - if (k == 0) /* Z specified? */ - cptr = get_glyph (cptr, gbuf, ','); - } - if ((d = get_addr (gbuf)) < 0) return SCPE_ARG; - if ((d & VAMASK) <= I_DISP) val[0] = val[0] | d; - else if (cflag && !k && (((addr ^ d) & I_PAGENO) == 0)) - val[0] = val[0] | (d & (I_IA | I_DISP)) | I_CP; - else return SCPE_ARG; - break; - - case I_V_ESH: /* extended shift */ - cptr = get_glyph (cptr, gbuf, 0); - d = (int32) get_uint (gbuf, 10, 16, &r); - if ((r != SCPE_OK) || (d == 0)) return SCPE_ARG; - val[0] = val[0] | (d & 017); - break; - - case I_V_EMR: /* extended mem ref */ - cptr = get_glyph (cptr, gbuf, 0); /* get next field */ - if ((d = get_addr (gbuf)) < 0) return SCPE_ARG; - val[1] = d; - ret = -1; - break; - - case I_V_IO1: /* IOT + optional C */ - cptr = get_glyph (cptr, gbuf, ','); /* get device */ - d = (int32) get_uint (gbuf, 8, I_DEVMASK, &r); - if (r != SCPE_OK) return SCPE_ARG; - val[0] = val[0] | d; - if (*cptr != 0) { - cptr = get_glyph (cptr, gbuf, 0); - if (strcmp (gbuf, "C")) return SCPE_ARG; - val[0] = val[0] | I_HC; - } - break; - - case I_V_IO2: /* IOT */ - cptr = get_glyph (cptr, gbuf, 0); /* get device */ - d = (int32) get_uint (gbuf, 8, I_DEVMASK, &r); - if (r != SCPE_OK) return SCPE_ARG; - val[0] = val[0] | d; - break; - - case I_V_EGZ: /* ext grp 1 op + 0 */ - cptr = get_glyph (cptr, gbuf, 0); /* get next field */ - if ((d = get_addr (gbuf)) < 0) return SCPE_ARG; - val[1] = d; - val[2] = 0; - ret = -2; - break; - - case I_V_EG2: /* ext grp 2 op */ - cptr = get_glyph (cptr, gbuf, 0); /* get next field */ - if ((d = get_addr (gbuf)) < 0) return SCPE_ARG; - cptr = get_glyph (cptr, gbuf, 0); /* get next field */ - if ((k = get_addr (gbuf)) < 0) return SCPE_ARG; - val[1] = d; - val[2] = k; - ret = -2; - break; - } /* end case */ - - if (*cptr != 0) return SCPE_ARG; /* junk at end? */ - return ret; - } /* end if opcode */ - -/* Shift or alter-skip - - Each opcode is matched by a mask, specifiying the bits affected, and - the value, specifying the value. As opcodes are processed, the mask - values are used to specify which fields have already been filled in. - - The mask has two subfields, the type bits (A/B and A/S), and the field - bits. The type bits, once specified by any instruction, must be - consistent in all other instructions. The mask bits assure that no - field is filled in twice. - - Two special cases: - - 1. The dual shift field in shift requires checking how much of the - target word has been filled in before assigning the shift value. - To implement this, shifts are listed twice is the decode table. - If the current subopcode is a shift in the first part of the table - (entries 0..15), and CLE has been seen or the first shift field is - filled in, the code forces a mismatch. The glyph will match in - the second part of the table. - - 2. CLE processing must be deferred until the instruction can be - classified as shift or alter-skip, since it has two different - bit values in the two classes. To implement this, CLE seen is - recorded as a flag and processed after all other subopcodes. -*/ - -clef = FALSE; -tbits = 0; -val[0] = 0; -for (cptr = get_glyph (iptr, gbuf, ','); gbuf[0] != 0; - cptr = get_glyph (cptr, gbuf, ',')) { /* loop thru glyphs */ - if (strcmp (gbuf, "CLE") == 0) { /* CLE? */ - if (clef) return SCPE_ARG; /* already seen? */ - clef = TRUE; /* set flag */ - continue; - } - for (i = 0; stab[i] != NULL; i++) { /* find subopcode */ - if ((strcmp (gbuf, stab[i]) == 0) && - ((i >= 16) || (!clef && ((val[0] & 001710) == 0)))) break; - } - if (stab[i] == NULL) return SCPE_ARG; - if (tbits & mtab[i] & (I_AB | I_ASKP) & (vtab[i] ^ val[0])) - return SCPE_ARG; - if (tbits & mtab[i] & ~(I_AB | I_ASKP)) return SCPE_ARG; - tbits = tbits | mtab[i]; /* fill type+mask */ - val[0] = val[0] | vtab[i]; /* fill value */ - } -if (clef) { /* CLE seen? */ - if (val[0] & I_ASKP) { /* alter-skip? */ - if (tbits & 0100) return SCPE_ARG; /* already filled in? */ - else val[0] = val[0] | 0100; - } - else val[0] = val[0] | 040; /* fill in shift */ - } -return ret; -} - - -/* Format a character into a printable string. - - Control characters are translated to readable strings. Printable characters - retain their original form but are enclosed in single quotes. Characters - outside of the ASCII range are represented as escaped octal values. -*/ - -const char *fmt_char (uint8 ch) -{ -static const char *const ctl [] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", - "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", - "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", - "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; -static char rep [5]; - -if (ch <= '\037') /* ASCII control character? */ - return ctl [ch]; /* return string representation */ - -else if (ch == '\177') /* ASCII delete? */ - return "DEL"; /* return string representation */ - -else if (ch > '\177') { /* beyond printable range? */ - sprintf (rep, "\\%03o", ch); /* format value */ - return rep; /* return escaped octal code */ - } - -else { /* printable character */ - rep [0] = '\''; /* form string */ - rep [1] = ch; /* containing character */ - rep [2] = '\''; - rep [3] = '\0'; - return rep; /* return quoted character */ - } -} - - -/* Set select code */ - -t_stat hp_setsc (UNIT *uptr, int32 num, char *cptr, void *desc) -{ -DEVICE *dptr = (DEVICE *) desc; -DIB *dibptr; -int32 i, newdev; -t_stat r; - -if (cptr == NULL) - return SCPE_ARG; - -if ((desc == NULL) || (num > 1)) - return SCPE_IERR; - -dibptr = (DIB *) dptr->ctxt; - -if (dibptr == NULL) - return SCPE_IERR; - -newdev = (int32) get_uint (cptr, 8, I_DEVMASK - num, &r); - -if (r != SCPE_OK) - return r; - -if (newdev < VARDEV) - return SCPE_ARG; - -for (i = 0; i <= num; i++, dibptr++) - dibptr->select_code = newdev + i; - -return SCPE_OK; -} - - -/* Show select code */ - -t_stat hp_showsc (FILE *st, UNIT *uptr, int32 num, void *desc) -{ -DEVICE *dptr = (DEVICE *) desc; -DIB *dibptr; -int32 i; - -if ((desc == NULL) || (num > 1)) - return SCPE_IERR; - -dibptr = (DIB *) dptr->ctxt; - -if (dibptr == NULL) - return SCPE_IERR; - -fprintf (st, "select code=%o", dibptr->select_code); - -for (i = 1; i <= num; i++) - fprintf (st, "/%o", dibptr->select_code + i); - -return SCPE_OK; -} - - -/* Set device number */ - -t_stat hp_setdev (UNIT *uptr, int32 num, char *cptr, void *desc) -{ -return hp_setsc (uptr, num, cptr, desc); -} - - -/* Show device number */ - -t_stat hp_showdev (FILE *st, UNIT *uptr, int32 num, void *desc) -{ -t_stat result; - -result = hp_showsc (st, uptr, num, desc); - -if (result == SCPE_OK) - fputc ('\n', st); - -return result; -} +/* hp2100_sys.c: HP 2100 simulator interface + + Copyright (c) 1993-2016, Robert M. Supnik + Copyright (c) 2017 J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Robert M Supnik shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Robert M Supnik. + + 15-Jan-17 JDB Corrected HLT decoding to add the 1060xx and 1070xx ranges + Corrected SFB decoding + 14-Jan-17 JDB Removed use of Fprintf functions for version 4.x and on + 30-Dec-16 JDB Corrected parsing of memory reference instructions + 13-May-16 JDB Modified for revised SCP API function parameter types + 19-Jun-15 JDB Conditionally use Fprintf function for version 4.x and on + 18-Jun-15 JDB Added cast to int for isspace parameter + 24-Dec-14 JDB Added casts to t_addr and t_value for 64-bit compatibility + Made local routines static + 05-Feb-13 JDB Added hp_fprint_stopped to handle HLT instruction message + 18-Mar-13 JDB Moved CPU state variable declarations to hp2100_cpu.h + 09-May-12 JDB Quieted warnings for assignments in conditional expressions + 10-Feb-12 JDB Deprecated DEVNO in favor of SC + Added hp_setsc, hp_showsc functions to support SC modifier + 15-Dec-11 JDB Added DA and dummy DC devices + 29-Oct-10 JDB DMA channels renamed from 0,1 to 1,2 to match documentation + 26-Oct-10 JDB Changed DIB access for revised signal model + 03-Sep-08 JDB Fixed IAK instruction dual-use mnemonic display + 07-Aug-08 JDB Moved hp_setdev, hp_showdev from hp2100_cpu.c + Changed sim_load to use WritePW instead of direct M[] access + 18-Jun-08 JDB Added PIF device + 17-Jun-08 JDB Moved fmt_char() function from hp2100_baci.c + 26-May-08 JDB Added MPX device + 24-Apr-08 JDB Changed fprint_sym to handle step with irq pending + 07-Dec-07 JDB Added BACI device + 27-Nov-07 JDB Added RTE OS/VMA/EMA mnemonics + 21-Dec-06 JDB Added "fwanxm" external for sim_load check + 19-Nov-04 JDB Added STOP_OFFLINE, STOP_PWROFF messages + 25-Sep-04 JDB Added memory protect device + Fixed display of CCA/CCB/CCE instructions + 01-Jun-04 RMS Added latent 13037 support + 19-Apr-04 RMS Recognize SFS x,C and SFC x,C + 22-Mar-02 RMS Revised for dynamically allocated memory + 14-Feb-02 RMS Added DMS instructions + 04-Feb-02 RMS Fixed bugs in alter/skip display and parsing + 01-Feb-02 RMS Added terminal multiplexor support + 16-Jan-02 RMS Added additional device support + 17-Sep-01 RMS Removed multiconsole support + 27-May-01 RMS Added multiconsole support + 14-Mar-01 RMS Revised load/dump interface (again) + 30-Oct-00 RMS Added examine to file support + 15-Oct-00 RMS Added dynamic device number support + 27-Oct-98 RMS V2.4 load interface +*/ + + +#include +#include "hp2100_defs.h" +#include "hp2100_cpu.h" + + +extern DEVICE mp_dev; +extern DEVICE dma1_dev, dma2_dev; +extern DEVICE ptr_dev, ptp_dev; +extern DEVICE tty_dev, clk_dev; +extern DEVICE lps_dev; +extern DEVICE lpt_dev; +extern DEVICE baci_dev; +extern DEVICE mpx_dev; +extern DEVICE mtd_dev, mtc_dev; +extern DEVICE msd_dev, msc_dev; +extern DEVICE dpd_dev, dpc_dev; +extern DEVICE dqd_dev, dqc_dev; +extern DEVICE drd_dev, drc_dev; +extern DEVICE ds_dev; +extern DEVICE muxl_dev, muxu_dev, muxc_dev; +extern DEVICE ipli_dev, iplo_dev; +extern DEVICE pif_dev; +extern DEVICE da_dev, dc_dev; + +/* SCP data structures and interface routines + + sim_name simulator name string + sim_PC pointer to saved PC register descriptor + sim_emax maximum number of words for examine/deposit + sim_devices array of pointers to simulated devices + sim_stop_messages array of pointers to stop messages + sim_load binary loader +*/ + +char sim_name[] = "HP 2100"; + +REG *sim_PC = &cpu_reg[0]; + +int32 sim_emax = 3; + +DEVICE *sim_devices[] = { + &cpu_dev, + &mp_dev, + &dma1_dev, &dma2_dev, + &ptr_dev, + &ptp_dev, + &tty_dev, + &clk_dev, + &lps_dev, + &lpt_dev, + &baci_dev, + &mpx_dev, + &dpd_dev, &dpc_dev, + &dqd_dev, &dqc_dev, + &drd_dev, &drc_dev, + &ds_dev, + &mtd_dev, &mtc_dev, + &msd_dev, &msc_dev, + &muxl_dev, &muxu_dev, &muxc_dev, + &ipli_dev, &iplo_dev, + &pif_dev, + &da_dev, &dc_dev, + NULL + }; + +const char *sim_stop_messages[] = { + "Unknown error", + "Unimplemented instruction", + "Non-existent I/O device", + "HALT instruction", + "Breakpoint", + "Indirect address loop", + "Indirect address interrupt (should not happen!)", + "No connection on interprocessor link", + "Device/unit offline", + "Device/unit powered off" + }; + + +/* Print additional information for simulator stops. + + The HP 21xx/1000 halt instruction ("HLT") opcode includes select code and + device flag hold/clear bit fields. In practice, these are not used to affect + the device interface; rather, they communicate to the operator the + significance of the particular halt encountered. + + Under simulation, the halt opcode must be communicated to the user as part of + the stop message. To so do, we define a sim_vm_fprint_stopped handler that + is called for all VM stops. When called for a STOP_HALT, the halt message + has been printed, and we add the opcode value in the T register before + returning TRUE, so that SCP will add the program counter value. For example: + + HALT instruction 102077, P: 00101 (NOP) + + Reasons other than STOP_HALT need no additional information. + + Implementation notes: + + 1. The octal halt instruction will always be of the form 10x0xx. We take + advantage of this to request 19 bits printed with leading spaces. This + adds a leading space to separate the value from the message. +*/ + +t_bool hp_fprint_stopped (FILE *st, t_stat reason) +{ +if (reason == STOP_HALT) + fprint_val (st, TR, 8, 19, PV_RSPC); + +return TRUE; +} + + +/* Binary loader + + The binary loader consists of blocks preceded and trailed by zero frames. + A block consists of 16b words (punched big endian), as follows: + + count'xxx + origin + word 0 + : + word count-1 + checksum + + The checksum includes the origin but not the count. +*/ + +static int32 fgetw (FILE *fileref) +{ +int c1, c2; + +if ((c1 = fgetc (fileref)) == EOF) return -1; +if ((c2 = fgetc (fileref)) == EOF) return -1; +return ((c1 & 0377) << 8) | (c2 & 0377); +} + +t_stat sim_load (FILE *fileref, CONST char *cptr, CONST char *fnam, int flag) +{ +int32 origin, csum, zerocnt, count, word, i; + +if ((*cptr != 0) || (flag != 0)) return SCPE_ARG; +for (zerocnt = 1;; zerocnt = -10) { /* block loop */ + for (;; zerocnt++) { /* skip 0's */ + if ((count = fgetc (fileref)) == EOF) return SCPE_OK; + else if (count) break; + else if (zerocnt == 0) return SCPE_OK; + } + if (fgetc (fileref) == EOF) return SCPE_FMT; + if ((origin = fgetw (fileref)) < 0) return SCPE_FMT; + csum = origin; /* seed checksum */ + for (i = 0; i < count; i++) { /* get data words */ + if ((word = fgetw (fileref)) < 0) return SCPE_FMT; + WritePW (origin, word); + origin = origin + 1; + csum = csum + word; + } + if ((word = fgetw (fileref)) < 0) return SCPE_FMT; + if ((word ^ csum) & DMASK) return SCPE_CSUM; + } +} + +/* Symbol tables */ + +#define I_V_FL 16 /* flag start */ +#define I_M_FL 017 /* flag mask */ +#define I_V_NPN 0 /* no operand */ +#define I_V_NPC 1 /* no operand + C */ +#define I_V_MRF 2 /* mem ref */ +#define I_V_ASH 3 /* alter/skip, shift */ +#define I_V_ESH 4 /* extended shift */ +#define I_V_EMR 5 /* extended mem ref */ +#define I_V_IO1 6 /* I/O + HC */ +#define I_V_IO2 7 /* I/O only */ +#define I_V_EGZ 010 /* ext grp, 1 op + 0 */ +#define I_V_EG2 011 /* ext grp, 2 op */ +#define I_V_ALT 012 /* alternate use instr */ +#define I_NPN (I_V_NPN << I_V_FL) +#define I_NPC (I_V_NPC << I_V_FL) +#define I_MRF (I_V_MRF << I_V_FL) +#define I_ASH (I_V_ASH << I_V_FL) +#define I_ESH (I_V_ESH << I_V_FL) +#define I_EMR (I_V_EMR << I_V_FL) +#define I_IO1 (I_V_IO1 << I_V_FL) +#define I_IO2 (I_V_IO2 << I_V_FL) +#define I_EGZ (I_V_EGZ << I_V_FL) +#define I_EG2 (I_V_EG2 << I_V_FL) +#define I_ALT (I_V_ALT << I_V_FL) + +static const int32 masks[] = { + 0177777, 0176777, 0074000, 0170000, + 0177760, 0177777, 0176700, 0177700, + 0177777, 0177777, 0177777 + }; + +static const char *opcode[] = { + +/* These mnemonics are used by debug printouts, so put them first. */ + + "$LIBR", "$LIBX", ".TICK", ".TNAM", /* RTE-6/VM OS firmware */ + ".STIO", ".FNW", ".IRT", ".LLS", + ".SIP", ".YLD", ".CPM", ".ETEQ", + ".ENTN", "$OTST", ".ENTC", ".DSPI", + "$DCPC", "$MPV", "$DEV", "$TBG", /* alternates for dual-use */ + + ".PMAP", "$LOC", "$VTST",/* --- */ /* RTE-6/VM VMA firmware */ +/* --- --- --- --- */ + ".IMAP", ".IMAR", ".JMAP", ".JMAR", + ".LPXR", ".LPX", ".LBPR", ".LBP", + + ".EMIO", "MMAP", "$ETST",/* --- */ /* RTE-IV EMA firmware */ +/* --- --- --- --- */ +/* --- --- --- --- */ +/* --- --- --- */ ".EMAP", + +/* Regular mnemonics. */ + + "NOP", "NOP", "AND", "JSB", + "XOR", "JMP", "IOR", "ISZ", + "ADA", "ADB" ,"CPA", "CPB", + "LDA", "LDB", "STA", "STB", + "DIAG", "ASL", "LSL", "TIMER", + "RRL", "ASR", "LSR", "RRR", + "MPY", "DIV", "DLD", "DST", + "FAD", "FSB", "FMP", "FDV", + "FIX", "FLT", + "STO", "CLO", "SOC", "SOS", + "HLT", "HLT", "STF", "CLF", /* 2nd HLT is 1060xx and 1070xx ranges */ + "SFC", "SFS", "MIA", "MIB", + "LIA", "LIB", "OTA", "OTB", + "STC", "CLC", + "SYA", "USA", "PAA", "PBA", + "XMA", + "XLA", "XSA", "XCA", "LFA", + "RSA", "RVA", + "MBI", "MBF", + "MBW", "MWI", "MWF", "MWW", + "SYB", "USB", "PAB", "PBB", + "SSM", "JRS", + "XMM", "XMS", "XMB", + "XLB", "XSB", "XCB", "LFB", + "RSB", "RVB", "DJP", "DJS", + "SJP", "SJS", "UJP", "UJS", + "SAX", "SBX", "CAX", "CBX", + "LAX", "LBX", "STX", + "CXA", "CXB", "LDX", + "ADX", "XAX", "XBX", + "SAY", "SBY", "CAY", "CBY", + "LAY", "LBY", "STY", + "CYA", "CYB", "LDY", + "ADY", "XAY", "XBY", + "ISX", "DSX", "JLY", "LBT", + "SBT", "MBT", "CBT", "SFB", + "ISY", "DSY", "JPY", "SBS", + "CBS", "TBS", "CMW", "MVW", + NULL, /* decode only */ + NULL + }; + +static const int32 opc_val[] = { + 0105340+I_NPN, 0105341+I_NPN, 0105342+I_NPN, 0105343+I_NPN, /* RTE-6/VM OS */ + 0105344+I_NPN, 0105345+I_NPN, 0105346+I_NPN, 0105347+I_NPN, + 0105350+I_NPN, 0105351+I_NPN, 0105352+I_NPN, 0105353+I_NPN, + 0105354+I_ALT, 0105355+I_ALT, 0105356+I_ALT, 0105357+I_ALT, + 0105354+I_NPN, 0105355+I_NPN, 0105356+I_NPN, 0105357+I_NPN, /* alternates */ + + 0105240+I_ALT, 0105241+I_ALT, 0105242+I_ALT, /* --- */ /* RTE-6/VM VMA */ +/* --- --- --- --- */ + 0105250+I_NPN, 0105251+I_NPN, 0105252+I_NPN, 0105253+I_NPN, + 0105254+I_NPN, 0105255+I_NPN, 0105256+I_NPN, 0105257+I_ALT, + + 0105240+I_NPN, 0105241+I_NPN, 0105242+I_NPN, /* RTE-IV EMA */ +/* --- --- --- --- */ +/* --- --- --- --- */ +/* --- --- --- */ 0105257+I_NPN, + + 0000000+I_NPN, 0002000+I_NPN, 0010000+I_MRF, 0014000+I_MRF, + 0020000+I_MRF, 0024000+I_MRF, 0030000+I_MRF, 0034000+I_MRF, + 0040000+I_MRF, 0044000+I_MRF, 0050000+I_MRF, 0054000+I_MRF, + 0060000+I_MRF, 0064000+I_MRF, 0070000+I_MRF, 0074000+I_MRF, + 0100000+I_NPN, 0100020+I_ESH, 0100040+I_ESH, 0100060+I_NPN, + 0100100+I_ESH, 0101020+I_ESH, 0101040+I_ESH, 0101100+I_ESH, + 0100200+I_EMR, 0100400+I_EMR, 0104200+I_EMR, 0104400+I_EMR, + 0105000+I_EMR, 0105020+I_EMR, 0105040+I_EMR, 0105060+I_EMR, + 0105100+I_NPN, 0105120+I_NPN, + 0102101+I_NPN, 0103101+I_NPN, 0102201+I_NPC, 0102301+I_NPC, + 0102000+I_IO1, 0106000+I_IO1, 0102100+I_IO2, 0103100+I_IO2, + 0102200+I_IO1, 0102300+I_IO1, 0102400+I_IO1, 0106400+I_IO1, + 0102500+I_IO1, 0106500+I_IO1, 0102600+I_IO1, 0106600+I_IO1, + 0102700+I_IO1, 0106700+I_IO1, + 0101710+I_NPN, 0101711+I_NPN, 0101712+I_NPN, 0101713+I_NPN, + 0101722+I_NPN, + 0101724+I_EMR, 0101725+I_EMR, 0101726+I_EMR, 0101727+I_NPN, + 0101730+I_NPN, 0101731+I_NPN, + 0105702+I_NPN, 0105703+I_NPN, + 0105704+I_NPN, 0105705+I_NPN, 0105706+I_NPN, 0105707+I_NPN, + 0105710+I_NPN, 0105711+I_NPN, 0105712+I_NPN, 0105713+I_NPN, + 0105714+I_EMR, 0105715+I_EG2, + 0105720+I_NPN, 0105721+I_NPN, 0105722+I_NPN, + 0105724+I_EMR, 0105725+I_EMR, 0105726+I_EMR, 0105727+I_NPN, + 0105730+I_NPN, 0105731+I_NPN, 0105732+I_EMR, 0105733+I_EMR, + 0105734+I_EMR, 0105735+I_EMR, 0105736+I_EMR, 0105737+I_EMR, + 0101740+I_EMR, 0105740+I_EMR, 0101741+I_NPN, 0105741+I_NPN, + 0101742+I_EMR, 0105742+I_EMR, 0105743+I_EMR, + 0101744+I_NPN, 0105744+I_NPN, 0105745+I_EMR, + 0105746+I_EMR, 0101747+I_NPN, 0105747+I_NPN, + 0101750+I_EMR, 0105750+I_EMR, 0101751+I_NPN, 0105751+I_NPN, + 0101752+I_EMR, 0105752+I_EMR, 0105753+I_EMR, + 0101754+I_NPN, 0105754+I_NPN, 0105755+I_EMR, + 0105756+I_EMR, 0101757+I_NPN, 0105757+I_NPN, + 0105760+I_NPN, 0105761+I_NPN, 0105762+I_EMR, 0105763+I_NPN, + 0105764+I_NPN, 0105765+I_EGZ, 0105766+I_EGZ, 0105767+I_NPN, + 0105770+I_NPN, 0105771+I_NPN, 0105772+I_EMR, 0105773+I_EG2, + 0105774+I_EG2, 0105775+I_EG2, 0105776+I_EGZ, 0105777+I_EGZ, + 0000000+I_ASH, /* decode only */ + -1 + }; + +/* Decode tables for shift and alter/skip groups */ + +static const char *stab[] = { + "ALS", "ARS", "RAL", "RAR", "ALR", "ERA", "ELA", "ALF", + "BLS", "BRS", "RBL", "RBR", "BLR", "ERB", "ELB", "BLF", + "CLA", "CMA", "CCA", "CLB", "CMB", "CCB", + "SEZ", "CLE", "CLE", "CME", "CCE", + "SSA", "SSB", "SLA", "SLB", + "ALS", "ARS", "RAL", "RAR", "ALR", "ERA", "ELA", "ALF", + "BLS", "BRS", "RBL", "RBR", "BLR", "ERB", "ELB", "BLF", + "INA", "INB", "SZA", "SZB", "RSS", + NULL + }; + +static const int32 mtab[] = { + 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, + 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, 0007700, + 0007400, 0007400, 0007400, 0007400, 0007400, 0007400, + 0002040, 0002040, 0002300, 0002300, 0002300, + 0006020, 0006020, 0004010, 0004010, + 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, + 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, 0006027, + 0006004, 0006004, 0006002, 0006002, 0002001, + 0 + }; + +static const int32 vtab[] = { + 0001000, 0001100, 0001200, 0001300, 0001400, 0001500, 0001600, 0001700, + 0005000, 0005100, 0005200, 0005300, 0005400, 0005500, 0005600, 0005700, + 0002400, 0003000, 0003400, 0006400, 0007000, 0007400, + 0002040, 0000040, 0002100, 0002200, 0002300, + 0002020, 0006020, 0000010, 0004010, + 0000020, 0000021, 0000022, 0000023, 0000024, 0000025, 0000026, 0000027, + 0004020, 0004021, 0004022, 0004023, 0004024, 0004025, 0004026, 0004027, + 0002004, 0006004, 0002002, 0006002, 0002001, + -1 + }; + +/* Symbolic decode + + Inputs: + *of = output stream + addr = current PC + *val = pointer to data + *uptr = pointer to unit + sw = switches + Outputs: + return = status code +*/ + +#define FMTASC(x) ((x) < 040)? "<%03o>": "%c", (x) + +t_stat fprint_sym (FILE *of, t_addr addr, t_value *val, + UNIT *uptr, int32 sw) +{ +int32 cflag, cm, i, j, inst, disp; +uint32 irq; + +cflag = (uptr == NULL) || (uptr == &cpu_unit); +inst = (int32) val[0]; +if (sw & SWMASK ('A')) { /* ASCII? */ + if (inst > 0377) return SCPE_ARG; + fprintf (of, FMTASC (inst & 0177)); + return SCPE_OK; + } +if (sw & SWMASK ('C')) { /* characters? */ + fprintf (of, FMTASC ((inst >> 8) & 0177)); + fprintf (of, FMTASC (inst & 0177)); + return SCPE_OK; + } +if (!(sw & SWMASK ('M'))) return SCPE_ARG; + +/* If we are being called as a result of a VM stop to display the next + instruction to be executed, check to see if an interrupt is pending and not + deferred. If so, then display the interrupt source and the trap cell + instruction as the instruction to be executed, rather than the instruction at + the current PC. +*/ + +if (sw & SIM_SW_STOP) { /* simulator stop? */ + irq = calc_int (); /* check interrupt */ + + if (irq && (!ion_defer || !calc_defer())) { /* pending interrupt and not deferred? */ + addr = irq; /* set display address to trap cell */ + val[0] = inst = ReadIO (irq, SMAP); /* load trap cell instruction */ + val[1] = ReadIO (irq + 1, SMAP); /* might be multi-word */ + val[2] = ReadIO (irq + 2, SMAP); /* although it's unlikely */ + fprintf (of, "IAK %2o: ", irq); /* report acknowledged interrupt */ + } + } + +for (i = 0; opc_val[i] >= 0; i++) { /* loop thru ops */ + j = (opc_val[i] >> I_V_FL) & I_M_FL; /* get class */ + if ((opc_val[i] & DMASK) == (inst & masks[j])) { /* match? */ + switch (j) { /* case on class */ + + case I_V_NPN: /* no operands */ + fprintf (of, "%s", opcode[i]); /* opcode */ + break; + + case I_V_NPC: /* no operands + C */ + fprintf (of, "%s", opcode[i]); + if (inst & I_HC) fprintf (of, " C"); + break; + + case I_V_MRF: /* mem ref */ + disp = inst & I_DISP; /* displacement */ + fprintf (of, "%s ", opcode[i]); /* opcode */ + if (inst & I_CP) { /* current page? */ + if (cflag) + fprintf (of, "%-o", ((uint32) addr & I_PAGENO) | disp); + else + fprintf (of, "C %-o", disp); + } + else fprintf (of, "%-o", disp); /* page zero */ + if (inst & I_IA) fprintf (of, ",I"); + break; + + case I_V_ASH: /* shift, alter-skip */ + cm = FALSE; + for (i = 0; mtab[i] != 0; i++) { + if ((inst & mtab[i]) == vtab[i]) { + inst = inst & ~(vtab[i] & 01777); + if (cm) fprintf (of, ","); + cm = TRUE; + fprintf (of, "%s", stab[i]); + } + } + if (!cm) return SCPE_ARG; /* nothing decoded? */ + break; + + case I_V_ESH: /* extended shift */ + disp = inst & 017; /* shift count */ + if (disp == 0) disp = 16; + fprintf (of, "%s %d", opcode[i], disp); + break; + + case I_V_EMR: /* extended mem ref */ + fprintf (of, "%s %-o", opcode[i], (uint32) val[1] & VAMASK); + if (val[1] & I_IA) fprintf (of, ",I"); + return -1; /* extra word */ + + case I_V_IO1: /* IOT with H/C */ + fprintf (of, "%s %-o", opcode[i], inst & I_DEVMASK); + if (inst & I_HC) fprintf (of, ",C"); + break; + + case I_V_IO2: /* IOT */ + fprintf (of, "%s %-o", opcode[i], inst & I_DEVMASK); + break; + + case I_V_EGZ: /* ext grp 1 op + 0 */ + fprintf (of, "%s %-o", opcode[i], (uint32) val[1] & VAMASK); + if (val[1] & I_IA) fprintf (of, ",I"); + return -2; /* extra words */ + + case I_V_EG2: /* ext grp 2 op */ + fprintf (of, "%s %-o", opcode[i], (uint32) val[1] & VAMASK); + if (val[1] & I_IA) fprintf (of, ",I"); + fprintf (of, " %-o", (uint32) val[2] & VAMASK); + if (val[2] & I_IA) fprintf (of, ",I"); + return -2; /* extra words */ + + case I_V_ALT: /* alternate use instr */ + if ((inst >= 0105354) && + (inst <= 0105357) && /* RTE-6/VM OS range? */ + (addr >= 2) && + (addr <= 077)) /* in trap cell? */ + continue; /* use alternate mnemonic */ + + else if ((inst >= 0105240) && /* RTE-6/VM VMA range? */ + (inst <= 0105257) && + (cpu_unit.flags & UNIT_EMA)) /* EMA enabled? */ + continue; /* use EMA mnemonics */ + + else + fprintf (of, "%s", opcode[i]); /* print opcode */ + break; + } + + return SCPE_OK; + } /* end if */ + } /* end for */ +return SCPE_ARG; +} + +/* Get address with indirection + + Inputs: + *cptr = pointer to input string + Outputs: + val = address + -1 if error +*/ + +static int32 get_addr (CONST char *cptr) +{ +int32 d; +t_stat r; +char gbuf[CBUFSIZE]; + +cptr = get_glyph (cptr, gbuf, ','); /* get next field */ +d = (int32) get_uint (gbuf, 8, VAMASK, &r); /* construe as addr */ +if (r != SCPE_OK) return -1; +if (*cptr != 0) { /* more? */ + cptr = get_glyph (cptr, gbuf, 0); /* look for indirect */ + if (*cptr != 0) return -1; /* should be done */ + if (strcmp (gbuf, "I")) return -1; /* I? */ + d = d | I_IA; + } +return d; +} + +/* Symbolic input + + Inputs: + *iptr = pointer to input string + addr = current PC + *uptr = pointer to unit + *val = pointer to output values + sw = switches + Outputs: + status = error status +*/ + +t_stat parse_sym (CONST char *iptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw) +{ +int32 cflag, d, i, j, k, clef, tbits; +t_stat r, ret; +CONST char *cptr; +char gbuf[CBUFSIZE]; + +cflag = (uptr == NULL) || (uptr == &cpu_unit); +while (isspace ((int) *iptr)) iptr++; /* absorb spaces */ +if ((sw & SWMASK ('A')) || ((*iptr == '\'') && iptr++)) { /* ASCII char? */ + if (iptr[0] == 0) return SCPE_ARG; /* must have 1 char */ + val[0] = (t_value) iptr[0] & 0177; + return SCPE_OK; + } +if ((sw & SWMASK ('C')) || ((*iptr == '"') && iptr++)) { /* char string? */ + if (iptr[0] == 0) return SCPE_ARG; /* must have 1 char */ + val[0] = (((t_value) iptr[0] & 0177) << 8) | + ((t_value) iptr[1] & 0177); + return SCPE_OK; + } + +ret = SCPE_OK; +cptr = get_glyph (iptr, gbuf, 0); /* get opcode */ +for (i = 0; (opcode[i] != NULL) && (strcmp (opcode[i], gbuf) != 0) ; i++) ; +if (opcode[i]) { /* found opcode? */ + val[0] = opc_val[i] & DMASK; /* get value */ + j = (opc_val[i] >> I_V_FL) & I_M_FL; /* get class */ + switch (j) { /* case on class */ + + case I_V_NPN: /* no operand */ + break; + + case I_V_NPC: /* no operand + C */ + if (*cptr != 0) { + cptr = get_glyph (cptr, gbuf, 0); + if (strcmp (gbuf, "C")) return SCPE_ARG; + val[0] = val[0] | I_HC; + } + break; + + case I_V_MRF: /* mem ref */ + cptr = get_glyph (cptr, gbuf, 0); /* get next field */ + + if (gbuf [0] == 'C' && gbuf [1] == '\0') { /* if the C modifier was specified */ + val[0] = val[0] | I_CP; /* then add the current-page flag */ + cptr = get_glyph (cptr, gbuf, 0); /* and get the address */ + k = 0; /* clear the implicit-page flag */ + } + + else if (gbuf [0] == 'Z' && gbuf [1] == '\0') { /* otherwise if the Z modifier was specified */ + cptr = get_glyph (cptr, gbuf, 0); /* then get the address */ + k = 0; /* and clear the implicit-page flag */ + } + + else /* otherwise neither modifier is present */ + k = 1; /* so set the flag to allow implicit-page addressing */ + + d = get_addr (gbuf); /* parse the address and optional indirection indicator */ + + if (d < 0) /* if a parse error occurred */ + return SCPE_ARG; /* then return an invalid argument error */ + + if ((d & VAMASK) <= I_DISP) /* if a base-page address was given */ + val[0] = val[0] | d; /* then merge the offset into the instruction */ + + else if (cflag && k /* otherwise if an implicit-page address is allowed */ + && ((addr ^ d) & I_PAGENO) == 0) /* and the target is in the current page */ + val[0] = val[0] | d & (I_IA | I_DISP) | I_CP; /* then merge the offset with the current-page flag */ + + else /* otherwise the address cannot be reached */ + return SCPE_ARG; /* from the current instruction's location */ + break; + + case I_V_ESH: /* extended shift */ + cptr = get_glyph (cptr, gbuf, 0); + d = (int32) get_uint (gbuf, 10, 16, &r); + if ((r != SCPE_OK) || (d == 0)) return SCPE_ARG; + val[0] = val[0] | (d & 017); + break; + + case I_V_EMR: /* extended mem ref */ + cptr = get_glyph (cptr, gbuf, 0); /* get next field */ + if ((d = get_addr (gbuf)) < 0) return SCPE_ARG; + val[1] = d; + ret = -1; + break; + + case I_V_IO1: /* IOT + optional C */ + cptr = get_glyph (cptr, gbuf, ','); /* get device */ + d = (int32) get_uint (gbuf, 8, I_DEVMASK, &r); + if (r != SCPE_OK) return SCPE_ARG; + val[0] = val[0] | d; + if (*cptr != 0) { + cptr = get_glyph (cptr, gbuf, 0); + if (strcmp (gbuf, "C")) return SCPE_ARG; + val[0] = val[0] | I_HC; + } + break; + + case I_V_IO2: /* IOT */ + cptr = get_glyph (cptr, gbuf, 0); /* get device */ + d = (int32) get_uint (gbuf, 8, I_DEVMASK, &r); + if (r != SCPE_OK) return SCPE_ARG; + val[0] = val[0] | d; + break; + + case I_V_EGZ: /* ext grp 1 op + 0 */ + cptr = get_glyph (cptr, gbuf, 0); /* get next field */ + if ((d = get_addr (gbuf)) < 0) return SCPE_ARG; + val[1] = d; + val[2] = 0; + ret = -2; + break; + + case I_V_EG2: /* ext grp 2 op */ + cptr = get_glyph (cptr, gbuf, 0); /* get next field */ + if ((d = get_addr (gbuf)) < 0) return SCPE_ARG; + cptr = get_glyph (cptr, gbuf, 0); /* get next field */ + if ((k = get_addr (gbuf)) < 0) return SCPE_ARG; + val[1] = d; + val[2] = k; + ret = -2; + break; + } /* end case */ + + if (*cptr != 0) return SCPE_ARG; /* junk at end? */ + return ret; + } /* end if opcode */ + +/* Shift or alter-skip + + Each opcode is matched by a mask, specifiying the bits affected, and + the value, specifying the value. As opcodes are processed, the mask + values are used to specify which fields have already been filled in. + + The mask has two subfields, the type bits (A/B and A/S), and the field + bits. The type bits, once specified by any instruction, must be + consistent in all other instructions. The mask bits assure that no + field is filled in twice. + + Two special cases: + + 1. The dual shift field in shift requires checking how much of the + target word has been filled in before assigning the shift value. + To implement this, shifts are listed twice is the decode table. + If the current subopcode is a shift in the first part of the table + (entries 0..15), and CLE has been seen or the first shift field is + filled in, the code forces a mismatch. The glyph will match in + the second part of the table. + + 2. CLE processing must be deferred until the instruction can be + classified as shift or alter-skip, since it has two different + bit values in the two classes. To implement this, CLE seen is + recorded as a flag and processed after all other subopcodes. +*/ + +clef = FALSE; +tbits = 0; +val[0] = 0; +for (cptr = get_glyph (iptr, gbuf, ','); gbuf[0] != 0; + cptr = get_glyph (cptr, gbuf, ',')) { /* loop thru glyphs */ + if (strcmp (gbuf, "CLE") == 0) { /* CLE? */ + if (clef) return SCPE_ARG; /* already seen? */ + clef = TRUE; /* set flag */ + continue; + } + for (i = 0; stab[i] != NULL; i++) { /* find subopcode */ + if ((strcmp (gbuf, stab[i]) == 0) && + ((i >= 16) || (!clef && ((val[0] & 001710) == 0)))) break; + } + if (stab[i] == NULL) return SCPE_ARG; + if (tbits & mtab[i] & (I_AB | I_ASKP) & (vtab[i] ^ val[0])) + return SCPE_ARG; + if (tbits & mtab[i] & ~(I_AB | I_ASKP)) return SCPE_ARG; + tbits = tbits | mtab[i]; /* fill type+mask */ + val[0] = val[0] | vtab[i]; /* fill value */ + } +if (clef) { /* CLE seen? */ + if (val[0] & I_ASKP) { /* alter-skip? */ + if (tbits & 0100) return SCPE_ARG; /* already filled in? */ + else val[0] = val[0] | 0100; + } + else val[0] = val[0] | 040; /* fill in shift */ + } +return ret; +} + + +/* Format a character into a printable string. + + Control characters are translated to readable strings. Printable characters + retain their original form but are enclosed in single quotes. Characters + outside of the ASCII range are represented as escaped octal values. +*/ + +const char *fmt_char (uint8 ch) +{ +static const char *const ctl [] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", + "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", + "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", + "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; +static char rep [5]; + +if (ch <= '\037') /* ASCII control character? */ + return ctl [ch]; /* return string representation */ + +else if (ch == '\177') /* ASCII delete? */ + return "DEL"; /* return string representation */ + +else if (ch > '\177') { /* beyond printable range? */ + sprintf (rep, "\\%03o", ch); /* format value */ + return rep; /* return escaped octal code */ + } + +else { /* printable character */ + rep [0] = '\''; /* form string */ + rep [1] = ch; /* containing character */ + rep [2] = '\''; + rep [3] = '\0'; + return rep; /* return quoted character */ + } +} + + +/* Set select code */ + +t_stat hp_setsc (UNIT *uptr, int32 num, CONST char *cptr, void *desc) +{ +DEVICE *dptr = (DEVICE *) desc; +DIB *dibptr; +int32 i, newdev; +t_stat r; + +if (cptr == NULL) + return SCPE_ARG; + +if ((desc == NULL) || (num > 1)) + return SCPE_IERR; + +dibptr = (DIB *) dptr->ctxt; + +if (dibptr == NULL) + return SCPE_IERR; + +newdev = (int32) get_uint (cptr, 8, I_DEVMASK - num, &r); + +if (r != SCPE_OK) + return r; + +if (newdev < VARDEV) + return SCPE_ARG; + +for (i = 0; i <= num; i++, dibptr++) + dibptr->select_code = newdev + i; + +return SCPE_OK; +} + + +/* Show select code */ + +t_stat hp_showsc (FILE *st, UNIT *uptr, int32 num, CONST void *desc) +{ +const DEVICE *dptr = (const DEVICE *) desc; +DIB *dibptr; +int32 i; + +if ((desc == NULL) || (num > 1)) + return SCPE_IERR; + +dibptr = (DIB *) dptr->ctxt; + +if (dibptr == NULL) + return SCPE_IERR; + +fprintf (st, "select code=%o", dibptr->select_code); + +for (i = 1; i <= num; i++) + fprintf (st, "/%o", dibptr->select_code + i); + +return SCPE_OK; +} + + +/* Set device number */ + +t_stat hp_setdev (UNIT *uptr, int32 num, CONST char *cptr, void *desc) +{ +return hp_setsc (uptr, num, cptr, desc); +} + + +/* Show device number */ + +t_stat hp_showdev (FILE *st, UNIT *uptr, int32 num, CONST void *desc) +{ +t_stat result; + +result = hp_showsc (st, uptr, num, desc); + +if (result == SCPE_OK) + fputc ('\n', st); + +return result; +} diff --git a/HP3000/hp3000_atc.c b/HP3000/hp3000_atc.c new file mode 100644 index 00000000..3d99590e --- /dev/null +++ b/HP3000/hp3000_atc.c @@ -0,0 +1,2865 @@ +/* hp3000_atc.c: HP 3000 30032B Asynchronous Terminal Controller simulator + + Copyright (c) 2014-2016, J. David Bryan + Copyright (c) 2002-2012, Robert M Supnik + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the authors shall not be + used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from the authors. + + ATCD,ATCC HP 30032B Asynchronous Terminal Controller + + 16-Sep-16 JDB Fixed atcd_detach to skip channel cancel if SIM_SW_REST + 12-Sep-16 JDB Changed DIB register macro usage from SRDATA to DIB_REG + 20-Jul-16 JDB Corrected poll_unit "wait" field initializer. + 26-Jun-16 JDB Removed tmxr_set_modem_control_passthru call in atcc_reset + 09-Jun-16 JDB Added casts for ptrdiff_t to int32 values + 16-May-16 JDB Fixed interrupt mask setting + 13-May-16 JDB Modified for revised SCP API function parameter types + 21-Mar-16 JDB Changed uint16 types to HP_WORD + 26-Aug-15 JDB First release version + 31-Jul-15 JDB Passes the terminal control diagnostic (D438A) + 11-Aug-14 JDB Passes the terminal data diagnostic (D427A) + 28-Jul-14 JDB Created from the HP 2100 12920A MUX device simulator + + References: + - 30032B Asynchronous Terminal Controller Installation and Service Manual + (30032-90004, February 1977) + - Stand-Alone HP 30032B Terminal Data Interface Diagnostic + (30032-90011, October 1980) + - Stand-Alone HP 30061A Terminal Controller Interface Diagnostic + (30060-90004, February 1976) + - HP 3000 Series III Engineering Diagrams Set + (30000-90141, April 1980) + + + The HP 30032B Asynchronous Terminal Controller is a 16-channel terminal + multiplexer used with the HP 3000 CX through Series III systems. The ATC + connects from 1 to 16 serial terminals or modems to the HP 3000 at + programmable baud rates from 75 to 2400 bits per second. Character sizes are + also programmable from 5 to 12 bits in length, including the start and stop + bits. Each channel can be independently configured, including for separate + send and receive rates. The ATC is not buffered, so the CPU has to retrieve + each character from a given channel before the next character arrives. To + avoid saturating the CPU with interrupt requests, the ATC maintains an + internal "mini-interrupt" system that queues requests and holds additional + interrupts off until the CPU acknowledges the current request. + + The HP 3000CX and Series I use a dedicated serial interface for the system + console, while user terminals are connected to the ATC. For the Series II + and III, the separate card is eliminated, and channel 0 of the ATC is + reserved for the console. + + This module is an adaptation of the code originally written by Bob Supnik for + the HP2100 MUX simulator. The MUX device simulates an HP 12920A interface + for an HP 2100/1000 computer. The 12920A is an ATC with the HP 3000 I/O bus + connection replaced by an HP 2100 I/O bus connection. Programming and + operation of the two multiplexers are virtually identical. + + The ATC consists of a Terminal Data Interface, which provides direct + connection for 16 serial terminals, and one or two optional Terminal Control + Interfaces, which provides control and status lines for Bell 103 and 202 data + sets, respectively. The ATC base product, order number 30032, consisted of + one TDI card. Option -001 added one TCI, and option -002 added two. A + second ATC subsystem could be added to support an additional 16 terminals or + modems. + + This simulation provides one TDI and one optional TCI. Each of the channels + may be connected either to a Telnet session or a serial port on the host + machine. Channel 0 is connected to the simulation console, which initially + performs I/O to the controlling window but may be rerouted instead to a + Telnet session or serial port, if desired. Additional channel configuration + options select the input mode (upshifted or normal), output mode (8-bit, + 7-bit, printable, or upshifted), and whether the HP-standard ENQ/ACK + handshaking is done by the external device or internally by the simulator. + + A device mode specifies whether terminals or diagnostic loopback cables are + connected to the TDI and TCI. Enabling the diagnostic mode simulates the + installation of eight HP 30062-60003 diagnostic test (loopback) cables + between channels 0-1, 2-3, etc., as required by the multiplexer diagnostics. + In this mode, sending data on one channel automatically receives the same + data on the alternate channel. In addition, all Telnet and serial sessions + are disconnected, and the TDI is detached from the listening port. While in + diagnostic mode, the ATTACH command is not allowed. Enabling terminal mode + allows the TDI to be attached to accept incoming connections again. + + Another device mode specifies whether the TDI operates in real-time or + optimized ("fast") timing mode. In the former, character send and receive + events occur at approximately the same rate (in machine instructions) as in + hardware. The latter mode increases the rate to the maximum value consistent + with correct operation in MPE. + + Both the TDI and TCI are normally enabled, although the TCI will not be used + unless MPE is configured to use data sets on one or more channels. When so + configured, logging off will cause the channel to disconnect the Telnet + session or drop the Data Terminal Ready signal on the serial port. A channel + controlled by the TCI will be marked as "data set" in a unit listing; + channels not controlled will be marked as "direct". + + The TDI and TCI may be disabled, if desired, although the TDI must be + detached from the listening port first. Disabling the TDI does not affect + the simulation console, as the CPU process clock will take over console + polling automatically. + + The Terminal Data Interface provides 16 send channels, 16 receive channels, + and 5 auxiliary channels. The auxiliary channels are receive-only and do + not connect to external devices. Rather, they may be connected as a group to + one or more of the other channels. Their primary purpose is to diagnose + conditions (e.g., baud rate) on the connected channel(s). + + In hardware, a recirculating memory stores seven 8-bit words of data, + parameters, and status for each of the 37 channels. A set of registers form + a "window" into the recirculating memory, and the memory makes one complete + pass every 69.44 microseconds. Serial transfer rates are determined by each + channel's parameter word, which specifies the number of recirculations that + occur for each bit sent or received. + + In simulation, the memory is represented by separate buffer, parameter, and + status arrays. Recirculation is simulated by indexing through each of the + arrays in sequence. + + + The TDI responds only to direct I/O instructions, as follows: + + TDI Control Word Format (CIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M | R | channel number | - - - - - - - | E | A | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + M = master reset + R = reset interrupts + E = enable store of preceding data or parameter word to memory + A = acknowledge interrupt + + + TDI Status Word Format (TIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | S | D | I | - | C | R | L | B | - - - - - - - - | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + S = SIO OK (always 0) + D = direct read/write I/O OK + I = interrupt request + C = read/write completion flag + R = receive/send (0/1) character interrupt + L = character lost + B = break status + + + TDI Parameter Word Format (WIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 1 | R | I | E | D | char size | baud rate | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + R = receive/send (0/1) configuration + I = enable channel completion interrupt + E = enable echo (receive) or generate parity (send) + D = diagnose using the auxiliary channels + + Character size: + The three least-significant bits of the sum of the data, parity, and stop + bits. For example, 7E1 is 1001, so 001 is coded. + + Baud rate: + The value (14400 / device bit rate) - 1. For example, 2400 baud is 005. + + + TDI Output Data Word Format (WIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | - - | S | send data | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + S = sync bit + data = right-justified with leading ones + + + TDI Input Data Word Format (RIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | channel | P | receive data | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + P = computed parity + data = right-justified with leading ones + + + The Terminal Control Interface provides two serial control outputs and two + serial status inputs for each of 16 channels. The first TCI connects to the + Request to Send (CA) and Data Terminal Ready (CD) control lines and the Data + Carrier Detect (CF) and Data Set Ready (CC) status lines. Addressable + latches hold the control line values and assert them continuously to the 16 + channels. In addition, a 16-word by 4-bit RAM holds the expected state for + each channel's status lines and the corresponding interrupt enable bits to + provide notification if those lines change. + + The TCI responds only to direct I/O instructions, as follows: + + + TCI Control Word Format (CIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M | R | S | U | channel | W | X | Q | T | Y | Z | C | D | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + M = master reset + R = reset interrupts + S = scan status + U = enable DCD/DSR state update + W = enable RTS change + X = enable DTR change + Q = new RTS state + T = new DTR state + Y = DCD interrupt enabled + Z = DSR interrupt enabled + C = expected DCD state + D = expected DSR state + + + TCI Status Word Format (TIO or RIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | I | 1 | channel | 0 | 0 | J | K | Y | Z | C | D | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + I = interrupt request + J = DCD interrupt present + K = DSR interrupt present + Y = DCD interrupt enabled + Z = DSR interrupt enabled + C = current DCD state + D = current DSR state + + + Implementation notes: + + 1. The UNIT_MODEM flag indicates that a channel is controlled by the TCI. + However, no modifier entry is provided, nor is one needed, as the flag is + set automatically when the TCI first initializes the channel. MPE + defines separate terminal subtype numbers for directly connected + terminals and modem-connected terminals, which are set at system + generation time. + + 2. Both TMXR_VALID and SCPE_KFLAG are set on internally generated ACKs only + so that a debug trace will record the generation correctly. +*/ + + + +#include + +#include "hp3000_defs.h" +#include "hp3000_io.h" + +#include "sim_tmxr.h" + + + +/* Program limits */ + +#define TERM_COUNT 16 /* number of terminal channels */ +#define AUX_COUNT 5 /* number of auxiliary channels */ +#define POLL_COUNT 1 /* number of poll units */ + +#define RECV_CHAN_COUNT (TERM_COUNT + AUX_COUNT) /* number of receive channels */ +#define SEND_CHAN_COUNT TERM_COUNT /* number of send channels */ +#define UNIT_COUNT (TERM_COUNT + POLL_COUNT) /* number of units */ + +#define FIRST_TERM 0 /* first terminal index */ +#define LAST_TERM (FIRST_TERM + TERM_COUNT - 1) /* last terminal index */ +#define FIRST_AUX TERM_COUNT /* first auxiliary index */ +#define LAST_AUX (FIRST_AUX + AUX_COUNT - 1) /* last auxiliary index */ + + +/* Program constants */ + +#define FAST_IO_TIME 500 /* initial fast receive/send time in event ticks */ + +#define POLL_RATE 100 /* poll 100 times per second (unless synchronized) */ +#define POLL_TIME mS (10) /* poll time is 10 milliseconds */ + +#define NUL '\000' /* null */ +#define ENQ '\005' /* enquire */ +#define ACK '\006' /* acknowledge */ +#define ASCII_MASK 000177u /* 7-bit ASCII character set mask */ + +#define GEN_ACK (TMXR_VALID | SCPE_KFLAG | ACK) /* a generated ACK character */ + +#define SCAN_ALL (-1) /* scan all channels for completion */ + + +/* Parity functions derived from the global lookup table */ + +#define RECV_PARITY(c) (odd_parity [(c) & D8_MASK] ? 0 : DDR_PARITY) +#define SEND_PARITY(c) (odd_parity [(c) & D8_MASK] ? 0 : DDS_PARITY) + + +/* Debug flags */ + +#define DEB_CSRW (1u << 0) /* trace command initiations and completions */ +#define DEB_XFER (1u << 1) /* trace data receptions and transmissions */ +#define DEB_IOB (1u << 2) /* trace I/O bus signals and data words */ +#define DEB_SERV (1u << 3) /* trace channel service scheduling calls */ +#define DEB_PSERV (1u << 4) /* trace poll service scheduling calls */ + + +/* Common per-unit multiplexer channel state variables */ + +#define recv_time u3 /* realistic receive time in event ticks */ +#define send_time u4 /* realistic send time in event ticks */ +#define stop_bits u5 /* stop bits to be added to each character received */ + + +/* Device flags */ + +#define DEV_DIAG_SHIFT (DEV_V_UF + 0) /* diagnostic loopback */ +#define DEV_REALTIME_SHIFT (DEV_V_UF + 1) /* timing mode is realistic */ + +#define DEV_DIAG (1u << DEV_DIAG_SHIFT) /* diagnostic mode flag */ +#define DEV_REALTIME (1u << DEV_REALTIME_SHIFT) /* realistic timing flag */ + + +/* Unit flags */ + +#define UNIT_CAPSLOCK_SHIFT (TTUF_V_UF + 0) /* caps lock mode */ +#define UNIT_LOCALACK_SHIFT (TTUF_V_UF + 1) /* local ACK mode */ +#define UNIT_MODEM_SHIFT (TTUF_V_UF + 2) /* modem control */ + +#define UNIT_CAPSLOCK (1u << UNIT_CAPSLOCK_SHIFT) /* caps lock is down flag */ +#define UNIT_LOCALACK (1u << UNIT_LOCALACK_SHIFT) /* ENQ/ACK mode is local flag */ +#define UNIT_MODEM (1u << UNIT_MODEM_SHIFT) /* channel connects to a data set flag */ + + +/* Unit references */ + +#define line_unit atcd_unit /* receive/send channel units */ +#define poll_unit atcd_unit [LAST_TERM + 1] /* input polling unit */ + + +/* Activation reasons */ + +typedef enum { + Receive, + Send, + Loop, + Stall + } ACTIVATOR; + + +/* TDI control word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M | R | channel number | - - - - - - - | E | A | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define DCN_MR 0100000u /* (M) master reset */ +#define DCN_IRQ_RESET 0040000u /* (R) interrupt request reset */ +#define DCN_CHAN_MASK 0037000u /* channel number mask */ +#define DCN_ENABLE 0000002u /* (E) enable store of preceding data or parameter word */ +#define DCN_ACKN 0000001u /* (A) acknowledge interrupt */ + +#define DCN_CHAN_SHIFT 9 /* channel number alignment shift */ + +#define DCN_CHAN(c) (((c) & DCN_CHAN_MASK) >> DCN_CHAN_SHIFT) + +static const BITSET_NAME tdi_control_names [] = { /* TDI control word names */ + "master reset", /* bit 0 */ + "reset interrupt", /* bit 1 */ + NULL, /* bit 2 */ + NULL, /* bit 3 */ + NULL, /* bit 4 */ + NULL, /* bit 5 */ + NULL, /* bit 6 */ + NULL, /* bit 7 */ + NULL, /* bit 8 */ + NULL, /* bit 9 */ + NULL, /* bit 10 */ + NULL, /* bit 11 */ + NULL, /* bit 12 */ + NULL, /* bit 13 */ + "store word", /* bit 14 */ + "acknowledge interrupt" /* bit 15 */ + }; + +static const BITSET_FORMAT tdi_control_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_control_names, 0, msb_first, no_alt, no_bar) }; + + +/* TDI status word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | - | D | I | - | C | R | L | B | - - - - - - - - | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define DST_DIO_OK 0040000u /* (D) direct I/O OK to use */ +#define DST_IRQ 0020000u /* (I) interrupt requested */ +#define DST_COMPLETE 0004000u /* (C) operation is complete and channel is ready to interrupt */ +#define DST_SEND_IRQ 0002000u /* (R) interrupt request is for character sent */ +#define DST_CHAR_LOST 0001000u /* (L) character was lost */ +#define DST_BREAK 0000400u /* (B) break occurred */ +#define DST_DIAGNOSE 0000000u /* status is from an auxiliary channel (not used on ATC) */ + +#define DST_CHAN(n) 0 /* position channel number for status (not used on ATC) */ + +static const BITSET_NAME tdi_status_names [] = { /* TDI status word names */ + "DIO OK", /* bit 1 */ + "interrupt", /* bit 2 */ + NULL, /* bit 3 */ + "complete", /* bit 4 */ + "\1send\0receive", /* bit 5 */ + "lost", /* bit 6 */ + "break" /* bit 7 */ + }; + +static const BITSET_FORMAT tdi_status_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_status_names, 8, msb_first, has_alt, no_bar) }; + + + +/* TDI parameter word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 1 | R | I | E | D | char size | baud rate | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + The baud rate is encoded as 14400 / device_bit_rate - 1, but the manual says + to round the result, so that, e.g., the 110 baud rate encoding of 129.91 is + rounded to 130. To reconstruct the rate without floating-point calculations, + the parameter print routine uses: + + baud_rate = (2 * 14400 / (encoded_rate + 1) + 1) / 2 + + ...which is equivalent to: + + baud_rate = (int) (14400 / (encoded_rate + 1) + 0.5) + + The multiplexer pads the received character data to the left with one-bits. + + The PAD_BITS function generates the pad bits, assuming that the received + character transmission has one stop bit. This isn't always correct, e.g., a + Teleprinter uses two stop bits at 110 baud, but there's no way to reconstruct + the number of stop bits from the receive parameter word. +*/ + +#define DPI_IS_PARAM 0100000u /* value is a parameter (always set) */ +#define DPI_IS_SEND 0040000u /* (R) value is a send parameter */ +#define DPI_ENABLE_IRQ 0020000u /* (I) enable interrupt requests */ +#define DPI_ENABLE_PARITY 0010000u /* (E) enable parity for send */ +#define DPI_ENABLE_ECHO 0010000u /* (E) enable echo for receive */ +#define DPI_DIAGNOSE 0004000u /* (D) connect to the auxiliary channels */ +#define DPI_SIZE_MASK 0003400u /* character size mask */ +#define DPI_RATE_MASK 0000377u /* baud rate mask */ + +#define DPI_CHAR_CONFIG (DPI_SIZE_MASK | DPI_RATE_MASK) /* character configuration data */ + +#define DPI_SIZE_SHIFT 8 /* character size alignment shift */ +#define DPI_RATE_SHIFT 0 /* baud rate alignment shift */ + +#define DPI_CHAR_SIZE(p) (((p) & DPI_SIZE_MASK) >> DPI_SIZE_SHIFT) +#define DPI_BAUD_RATE(p) (((p) & DPI_RATE_MASK) >> DPI_RATE_SHIFT) + +#define BAUD_RATE(p) ((28800 / (DPI_BAUD_RATE (p) + 1) + 1) / 2) + +#define PAD_BITS(c) (~((1u << bits_per_char [DPI_CHAR_SIZE (c)] - 2) - 1)) + + +static const uint32 bits_per_char [8] = { /* bits per character, indexed by DPI_CHAR_SIZE encoding */ + 9, 10, 11, 12, 5, 6, 7, 8 + }; + +static const BITSET_NAME tdi_parameter_names [] = { /* TDI parameter word names */ + "\1send\0receive", /* bit 1 */ + "enable interrupt", /* bit 2 */ + "enable parity/echo", /* bit 3 */ + "diagnose" /* bit 4 */ + }; + +static const BITSET_FORMAT tdi_parameter_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_parameter_names, 11, msb_first, has_alt, append_bar) }; + + +/* TDI output (send) data word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | - - | S | send data | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define DDS_IS_SEND 0040000u /* value is a send data word (always set) */ +#define DDS_SYNC 0004000u /* (S) sync */ +#define DDS_DATA_MASK 0003777u /* data value mask */ +#define DDS_PARITY 0000200u /* data parity bit */ + +#define DDS_MARK (DDS_SYNC | DDS_DATA_MASK) /* all-mark character */ + +#define DDS_DATA(d) ((d) & DDS_DATA_MASK) + + +static const BITSET_NAME tdi_output_data_names [] = { /* TDI output data word names */ + "send", /* bit 1 */ + NULL, /* bit 2 */ + NULL, /* bit 3 */ + "sync" /* bit 4 */ + }; + +static const BITSET_FORMAT tdi_output_data_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_output_data_names, 11, msb_first, no_alt, append_bar) }; + + +/* TDI input (receive) data word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | channel | P | receive data | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define DDR_CHAN_MASK 0174000u /* channel number mask */ +#define DDR_PARITY 0002000u /* (P) computed parity bit */ +#define DDR_DATA_MASK 0001777u /* data value mask */ + +#define DDR_CHAN_SHIFT 11 /* channel number alignment shift */ +#define DDR_DATA_SHIFT 0 /* data alignment shift */ + +#define DDR_CHAN(n) ((n) << DDR_CHAN_SHIFT & DDR_CHAN_MASK) +#define DDR_DATA(d) ((d) << DDR_DATA_SHIFT & DDR_DATA_MASK) + +#define DDR_TO_CHAN(w) (((w) & DDR_CHAN_MASK) >> DDR_CHAN_SHIFT) +#define DDR_TO_DATA(w) (((w) & DDR_DATA_MASK) >> DDR_DATA_SHIFT) + + +static const BITSET_NAME tdi_input_data_names [] = { /* TDI input data word names */ + "\1odd parity\0even parity", /* bit 5 */ + }; + +static const BITSET_FORMAT tdi_input_data_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tdi_input_data_names, 10, msb_first, has_alt, append_bar) }; + + +/* TCI control word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M | R | S | U | channel | W | X | Q | T | Y | Z | C | D | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define CCN_MR 0100000u /* (M) master reset */ +#define CCN_IRQ_RESET 0040000u /* (R) interrupt request reset */ +#define CCN_SCAN 0020000u /* (S) scan enable */ +#define CCN_UPDATE 0010000u /* (U) update enable */ +#define CCN_CHAN_MASK 0007400u /* channel number mask */ +#define CCN_ECX_MASK 0000300u /* control output enable mask */ +#define CCN_EC2 0000200u /* (W) C2 output enable */ +#define CCN_EC1 0000100u /* (X) C1 output enable */ +#define CCN_CX_MASK 0000060u /* output mask */ +#define CCN_C2 0000040u /* (Q) C2 output [RTS] */ +#define CCN_C1 0000020u /* (T) C1 output [DTR] */ +#define CCN_STAT_MASK 0000017u /* status RAM mask */ +#define CCN_ESX_MASK 0000014u /* status interrupt enable mask */ +#define CCN_ES2 0000010u /* (Y) S2 interrupt enable */ +#define CCN_ES1 0000004u /* (Z) S1 interrupt enable */ +#define CCN_SX_MASK 0000003u /* status mask */ +#define CCN_S2 0000002u /* (C) S2 status [DCD]*/ +#define CCN_S1 0000001u /* (D) S1 status [DSR] */ + +#define CCN_CHAN_SHIFT 8 /* channel number alignment shift */ +#define CCN_CX_SHIFT 4 /* control alignment shift */ +#define CCN_ECX_SHIFT 2 /* control output enable alignment shift (to Cx) */ +#define CCN_ESX_SHIFT 2 /* status interrupt enable alignment shift */ + +#define CCN_CHAN(c) (((c) & CCN_CHAN_MASK) >> CCN_CHAN_SHIFT) +#define CCN_ECX(c) (((c) & CCN_ECX_MASK) >> CCN_ECX_SHIFT) +#define CCN_CX(c) (((c) & CCN_CX_MASK) >> CCN_CX_SHIFT) +#define CCN_ESX(c) (((c) & CCN_ESX_MASK) >> CCN_ESX_SHIFT) + +static const BITSET_NAME tci_control_names [] = { /* TCI control word names */ + "master reset", /* bit 0 */ + "reset interrupt", /* bit 1 */ + "scan", /* bit 2 */ + "update", /* bit 3 */ + NULL, /* bit 4 */ + NULL, /* bit 5 */ + NULL, /* bit 6 */ + NULL, /* bit 7 */ + "EC2", /* bit 8 */ + "EC1", /* bit 9 */ + "\1C2\0~C2", /* bit 10 */ + "\1C1\0~C1", /* bit 11 */ + "ES2", /* bit 12 */ + "ES1", /* bit 13 */ + "\1S2\0~S2", /* bit 14 */ + "\1S1\0~S1" /* bit 15 */ + }; + +static const BITSET_FORMAT tci_control_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tci_control_names, 0, msb_first, has_alt, no_bar) }; + + +/* TCI status word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | - | 1 | I | 1 | channel | - | - | J | K | Y | Z | C | D | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define CST_DIO_OK 0040000u /* direct I/O OK to use (always set) */ +#define CST_IRQ 0020000u /* (I) interrupt request */ +#define CST_ON 0010000u /* (always set) */ +#define CST_CHAN_MASK 0007400u /* channel number mask */ +#define CST_IX_MASK 0000060u /* status interrupt mask */ +#define CST_I2 0000040u /* (J) S2 interrupt */ +#define CST_I1 0000020u /* (K) S1 interrupt */ +#define CST_ESX_MASK 0000014u /* status interrupt enable mask */ +#define CST_ES2 0000010u /* (Y) S2 interrupt enable */ +#define CST_ES1 0000004u /* (Z) S1 interrupt enable */ +#define CST_SX_MASK 0000003u /* status mask */ +#define CST_S2 0000002u /* (C) S2 status [DCD] */ +#define CST_S1 0000001u /* (D) S1 status [DSR] */ + +#define CST_CHAN_SHIFT 8 /* channel number alignment shift */ +#define CST_IX_SHIFT 4 /* status interrupt alignment shift */ + +#define CST_CHAN(n) ((n) << CST_CHAN_SHIFT & CST_CHAN_MASK) +#define CST_IX(i) ((i) << CST_IX_SHIFT & CST_IX_MASK) + +static const BITSET_NAME tci_status_names [] = { /* TCI status word names */ + "interrupt", /* bit 2 */ + NULL, /* bit 3 */ + NULL, /* bit 4 */ + NULL, /* bit 5 */ + NULL, /* bit 6 */ + NULL, /* bit 7 */ + NULL, /* bit 8 */ + NULL, /* bit 9 */ + "I2", /* bit 10 */ + "I1", /* bit 11 */ + "ES2", /* bit 12 */ + "ES1", /* bit 13 */ + "\1S2\0~S2", /* bit 14 */ + "\1S1\0~S1" /* bit 15 */ + }; + +static const BITSET_FORMAT tci_status_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tci_status_names, 0, msb_first, has_alt, no_bar) }; + + +/* TCI #1 serial line bits */ + +#define RTS CCN_C2 /* TCI #1 C2 = Request to Send */ +#define DTR CCN_C1 /* TCI #1 C1 = Data Terminal Ready */ +#define DCD CCN_S2 /* TCI #1 S2 = Data Carrier Detect */ +#define DSR CCN_S1 /* TCI #1 S1 = Data Set Ready */ + +static const BITSET_NAME tci_line_names [] = { /* TCI serial line status names */ + "RTS", /* bit 10 */ + "DTR", /* bit 11 */ + NULL, /* bit 12 */ + NULL, /* bit 13 */ + "DCD", /* bit 14 */ + "DSR" /* bit 15 */ + }; + +static const BITSET_FORMAT tci_line_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (tci_line_names, 0, msb_first, no_alt, no_bar) }; + + +/* ATC global state */ + +t_bool atc_is_polling = TRUE; /* TRUE if the ATC is polling for the simulation console */ + + +/* TDI interface state */ + +static HP_WORD tdi_control_word = 0; /* control word */ +static HP_WORD tdi_status_word = 0; /* status word */ +static HP_WORD tdi_read_word = 0; /* read word */ +static HP_WORD tdi_write_word = 0; /* write word */ + +static FLIP_FLOP tdi_interrupt_mask = SET; /* interrupt mask flip-flop */ +static FLIP_FLOP tdi_data_flag = CLEAR; /* data flag */ + +static int32 fast_data_time = FAST_IO_TIME; /* fast receive/send time */ + + +/* TDI per-channel state */ + +static HP_WORD recv_status [RECV_CHAN_COUNT]; /* receive status words */ +static HP_WORD recv_param [RECV_CHAN_COUNT]; /* receive parameter words */ +static HP_WORD recv_buffer [RECV_CHAN_COUNT]; /* receive character buffers */ + +static HP_WORD send_status [SEND_CHAN_COUNT]; /* send status words */ +static HP_WORD send_param [SEND_CHAN_COUNT]; /* send parameter words */ +static HP_WORD send_buffer [SEND_CHAN_COUNT]; /* send character buffers */ + + +/* TCI interface state */ + +static HP_WORD tci_control_word = 0; /* control word */ +static HP_WORD tci_status_word = 0; /* status word */ +static uint32 tci_cntr = 0; /* channel counter */ + +static FLIP_FLOP tci_interrupt_mask = SET; /* interrupt mask flip-flop */ +static FLIP_FLOP tci_scan = CLEAR; /* scanning enabled flip-flop */ + + +/* TCI per-channel state */ + +static uint8 cntl_status [TERM_COUNT]; /* C2/C1/S2/S1 line status */ +static uint8 cntl_param [TERM_COUNT]; /* ES2/ES1/S2/S1 parameter RAM */ + + +/* ATC local SCP support routines */ + +static CNTLR_INTRF atcd_interface; +static CNTLR_INTRF atcc_interface; + +static t_stat atc_set_endis (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat atc_set_mode (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat atc_show_mode (FILE *st, UNIT *uptr, int32 value, CONST void *desc); +static t_stat atc_show_status (FILE *st, UNIT *uptr, int32 value, CONST void *desc); + +static t_stat atcd_reset (DEVICE *dptr); +static t_stat atcc_reset (DEVICE *dptr); + +static t_stat atcd_attach (UNIT *uptr, CONST char *cptr); +static t_stat atcd_detach (UNIT *uptr); + + +/* ATC local utility routines */ + +static void tdi_set_interrupt (void); +static void tdi_master_reset (void); +static void tci_master_reset (void); + +static t_stat line_service (UNIT *uptr); +static t_stat poll_service (UNIT *uptr); +static t_stat activate_unit (UNIT *uptr, ACTIVATOR reason); +static uint32 service_time (HP_WORD control, ACTIVATOR reason); +static void store (HP_WORD control, HP_WORD data); +static void receive (int32 channel, int32 data, t_bool loopback); +static void diagnose (HP_WORD control, int32 data); +static void scan_channels (int32 channel); +static HP_WORD scan_status (void); + + +/* ATC SCP data structures */ + +DEVICE atcd_dev; /* incomplete device structure */ +DEVICE atcc_dev; /* incomplete device structure */ + + +/* Terminal multiplexer library structures. + + The ATC uses the connection line order feature to bypass channel 0, which is + dedicated to the system console. For convenience, the system console is + connected to the simulation console. As such, it calls the console I/O + routines instead of the terminal multiplexer routines. + + User-defined line order is not supported. +*/ + +static int32 atcd_order [TERM_COUNT] = { /* line connection order */ + 1, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15 }; + +static TMLN atcd_ldsc [TERM_COUNT] = { /* line descriptors */ + { 0 } + }; + +static TMXR atcd_mdsc = { /* multiplexer descriptor */ + TERM_COUNT, /* number of terminal lines */ + 0, /* listening port (reserved) */ + 0, /* master socket (reserved) */ + atcd_ldsc, /* line descriptors */ + atcd_order, /* line connection order */ + NULL /* multiplexer device (derived internally) */ + }; + + +/* Device information blocks */ + +static DIB atcd_dib = { + &atcd_interface, /* device interface */ + 7, /* device number */ + SRNO_UNUSED, /* service request number */ + 0, /* interrupt priority */ + INTMASK_E /* interrupt mask */ + }; + +static DIB atcc_dib = { + &atcc_interface, /* device interface */ + 8, /* device number */ + SRNO_UNUSED, /* service request number */ + 8, /* interrupt priority */ + INTMASK_E /* interrupt mask */ + }; + + +/* Unit lists. + + The first sixteen TDI units correspond to the sixteen multiplexer main + send/receive channels. These handle character I/O via the Telnet library. A + seventeenth unit is responsible for polling for connections and socket I/O. + It also holds the master socket. + + Channel 0 is reserved for the system console and is connected to the + simulation console. As such, it's not likely to be using an HP terminal + emulator, so the default is CAPSLOCK input mode and 7P output mode. The + remainder of the channels default to NOCAPSLOCK and 7B, as they're likely to + be connected to HP terminals or terminal emulators. All channels initially + omit the UNIT_MODEM flag to allow the MPE terminal subtype configuration to + determine which channels support data sets and which do not. + + The TDI line service routine runs only when there are characters to read or + write. It is scheduled either at a realistic rate corresponding to the + programmed baud rate of the channel to be serviced, or at a somewhat faster + optimized rate. The multiplexer connection and input poll must run + continuously, but it may operate much more slowly, as the only requirement is + that it must not present a perceptible lag to human input. It is coscheduled + with the process clock to permit idling. The poll unit is hidden by + disabling it, so as to present a logical picture of the multiplexer to the + user. + + The TCI does not use any units, but a dummy one is defined to satisfy SCP + requirements. + + + Implementation notes: + + 1. There are no units corresponding to the auxiliary receive channels. This + is because reception isn't scheduled on these channels but instead occurs + concurrently with the main channel that is connected to the auxiliary + channels. +*/ + +static UNIT atcd_unit [UNIT_COUNT] = { + { UDATA (&line_service, TT_MODE_7P | UNIT_LOCALACK | UNIT_CAPSLOCK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&line_service, TT_MODE_7B | UNIT_LOCALACK, 0) }, + { UDATA (&poll_service, UNIT_ATTABLE | UNIT_DIS | UNIT_IDLE, 0), POLL_TIME } /* multiplexer poll unit */ + }; + +static UNIT atcc_unit [] = { /* a dummy unit to satisfy SCP requirements */ + { UDATA (NULL, 0, 0) } + }; + + +/* Register lists. + + The internal state of the TDI and TCI are exposed to the user and to ensure + that SAVE and RESTORE pick up the values. The user may set FTIME explicitly + as needed to accommodate software that does not work with the default + setting. + + + Implementation notes: + + 1. The TCI control and status line register definitions use the VM-defined + FBDATA macro. This macro defines a flag that is replicated in the same + bit position in each element of an array. +*/ + +static REG atcd_reg [] = { +/* Macro Name Location Radix Width Offset Depth Flags */ +/* ------ ------ ------------------- ----- ----- ------ ---------------- --------------- */ + { ORDATA (CNTL, tdi_control_word, 16), REG_FIT }, + { ORDATA (STAT, tdi_status_word, 16), REG_FIT }, + { ORDATA (READ, tdi_read_word, 16), REG_A | REG_FIT }, + { ORDATA (WRITE, tdi_write_word, 16), REG_A | REG_FIT }, + { FLDATA (FLAG, tdi_data_flag, 0) }, + { FLDATA (MASK, tdi_interrupt_mask, 0) }, + { DRDATA (FTIME, fast_data_time, 24), PV_LEFT }, + { BRDATA (RSTAT, recv_status, 8, 16, RECV_CHAN_COUNT) }, + { BRDATA (RPARM, recv_param, 8, 16, RECV_CHAN_COUNT) }, + { BRDATA (RBUFR, recv_buffer, 8, 16, RECV_CHAN_COUNT), REG_A }, + { BRDATA (SSTAT, send_status, 8, 16, SEND_CHAN_COUNT) }, + { BRDATA (SPARM, send_param, 8, 16, SEND_CHAN_COUNT) }, + { BRDATA (SBUFR, send_buffer, 8, 16, SEND_CHAN_COUNT), REG_A }, + { FLDATA (POLL, atc_is_polling, 0), REG_HRO }, + + DIB_REGS (atcd_dib), + + { NULL } + }; + +static REG atcc_reg [] = { +/* Macro Name Location Width Offset Depth Flags */ +/* ------ ------ ------------------- ----- ------ ---------- ------- */ + { ORDATA (CNTL, tci_control_word, 16), REG_FIT }, + { ORDATA (STAT, tci_status_word, 16), REG_FIT }, + { DRDATA (CNTR, tci_cntr, 4) }, + { FLDATA (SCAN, tci_scan, 0) }, + { FLDATA (MASK, tci_interrupt_mask, 0) }, + + { FBDATA (C2, cntl_status, 5, TERM_COUNT, PV_RZRO) }, + { FBDATA (C1, cntl_status, 4, TERM_COUNT, PV_RZRO) }, + { FBDATA (S2, cntl_status, 1, TERM_COUNT, PV_RZRO) }, + { FBDATA (S1, cntl_status, 0, TERM_COUNT, PV_RZRO) }, + + { FBDATA (ES2, cntl_param, 3, TERM_COUNT, PV_RZRO) }, + { FBDATA (ES1, cntl_param, 2, TERM_COUNT, PV_RZRO) }, + { FBDATA (MS2, cntl_param, 1, TERM_COUNT, PV_RZRO) }, + { FBDATA (MS1, cntl_param, 0, TERM_COUNT, PV_RZRO) }, + + DIB_REGS (atcc_dib), + + { NULL } + }; + + +/* Modifier lists */ + +typedef enum { + Fast_Time, + Real_Time, + Terminal, + Diagnostic + } DEVICE_MODES; + + +static MTAB atcd_mod [] = { +/* Mask Value Match Value Print String Match String Validation Display Descriptor */ +/* ------------- ------------- ---------------- ------------ ---------- ------- ---------- */ + { UNIT_MODEM, UNIT_MODEM, "data set", NULL, NULL, NULL, NULL }, + { UNIT_MODEM, 0, "direct", NULL, NULL, NULL, NULL }, + + { UNIT_LOCALACK, UNIT_LOCALACK, "local ENQ/ACK", "LOCALACK", NULL, NULL, NULL }, + { UNIT_LOCALACK, 0, "remote ENQ/ACK", "REMOTEACK", NULL, NULL, NULL }, + + { UNIT_CAPSLOCK, UNIT_CAPSLOCK, "CAPS LOCK down", "CAPSLOCK", NULL, NULL, NULL }, + { UNIT_CAPSLOCK, 0, "CAPS LOCK up", "NOCAPSLOCK", NULL, NULL, NULL }, + + { TT_MODE, TT_MODE_UC, "UC output", "UC", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_7B, "7b output", "7B", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_7P, "7p output", "7P", NULL, NULL, NULL }, + { TT_MODE, TT_MODE_8B, "8b output", "8B", NULL, NULL, NULL }, + +/* Entry Flags Value Print String Match String Validation Display Descriptor */ +/* -------------------- ----------- ------------- ------------ --------------- ---------------- ------------------- */ + { MTAB_XUN | MTAB_NC, 0, "LOG", "LOG", &tmxr_set_log, &tmxr_show_log, (void *) &atcd_mdsc }, + { MTAB_XUN | MTAB_NC, 0, NULL, "NOLOG", &tmxr_set_nolog, NULL, (void *) &atcd_mdsc }, + { MTAB_XUN, 0, NULL, "DISCONNECT", &tmxr_dscln, NULL, (void *) &atcd_mdsc }, + + { MTAB_XDV, Fast_Time, NULL, "FASTTIME", &atc_set_mode, NULL, (void *) &atcd_dev }, + { MTAB_XDV, Real_Time, NULL, "REALTIME", &atc_set_mode, NULL, (void *) &atcd_dev }, + { MTAB_XDV, Terminal, NULL, "TERMINAL", &atc_set_mode, NULL, (void *) &atcd_dev }, + { MTAB_XDV, Diagnostic, NULL, "DIAGNOSTIC", &atc_set_mode, NULL, (void *) &atcd_dev }, + { MTAB_XDV, 0, "MODES", NULL, NULL, &atc_show_mode, (void *) &atcd_dev }, + + { MTAB_XDV, 0, "", NULL, NULL, &atc_show_status, (void *) &atcd_mdsc }, + { MTAB_XDV | MTAB_NMO, 1, "CONNECTIONS", NULL, NULL, &tmxr_show_cstat, (void *) &atcd_mdsc }, + { MTAB_XDV | MTAB_NMO, 0, "STATISTICS", NULL, NULL, &tmxr_show_cstat, (void *) &atcd_mdsc }, + + { MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib }, + { MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib }, + { MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &atcd_dib }, + + { MTAB_XDV | MTAB_NMO, 1, NULL, "ENABLED", &atc_set_endis, NULL, NULL }, + { MTAB_XDV | MTAB_NMO, 0, NULL, "DISABLED", &atc_set_endis, NULL, NULL }, + { 0 } + }; + +static MTAB atcc_mod [] = { +/* Entry Flags Value Print String Match String Validation Display Descriptor */ +/* ----------- ----------- ------------ ------------ ------------- -------------- ------------------ */ + { MTAB_XDV, Terminal, NULL, "TERMINAL", &atc_set_mode, NULL, (void *) &atcc_dev }, + { MTAB_XDV, Diagnostic, NULL, "DIAGNOSTIC", &atc_set_mode, NULL, (void *) &atcc_dev }, + { MTAB_XDV, 1, "MODES", NULL, NULL, &atc_show_mode, (void *) &atcc_dev }, + + { MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib }, + { MTAB_XDV, VAL_INTMASK, "INTMASK", "INTMASK", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib }, + { MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &atcc_dib }, + { 0 } + }; + + +/* Debugging trace lists */ + +static DEBTAB atcd_deb [] = { + { "CSRW", DEB_CSRW }, /* Interface control, status, read, and write actions */ + { "SERV", DEB_SERV }, /* Channel unit service scheduling calls */ + { "PSERV", DEB_PSERV }, /* Poll unit service scheduling calls */ + { "XFER", DEB_XFER }, /* Data receptions and transmissions */ + { "IOBUS", DEB_IOB }, /* Interface I/O bus signals and data words */ + { NULL, 0 } + }; + +static DEBTAB atcc_deb [] = { + { "CSRW", DEB_CSRW }, /* Interface control, status, read, and write actions */ + { "PSERV", DEB_PSERV }, /* Poll unit service scheduling calls */ + { "XFER", DEB_XFER }, /* Control and status line changes */ + { "IOBUS", DEB_IOB }, /* Interface I/O bus signals and data words */ + { NULL, 0 } + }; + + +/* Device descriptors. + + Both devices may be disabled. However, we want to be able to disable the TDI + while it is polling for the simulation console, which the standard SCP + routine will not do (it refuses if any unit is active). So we define our own + DISABLED and ENABLED modifiers and a validation routine that sets or clears + the DEV_DIS flag and then calls atcd_reset. The reset routine cancels or + reenables the poll as indicated. + + + Implementation notes: + + 1. The ATCD device does not specify the DEV_DISABLE flag to avoid the + DISABLED and ENABLED modifiers from being listed twice for a SHOW ATCD + MODIFIERS command. SIMH 3.9 tested for user-defined ENABLED/DISABLED + modifiers and skipped the printing that results from specifying + DEV_DISABLE. SIMH 4.0 no longer does this, so we omit the flag to + suppress the duplicate printing (the flag is otherwise used only to + validate the SET DISABLED command). +*/ + +DEVICE atcd_dev = { + "ATCD", /* device name */ + atcd_unit, /* unit array */ + atcd_reg, /* register array */ + atcd_mod, /* modifier array */ + UNIT_COUNT, /* number of units */ + 10, /* address radix */ + PA_WIDTH, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + DV_WIDTH, /* data width */ + &tmxr_ex, /* examine routine */ + &tmxr_dep, /* deposit routine */ + &atcd_reset, /* reset routine */ + NULL, /* boot routine */ + &atcd_attach, /* attach routine */ + &atcd_detach, /* detach routine */ + &atcd_dib, /* device information block pointer */ + DEV_DEBUG, /* device flags */ + 0, /* debug control flags */ + atcd_deb, /* debug flag name array */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + +DEVICE atcc_dev = { + "ATCC", /* device name */ + atcc_unit, /* unit array */ + atcc_reg, /* register array */ + atcc_mod, /* modifier array */ + 1, /* number of units */ + 10, /* address radix */ + PA_WIDTH, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + DV_WIDTH, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &atcc_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &atcc_dib, /* device information block pointer */ + DEV_DEBUG | DEV_DISABLE, /* device flags */ + 0, /* debug control flags */ + atcc_deb, /* debug flag name array */ + NULL, /* memory size change routine */ + NULL }; /* logical device name */ + + + +/* ATC local SCP support routines */ + + + +/* TDI interface. + + The interface is installed on the IOP bus and receives direct I/O commands + from the IOP. In simulation, the asserted signals on the bus are represented + as bits in the inbound_signals set. Each signal is processed sequentially in + numerical order, and a set of similar outbound_signals is assembled and + returned to the caller, simulating assertion of the corresponding backplane + signals. + + Before a channel can receive or send, it must be configured. The number of + the channel to configure is set via a CIO instruction, followed by parameters + for baud rate and character size via WIO instructions. Data to be sent is + passed to the interface via WIO, while received data is picked up with RIO + instructions. + + When a channel has completed sending or receiving a character, it will set + its completion flag. If the TDI data flag is clear, indicating that all + prior interrupts have been serviced, a scan of the serviced channel is made + to see if the channel is enabled to interrupt. If it is, the TDI data flag + will be set, the channel flag will be cleared, and an interrupt will be + requested. When the interrupt is serviced and acknowledged, the flag will be + cleared, and the scan will continue to look for other channel flags. + + The status word is set during the scan to reflect the interrupting channel + status. If status bit 3 (DST_COMPLETE) is clear, then status bits 5, 6, and + 7 (DST_SEND_IRQ, DST_CHAR_LOST, and DST_BREAK) retain their values from the + prior send or receive interrupt. + + + Implementation notes: + + 1. In hardware, the DIO OK status bit (bit 1) is denied when a store to the + recirculating memory is pending and is reasserted once the designated + channel rotates into the window and the parameter or data is stored. The + duration of the denial varies from 0 to 69.44 microseconds, depending on + the location of the window in memory when DWRITESTB is asserted. In + simulation, DIO OK is always asserted. + + 2. Receipt of a DRESETINT signal clears the interrupt request and active + flip-flops but does not cancel a request pending but not yet serviced by + the IOP. However, when the IOP does service the request by asserting + INTPOLLIN, the interface routine returns INTPOLLOUT, which will cancel + the request. +*/ + +static SIGNALS_DATA atcd_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value) +{ +INBOUND_SIGNAL signal; +INBOUND_SET working_set = inbound_signals; +HP_WORD outbound_value = 0; +OUTBOUND_SET outbound_signals = NO_SIGNALS; + +dprintf (atcd_dev, DEB_IOB, "Received data %06o with signals %s\n", + inbound_value, fmt_bitset (inbound_signals, inbound_format)); + +while (working_set) { /* while signals remain */ + signal = IONEXTSIG (working_set); /* isolate the next signal */ + + switch (signal) { /* dispatch the I/O signal */ + + case DCONTSTB: + dprintf (atcd_dev, DEB_CSRW, (inbound_value & DCN_ENABLE + ? "Control is %s | channel %u\n" + : "Control is %s\n"), + fmt_bitset (inbound_value, tdi_control_format), + DCN_CHAN (inbound_value)); + + tdi_control_word = inbound_value; /* save the control word */ + + if (tdi_control_word & DCN_MR) /* if master reset is requested */ + tdi_master_reset (); /* then perform an I/O reset */ + + if (tdi_control_word & DCN_IRQ_RESET) /* if reset interrupt is requested */ + dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */ + + if (tdi_control_word & DCN_ENABLE) /* if output is enabled */ + store (tdi_control_word, tdi_write_word); /* then store the parameter or data word */ + + if (tdi_control_word & DCN_ACKN) { /* if acknowledge interrupt is requested */ + tdi_data_flag = CLEAR; /* then clear the data flag */ + + scan_channels (SCAN_ALL); /* scan all channels for a new interrupt request */ + } + break; + + + case DSTATSTB: + tdi_status_word |= DST_DIO_OK; /* the interface is always ready for commands */ + + if (dibptr->interrupt_request == SET) /* reflect the interrupt request value */ + tdi_status_word |= DST_IRQ; /* in the status word */ + else /* to indicate */ + tdi_status_word &= ~DST_IRQ; /* whether or not a request is pending */ + + if (tdi_data_flag == SET) /* reflect the data flag value */ + tdi_status_word |= DST_COMPLETE; /* in the status word */ + else /* to indicate */ + tdi_status_word &= ~DST_COMPLETE; /* whether or not a channel has completed */ + + outbound_value = tdi_status_word; /* return the status word */ + + dprintf (atcd_dev, DEB_CSRW, "Status is %s\n", + fmt_bitset (outbound_value, tdi_status_format)); + break; + + + case DWRITESTB: + tdi_write_word = inbound_value; /* save the data or parameter word */ + + if (DPRINTING (atcd_dev, DEB_CSRW)) + if (inbound_value & DPI_IS_PARAM) + hp_debug (&atcd_dev, DEB_CSRW, "Parameter is %s%u bits | %u baud\n", + fmt_bitset (inbound_value, tdi_parameter_format), + bits_per_char [DPI_CHAR_SIZE (inbound_value)], + BAUD_RATE (inbound_value)); + + else + hp_debug (&atcd_dev, DEB_CSRW, "Output data is %s%04o\n", + fmt_bitset (inbound_value, tdi_output_data_format), + DDS_DATA (inbound_value)); + break; + + + case DREADSTB: + outbound_value = tdi_read_word; /* return the data word */ + + dprintf (atcd_dev, DEB_CSRW, "Input data is channel %u | %s%04o\n", + DDR_TO_CHAN (outbound_value), + fmt_bitset (outbound_value, tdi_input_data_format), + DDR_TO_DATA (outbound_value)); + break; + + + case DSETINT: + dibptr->interrupt_request = SET; /* request an interrupt */ + + if (tdi_interrupt_mask) /* if the interrupt mask is satisfied */ + outbound_signals |= INTREQ; /* then assert the INTREQ signal */ + break; + + + case DRESETINT: + dibptr->interrupt_active = CLEAR; /* reset the interrupt active flip-flop */ + break; + + + case INTPOLLIN: + if (dibptr->interrupt_request) { /* if a request is pending */ + dibptr->interrupt_request = CLEAR; /* then clear it */ + dibptr->interrupt_active = SET; /* and mark it now active */ + + outbound_signals |= INTACK; /* acknowledge the interrupt */ + outbound_value = dibptr->device_number; /* and return our device number */ + } + else /* otherwise the request has been reset */ + outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */ + break; + + + case DSETMASK: + if (dibptr->interrupt_mask == INTMASK_E) /* if the mask is always enabled */ + tdi_interrupt_mask = SET; /* then set the mask flip-flop */ + else /* otherwise */ + tdi_interrupt_mask = D_FF (dibptr->interrupt_mask /* set the mask flip-flop if the mask bit */ + & inbound_value); /* is present in the mask value */ + + if (tdi_interrupt_mask && dibptr->interrupt_request) /* if the mask is enabled and a request is pending */ + outbound_signals |= INTREQ; /* then assert INTREQ */ + break; + + + case DSTARTIO: /* not used by this interface */ + case XFERERROR: /* not used by this interface */ + case ACKSR: /* not used by this interface */ + case TOGGLESR: /* not used by this interface */ + case TOGGLESIOOK: /* not used by this interface */ + case TOGGLEINXFER: /* not used by this interface */ + case TOGGLEOUTXFER: /* not used by this interface */ + case READNEXTWD: /* not used by this interface */ + case PREADSTB: /* not used by this interface */ + case PWRITESTB: /* not used by this interface */ + case PCMD1: /* not used by this interface */ + case PCONTSTB: /* not used by this interface */ + case PSTATSTB: /* not used by this interface */ + case DEVNODB: /* not used by this interface */ + case SETINT: /* not used by this interface */ + case EOT: /* not used by this interface */ + case SETJMP: /* not used by this interface */ + case CHANSO: /* not used by this interface */ + case PFWARN: /* not used by this interface */ + break; + } + + IOCLEARSIG (working_set, signal); /* remove the current signal from the set */ + } + +dprintf (atcd_dev, DEB_IOB, "Returned data %06o with signals %s\n", + outbound_value, fmt_bitset (outbound_signals, outbound_format)); + +return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */ +} + + +/* TCI interface. + + The interface is installed on the IOP bus and receives direct I/O commands + from the IOP. In simulation, the asserted signals on the bus are represented + as bits in the inbound_signals set. Each signal is processed sequentially in + numerical order, and a set of similar outbound_signals is assembled and + returned to the caller, simulating assertion of the corresponding backplane + signals. For this interface, a read order executes identically to a test + order, and a write order is ignored. + + The control word contains three independent enables that affect the + interpretation of the rest of the word. Bit 3 (CCN_UPDATE) must be set to + enable storing bits 12-15 (CCN_ES2/1 and CCN_S2/1) into the state RAM. Bits + 8 (CCN_EC2) and 9 (CCN_EC1) must be set to enable storing bits 10 (CCN_C2) + and 11 (CCN_C1), respectively, into the addressable latch. If none of these + enables are set, then only bits 0-2 are interpreted. + + + Implementation notes: + + 1. The cntl_status array contains the values for the serial device control + and status lines. The line bit positions in the array correspond to the + C2/C1 and S2/S1 positions in the control word. + + 2. A control word write directed to a given channel sets that channel's + UNIT_MODEM flag to indicate that the serial line status should be + updated at each input poll service. + + 3. The terminal multiplexer library will disconnect an associated Telnet + session if DTR is dropped. + + 4. Receipt of a DRESETINT signal clears the interrupt request and active + flip-flops but does not cancel a request pending but not yet serviced by + the IOP. However, when the IOP does service the request by asserting + INTPOLLIN, the interface routine returns INTPOLLOUT, which will cancel + the request. +*/ + +static SIGNALS_DATA atcc_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value) +{ +INBOUND_SIGNAL signal; +INBOUND_SET working_set = inbound_signals; +HP_WORD outbound_value = 0; +OUTBOUND_SET outbound_signals = NO_SIGNALS; +int32 set_lines, clear_lines; + +dprintf (atcc_dev, DEB_IOB, "Received data %06o with signals %s\n", + inbound_value, fmt_bitset (inbound_signals, inbound_format)); + +while (working_set) { /* while signals remain */ + signal = IONEXTSIG (working_set); /* isolate the next signal */ + + switch (signal) { /* dispatch the I/O signal */ + + case DCONTSTB: + tci_cntr = CCN_CHAN (inbound_value); /* set the counter to the target channel */ + + dprintf (atcc_dev, DEB_CSRW, "Control is channel %u | %s\n", + tci_cntr, fmt_bitset (inbound_value, tci_control_format)); + + tci_control_word = inbound_value; /* save the control word */ + + line_unit [tci_cntr].flags |= UNIT_MODEM; /* set the modem control flag on this unit */ + + if (tci_control_word & CCN_MR) /* if master reset is requested */ + tci_master_reset (); /* then perform an I/O reset */ + + if (tci_control_word & CCN_IRQ_RESET) /* if reset interrupt is requested */ + dibptr->interrupt_request = CLEAR; /* then clear the interrupt request */ + + cntl_status [tci_cntr] = cntl_status [tci_cntr] /* set the control lines */ + & ~CCN_ECX (tci_control_word) /* that are enabled for output */ + | CCN_CX_MASK /* to the control bits */ + & CCN_ECX (tci_control_word) /* that are enabled */ + & tci_control_word; /* in the control word */ + + dprintf (atcc_dev, DEB_XFER, "Channel %u line status is %s\n", + tci_cntr, fmt_bitset (cntl_status [tci_cntr], tci_line_format)); + + if (atcc_dev.flags & DEV_DIAG) { /* if the interface is in diagnostic mode */ + cntl_status [tci_cntr ^ 1] = /* then loop the control lines */ + cntl_status [tci_cntr ^ 1] & ~CCN_SX_MASK /* back to the alternate channel */ + | CCN_CX (cntl_status [tci_cntr]); /* from the selected channel */ + + dprintf (atcc_dev, DEB_XFER, "Channel %u line status is %s\n", + tci_cntr ^ 1, fmt_bitset (cntl_status [tci_cntr ^ 1], tci_line_format)); + } + + else if (tci_control_word & CCN_ECX_MASK) { /* otherwise if either control line is enabled */ + set_lines = 0; /* then prepare the multiplexer library to set */ + clear_lines = 0; /* the modem status (either real or simulated) */ + + if (tci_control_word & CCN_EC2) /* if control line 2 is enabled for output */ + if (RTS & cntl_status [tci_cntr]) /* then if the line is asserted */ + set_lines |= TMXR_MDM_RTS; /* then set the RTS line up */ + else /* otherwise */ + clear_lines |= TMXR_MDM_RTS; /* set it down */ + + if (tci_control_word & CCN_EC1) /* if control line 1 is enabled for output */ + if (DTR & cntl_status [tci_cntr]) /* then if the line is asserted */ + set_lines |= TMXR_MDM_DTR; /* then set the DTR line up */ + else { /* otherwise */ + clear_lines |= TMXR_MDM_DTR; /* set it down */ + + if (cntl_status [tci_cntr] & DCD) /* setting DTR down will disconnect the channel */ + dprintf (atcc_dev, DEB_CSRW, "Channel %u disconnected by DTR drop\n", + tci_cntr); + } + + tmxr_set_get_modem_bits (&atcd_ldsc [tci_cntr], /* tell the multiplexer library */ + set_lines, clear_lines, /* to set or clear the indicated lines */ + NULL); /* and omit returning the current status */ + } + + if (tci_control_word & CCN_UPDATE) /* if the status output is enabled */ + cntl_param [tci_cntr] = tci_control_word /* then store the status line enables and states */ + & CCN_STAT_MASK; /* in the parameter RAM */ + + tci_scan = D_FF (tci_control_word & CCN_SCAN); /* set or clear the scan flip-flop as directed */ + + if (tci_scan) /* if scanning is enabled */ + scan_status (); /* then look for channel status changes */ + break; + + + case DREADSTB: /* RIO and TIO return the same value */ + case DSTATSTB: + tci_status_word = CST_DIO_OK | CST_ON /* form the status word */ + | CST_CHAN (tci_cntr) + | cntl_param [tci_cntr] & CST_ESX_MASK + | cntl_status [tci_cntr] & CST_SX_MASK + | scan_status (); + + if (dibptr->interrupt_request == SET) /* reflect the interrupt request value */ + tci_status_word |= CST_IRQ; /* in the status word */ + + outbound_value = tci_status_word; /* return the status word */ + + dprintf (atcc_dev, DEB_CSRW, "Status is channel %u | %s\n", + tci_cntr, fmt_bitset (outbound_value, tci_status_format)); + break; + + + case DSETINT: + dibptr->interrupt_request = SET; /* request an interrupt */ + + if (tci_interrupt_mask) /* if the interrupt mask is satisfied */ + outbound_signals |= INTREQ; /* then assert the INTREQ signal */ + break; + + + case DRESETINT: + dibptr->interrupt_active = CLEAR; /* reset the interrupt active flip-flop */ + break; + + + case INTPOLLIN: + if (dibptr->interrupt_request) { /* if a request is pending */ + dibptr->interrupt_request = CLEAR; /* then clear it */ + dibptr->interrupt_active = SET; /* and mark it now active */ + + outbound_signals |= INTACK; /* acknowledge the interrupt */ + outbound_value = dibptr->device_number; /* and return our device number */ + } + else /* otherwise the request has been reset */ + outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */ + break; + + + case DSETMASK: + if (dibptr->interrupt_mask == INTMASK_E) /* if the mask is always enabled */ + tci_interrupt_mask = SET; /* then set the mask flip-flop */ + else /* otherwise */ + tci_interrupt_mask = D_FF (dibptr->interrupt_mask /* set the mask flip-flop if the mask bit */ + & inbound_value); /* is present in the mask value */ + + if (tci_interrupt_mask && dibptr->interrupt_request) /* if the mask is enabled and a request is pending */ + outbound_signals |= INTREQ; /* then assert INTREQ */ + break; + + + case DWRITESTB: /* not used by this interface */ + case DSTARTIO: /* not used by this interface */ + case XFERERROR: /* not used by this interface */ + case ACKSR: /* not used by this interface */ + case TOGGLESR: /* not used by this interface */ + case TOGGLESIOOK: /* not used by this interface */ + case TOGGLEINXFER: /* not used by this interface */ + case TOGGLEOUTXFER: /* not used by this interface */ + case READNEXTWD: /* not used by this interface */ + case PREADSTB: /* not used by this interface */ + case PWRITESTB: /* not used by this interface */ + case PCMD1: /* not used by this interface */ + case PCONTSTB: /* not used by this interface */ + case PSTATSTB: /* not used by this interface */ + case DEVNODB: /* not used by this interface */ + case SETINT: /* not used by this interface */ + case EOT: /* not used by this interface */ + case SETJMP: /* not used by this interface */ + case CHANSO: /* not used by this interface */ + case PFWARN: /* not used by this interface */ + break; + } + + IOCLEARSIG (working_set, signal); /* remove the current signal from the set */ + } + +dprintf (atcc_dev, DEB_IOB, "Returned data %06o with signals %s\n", + outbound_value, fmt_bitset (outbound_signals, outbound_format)); + +return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */ +} + + +/* Enable or disable the TDI. + + This validation routine is entered with "value" set to 1 for an ENABLE and 0 + for a DISABLE, and "cptr" pointing to the next character after the keyword. + If the TDI is already enabled or disabled, respectively, the routine returns + with no further action. Otherwise, if "value" is 1, the device is enabled by + clearing the DEV_DIS flag, and the polling flag is set TRUE to indicate that + the TDI is polling for the simulation console. If "value" is 0, a check is + made to see if the TDI is listening for connections. If it is, the disable + request is rejected; the device must be detached first. Otherwise, the + device is disabled by setting the DEV_DIS flag, and the polling flag is set + FALSE to indicate that the TDI is no longer polling for the simulation + console (the PCLK device will take over when the polling flag is FALSE). + + In either case, the device is reset, which will restart or cancel the poll, + as appropriate. +*/ + +static t_stat atc_set_endis (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +if (value) /* if this is an ENABLE request */ + if (atcd_dev.flags & DEV_DIS) { /* then if the device is disabled */ + atcd_dev.flags &= ~DEV_DIS; /* then reenable it */ + atc_is_polling = TRUE; /* and set the polling flag */ + } + + else /* otherwise the device is already enabled */ + return SCPE_OK; /* so there's nothing to do */ + +else /* otherwise this is a DISABLE request */ + if (atcd_dev.flags & DEV_DIS) /* so if the device is already disabled */ + return SCPE_OK; /* so there's nothing to do */ + + else if (poll_unit.flags & UNIT_ATT) /* otherwise if the poll unit is still attached */ + return SCPE_NOFNC; /* then report that the command failed */ + + else { /* otherwise */ + atcd_dev.flags |= DEV_DIS; /* disable the device */ + atc_is_polling = FALSE; /* and clear the polling flag */ + } + +return atcd_reset (&atcd_dev); /* reset the TDI and restart or cancel polling */ +} + + +/* Set the device modes. + + The device flag implied by the DEVICE_MODES "value" passed to the routine is + set or cleared in the device specified by the "desc" parameter. The unit and + character pointers are not used. + + + Implementation notes: + + 1. In hardware, terminals and modems must be disconnected from the ATC and + loopback cables installed between each pair or channels when the + diagnostic is run. In simulation, setting DIAG mode detaches any + existing listening port, so that Telnet sessions will not interfere with + the internal loopback connections from the send to the receive channels. +*/ + +static t_stat atc_set_mode (UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ +DEVICE * const dptr = (DEVICE *) desc; /* a pointer to the device */ + +switch ((DEVICE_MODES) value) { /* dispatch based on the mode to set */ + + case Fast_Time: /* entering optimized timing mode */ + dptr->flags &= ~DEV_REALTIME; /* so clear the real-time flag */ + break; + + + case Real_Time: /* entering realistic timing mode */ + dptr->flags |= DEV_REALTIME; /* so set the flag */ + break; + + + case Terminal: /* entering terminal mode */ + dptr->flags &= ~DEV_DIAG; /* so clear the diagnostic flag */ + break; + + + case Diagnostic: /* entering the diagnostic mode */ + dptr->flags |= DEV_DIAG; /* so set the flag */ + + if (dptr == &atcd_dev) /* if we're setting the TDI mode */ + atcd_detach (&poll_unit); /* then detach any existing connections */ + break; + } + +return SCPE_OK; +} + + +/* Show the device modes. + + The output stream and device pointer are passed in the "st" and "desc" + parameters, respectively. If "value" is 0, then all of the flags are checked + for the TDI. If "value" is 1, then only the diagnostic flag is checked for + the TCI. The unit pointer is not used. +*/ + +static t_stat atc_show_mode (FILE *st, UNIT *uptr, int32 value, CONST void *desc) +{ +const DEVICE * const dptr = (const DEVICE *) desc; /* a pointer to the device */ + +if (value == 0) /* if this is the TDI */ + if (dptr->flags & DEV_REALTIME) /* then if the real-time flag is set */ + fputs ("realistic timing, ", st); /* then report that we are using realistic timing */ + else /* otherwise */ + fputs ("fast timing, ", st); /* report that we are using optimized timing */ + +if (dptr->flags & DEV_DIAG) /* if the diagnostic flag is set */ + fputs ("diagnostic mode", st); /* then report that we're in loopback mode */ +else /* otherwise */ + fputs ("terminal mode", st); /* we're in normal (terminal) mode */ + +return SCPE_OK; +} + + +/* Show the TDI device status. + + The attachment condition and connection count are printed to the stream + specified by "st" as part of the ATCD device display. The "desc" parameter + is a pointer to the terminal multiplexer library descriptor; the unit pointer + and value parameters are not used. +*/ + +static t_stat atc_show_status (FILE *st, UNIT *uptr, int32 value, CONST void *desc) +{ +if (poll_unit.flags & UNIT_ATT) /* if the poll unit is attached */ + fprintf (st, "attached to port %s, ", /* then report it */ + poll_unit.filename); /* with the listening port number */ +else /* otherwise */ + fprintf (st, "not attached, "); /* report the condition */ + +tmxr_show_summ (st, uptr, value, desc); /* also report the count of connections */ + +return SCPE_OK; +} + + +/* TDI device reset. + + This routine is called for a RESET or RESET ATCD command. It is the + simulation equivalent of the IORESET signal, which is asserted by the front + panel LOAD and DUMP switches. + + If a power-on reset (RESET -P) is being done, the poll timer is initialized. + In addition, the original FASTTIME setting is restored, in case it's been + changed by the user. + + If the polling flag is set, then start or resynchronize the poll unit with + the process clock to enable idling. If the CPU process clock is calibrated, + then the poll event service is synchronized with the process clock service. + Otherwise, the service time is set up but is otherwise asynchronous with the + process clock. + + If the polling flag is clear, then the poll is stopped, as it's not needed. + + + Implementation notes: + + 1. To synchronize events, the poll must be activated absolutely, as a + service event may already be scheduled, and normal activation will not + disturb an existing event. +*/ + +static t_stat atcd_reset (DEVICE *dptr) +{ +tdi_master_reset (); /* perform a master reset */ + +if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */ + sim_rtcn_init (poll_unit.wait, TMR_ATC); /* then initialize the poll timer */ + fast_data_time = FAST_IO_TIME; /* restore the initial fast data time */ + } + +if (atc_is_polling) { /* if we're polling for the simulation console */ + if (cpu_is_calibrated) /* then if the process clock is calibrated */ + poll_unit.wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */ + else /* otherwise */ + poll_unit.wait = POLL_TIME; /* set up an independent poll time */ + + sim_activate_abs (&poll_unit, poll_unit.wait); /* restart the poll timer */ + } + +else /* otherwise */ + sim_cancel (&poll_unit); /* cancel the poll */ + +return SCPE_OK; +} + + +/* TCI device reset. + + This routine is called for a RESET or RESET ATCC command. It is the + simulation equivalent of the IORESET signal, which is asserted by the front + panel LOAD and DUMP switches. + + If a power-on reset (RESET -P) is being done, then local modem control is + established by setting DTR on all channels. This is necessary so that + channels not controlled by the TCI will be able to connect (TCI-controlled + channels will have their DTR and RTS state set by the MPE TCI initialization + routine). +*/ + +static t_stat atcc_reset (DEVICE *dptr) +{ +uint32 channel; + +tci_master_reset (); /* perform a master reset */ + +if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */ + for (channel = 0; channel < TERM_COUNT; channel++) /* then for each terminal channel */ + tmxr_set_get_modem_bits (&atcd_ldsc [channel], /* set the DTR line on */ + TMXR_MDM_DTR, /* to allow non-TCI channels to connect */ + 0, NULL); + } + +return SCPE_OK; +} + + +/* Attach the TDI to a Telnet listening port. + + This routine is called by the ATTACH ATCD command to attach the TDI to + the listening port indicated by . Logically, it is the ATCD device + that is attached; however, SIMH only allows units to be attached. This makes + sense for devices such as tape drives, where the attached media is a property + of a specific drive. In our case, though, the listening port is a property + of the TDI card, not of any given serial line. As ATTACH ATCD is equivalent + to ATTACH ATCD0, the port would, by default, be attached to the first channel + and be reported there in a SHOW ATCD command. + + To preserve the logical picture, we attach the port to the Telnet poll unit, + which is normally disabled to inhibit its display. Attaching to a disabled + unit is not allowed, so we first enable the unit, then attach it, then + disable it again. Attachment is reported by the "atc_show_status" routine. + + A direct attach to the poll unit is allowed only when restoring a previously + saved session via the RESTORE command. +*/ + +static t_stat atcd_attach (UNIT *uptr, CONST char *cptr) +{ +t_stat status; + +if (atcd_dev.flags & DEV_DIAG) /* if the TDI is in diagnostic mode */ + return SCPE_NOFNC; /* then the command is not allowed */ + +if (uptr != line_unit /* if we're not attaching unit 0 */ + && (uptr != &poll_unit || !(sim_switches & SIM_SW_REST))) /* and not we're not restoring the poll unit */ + return SCPE_NOATT; /* then the unit specified is not attachable */ + +poll_unit.flags &= ~UNIT_DIS; /* enable the poll unit */ +status = tmxr_attach (&atcd_mdsc, &poll_unit, cptr); /* and attach it to the specified listening port */ +poll_unit.flags |= UNIT_DIS; /* and then disable it again */ + +return status; +} + + +/* Detach the TDI. + + Normally, this routine is called by the DETACH ATCD command, which is + equivalent to DETACH ATCD0. However, it may be called with other units in + three cases. + + A DETACH ALL command will call us for unit 16 (the poll unit) if it is + attached. A RESTORE command also will call us for unit 16 if it is attached. + In the latter case, the terminal channels will have already been rescheduled + as appropriate, so canceling them is skipped. Also, during simulator + shutdown, we will be called for units 0-15 (detach_all in scp.c calls the + detach routines of all units that do NOT have UNIT_ATTABLE), as well as for + unit 16 if it is attached. In all cases, it is imperative that we not reject + the request for unit 16; otherwise any remaining device detaches will not be + performed. +*/ + +static t_stat atcd_detach (UNIT *uptr) +{ +uint32 channel; +t_stat status = SCPE_OK; + +if (uptr == line_unit || uptr == &poll_unit) { /* if we're detaching the base unit or poll unit */ + status = tmxr_detach (&atcd_mdsc, &poll_unit); /* then detach the listening port */ + + if ((sim_switches & SIM_SW_REST) == 0) /* if this is not a RESTORE call */ + for (channel = 0; channel < TERM_COUNT; channel++) { /* then for each terminal channel */ + atcd_ldsc [channel].rcve = FALSE; /* disable reception */ + sim_cancel (&line_unit [channel]); /* and cancel any transfer in progress */ + } + } + +return status; +} + + + +/* ATC local utility routines */ + + + +/* Request a TDI interrupt. + + The data flag and interrupt request flip-flops are set. If the interrupt + mask permits, the interrupt request is passed to the IOP. +*/ + +static void tdi_set_interrupt (void) +{ +tdi_data_flag = SET; /* set the data flag */ + +atcd_dib.interrupt_request = SET; /* request an interrupt */ + +if (tdi_interrupt_mask) /* if the interrupt mask is satisfied */ + iop_assert_INTREQ (&atcd_dib); /* then assert the INTREQ signal to the IOP */ + +return; +} + + +/* TDI master reset. + + A master reset is generated either by an IORESET signal or a programmed + master reset (CIO bit 0 set). It clears any pending or active interrupt, + sets the interrupt mask, clears the status word and data flag, and resets all + channels to their initial, unconfigured state. + + + Implementation notes: + + 1. In hardware, a master reset sets the Initialize flip-flop. This causes a + direct clear of the recirculating memory window registers, thereby + clearing each channel's buffer, parameter, and status values as they + pass through the window. The flip-flop is cleared when a control word is + sent with the master clear bit (CIO bit 0) cleared. A full recirculation + takes 69.44 microseconds, so the CPU must allow at least this time for + each channel to pass through the window to ensure that all memory + locations are reset. In simulation, the clear occurs "instantaneously." +*/ + +static void tdi_master_reset (void) +{ +uint32 chan; + +atcd_dib.interrupt_request = CLEAR; /* clear any current */ +atcd_dib.interrupt_active = CLEAR; /* interrupt request */ + +tdi_interrupt_mask = SET; /* set the interrupt mask */ + +tdi_status_word = 0; /* clear the status word */ +tdi_data_flag = CLEAR; /* and the data flag */ + +for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) { /* for each terminal channel */ + recv_buffer [chan] = 0; /* clear the receive data buffer */ + recv_param [chan] = 0; /* and parameter */ + recv_status [chan] = 0; /* and status */ + + send_buffer [chan] = 0; /* also clear the send data buffer */ + send_param [chan] = 0; /* and parameter */ + send_status [chan] = 0; /* and status */ + + sim_cancel (&line_unit [chan]); /* cancel any transfer in progress */ + } + +for (chan = FIRST_AUX; chan <= LAST_AUX; chan++) { /* for each auxiliary channel */ + recv_buffer [chan] = 0; /* clear the receive data buffer */ + recv_param [chan] = 0; /* and parameter */ + recv_status [chan] = 0; /* and status */ + } + +return; +} + + +/* TCI master reset. + + A master reset is generated either by an IORESET signal or a programmed + master reset (CIO bit 0 set). It clears any pending or active interrupt, + sets the interrupt mask, clears the control word and channel counter, and + resets all channels to their initial, unconfigured state. + + + Implementation notes: + + 1. In hardware, a master reset sets the Status Clear flip-flop. This causes + a direct clear of the Control Word Holding Register and enables writing + into each location of the addressable latches and state RAM. The + flip-flop is reset automatically when the channel counter rolls over. + This takes approximately 12 microseconds, so the CPU must allow at least + this time before sending new control information. In simulation, the + master reset occurs "instantaneously." + + 2. In hardware, the C2 and C1 control line outputs are cleared by a master + clear. In simulation, we also clear the S2 and S1 status line input + values. This is OK, because they will be reestablished at the next poll + service entry. +*/ + + +static void tci_master_reset (void) +{ +uint32 chan; + +atcc_dib.interrupt_request = CLEAR; /* clear any current */ +atcc_dib.interrupt_active = CLEAR; /* interrupt request */ + +tci_interrupt_mask = SET; /* set the interrupt mask */ + +tci_control_word = 0; /* clear the control word */ +tci_cntr = 0; /* and the channel counter */ + +for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) { /* for each terminal channel */ + cntl_status [chan] = 0; /* clear all serial line values */ + cntl_param [chan] = 0; /* and the parameter RAM */ + } + +return; +} + + +/* Multiplexer channel service. + + The channel service routine runs only when there are characters to read or + write. It is scheduled either at a realistic rate corresponding to the + programmed baud rate of the channel to be serviced, or at a somewhat faster + optimized rate. It is entered when a channel buffer is ready for output or + when the poll routine determines that there are characters ready for input. + + On entry, the receive channel buffer is checked for a character. If one is + not already present, then the terminal multiplexer library is called to + retrieve the waiting character. If a valid character is now available, it is + processed. If the receive channel has its "diagnose" bit set, the character + is also passed to the auxiliary channels. + + The send channel buffer is then checked for a character to output. If one is + present, then if it is an all-mark (sync) character, it is discarded, as the + receiver would never see it. Otherwise, if the TDI is in diagnostic mode, + then the character is looped back to the associated receive channel by + storing it in that channel's receive buffer and then recursively calling the + routine for that channel. + + If the TDI is in terminal mode, then if the channel flag is set for local + ENQ/ACK handshaking, and the character is an ENQ, it is discarded, an ACK is + stored in the channel's receive buffer, and its reception is scheduled. + Otherwise, the character is processed and then transmitted either to the + simulation console (if output is to channel 0) or to the terminal multiplexer + library for output via Telnet or a serial port on the host machine. If the + channel has its "diagnose" bit set, the character is also passed to the + auxiliary channels. + + If the data flag is clear, the indicated receive and send channels are + checked for completion flags. If either is set, an interrupt is requested. + + + Implementation notes: + + 1. Calling "tmxr_getc_ln" for channel 0 is OK, as reception is disabled by + default and therefore will return 0. + + 2. The send channel buffer will always be non-zero if a character is present + (even a NUL) because the data word will have DDS_IS_SEND set. + + The receive buffer will always be non-zero if a character is present + (even a NUL) because characters from the console will have SCPE_KFLAG + set, characters from the terminal multiplexer library will have + TMXR_VALID set, and characters looped back from sending will have + DDS_IS_SEND set. + + 3. Reception of a loopback character is performed immediately because the + reception occurs concurrently with transmission. Reception of a locally + generated ACK is scheduled with a one-character delay to reflect the + remote device transmission delay. + + 4. If storing an ACK locally overwrites a character already present but not + yet processed, then the receive routine will set the character lost flag. + + 5. Both TMXR_VALID and SCPE_KFLAG are set on internally generated ACKs only + so that a debug trace will record the generation correctly. + + 6. The console library "sim_putchar_s" routine and the terminal multiplexer + library "tmxr_putc_ln" routine return SCPE_STALL if the Telnet output + buffer is full. In this case, transmission is rescheduled with a delay + to allow the buffer to drain. + + They also return SCPE_LOST if the line has been dropped on the remote + end. We ignore the error here to allow the simulation to continue while + ignoring the output. + + 7. The receive/send completion flag (buffer flag) will not set unless the + interrupt enable flag for that channel is also set. If enable is not + set, the completion indication will be lost. +*/ + +static t_stat line_service (UNIT *uptr) +{ +const int32 channel = (int32) (uptr - line_unit); /* the channel number */ +const int32 alt_channel = channel ^ 1; /* alternate channel number for diagnostic mode */ +const t_bool loopback = (atcd_dev.flags & DEV_DIAG) != 0; /* TRUE if device is set for diagnostic mode */ +int32 recv_data, send_data, char_data, cvtd_data; +t_stat result = SCPE_OK; + +dprintf (atcd_dev, DEB_SERV, "Channel %d service entered\n", + channel); + + +/* Reception service */ + +recv_data = recv_buffer [channel]; /* get the current buffer character */ + +if (recv_data == 0) /* if there's none present */ + recv_data = tmxr_getc_ln (&atcd_ldsc [channel]); /* then see if there's a character ready via Telnet */ + +if (recv_data & ~DDR_DATA_MASK) { /* if we now have a valid character */ + receive (channel, recv_data, loopback); /* then process the reception */ + + if (recv_param [channel] & DPI_DIAGNOSE) /* if a diagnosis is requested */ + diagnose (recv_param [channel], recv_data); /* then route the data to the auxiliary channels */ + } + +/* Transmission service */ + +if (send_buffer [channel]) { /* if data is available to send */ + send_data = DDS_DATA (send_buffer [channel]); /* then pick up the data and stop bits */ + char_data = send_data & ASCII_MASK; /* and also the ASCII character value */ + + if (send_status [channel] & DST_COMPLETE) { /* if the last completion hasn't been acknowledged */ + send_status [channel] |= DST_CHAR_LOST; /* then indicate an overrun condition */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d send data overrun\n", + channel); + } + + if ((send_buffer [channel] & DDS_MARK) == DDS_MARK) { /* if it's an all-mark character */ + send_buffer [channel] = 0; /* then the receiver won't see it */ + + if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */ + send_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + + dprintf (atcd_dev, DEB_XFER, (loopback + ? "Channel %d sync character sent to channel %d\n" + : "Channel %d sync character sent\n"), + channel, alt_channel); + } + + else if (loopback) { /* otherwise if the device is in loopback mode */ + if (send_param [channel] & DPI_DIAGNOSE) /* then if a diagnosis is requested */ + diagnose (send_param [channel], send_data); /* then route the data to the auxiliary channels */ + + if ((send_buffer [channel] & DDR_DATA_MASK) == 0) /* if all bits are clear */ + recv_buffer [alt_channel] = SCPE_BREAK; /* then it will be seen as a BREAK */ + else /* otherwise a character will be received */ + recv_buffer [alt_channel] = send_buffer [channel]; /* so store it in the buffer */ + + send_buffer [channel] = 0; /* clear the send buffer */ + + if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */ + send_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + + dprintf (atcd_dev, DEB_XFER, "Channel %d character %s sent to channel %d\n", + channel, fmt_char (char_data), alt_channel); + + line_service (&line_unit [alt_channel]); /* receive the character on the alternate channel */ + } + + else if (char_data == ENQ && uptr->flags & UNIT_LOCALACK) { /* otherwise if it's an ENQ and local reply is enabled */ + recv_buffer [channel] = GEN_ACK; /* then "receive" an ACK on the channel */ + + send_buffer [channel] = 0; /* discard the ENQ */ + + if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */ + send_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + + dprintf (atcd_dev, DEB_XFER, "Channel %d character ENQ absorbed internally\n", + channel); + + activate_unit (uptr, Receive); /* schedule the reception */ + } + + else { /* otherwise it's a normal character */ + cvtd_data = sim_tt_outcvt (LOWER_BYTE (send_data), /* so convert it as directed */ + TT_GET_MODE (uptr->flags)); /* by the output mode flag */ + + if (cvtd_data >= 0) /* if the converted character is printable */ + if (channel == 0) /* then if we are writing to channel 0 */ + result = sim_putchar_s (cvtd_data); /* then output it to the simulation console */ + else /* otherwise */ + result = tmxr_putc_ln (&atcd_ldsc [channel], /* output it to the multiplexer line */ + cvtd_data); + + if (result == SCPE_STALL) { /* if the buffer is full */ + activate_unit (uptr, Stall); /* then retry the output a while later */ + result = SCPE_OK; /* and return OK to continue */ + } + + else if (result == SCPE_OK || result == SCPE_LOST) { /* otherwise if the character is queued to transmit */ + tmxr_poll_tx (&atcd_mdsc); /* then send (or ignore) it */ + + if (DPRINTING (atcd_dev, DEB_XFER)) + if (result == SCPE_LOST) + hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s discarded by connection loss\n", + channel, fmt_char (char_data)); + + else if (cvtd_data >= 0) + hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s sent\n", + channel, fmt_char (cvtd_data)); + + else + hp_debug (&atcd_dev, DEB_XFER, "Channel %d character %s discarded by output filter\n", + channel, fmt_char (char_data)); + + if (send_param [channel] & DPI_DIAGNOSE) /* if a diagnosis is requested */ + diagnose (send_param [channel], send_data); /* then route the data to the auxiliary channels */ + + send_buffer [channel] = 0; /* clear the buffer */ + + if (send_param [channel] & DPI_ENABLE_IRQ) /* if this channel is enabled to interrupt */ + send_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + + result = SCPE_OK; /* return OK in case the connection was lost */ + } + } + } + + +if (tdi_data_flag == CLEAR) /* if an interrupt is not currently pending */ + scan_channels (channel); /* then scan the channels for completion flags */ + +return result; /* return the result of the service */ +} + + +/* Multiplexer poll service. + + The poll service routine is used to poll for Telnet connections and incoming + characters. It also polls the simulation console for channel 0. Polling + starts at simulator startup or when the TDI is enabled and stops when it is + disabled. + + + Implementation notes: + + 1. The poll service routine may be entered with the TCI either enabled or + disabled. It will not be entered if the TDI is disabled, as it may be + disabled only when it is detached from a listening port. + + 2. If a character is received on the simulation console, we must call the + channel 0 line service directly. This is necessary because the poll time + may be shorter than the channel service time, and as the console provides + no buffering, a second character received before the channel service had + been entered would be lost. +*/ + +static t_stat poll_service (UNIT *uptr) +{ +int32 chan, line_state; +t_stat status = SCPE_OK; + +dprintf (atcd_dev, DEB_PSERV, "Poll service entered\n"); + +if ((atcc_dev.flags & DEV_DIS) == 0) + dprintf (atcc_dev, DEB_PSERV, "Poll service entered\n"); + +if ((atcd_dev.flags & DEV_DIAG) == 0) { /* if we're not in diagnostic mode */ + chan = tmxr_poll_conn (&atcd_mdsc); /* then check for a new multiplex connection */ + + if (chan != -1) { /* if a new connection was established */ + atcd_ldsc [chan].rcve = TRUE; /* then enable the channel to receive */ + + dprintf (atcc_dev, DEB_XFER, "Channel %d connected\n", + chan); + } + } + +tmxr_poll_rx (&atcd_mdsc); /* poll the multiplex connections for input */ + +if ((atcc_dev.flags & (DEV_DIAG | DEV_DIS)) == 0) /* if we're not in diagnostic mode or are disabled */ + for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) /* then scan the channels for line state changes */ + if (line_unit [chan].flags & UNIT_MODEM) { /* if the channel is controlled by the TCI */ + tmxr_set_get_modem_bits (&atcd_ldsc [chan], /* then get the current line state */ + 0, 0, &line_state); + + if (line_state & TMXR_MDM_DCD) /* if DCD is set */ + cntl_status [chan] |= DCD; /* then set the corresponding line flag */ + + else { /* otherwise DCD is clear */ + if (cntl_status [chan] & DCD) /* and a disconnect occurred if DCD was previously set */ + dprintf (atcc_dev, DEB_XFER, "Channel %d disconnect dropped DCD and DSR\n", + chan); + + cntl_status [chan] &= ~DCD; /* clear the corresponding flag */ + } + + if (line_state & TMXR_MDM_DSR) /* if DSR is set */ + cntl_status [chan] |= DSR; /* then set the corresponding line flag */ + else /* otherwise */ + cntl_status [chan] &= ~DSR; /* clear the flag */ + } + +status = sim_poll_kbd (); /* poll the simulation console keyboard for input */ + +if (status >= SCPE_KFLAG) { /* if a character was present */ + recv_buffer [0] = (HP_WORD) status; /* then save it for processing */ + status = SCPE_OK; /* and then clear the status */ + + line_service (&line_unit [0]); /* run the system console's I/O service */ + } + +for (chan = FIRST_TERM; chan <= LAST_TERM; chan++) /* check each of the receive channels for available input */ + if (tmxr_rqln (&atcd_ldsc [chan])) /* if characters are available on this channel */ + activate_unit (&line_unit [chan], Receive); /* then activate the channel's I/O service */ + +if (cpu_is_calibrated) /* if the process clock is calibrated */ + uptr->wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */ +else /* otherwise */ + uptr->wait = sim_rtcn_calb (POLL_RATE, TMR_ATC); /* calibrate the poll timer independently */ + +sim_activate (uptr, uptr->wait); /* continue polling */ + +if (tci_scan) /* if scanning is active */ + scan_status (); /* then check for line status changes */ + +return status; /* return the service status */ +} + + +/* Activate a channel unit. + + The specified unit is activated to receive or send a character. The reason + for the activation is specified by the "reason" parameter. If the TDI is in + real-time mode, the previously calculated service time is used to schedule + the event. Otherwise, the current value of the optimized timing delay is + used. If tracing is enabled, the activation is logged to the debug file. + + + Implementation notes: + + 1. The loopback time is the difference between the reception and + transmission times, as the latter event has already occurred when we are + called. +*/ + +static t_stat activate_unit (UNIT *uptr, ACTIVATOR reason) +{ +const int32 channel = (int32) (uptr - line_unit); /* the channel number */ +int32 delay = 0; + +if (atcd_dev.flags & (DEV_DIAG | DEV_REALTIME)) /* if either diagnostic or real-time mode is set */ + switch (reason) { /* then dispatch the REALTIME activation */ + + case Receive: /* reception event */ + delay = uptr->recv_time; /* schedule for the realistic reception time */ + break; + + case Send: /* transmission event */ + delay = uptr->send_time; /* schedule for the realistic transmission time */ + break; + + case Loop: /* diagnostic loopback reception event */ + delay = uptr->recv_time - uptr->send_time; /* schedule the additional reception overhead */ + + if (delay < 0) /* if the receive time is less than the send time */ + delay = 0; /* then schedule the reception immediately */ + break; + + case Stall: /* transmission stall event */ + delay = uptr->send_time / 10; /* reschedule the transmission after a delay */ + break; + } + +else /* otherwise, we are in optimized timing mode */ + switch (reason) { /* so dispatch the FASTTIME activation */ + + case Receive: /* reception event */ + case Send: /* transmission event */ + delay = fast_data_time; /* use the optimized timing value */ + break; + + case Loop: /* diagnostic loopback reception event */ + delay = 1; /* use a nominal delay */ + break; + + case Stall: /* transmission stall event */ + delay = fast_data_time / 10; /* reschedule the transmission after a delay */ + break; + } + +dprintf (atcd_dev, DEB_SERV, "Channel %d delay %d service scheduled\n", + channel, delay); + +return sim_activate (uptr, delay); /* activate the unit and return the activation status */ +} + + +/* Calculate the service time. + + The realistic channel service time in event ticks per character is calculated + from the encoded character size and baud rate in the supplied control word. + The time consists of the transfer time plus a small overhead, which is + different for receiving and sending. + + The character size field in the control word is generated by this equation: + + encoded_size = (bits_per_character - 1) AND 7 + + That is, the encoded character size is the value expressed by the three + least-significant bits of the count of the data and stop bits. Therefore, + the actual number of bits per character (including the start bit) is encoded + as: + + Actual Encoded + ------ ------- + 5 4 + 6 5 + 7 6 + 8 7 + 9 0 + 10 1 + 11 2 + 12 3 + + The baud rate field in the control word is generated by this equation: + + 14400 + encoded_rate = --------- - 1 + baud_rate + + The transmission and overhead times are related to the recirculation of the + multiplexer's internal memory, which contains the data, parameters, and + status for each of the 16 send channels, 16 receive channels, and 5 auxiliary + channels. Data for a given channel can be accessed only once per + recirculation, which takes 69.44 microseconds (1/14400 of a second). The + encoded rate plus one gives the number of recirculations corresponding to a + single bit time; multiplying by the number of bits per character gives the + number of recirculations to send or receive an entire character. + + All operations encounter two overhead delays. First, an average of one-half + of a recirculation must occur to align the memory with the channel of + interest. Second, a full recirculation is required after receiving or + sending is complete before an interrupt may be generated. + + For receiving, there is an additional delay to right-justify the received + character in the data accumulator. The accumulator is a 12-bit shift + register, with received data bits are shifted from left to right. When the + final bit is entered, the register must be shifted additionally until the + first data bit is in the LSB (i.e., until the start bit is shifted out of the + register). One shift per recirculation is performed, and the number of + additional shifts required is 12 + 1 - the number of bits per character. + + Justification begins immediately after the stop bit has been received, so the + full set of recirculations for that bit are skipped in lieu of justification. + Also, reception of the start bit is delayed by one-half of the bit time to + improve noise immunity. + + Therefore, given R = encoded_rate + 1 and B = bits_per_character, the number + of memory recirculations required for sending is: + + 0.5 to align memory with the target channel (on average) + + R * B to send the start, data, and stop bits + + 1 to set the data flag to request an interrupt + + For example, at 2400 baud (encoded rate 5), a 10-bit character size, and + 69.44 microseconds per circulation, the service time would be: + + 34.7 usec to align + 4166.7 usec to send the start, data, and stop bits + 69.4 usec to set the data flag + =========== + 4270.8 usec from initiation to data flag + + The number of memory recirculations required for receiving is: + + 0.5 to align memory with the target channel (on average) + + R / 2 to receive the start bit + + R * (B - 1) to receive the data and stop bits + + (12 - B + 1) to right-justify the data + + 1 to set the data flag to request an interrupt + + Using the same example as above, the service time would be: + + 34.7 usec to align + 208.3 usec to receive the start bit + 3750.0 usec to receive the data and stop bits + 208.3 usec to right-justify the data + 69.4 usec to set the data flag + =========== + 4270.7 usec from initiation to data flag + + + Implementation notes: + + 1. The multiplexer uses an 8-bit field to set the baud rate. In practice, + only the common rates (110, 150, 300, 600, 1200, 2400) will be used. + Still, the real-time calculation must accommodate any valid setting, so a + lookup table is infeasible. + + 2. The receive calculation is simplified by noting that R / 2 + R * (B - 1) + is equivalent to R * B - R / 2, so the send calculation may be reused. + Note that the receive time may be less than the send time, typically when + the baud rate is low, so that the time to send the stop bits is longer + than the time to right-justify the reception. This means that the + "addition" of the receive overhead may actually be a subtraction. +*/ + +static uint32 service_time (HP_WORD control, ACTIVATOR reason) +{ +const double recirc_time = 69.44; /* microseconds per memory recirculation */ +const uint32 recirc_per_bit = DPI_BAUD_RATE (control) + 1; /* number of memory recirculations per bit */ +const uint32 char_size = bits_per_char [DPI_CHAR_SIZE (control)]; /* number of bits per character */ +double usec_per_char; + +usec_per_char = recirc_time * /* calculate the overhead for sending */ + (char_size * recirc_per_bit + 1.5); + +if (reason == Receive) /* if we're receiving */ + usec_per_char += recirc_time * /* then add the additional receiving overhead */ + (12 - char_size + 1 + - recirc_per_bit / 2.0); + +return (uint32) (usec_per_char / USEC_PER_EVENT); /* return the service time for indicated rate */ +} + + +/* Store a word in the recirculating memory. + + A parameter or data word is stored in the recirculating memory for the + channel indicated by the associated field of the "control" parameter. If the + channel number is out of range, the store is ignored. + + For receive and send parameters, the realistic service time is calculated and + stored in the unit for use when a receive or send event is scheduled. For + send data, parity is calculated and added if specified by the channel's + parameter, and the transmission event is scheduled. For a receive parameter, + the pad bits that would normally be added during right-justification after + reception are calculated and stored in the unit. + + + Implementation notes: + + 1. Service times are not calculated or set for auxiliary channels because + events are not scheduled on them (and so no units are allocated for + them). + + 2. Pad bits begin with the stop bit and continue until the character is + right-justified in the receive buffer. The calculation assumes one stop + bit, but there is no way of ascertaining the actual number of stop bits + from the parameter word. +*/ + +static void store (HP_WORD control, HP_WORD data) +{ +const uint32 channel = DCN_CHAN (control); /* current channel number */ + +if (data & DDS_IS_SEND) /* if this is a send parameter or data */ + if (channel > LAST_TERM) /* then report if the channel number is out of range */ + dprintf (atcd_dev, DEB_CSRW, "Send channel %u invalid\n", + channel); + + else if (data & DPI_IS_PARAM) { /* otherwise if this is a parameter store */ + send_param [channel] = data; /* then save it */ + line_unit [channel].send_time = /* and set the service time */ + service_time (data, Send); + + dprintf (atcd_dev, DEB_CSRW, "Channel %u send parameter %06o stored\n", + channel, data); + } + + else { /* otherwise this is a data store */ + if (send_param [channel] & DPI_ENABLE_PARITY) /* if parity is enabled */ + data = data & ~DDS_PARITY /* then replace the parity bit */ + | SEND_PARITY (data); /* with the calculated value */ + + send_buffer [channel] = data; /* store it in the buffer */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %u send data %06o stored\n", + channel, data); + + activate_unit (&line_unit [channel], Send); /* schedule the transmission event */ + } + +else /* otherwise this is a receive parameter */ + if (channel >= RECV_CHAN_COUNT) /* report if the channel number is out of range */ + dprintf (atcd_dev, DEB_CSRW, "Receive channel %u invalid\n", + channel); + + else if (data & DPI_IS_PARAM) { /* otherwise this is a parameter store */ + recv_param [channel] = data; /* then save it */ + + if (channel <= LAST_TERM) { /* if this is a terminal channel */ + line_unit [channel].recv_time = /* and not an auxiliary channel */ + service_time (data, Receive); /* then set the service time */ + + line_unit [channel].stop_bits = /* set the stop bits mask for reception */ + PAD_BITS (data); + } + + dprintf (atcd_dev, DEB_CSRW, "Channel %u receive parameter %06o stored\n", + channel, data); + } + + else /* otherwise a data store to a receive channel is invalid */ + dprintf (atcd_dev, DEB_CSRW, "Channel %u receive output data word %06o invalid\n", + channel, data); +} + + +/* Process a character received from a channel. + + This routine is called to process received data on a channel, typically when + a character exists in the channel's receive buffer, but also when a character + is received on an auxiliary channel. The "channel" parameter indicates the + channel on which reception occurred, "data" is the (full) character data + as received from the console or terminal multiplexer libraries, and + "loopback" is TRUE if the data should be looped back to the alternate channel + for diagnostic execution. + + On entry, the bits required to pad the character are obtained. If a BREAK + was detected, then break status is set, and the character is set to NUL, + reflecting the all-space reception. Otherwise, if a character is already + present in the receive buffer, "character lost" status is set to indicate + that it will be overwritten. + + If this is a loopback reception, and echo is enabled on the channel, the + character is sent back to the alternate channel. Otherwise, if this is a + main and not auxiliary channel reception, the character is upshifted if the + UNIT_CAPSLOCK flag is set. If echo is enabled, the character is written back + to the console or terminal multiplexer library line. Finally, the completion + flag is set if enabled. + + + Implementation notes: + + 1. The echo to a terminal multiplexer library line will return SCPE_LOST if + the line has been dropped on the remote end. We can ignore the error + here, as the line drop will be picked up when the next input poll is + performed. + + In addition, the SCPE_STALL returned for a full output buffer is also + ignored, as there's no way of queuing echoed characters while waiting for + the buffer to empty. +*/ + +static void receive (int32 channel, int32 data, t_bool loopback) +{ +int32 recv_data, char_data, char_echo, pad; + +recv_data = data & DDR_DATA_MASK; /* mask to just the character data */ +char_data = recv_data & ASCII_MASK; /* and to the equivalent ASCII character */ + +if (channel <= LAST_TERM) /* if this is a receive channel */ + pad = line_unit [channel].stop_bits; /* then set the stop-bit padding from the unit */ +else /* otherwise it's an auxiliary channel */ + pad = PAD_BITS (recv_param [channel]); /* so calculate the padding */ + + +if (data & SCPE_BREAK) { /* if a break was detected */ + recv_buffer [channel] = NUL; /* then return a NUL character */ + recv_status [channel] |= DST_BREAK; /* and set break reception status */ + + dprintf (atcd_dev, DEB_XFER, "Channel %d break detected\n", + channel); + } + +else { /* otherwise a normal character was received */ + if (recv_status [channel] & DST_COMPLETE) { /* if a character is already pending */ + recv_status [channel] |= DST_CHAR_LOST; /* then the previous character will be lost */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d receive data overrun\n", + channel); + } + + recv_buffer [channel] = recv_data | pad; /* save the character and padding in the buffer */ + + if (loopback) { /* if this channel has a loopback cable installed */ + if (recv_param [channel] & DPI_ENABLE_ECHO) { /* and the channel has echo enabled */ + recv_buffer [channel ^ 1] = data; /* then send the data back to the other channel */ + + activate_unit (&line_unit [channel ^ 1], Loop); /* schedule the reception */ + + dprintf (atcd_dev, DEB_XFER, "Channel %d character %s echoed to channel %d\n", + channel, fmt_char (char_data), channel ^ 1); + } + } + + else if (channel <= LAST_TERM) { /* otherwise if it's a receive channel */ + if (line_unit [channel].flags & UNIT_CAPSLOCK) { /* then if caps lock is down */ + recv_data = toupper (recv_data); /* then convert to upper case if lower */ + recv_buffer [channel] = recv_data | pad; /* and replace the character in the buffer */ + } + + if (recv_param [channel] & DPI_ENABLE_ECHO) { /* if the channel has echo enabled */ + char_echo = sim_tt_outcvt (recv_data, /* then convert the character per the output mode */ + TT_GET_MODE (line_unit [channel].flags)); + + if (char_echo >= 0) { /* if the converted character is valid for the mode */ + if (channel == 0) /* then if this is for channel 0 */ + sim_putchar (char_echo); /* then write it back to the simulation console */ + + else { /* otherwise */ + tmxr_putc_ln (&atcd_ldsc [channel], char_echo); /* write it to the multiplexer output line */ + tmxr_poll_tx (&atcd_mdsc); /* and poll to transmit it now */ + } + + dprintf (atcd_dev, DEB_XFER, ("Channel %d character %s echoed\n"), + channel, fmt_char (char_echo)); + } + + else /* otherwise the echo character was discarded */ + dprintf (atcd_dev, DEB_XFER, "Channel %d character %s echo discarded by output filter\n", + channel, fmt_char (char_data)); + } + } + } + +if (recv_param [channel] & DPI_ENABLE_IRQ) /* if the channel is enabled to interrupt */ + recv_status [channel] |= DST_COMPLETE; /* then set the completion flag */ + +dprintf (atcd_dev, DEB_XFER, "Channel %d character %s %s\n", + channel, fmt_char (char_data), + (data == GEN_ACK ? "generated internally" : "received")); + +return; +} + + +/* Check for a character received on an auxiliary channel. + + If a send or receive channel has its "diagnose" bit set, then this routine is + called to check if any of the auxiliary channels would receive the character + too. If one or more would, then the "receive" routine is called to store the + character in the appropriate buffer. + + The diagnosis mode is typically used to speed-sense a receive channel. + In hardware, reception on a given channel is simultaneously received on the + five auxiliary channels, with each channel set for a different baud rate. + When a specific character (e.g., CR) is sent, only the channel with the + correct baud rate setting will receive the intended character. By + determining which channel received the correct data, the baud rate of the + sending terminal may be obtained. + + In simulation, a main channel will receive a character regardless of the baud + rate configuration. Therefore, an auxiliary channel will receive the same + character only if it is configured for the same baud rate and character size. +*/ + +static void diagnose (HP_WORD control, int32 data) +{ +const HP_WORD config = control & DPI_CHAR_CONFIG; /* main channel character size and baud rate */ +int32 channel; + +for (channel = FIRST_AUX; channel <= LAST_AUX; channel++) /* scan the auxiliary channels */ + if ((recv_param [channel] & DPI_CHAR_CONFIG) == config) /* if the character configurations match */ + receive (channel, data, FALSE); /* then receive the data on this channel */ + +return; +} + + +/* Scan the channels for a transfer completion interrupt. + + If the multiplexer data flag is not set, this routine is called to scan the + channels for completion flags. If the "channel" parameter value is SCAN_ALL, + then all of the channels are checked. Otherwise, only the specified channel + is checked. + + If a channel has its completion flag set, the multiplexer data and status + words are set for return to the CPU, the data flag is set, and an interrupt + is requested. The channel requesting the interrupt is contained in the + status word. + + In hardware, the recirculating buffer consists of the sixteen receive + channels, then the sixteen send channels, and then the five auxiliary + channels. The completion flags are checked in this order during the + recirculation after a completion flag is set. If the scan has been inhibited + by the data flag, it will commence with the channel currently in the + recirculation window at the time the flag was cleared and then continue in + the order indicated. + + In simulation, the scan is always initiated as though at the beginning of a + recirculation. + + + Implementation notes: + + 1. After a send completion, the data word contains all ones (stop bits). +*/ + +static void scan_channels (int32 channel) +{ +int32 chan, first_chan, last_chan; + +if (channel == SCAN_ALL) { /* if all channels are to be scanned */ + first_chan = FIRST_TERM; /* then set the loop limits */ + last_chan = LAST_TERM; /* to the full range of channels */ + } + +else /* otherwise scan just the channel indicated */ + first_chan = last_chan = channel; /* plus the auxiliary channels if requested */ + +for (chan = first_chan; chan <= last_chan; chan++) { /* scan the receive channels */ + if (recv_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */ + tdi_read_word = DDR_DATA (recv_buffer [chan]) /* then form the input data word */ + | DDR_CHAN (chan) /* from the character, channel, and parity */ + | RECV_PARITY (recv_buffer [chan]); + + tdi_status_word = recv_status [chan] /* form the partial status word */ + | DST_CHAN (chan); + + recv_buffer [chan] = 0; /* clear the receive buffer */ + recv_status [chan] = 0; /* and the channel status */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d receive interrupt requested\n", + chan); + + tdi_set_interrupt (); /* set the data flag and request an interrupt */ + return; /* and terminate scanning */ + } + } + +for (chan = first_chan; chan <= last_chan; chan++) { /* scan the send channels */ + if (send_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */ + tdi_read_word = DDR_DATA_MASK /* then form the input data word from the */ + | DDR_CHAN (chan); /* data input buffer and the channel number */ + + tdi_status_word = send_status [chan] /* form the partial status word */ + | DST_CHAN (chan) + | DST_SEND_IRQ; + + send_status [chan] = 0; /* clear the channel status */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d send interrupt requested\n", + chan); + + tdi_set_interrupt (); /* set the data flag and request an interrupt */ + return; /* and terminate scanning */ + } + } + +if (channel == SCAN_ALL /* if we're scanning all channels */ + || send_param [channel] & DPI_DIAGNOSE /* or the indicated channel is diagnosing */ + || recv_param [channel] & DPI_DIAGNOSE) /* its transmission or reception */ + for (chan = FIRST_AUX; chan <= LAST_AUX; chan++) { /* then scan the auxiliary channels */ + if (recv_status [chan] & DST_COMPLETE) { /* if this channel's completion flag is set */ + tdi_read_word = DDR_DATA (recv_buffer [chan]) /* then form the input data word */ + | DDR_CHAN (chan) /* from the character, channel, and parity */ + | RECV_PARITY (recv_buffer [chan]); + + tdi_status_word = recv_status [chan] /* form the partial status word */ + | DST_CHAN (chan) + | DST_DIAGNOSE; + + recv_buffer [chan] = 0; /* clear the receive buffer */ + recv_status [chan] = 0; /* and the channel status */ + + dprintf (atcd_dev, DEB_CSRW, "Channel %d receive interrupt requested\n", + chan); + + tdi_set_interrupt (); /* set the data flag and request an interrupt */ + return; /* and terminate scanning */ + } + } + +return; /* no channel has completed */ +} + + +/* Check for a control interrupt. + + If the scan flag is clear, then return the interrupt status bits for the + channel indicated by the current control counter value. Otherwise, scan all + of the control channels, starting with the current counter, to check for a + status mismatch. This occurs when either of the incoming status bits does + not match the stored status, and the corresponding mismatch detection is + enabled. If an enabled mismatch is found, request an interrupt from the CPU, + clear the scan flag, and return the interrupt status bits with the counter + pointing at the interrupting channel. +*/ + +static HP_WORD scan_status (void) +{ +uint32 chan_count; +HP_WORD interrupts; + +if (tci_scan) /* if the control interface is scanning */ + chan_count = TERM_COUNT; /* then look at all of the channels */ +else /* otherwise */ + chan_count = 1; /* look at only the current channel */ + +while (chan_count > 0) { /* scan the control channels */ + interrupts = CST_IX (CCN_ESX (cntl_param [tci_cntr]) /* check for an enabled status mismatch */ + & (cntl_param [tci_cntr] ^ cntl_status [tci_cntr])); + + if (tci_scan) { /* if the interface is scanning */ + if (interrupts) { /* and a mismatch was found */ + atcc_dib.interrupt_request = SET; /* then request an interrupt */ + + if (tci_interrupt_mask) /* if the interrupt mask is satisfied */ + iop_assert_INTREQ (&atcc_dib); /* then assert the INTREQ signal */ + + tci_scan = CLEAR; /* stop the scan at the current channel */ + + dprintf (atcc_dev, DEB_CSRW, "Channel %u interrupt requested\n", + tci_cntr); + break; + } + + tci_cntr = (tci_cntr + 1) % TERM_COUNT; /* set the counter to the next channel in sequence */ + } + + chan_count = chan_count - 1; /* drop the count of channels to check */ + } + +return interrupts; /* return the interrupt status bits */ +} diff --git a/HP3000/hp3000_clk.c b/HP3000/hp3000_clk.c new file mode 100644 index 00000000..1015da6d --- /dev/null +++ b/HP3000/hp3000_clk.c @@ -0,0 +1,942 @@ +/* hp3000_clk.c: HP 3000 30135A System Clock/Fault Logging Interface simulator + + Copyright (c) 2016, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be used + in advertising or otherwise to promote the sale, use or other dealings in + this Software without prior written authorization from the author. + + CLK HP 30135A System Clock/Fault Logging Interface + + 12-Sep-16 JDB Changed DIB register macro usage from SRDATA to DIB_REG + 11-Jul-16 JDB Change "clk_unit" from a UNIT to an array of one UNIT + 08-Jul-16 JDB Added REG entry to save the unit wait field + 09-Jun-16 JDB Clarified the IRQ FF set code in DRESETINT + 08-Jun-16 JDB Corrected %d format to %u for unsigned values + 21-Mar-16 JDB Changed inbound_value and outbound_value types to HP_WORD + 08-Jun-15 JDB First release version + 12-Aug-14 JDB Passed the system clock diagnostic (D426A) + 05-Jul-14 JDB Created + + References: + - Stand-Alone System Clock Diagnostic + (32230-90005, January 1979) + - HP 3000 Series III Engineering Diagrams Set + (30000-90141, April 1980) + + + The HP 30135A System Clock/Fault Logging Interface is used with Series II and + III systems and provides two devices on a single I/O card: a programmable + interval clock employed as the MPE system clock and an interface to the ECC + fault logging RAMs on the semiconductor main memory arrays. This replaced + the earlier 30031A System Clock/Console Interface that had been used with the + CX and Series I machines, which used core memory. As part of this change, + the system console moved from the dedicated card to ATC port 0. + + The clock provides programmable periods of 10 microseconds to 10 seconds in + decade increments. Each "tick" of the clock increments a presettable counter + that may be compared to a selected limit value. The clock may request an + interrupt when the values are equal, and a status indication is provided if + the counter reaches the limit a second time without acknowledgement. + + The clock simulation provides both a REALTIME mode that establishes periods + in terms of event intervals, based on an average instruction time of 2.5 + microseconds, and a CALTIME mode that calibrates the time delays to match + wall-clock time. As an example, in the former mode, a 1 millisecond period + will elapse after 400 instructions are executed, whereas in the latter mode, + the same period will elapse after 1 millisecond of wall-clock time. As the + simulator is generally one or two orders of magnitude faster than a real HP + 3000, the real-time mode will satisfy the expectations of software that times + external events, such as a disc seek, via a delay loop, whereas the + calibrated mode will update a time-of-day clock as expected by users of the + system. In practice, this means that setting REALTIME mode is necessary to + satisfy the hardware diagnostics, and setting CALTIME mode is necessary when + running MPE. + + Currently, the Fault Logging Interface simulator is not implemented. This + interface is accessed via DRT 2 by the MPE memory logging process, MEMLOGP, + but the process is smart enough to terminate if DRT 2 does not respond. As + the simulator relies on a host memory array to simulate RAM and does not + simulate the ECC check bits, an FLI implementation would always return a "no + errors detected" condition. + + + The clock interface responds only to direct I/O instructions, as follows: + + Control Word Format (CIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M / rate | E | - | irq reset | C | L | A | - - - - | I | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + M = master reset (if bit 3 = 0) + E = master reset enable/load count rate (0/1) + C = reset count register after LR=CR interrupt + L = a WIO addresses the limit/count (0/1) register + A = reset all interrupts + I = enable clock interrupts + + Count Rate Selection: + + 000 = unused + 001 = 10 microseconds + 010 = 100 microseconds + 011 = 1 millisecond + 100 = 10 milliseconds + 101 = 100 milliseconds + 110 = 1 second + 111 = 10 seconds + + IRQ Reset: + + 000 = none + 001 = clear LR = CR interrupt + 010 = clear LR = CR overflow interrupt + 011 = clear I/O system interrupt (SIN) + 100 = unused + 101 = unused + 110 = unused + 111 = unused + + + Status Word Format (TIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | S | D | rate | - - - - - | C | F | - | I | L | R | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + S = SIO OK (always 0) + D = direct read/write I/O OK (always 1) + C = limit register = count register + F = limit register = count register overflow (lost tick) + I = I/O system interrupt request (SIN) + L = limit/count (0/1) register selected + R = reset count register after interrupt + + + Output Data Word Format (WIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | limit register value/count register reset | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + If control word bit 9 is 0, the value is written to the limit register. If + control word bit 9 is 1, the count register is cleared to zero; the output + value is ignored. + + + Input Data Word Format (RIO): + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | count register value | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + + Implementation notes: + + 1. MPE sets the system clock to a 1 millisecond period and a 100 millisecond + limit to achieve the 10 interrupts per second rate required by the + time-of-day clock maintained by the OS. The short period precludes + idling. Therefore, this configuration is detected and implemented + internally as a 10 millisecond service time with the counter incremented + by 10 for each event service. In addition, the clock service is + synchronized with the CPU process clock service and the ATC poll service + to improve idling. + + 2. If the clock is calibrated, a prescaler is used to achieve the 1 second + and 10 second periods while the event service time remains at 100 + milliseconds. For periods shorter than 1 second, and for all realtime + periods, the prescaler is not used. The prescaler is necessary because + the "sim_rtcn_calb" routine in the sim_timer library requires an integer + ticks-per-second parameter. +*/ + + + +#include "hp3000_defs.h" +#include "hp3000_io.h" + + + +/* Program constants */ + +#define CLK_MULTIPLIER 10 /* number of MPE clock ticks per service */ +#define CLK_RATE (1000 / CLK_MULTIPLIER) /* MPE clock rate in ticks per second */ + + +static const int32 delay [8] = { /* clock delays, in event ticks per interval */ + 0, /* 000 = unused */ + uS (10), /* 001 = 10 microseconds */ + uS (100), /* 010 = 100 microseconds */ + mS (1), /* 011 = 1 millisecond */ + mS (10), /* 100 = 10 milliseconds */ + mS (100), /* 101 = 100 milliseconds */ + S (1), /* 110 = 1 second */ + S (10) /* 111 = 10 seconds */ + }; + +static const int32 ticks [8] = { /* clock ticks per second */ + 0, /* 000 = unused */ + 100000, /* 001 = 10 microseconds */ + 10000, /* 010 = 100 microseconds */ + 1000, /* 011 = 1 millisecond */ + 100, /* 100 = 10 milliseconds */ + 10, /* 101 = 100 milliseconds */ + 10, /* 110 = 1 second */ + 10 /* 111 = 10 seconds */ + }; + +static const int32 scale [8] = { /* prescaler counts per clock tick */ + 1, /* 000 = unused */ + 1, /* 001 = 10 microseconds */ + 1, /* 010 = 100 microseconds */ + 1, /* 011 = 1 millisecond */ + 1, /* 100 = 10 milliseconds */ + 1, /* 101 = 100 milliseconds */ + 10, /* 110 = 1 second */ + 100 /* 111 = 10 seconds */ + }; + + +/* Unit flags */ + +#define UNIT_CALTIME_SHIFT (UNIT_V_UF + 0) /* calibrated timing mode */ + +#define UNIT_CALTIME (1u << UNIT_CALTIME_SHIFT) + + +/* Debug flags */ + +#define DEB_CSRW (1u << 0) /* trace commands received and status returned */ +#define DEB_PSERV (1u << 1) /* trace unit service scheduling calls */ +#define DEB_IOB (1u << 2) /* trace I/O bus signals and data words exchanged */ + + +/* Control word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | M / rate | E | - | irq reset | C | L | A | - - - - | I | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define CN_MR 0100000u /* (M) master reset (if bit 3 = 0) */ +#define CN_RATE_MASK 0160000u /* clock rate selector mask (if bit 3 = 1) */ +#define CN_RESET_LOAD_SEL 0010000u /* (E) select reset/load rate (0/1) */ +#define CN_IRQ_RESET_MASK 0003400u /* interrupt request reset selector mask */ +#define CN_COUNT_RESET 0000200u /* (C) reset count register after LR=CR interrupt */ +#define CN_LIMIT_COUNT_SEL 0000100u /* (L) select limit/count (0/1) register */ +#define CN_IRQ_RESET_ALL 0000040u /* (A) reset all interrupt requests */ +#define CN_IRQ_ENABLE 0000001u /* (I) enable clock interrupts */ + +#define CN_RATE_SHIFT 13 /* clock rate alignment shift */ +#define CN_IRQ_RESET_SHIFT 8 /* interrupt request reset alignment shift */ + +#define CN_RATE(c) (((c) & CN_RATE_MASK) >> CN_RATE_SHIFT) +#define CN_RESET(c) (((c) & CN_IRQ_RESET_MASK) >> CN_IRQ_RESET_SHIFT) + +static const char *const rate_name [8] = { /* clock rate selector names */ + "unused", /* 000 = unused */ + "10 microsecond", /* 001 = 10 microseconds */ + "100 microsecond", /* 010 = 100 microseconds */ + "1 millisecond", /* 011 = 1 millisecond */ + "10 millisecond", /* 100 = 10 milliseconds */ + "100 millisecond", /* 101 = 100 milliseconds */ + "1 second", /* 110 = 1 second */ + "10 second" /* 111 = 10 seconds */ + }; + +static const char *const irq_reset_name [8] = { /* IRQ reset selector names */ + "", /* 000 = none */ + " | reset LR = CR irq", /* 001 = LR equal CR */ + " | reset LR = CR overflow irq", /* 010 = LR equal CR overflow */ + " | reset SIN irq", /* 011 = I/O system */ + "", /* 100 = unused */ + "", /* 101 = unused */ + "", /* 110 = unused */ + "" /* 111 = unused */ + }; + +static const BITSET_NAME control_names [] = { /* Control word names */ + "master reset", /* bit 0 */ + NULL, /* bit 1 */ + NULL, /* bit 2 */ + "load rate", /* bit 3 */ + NULL, /* bit 4 */ + NULL, /* bit 5 */ + NULL, /* bit 6 */ + NULL, /* bit 7 */ + "reset count", /* bit 8 */ + "\1select count\0select limit", /* bit 9 */ + "reset interrupts", /* bit 10 */ + NULL, /* bit 11 */ + NULL, /* bit 12 */ + NULL, /* bit 13 */ + NULL, /* bit 14 */ + "enable interrupts" /* bit 15 */ + }; + +static const BITSET_FORMAT control_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (control_names, 0, msb_first, has_alt, no_bar) }; + + +/* Status word. + + 0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | - | D | rate | - - - - - | C | F | - | I | L | R | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +*/ + +#define ST_DIO_OK 0040000u /* (D) direct I/O OK to use */ +#define ST_RATE_MASK 0034000u /* clock rate mask */ +#define ST_LR_EQ_CR 0000040u /* (C) limit register = count register */ +#define ST_LR_EQ_CR_OVFL 0000020u /* (F) limit register = count register overflow */ +#define ST_SYSTEM_IRQ 0000004u /* (I) I/O system interrupt request */ +#define ST_LIMIT_COUNT_SEL 0000002u /* (L) limit/count (0/1) register selected */ +#define ST_COUNT_RESET 0000001u /* (R) count register is reset after LR=CR interrupt */ + +#define ST_RATE_SHIFT 11 /* clock rate alignment shift */ + +#define ST_RATE(r) ((r) << ST_RATE_SHIFT & ST_RATE_MASK) + +#define ST_TO_RATE(s) (((s) & ST_RATE_MASK) >> ST_RATE_SHIFT) + +static const BITSET_NAME status_names [] = { /* Status word names */ + "DIO OK", /* bit 1 */ + NULL, /* bit 2 */ + NULL, /* bit 3 */ + NULL, /* bit 4 */ + NULL, /* bit 5 */ + NULL, /* bit 6 */ + NULL, /* bit 7 */ + NULL, /* bit 8 */ + NULL, /* bit 9 */ + "LR = CR", /* bit 10 */ + "LR = CR overflow", /* bit 11 */ + NULL, /* bit 12 */ + "system interrupt", /* bit 13 */ + "\1count selected\0limit selected", /* bit 14 */ + "reset after interrupt" /* bit 15 */ + }; + +static const BITSET_FORMAT status_format = /* names, offset, direction, alternates, bar */ + { FMT_INIT (status_names, 0, msb_first, has_alt, append_bar) }; + + +/* System clock state */ + +static FLIP_FLOP system_irq = CLEAR; /* SIN interrupt request flip-flop */ +static FLIP_FLOP limit_irq = CLEAR; /* limit = count interrupt request flip-flop */ +static FLIP_FLOP lost_tick_irq = CLEAR; /* limit = count overflow interrupt request flip-flop */ + +static HP_WORD control_word; /* control word */ +static HP_WORD status_word; /* status word */ +static HP_WORD count_register; /* counter register */ +static HP_WORD limit_register; /* limit register */ +static uint32 rate; /* clock rate */ +static uint32 prescaler; /* clock rate prescaler */ + +static uint32 increment = 1; /* count register increment */ +static t_bool coschedulable = FALSE; /* TRUE if the clock can be coscheduled with PCLK */ +static t_bool coscheduled = FALSE; /* TRUE if the clock is coscheduled with PCLK */ + + +/* System clock local SCP support routines */ + +static CNTLR_INTRF clk_interface; +static t_stat clk_service (UNIT *uptr); +static t_stat clk_reset (DEVICE *dptr); + + +/* System clock local utility routines */ + +static void resync_clock (void); + + +/* System clock SCP interface data structures */ + + +/* Device information block */ + +static DIB clk_dib = { + &clk_interface, /* device interface */ + 3, /* device number */ + SRNO_UNUSED, /* service request number */ + 1, /* interrupt priority */ + INTMASK_UNUSED /* interrupt mask */ + }; + +/* Unit list */ + +static UNIT clk_unit [] = { + { UDATA (&clk_service, UNIT_IDLE | UNIT_CALTIME, 0) } + }; + +/* Register list */ + +static REG clk_reg [] = { +/* Macro Name Location Width Offset Flags */ +/* ------ ------ ----------------- ----- ------ ------------------ */ + { ORDATA (CNTL, control_word, 16) }, + { ORDATA (STAT, status_word, 16) }, + { ORDATA (COUNT, count_register, 16) }, + { ORDATA (LIMIT, limit_register, 16) }, + { ORDATA (RATE, rate, 3) }, + { FLDATA (SYSIRQ, system_irq, 0) }, + { FLDATA (LIMIRQ, limit_irq, 0) }, + { FLDATA (OVFIRQ, lost_tick_irq, 0) }, + + { DRDATA (SCALE, prescaler, 16), REG_HRO }, + { DRDATA (INCR, increment, 16), REG_HRO }, + { FLDATA (COSOK, coschedulable, 0), REG_HRO }, + { FLDATA (COSCH, coscheduled, 0), REG_HRO }, + { DRDATA (UWAIT, clk_unit [0].wait, 32), PV_LEFT | REG_HRO }, + + DIB_REGS (clk_dib), + + { NULL } + }; + +/* Modifier list */ + +static MTAB clk_mod [] = { +/* Mask Value Match Value Print String Match String Validation Display Descriptor */ +/* ------------ ------------ ------------------- ------------ ---------- ------- ---------- */ + { UNIT_CALTIME, UNIT_CALTIME, "calibrated timing", "CALTIME", NULL, NULL, NULL }, + { UNIT_CALTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL }, + +/* Entry Flags Value Print String Match String Validation Display Descriptor */ +/* ----------- ----------- ------------ ------------ ------------ ------------- ----------------- */ + { MTAB_XDV, VAL_DEVNO, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &clk_dib }, + { MTAB_XDV, VAL_INTPRI, "INTPRI", "INTPRI", &hp_set_dib, &hp_show_dib, (void *) &clk_dib }, + { 0 } + }; + +/* Debugging trace list */ + +static DEBTAB clk_deb [] = { + { "CSRW", DEB_CSRW }, /* interface control, status, read, and write actions */ + { "PSERV", DEB_PSERV }, /* clock unit service scheduling calls */ + { "IOBUS", DEB_IOB }, /* interface I/O bus signals and data words */ + { NULL, 0 } + }; + +/* Device descriptor */ + +DEVICE clk_dev = { + "CLK", /* device name */ + clk_unit, /* unit array */ + clk_reg, /* register array */ + clk_mod, /* modifier array */ + 1, /* number of units */ + 8, /* address radix */ + PA_WIDTH, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + DV_WIDTH, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &clk_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + &clk_dib, /* device information block pointer */ + DEV_DISABLE | DEV_DEBUG, /* device flags */ + 0, /* debug control flags */ + clk_deb, /* debug flag name array */ + NULL, /* memory size change routine */ + NULL /* logical device name */ + }; + + + +/* System clock global routines */ + + + +/* Update the counter register. + + If the clock is currently coscheduled with the CPU process clock, then the + service interval is actually ten times the programmed rate. To present the + correct value when the counter register is read, this routine is called to + increment the count by an amount proportional to the fraction of the service + interval that has elapsed. In addition, it's called by the CPU instruction + postlude, so that the counter will have the correct value if it's examined + from the SCP command prompt. + + This routine is also called when the counter is to be reset. This ensures + that the increment is reduced by the time elapsed before the counter is + zeroed. +*/ + +void clk_update_counter (void) +{ +int32 elapsed, ticks; + +if (coscheduled) { /* if the clock is coscheduled, then adjust the count */ + elapsed = clk_unit [0].wait /* the elapsed time is the original wait time */ + - sim_activate_time (&clk_unit [0]); /* less the time remaining before the next service */ + + ticks = (elapsed * CLK_MULTIPLIER) / clk_unit [0].wait /* the adjustment is the elapsed fraction of the multiplier */ + - (CLK_MULTIPLIER - increment); /* less the amount of any adjustment already made */ + + count_register = count_register + ticks & R_MASK; /* update the clock counter with rollover */ + increment = increment - ticks; /* and reduce the amount remaining to add at service */ + } + +return; +} + + + +/* System clock local SCP support routines */ + + + +/* System clock interface. + + The system clock is installed on the IOP bus and receives direct I/O commands + from the IOP. It does not respond to Programmed I/O (SIO) commands. + + In simulation, the asserted signals on the bus are represented as bits in the + inbound_signals set. Each signal is processed sequentially in numerical + order, and a set of similar outbound_signals is assembled and returned to the + caller, simulating assertion of the corresponding bus signals. + + There is no interrupt mask; interrupts are always unmasked, and the interface + does not respond to the SMSK I/O order. + + + Implementation notes: + + 1. In hardware, setting the tick rate in the control word addresses a + multiplexer that selects one of the 10 MHz clock division counter outputs + as the clock source for the count register. Setting the rate bits to 0 + inhibits the count register, although the division counter continues to + run. In simulation, setting a new rate stops and then restarts the event + service with the new delay time, equivalent in hardware to clearing the + clock division counter. + + 2. Receipt of a DRESETINT signal clears the interrupt request and active + flip-flops but does not cancel a request that is pending but not yet + serviced by the IOP. However, when the IOP does service the request by + asserting INTPOLLIN, the interface routine returns INTPOLLOUT, which will + cancel the request. + + 3. The "%.0s" print specification in the DCONTSTB trace call absorbs the + rate name parameter without printing when the rate is not specified. +*/ + +static SIGNALS_DATA clk_interface (DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value) +{ +INBOUND_SIGNAL signal; +INBOUND_SET working_set = inbound_signals; +HP_WORD outbound_value = 0; +OUTBOUND_SET outbound_signals = NO_SIGNALS; + +dprintf (clk_dev, DEB_IOB, "Received data %06o with signals %s\n", + inbound_value, fmt_bitset (inbound_signals, inbound_format)); + +while (working_set) { + signal = IONEXTSIG (working_set); /* isolate the next signal */ + + switch (signal) { /* dispatch an I/O signal */ + + case DCONTSTB: + control_word = inbound_value; /* save the control word */ + + if (control_word & CN_RESET_LOAD_SEL) { /* if the reset/load selector is set */ + rate = CN_RATE (control_word); /* then load the clock rate */ + + if (clk_unit [0].flags & UNIT_CALTIME) /* if in calibrated timing mode */ + prescaler = scale [rate]; /* then set the prescaler */ + else /* otherwise */ + prescaler = 1; /* the prescaler isn't used */ + + sim_cancel (&clk_unit [0]); /* changing the rate restarts the timing divider */ + + if (rate > 0) { /* if the rate is valid */ + clk_unit [0].wait = delay [rate]; /* then set the initial service delay */ + sim_rtcn_init (clk_unit [0].wait, TMR_CLK); /* initialize the clock */ + resync_clock (); /* and reschedule the service */ + } + } + + else if (control_word & CN_MR) { /* otherwise, if the master reset bit is set */ + clk_reset (&clk_dev); /* then reset the interface */ + control_word = 0; /* (which clears the other settings) */ + } + + if (control_word & CN_IRQ_RESET_ALL) { /* if a reset of all interrupts is requested */ + limit_irq = CLEAR; /* then clear the limit = count, */ + lost_tick_irq = CLEAR; /* limit = count overflow, */ + system_irq = CLEAR; /* and system flip-flops */ + } + + else if (control_word & CN_IRQ_RESET_MASK) /* otherwise if any single resets are requested */ + switch (CN_RESET (control_word)) { /* then reset the specified flip-flop */ + case 1: + limit_irq = CLEAR; /* clear the limit = count interrupt request */ + break; + + case 2: + lost_tick_irq = CLEAR; /* clear the limit = count overflow interrupt request */ + break; + + case 3: + system_irq = CLEAR; /* clear the system interrupt request */ + break; + + default: /* the rest of the values do nothing */ + break; + } + + if (dibptr->interrupt_active == CLEAR) /* if no interrupt is active */ + working_set |= DRESETINT; /* then recalculate interrupt requests */ + + dprintf (clk_dev, DEB_CSRW, (inbound_value & CN_RESET_LOAD_SEL + ? "Control is %s | %s rate%s\n" + : "Control is %s%.0s%s\n"), + fmt_bitset (inbound_value, control_format), + rate_name [CN_RATE (inbound_value)], + irq_reset_name [CN_RESET (inbound_value)]); + break; + + + case DSTATSTB: + status_word = ST_DIO_OK | ST_RATE (rate); /* set the clock rate */ + + if (limit_irq) /* if the limit = count flip-flop is set */ + status_word |= ST_LR_EQ_CR; /* set the corresponding status bit */ + + if (lost_tick_irq) /* if the limit = count overflow flip-flop is set */ + status_word |= ST_LR_EQ_CR_OVFL; /* set the corresponding status bit */ + + if (system_irq) /* if the system interrupt request flip-flop is set */ + status_word |= ST_SYSTEM_IRQ; /* set the corresponding status bit */ + + if (control_word & CN_LIMIT_COUNT_SEL) /* if the limit/count selector is set */ + status_word |= ST_LIMIT_COUNT_SEL; /* set the corresponding status bit */ + + if (control_word & CN_COUNT_RESET) /* if the reset-after-interrupt selector is set */ + status_word |= ST_COUNT_RESET; /* set the corresponding status bit */ + + outbound_value = status_word; /* return the status word */ + + dprintf (clk_dev, DEB_CSRW, "Status is %s%s rate\n", + fmt_bitset (outbound_value, status_format), + rate_name [ST_TO_RATE (outbound_value)]); + break; + + + case DREADSTB: + clk_update_counter (); /* update the clock counter register */ + outbound_value = LOWER_WORD (count_register); /* and then read it */ + + dprintf (clk_dev, DEB_CSRW, "Count register value %u returned\n", + count_register); + break; + + + case DWRITESTB: + if (control_word & CN_LIMIT_COUNT_SEL) { /* if the limit/count selector is set */ + clk_update_counter (); /* then update the clock counter register */ + count_register = 0; /* and then clear it */ + + dprintf (clk_dev, DEB_CSRW, "Count register cleared\n"); + } + + else { /* otherwise */ + limit_register = inbound_value; /* set the limit register to the supplied value */ + + dprintf (clk_dev, DEB_CSRW, "Limit register value %u set\n", + limit_register); + + coschedulable = (ticks [rate] == 1000 /* the clock can be coscheduled if the rate */ + && limit_register == 100); /* is 1 msec and the limit is 100 ticks */ + } + break; + + + case DSETINT: + system_irq = SET; /* set the system interrupt request flip-flop */ + + dibptr->interrupt_request = SET; /* request an interrupt */ + outbound_signals |= INTREQ; /* and notify the IOP */ + break; + + + case DRESETINT: + dibptr->interrupt_active = CLEAR; /* clear the Interrupt Active flip-flop */ + + if ((limit_irq == SET || lost_tick_irq == SET) /* if the limit or lost tick flip-flops are set */ + && control_word & CN_IRQ_ENABLE) /* and interrupts are enabled */ + dibptr->interrupt_request = SET; /* then set the interrupt request flip-flop */ + else /* otherwise */ + dibptr->interrupt_request = system_irq; /* request an interrupt if the system flip-flop is set */ + + if (dibptr->interrupt_request) /* if a request is pending */ + outbound_signals |= INTREQ; /* then notify the IOP */ + break; + + + case INTPOLLIN: + if (dibptr->interrupt_request) { /* if a request is pending */ + dibptr->interrupt_request = CLEAR; /* then clear it */ + dibptr->interrupt_active = SET; /* and mark it as now active */ + + outbound_signals |= INTACK; /* acknowledge the interrupt */ + outbound_value = dibptr->device_number; /* and return our device number */ + } + + else /* otherwise the request has been reset */ + outbound_signals |= INTPOLLOUT; /* so let the IOP know to cancel it */ + break; + + + case DSTARTIO: /* not used by this interface */ + case DSETMASK: /* not used by this interface */ + case ACKSR: /* not used by this interface */ + case TOGGLESR: /* not used by this interface */ + case SETINT: /* not used by this interface */ + case PCMD1: /* not used by this interface */ + case PCONTSTB: /* not used by this interface */ + case SETJMP: /* not used by this interface */ + case PSTATSTB: /* not used by this interface */ + case PWRITESTB: /* not used by this interface */ + case PREADSTB: /* not used by this interface */ + case EOT: /* not used by this interface */ + case TOGGLEINXFER: /* not used by this interface */ + case TOGGLEOUTXFER: /* not used by this interface */ + case READNEXTWD: /* not used by this interface */ + case TOGGLESIOOK: /* not used by this interface */ + case DEVNODB: /* not used by this interface */ + case XFERERROR: /* not used by this interface */ + case CHANSO: /* not used by this interface */ + case PFWARN: /* not used by this interface */ + break; + } + + IOCLEARSIG (working_set, signal); /* remove the current signal from the set */ + } + +dprintf (clk_dev, DEB_IOB, "Returned data %06o with signals %s\n", + outbound_value, fmt_bitset (outbound_signals, outbound_format)); + +return IORETURN (outbound_signals, outbound_value); /* return the outbound signals and value */ +} + + +/* Service the system clock unit. + + At each "tick" of the clock, the count register is incremented and compared + to the limit register. If they are equal, then the counter is cleared (if + enabled) and an interrupt is generated (if enabled). + + If the clock is calibrated, a prescaler is used to achieve the 1 second and + 10 second periods while the event time remains at 100 milliseconds. For + periods shorter than 1 second, and for all realtime periods, the prescaler is + not used (by setting the value to 1). + + If the clock is currently coscheduled with the CPU process clock, then the + service interval is actually ten times the programmed rate, so the count + register increment per service entry is 10 instead of 1. + + + Implementation notes: + + 1. The count/limit comparison hardware provides only an equal condition. If + the limit register is set to a value below the current count, or the + LR=CR interrupt is not enabled until after the count register value has + exceeded the limit, comparison will not occur until the count register + overflows and again reaches the limit. +*/ + +static t_stat clk_service (UNIT *uptr) +{ +dprintf (clk_dev, DEB_PSERV, "Service entered with counter %u increment %u limit %u\n", + count_register, increment, limit_register); + +prescaler = prescaler - 1; /* decrement the prescaler count */ + +if (prescaler == 0) { /* if the prescaler count has expired */ + count_register = count_register + increment & R_MASK; /* then the count register counts up */ + + if (count_register == limit_register) { /* if the limit has been reached */ + if (limit_irq == SET) /* then if the last limit interrupt wasn't serviced */ + lost_tick_irq = SET; /* then set the overflow interrupt */ + else /* otherwise */ + limit_irq = SET; /* set the limit interrupt */ + + if (control_word & CN_COUNT_RESET) /* if the counter reset option is selected */ + count_register = 0; /* then clear the count register */ + + if (control_word & CN_IRQ_ENABLE /* if clock interrupts are enabled */ + && clk_dib.interrupt_active == CLEAR) { /* and the interrupt active flip-flop is clear */ + clk_dib.interrupt_request = SET; /* then request an interrupt */ + iop_assert_INTREQ (&clk_dib); /* and notify the IOP of the INTREQ signal */ + } + } + + if (uptr->flags & UNIT_CALTIME) /* if in calibrated timing mode */ + prescaler = scale [rate]; /* then reset the prescaler */ + else /* otherwise */ + prescaler = 1; /* the prescaler isn't used */ + } + +if (!(uptr->flags & UNIT_CALTIME)) { /* if the clock is in real timing mode */ + uptr->wait = delay [rate]; /* then set an event-based delay */ + increment = 1; /* equal to the selected period */ + coscheduled = FALSE; /* the clock is not coscheduled with the process clock */ + } + +else if (coschedulable && cpu_is_calibrated) { /* otherwise if the process clock is calibrated */ + uptr->wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */ + increment = CLK_MULTIPLIER; /* at one-tenth of the selected period */ + coscheduled = TRUE; /* the clock is coscheduled with the process clock */ + } + +else { /* otherwise */ + uptr->wait = sim_rtcn_calb (ticks [rate], TMR_CLK); /* calibrate the clock to a delay */ + increment = 1; /* equal to the selected period */ + coscheduled = FALSE; /* the clock is not coscheduled with the process clock */ + } + +dprintf (clk_dev, DEB_PSERV, "Rate %s delay %d service %s\n", + rate_name [rate], uptr->wait, + (coscheduled ? "coscheduled" : "scheduled")); + +return sim_activate (uptr, uptr->wait); /* activate the unit and return the status */ +} + + +/* Device reset. + + This routine is called for a RESET or RESET CLK command. It is the + simulation equivalent of the IORESET signal, which is asserted by the front + panel LOAD and DUMP switches. + + For this interface, IORESET is identical to a Programmed Master Reset + (control word bit 0 set with bit 3 clear). + + A master reset is generated either by an IORESET signal or a Direct I/O + Master Reset (control word bit 0 set with bit 3 clear). + + + Implementation notes: + + 1. In simulation, the Enable Clock Interrupts flip-flop, the Reset Count + Register after LR=CR Interrupt flip-flop, and the Address Limit/Count + Register flip-flop are maintained in the control word rather than as + separate values. + + 2. The hardware interrupt circuitry contains an Interrupt Active flip-flop + and an Interrupt Priority latch but no Interrupt Request flip-flop. + Instead, the INTREQ signal is the logical OR of the LR=CR Interrupt and + LR=CR Overflow Interrupt flip-flops (if enabled by the Enable Clock + Interrupts flip-flop) with the the System Interrupt flip-flop. In + simulation, the interrupt_request flip-flop in the Device Information + Block is set explicitly to reflect this logic. Clearing the three + interrupt source flip-flops therefore clears the interrupt_request + flip-flop as well. + + 3. In simulation, the clock division counters are represented by the event + service delay. Stopping and restarting the delay is equivalent to + clearing the division counters. +*/ + +static t_stat clk_reset (DEVICE *dptr) +{ +count_register = 0; /* clear the count */ +limit_register = 0; /* and limit registers */ + +rate = 0; /* clear the clock rate */ +prescaler = 1; /* and set the clock prescaler */ + +sim_cancel (dptr->units); /* clearing the rate stops the clock */ + +clk_dib.interrupt_request = CLEAR; /* clear any current */ +clk_dib.interrupt_active = CLEAR; /* interrupt request */ + +system_irq = CLEAR; /* clear the system, */ +limit_irq = CLEAR; /* limit = count, */ +lost_tick_irq = CLEAR; /* and limit = count overflow flip-flops */ + +control_word = 0; /* clear the enable, write select, and count reset actions */ + +return SCPE_OK; +} + + + +/* System clock local utility routines */ + + + +/* Resynchronize the clock. + + After changing the rate or the limit, the new values are examined to see if + the clock may be coscheduled with the process clock to permit idling. If + coscheduling is possible and both the system clock and the CPU process clock + are calibrated, then the clock event service is synchronized with the process + clock service. Otherwise, the service time is set up but is otherwise + asynchronous with the process clock. + + + Implementation notes: + + 1. To synchronize events, the clock must be activated absolutely, as a + service event may already be scheduled, and normal activation will not + disturb an existing event. +*/ + +static void resync_clock (void) +{ +coschedulable = (ticks [rate] == 1000 /* the clock can be coscheduled if the rate */ + && limit_register == 100); /* is 1 msec and the limit is 100 ticks */ + +if (clk_unit [0].flags & UNIT_CALTIME /* if the clock is in calibrated timing mode */ + && coschedulable /* and may be coscheduled with the process clock */ + && cpu_is_calibrated) { /* and the process clock is calibrated */ + clk_unit [0].wait = sim_activate_time (cpu_pclk_uptr); /* then synchronize with it */ + coscheduled = TRUE; /* the clock is coscheduled with the process clock */ + } + +else { /* otherwise */ + clk_unit [0].wait = delay [rate]; /* set up an independent clock */ + coscheduled = FALSE; /* the clock is not coscheduled with the process clock */ + } + +dprintf (clk_dev, DEB_PSERV, "Rate %s delay %d service rescheduled\n", + rate_name [rate], clk_unit [0].wait); + +sim_activate_abs (&clk_unit [0], clk_unit [0].wait); /* restart the clock */ + +return; +} diff --git a/HP3000/hp3000_cpu.c b/HP3000/hp3000_cpu.c new file mode 100644 index 00000000..60914314 --- /dev/null +++ b/HP3000/hp3000_cpu.c @@ -0,0 +1,4606 @@ +/* hp3000_cpu.c: HP 3000 Central Processing Unit simulator + + Copyright (c) 2016-2017, J. David Bryan + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of the author shall not be used + in advertising or otherwise to promote the sale, use or other dealings in + this Software without prior written authorization from the author. + + CPU HP 3000 Series III Central Processing Unit + + 19-Jan-17 JDB Added comments describing the OPND and EXEC trace options + 29_Dec-16 JDB Changed the status mnemonic flag from REG_S to REG_T + 07-Nov-16 JDB Renamed cpu_byte_to_word_ea to cpu_byte_ea + 03-Nov-16 JDB Added zero offsets to the cpu_call_procedure calls + 01-Nov-16 JDB Added per-instruction trace capability + 24-Oct-16 JDB Renamed SEXT macro to SEXT16 + 22-Sep-16 JDB Moved byte_to_word_address from hp3000_cpu_base.c + 21-Sep-16 JDB Added CIS/NOCIS option + 01-Sep-16 JDB Add power fail/power restore support + 23-Aug-16 JDB Add module interrupt support + 14-Jul-16 JDB Implemented the cold dump process + Changed "loading" EXEC_STATE to "waiting" + 11-Jul-16 JDB Change "cpu_unit" from a UNIT to an array of one UNIT + 08-Jun-16 JDB Corrected %d format to %u for unsigned values + 16-May-16 JDB ACCESS_PROPERTIES.name is now a pointer-to-constant + 13-May-16 JDB Modified for revised SCP API function parameter types + 24-Mar-16 JDB Changed memory word type from uint16 to MEMORY_WORD + 21-Mar-16 JDB Changed cpu_ccb_table type from uint16 to HP_WORD + 11-Mar-16 JDB Fixed byte EA calculations with negative indexes + 22-Dec-15 JDB First release version + 01-Apr-15 JDB First successful run of MPE-V/R through account login + 11-Dec-12 JDB Created + + References: + - HP 3000 Series II System Microprogram Listing + (30000-90023, August 1976) + - HP 3000 Series II/III System Reference Manual + (30000-90020, July 1978) + - Machine Instruction Set Reference Manual + (30000-90022, June 1984) + + + The HP 3000 is a family of general-purpose business computers that were sold + by Hewlett-Packard from 1972 through 2001. There are two major divisions + within this family: the "classic" 16-bit, stack-oriented CISC machines, and + the "Precision Architecture" 32-bit, register-oriented RISC machines that + succeeded them. All machines run versions of MPE, the "Multiprogramming + Executive" operating system. + + Within the "classic" division, there are two additional subdivisions, based + on the method used for peripheral connections: the original "SIO" machines, + and the later "HP-IB" machines. The I/O interfacing hardware differs between + the two types of machines, as do the privileged I/O machine instructions. + The user instruction sets are identical, as are the register sets visible to + the programmer. The I/O drivers are different to account for the hardware + differences, and therefore they run slightly different versions of MPE. + + This implementation is a simulator for the classic SIO machines. This group + consists of the 3000 CX, the Series I, Series II, and Series III; the last is + simulated here. The CX and Series I, which is a repackaged CX, are + essentially subsets of the Series II/III -- a smaller instruction set, + limited memory size, and lower-precision floating-point instructions. + Simulation of these machines may be added in the future. Future simulation + of the HP-IB machines (the Series 30 through 70) is desirable, as the latest + MPE versions run only on these machines, but documentation on the internals + of the HP-IB hardware controllers is nonexistent. + + The CX and Series I support 64K 16-bit words of memory. The Series II + supports up to 256K, divided into four banks of 64K words each, and the + Series III extends this to 1024K words using 16 banks. Memory is divided + into variable-length code and data segments, with the latter containing a + program's global data area and stack. + + Memory protection is accomplished by checking program, data, and stack + accesses against segment base and limit registers, which can be set only by + MPE. Bounds violations cause automatic hardware traps to a handler routine + within MPE. Some violations may be permitted; for example, a Stack Overflow + trap may cause MPE to allocate a larger stack and then restart the + interrupted instruction. Almost all memory references are position- + independent, so moving segments to accommodate expansion requires only + resetting of the segment registers to point at the new locations. Code + segments are fully reentrant and shareable, and both code and data are + virtual, as the hardware supports absent code and data segment traps. + + The classic 3000s are stack machines. Most of the instructions operate on + the value on the top of the stack (TOS) or on the TOS and the next-to-the-top + of the stack (NOS). To improve execution speed, the 3000 has a set of + hardware registers that are accessed as the first four locations at the top + of the stack, while the remainder of the stack locations reside in main + memory. A hardware register renamer provides fast stack pushes and pops + without physically copying values between registers. + + In hardware, the stack registers are referenced internally as TR0-TR3 and + externally as RA-RD. An access to the RA (TOS) register is translated by the + renamer to access TR0, TR1, etc. depending on which internal register is + designated as the current top of the stack. For example, assume that RA + corresponds to TR0. To push a new value onto the top of the stack, the + renamer is adjusted so that RA corresponds to TR1, and the new value is + stored there. In this state, RB corresponds to TR0, the previous TOS (and + current NOS) value. Additional pushes rename RA as TR2 and then TR3, with RB + being renamed to TR1 and then TR2, and similarly for RC and RD. The number + of valid TOS registers is given by the value in the SR register, and the + first stack location in memory is given by the value in the SM register. + Pops reverse the sequence: a pop with RA corresponding to TR3 renames RA to + TR2, RB to TR1, etc. When all four stack registers are in use, a push will + copy the value in RD to the top of the memory stack (SM + 1) before the + registers are renamed and the new value stored into RA. Similarly, when all + four stack registers are empty, a pop simply decrements the SM register, + effectively deleting the top of the memory stack. + + Because of the renamer, the microcode can always use RA to refer to the top + of the stack, regardless of which internal TR register is being used, and + similarly for RB-RD. Execution of each stack instruction begins with a + preadjustment, if needed, that loads the TOS registers that will be used by + the instruction from the top of the memory stack. For example, if only RA + contains a value (i.e., the SR register value is 1), the ADD instruction, + which adds the values in RA and RB, will load RB with the value on the top of + the memory stack, the SR count will be incremented, and the SM register will + be decremented. On the other hand, if both RA and RB contained values (SR >= + 2), then no preadjustment would be required before the ADD instruction + microcode manipulated RA and RB. + + In simulation, the renamer is implemented by physically copying the values + between registers, as this is much faster than mapping via array index + values, as the hardware does. A set of functions provides the support to + implement the hardware stack operations: + + cpu_push - empty the TOS register (SR++, caller stores into RA) + cpu_pop - delete the TOS register (SR--) + cpu_queue_up - move from memory to the lowest TOS register (SR++, SM--) + cpu_queue_down - move from the lowest TOS register to memory (SR--, SM++) + cpu_flush - move all stack registers to memory (SR = 0 on return) + cpu_adjust_sr - queue up until the required SR count is reached + cpu_mark_stack - write a stack marker to memory + + The renamer is described in US Patent 3,737,871 (Katzman, "Stack Register + Renamer", June 1973). + + The MPE operating system is supported by special microcode features that + perform code and data segment mapping, segment access bounds checking, + privilege checking, etc. The layout of certain in-memory tables is known to + both the OS and the microcode and is used to validate execution of + instructions. For instance, every stack instruction is checked for a valid + access within the stack segment boundaries, which are set up by the OS + before program dispatch. For this reason, the 3000 cannot be operated as a + "bare" machine, because these tables would not have been initialized. + Similarly, the "cold load" process by which the OS is loaded from storage + media into memory is entirely in microcode, as machine instructions cannot be + executed until the required tables are loaded into memory. + + This OS/microcode integration means that the microcode may detect conditions + that make continued execution impossible. An example would be a not-present + segment fault for the segment containing the disc I/O driver. If such a + condition is detected, the CPU does a "system halt." This fatal microcode + error, distinct from a regular programmed halt, causes operation to cease + until the CPU is reset. + + The CPU hardware includes a free-running 16-bit process clock that increments + once per millisecond whenever a user program is executing. MPE uses the + process clock to charge CPU usage to programs, and thereby to users, groups, + and accounts. Instructions are provided to read (RCLK) and set (SCLK) the + process clock. + + + The data types supported by the instruction set are: + + - 8-bit unsigned byte + - 16-bit unsigned integer ("logical" format) + - 16-bit two's-complement integer + - 32-bit two's-complement integer + - 32-bit sign-magnitude floating point + - 64-bit sign-magnitude floating point + + Multi-word values are stored in memory with the most-significant words in the + lowest addresses. The stack is organized in memory from lower to higher + addresses, so a value on the stack has its least-significant word on the top + of the stack. + + Machine instructions are initially decoded by a sub-opcode contained in the + four highest bits, as follows (note that the HP 3000 numbers bits from + left-to-right, i.e., bit 0 is the MSB and bit 15 is the LSB): + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 0 0 | 1st stack opcode | 2nd stack opcode | Stack + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 0 1 | X | shift opcode | shift count | Shift + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 0 1 | I | branch opcode |+/-| P displacement | Branch + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 0 1 | X | bit test opcode | bit position | Bit Test + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 0 | move op | opts/S decrement | Move + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 0 | special op | 0 0 | sp op | Special + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | option set | option subset | Firmware + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | imm opcode | immediate operand | Immediate + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | field opcode | J field | K field | Field + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | register op | SK| DB| DL| Z |STA| X | Q | S | Register + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | 0 0 0 0 | I/O opcode | K field | I/O + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | 0 0 0 0 | cntl opcode | 0 0 | cn op | Control + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | program op | N field | Program + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | immediate op | immediate operand | Immediate + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 1 | memory op | P displacement | Memory + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + The memory, loop, and branch instructions occupy the remainder of the + sub-opcodes (04-17): + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | memory op | X | I | mode and displacement | Memory + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 0 | P+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | P- displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 0 | DB+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 0 | Q+ displacement 0-127 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | 0 | Q- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | 1 | S- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | memory op | X | I | s | mode and displacement | Memory + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | DB+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+ + | 1 | 0 | Q+ displacement 0-127 | + +---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 0 | Q- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | S- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 1 0 1 |loop op| 0 |+/-| P-relative displacement | Loop + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 1 1 0 0 | I | 0 1 | > | = | < | P+- displacement 0-31 | Branch + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Optional firmware extension instruction sets occupy instruction codes + 020400-020777, except for the DMUL (020570) and DDIV (020571) instructions + that are part of the base set, as follows: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | 0 1 1 1 | 1 0 0 | x | DMUL/DDIV + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | 0 0 0 0 | 1 | ext fp op | Extended FP + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | 0 0 0 1 | APL op | APL + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | 0 0 1 1 | COBOL op | COBOL + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 0 1 0 | 0 0 0 1 | 1 | options | decimal op | Decimal + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Most instructions are defined by unique 16-bit codes that specify the + operations, operands, addressing modes, shift counts, etc. For each of these + instructions, there is only one canonical form that encodes a given + instruction (or instruction pair, in the case of stack instructions). For + example, the octal value 140003 is the only value that encodes the "BR P+3" + instruction. + + There are also instruction codes that contain one or more bits designated as + "reserved" in the Machine Instruction Set Reference Manual. For some of + these instructions, the reserved bits do not affect instruction decoding. + Each instruction of this type has a defined canonical form -- typically with + the reserved bits set to zero -- but will execute identically if one of the + undefined forms is used. For example, the "MOVE" instructions define bits + 12-13 as 00, but the bits are not decoded, so values of 01, 10, and 11 for + these bits will result in identical execution. Specifically, "MOVE 0" is + encoded canonically as octal value 020020, but the undefined codes 020024, + 020030, and 020034 execute "MOVE 0" as well. + + For the rest of these instructions, the reserved bits are decoded and will + affect the instruction interpretation. An example of this is the "IXIT" + instruction. It also defines bits 12-13 as 00 (canonical encoding 020360), + but in this case the bits must be 00 for the instruction to execute; any + other value (e.g., undefined codes 020364, 020370, or 020374) executes as a + "PCN" instruction, whose canonical encoding is 020362. + + Finally, some codes are not assigned to any instructions, or they are + assigned to instructions that are supplied by optional firmware that is not + present in the machine. These instruction codes are designated as + "unimplemented" and will cause Unimplemented Instruction traps if they are + executed. Examples are the stack operation code 072 in either the left-hand + or right-hand position (i.e., instruction codes 0072xx and 00xx72), codes + 020410-020415 if the Extended Instruction Set firmware option is not + installed, and codes 036000-036777. + + When the simulator examines the bit patterns of instructions to execute, each + will fall into one of four categories: + + 1. Defined (canonical) instruction encodings. + + 2. Undefined (non-canonical) instruction encodings, where reserved fields + are "don't care" bits (e.g., MOVE). + + 3. Undefined (non-canonical) instruction encodings, where reserved fields + are decoded (e.g., IXIT). + + 4. Unimplemented instruction encodings (e.g., stack opcode 072 or EADD with + no EIS installed). + + When examining memory or register values in instruction-mnemonic form, the + names of the canonical instructions in category 1 are displayed in uppercase, + as are the names of the non-canonical instructions in category 2. The + non-canonical instruction names in category 3 are displayed in lowercase. + This is to indicate to the user that the instructions that will be executed + may not be the ones expected from the decoding. Instruction names in + category 4 that correspond to supported firmware options are displayed in + uppercase, regardless of whether or not the option is enabled. Category 4 + encodings that do not correspond to instructions are displayed in octal. + + All of the base set instructions are one word in length. Most of the + firmware extension instructions occupy one word as well, although some are + two words long. If the first word of a two-word instruction is valid but the + second is not, an Unimplemented Instruction trap will occur, with P pointing + to the location after the second word. + + + The simulator provides four stop conditions related to instruction execution + that may be enabled with a SET CPU STOP= command: + + Action + ------ ------------------------------------ + LOOP stop on an infinite loop + PAUSE stop on a PAUS instruction + UNDEF stop on an undefined instruction + UNIMPL stop on an unimplemented instruction + + If an enabled stop condition is detected, execution ceases with the + instruction pending, and control returns to the SCP prompt. When simulation + stops, execution may be resumed in two ways. If the cause of the stop has + not been remedied and the stop has not been disabled, resuming execution with + CONTINUE, STEP, GO, or RUN will cause the stop to occur again. Alternately, + specifying the "-B" switch with any of the preceding commands will resume + execution while bypassing the stop for the current instruction. + + The LOOP option stops the simulator if it attempts to execute an instruction + that enters an infinite loop (e.g., BR P+0). The branch instructions TBA, + TBX, BCC, BR, BCY, BNCY, BOV, and BNOV result in an infinite loop if the + branch displacement is zero and the branch condition is true. The remaining + branch instructions cannot result in an infinite loop, as they all modify the + CPU state and so eventually reach a point where they drop out of the loop. + + The PAUSE option stops the simulator if execution of a PAUS instruction is + attempted. This instruction normally suspends instruction fetching until an + interrupt occurs. Clearing the stop and resuming execution suspends the + fetch/execute process until an external interrupt occurs. Resuming with the + stop bypassed continues execution with the instruction following the PAUS; + this is the same action that occurs when pressing the HALT button and then + the RUN button in hardware. + + The UNDEF option stops the simulator if execution of a non-canonical + instruction from decoding category 3 (i.e., an instruction containing a + decoded reserved bit pattern other than that defined in the Machine + Instruction Set manual) is attempted. The intent is to catch instructions + containing reserved fields with values that change the meaning of those + instructions. Bypassing the stop will decode and execute the instruction in + the same manner as the HP 3000 microcode. + + The UNIMPL option stops the simulator if execution of an instruction from + decoding category 4 is attempted. Bypassing the stop will cause an + Unimplemented Instruction trap. Instructions that depend on the presence of + firmware options are implemented if the option is present and unimplemented + if the option is absent. For example, instruction code 020410 executes as + the EADD instruction if the Extended Instruction Set is enabled but is + unimplemented if the EIS is disabled. It is displayed as EADD in either + case. + + The instructions in category 2 whose non-canonical forms do not cause an + UNDEF stop are: + + Canonical Reserved + Inst Encoding Bits Defined As Decoded As + ---- --------- -------- ----------- ----------- + SCAN 010600 10-15 0 0 0 0 0 0 x x x x x x + TNSL 011600 10-15 0 0 0 0 0 0 x x x x x x + MOVE 020000 12-13 0 0 x x + MVB 020040 12-13 0 0 x x + MVBL 020100 13 0 x + SCW 020120 13 0 x + MVLB 020140 13 0 x + SCU 020160 13 0 x + CMPB 020240 12-13 0 0 x x + RSW 020300 12-14 0 0 0 x x x + LLSH 020301 12-14 0 0 0 x x x + PLDA 020320 12-14 0 0 0 x x x + PSTA 020321 12-14 0 0 0 x x x + LSEA 020340 12-13 0 0 x x + SSEA 020341 12-13 0 0 x x + LDEA 020342 12-13 0 0 x x + SDEA 020343 12-13 0 0 x x + PAUS 030020 12-15 0 0 0 0 x x x x + + The instructions in category 3 whose non-canonical forms cause an UNDEF stop + are: + + Canonical Reserved + Inst Encoding Bits Defined As Decoded As + ---- --------- -------- ---------- ---------- + IXIT 020360 12-15 0 0 0 0 0 0 0 0 + LOCK 020361 12-15 0 0 0 1 n n 0 1 + PCN 020362 12-15 0 0 1 0 n n n 0 + UNLK 020363 12-15 0 0 1 1 n n 1 1 + + SED 030040 12-15 0 0 0 x n n n x + + XCHD 030060 12-15 0 0 0 0 0 0 0 0 + PSDB 030061 12-15 0 0 0 1 n n 0 1 + DISP 030062 12-15 0 0 1 0 n n n 0 + PSEB 030063 12-15 0 0 1 1 n n 1 1 + + SMSK 030100 12-15 0 0 0 0 0 0 0 0 + SCLK 030101 12-15 0 0 0 1 n n n n + RMSK 030120 12-15 0 0 0 0 0 0 0 0 + RCLK 030121 12-15 0 0 0 1 n n n n + + Where: + + x = 0 or 1 + n = any collective value other than 0 + + In hardware, the SED instruction works correctly only if opcodes 030040 and + 030041 are used. Opcodes 030042-030057 also decode as SED, but the status + register is set improperly (the I bit is cleared, bits 12-15 are rotated + right twice and then ORed into the status register). In simulation, opcodes + 030042-030057 work correctly but will cause an UNDEF simulation stop if + enabled. + + + The CPU simulator provides extensive tracing capabilities that may be enabled + with the SET CONSOLE DEBUG= and SET CPU DEBUG= commands. + The trace options that may be specified are: + + Trace Action + ----- ------------------------------------------- + INSTR trace instructions executed + DATA trace memory data accesses + FETCH trace memory instruction fetches + REG trace registers + OPND trace memory operands + EXEC trace matching instruction execution states + PSERV trace process clock service events + + A section of an example trace is: + + >>CPU fetch: 00.010342 020320 instruction fetch + >>CPU instr: 00.010341 000300 ZROX,NOP + >>CPU reg: 00.006500 000000 X 000000, M i t r o c CCG + >>CPU fetch: 00.010343 041100 instruction fetch + >>CPU instr: 00.010342 020320 PLDA + >>CPU data: 00.000000 001340 absolute read + >>CPU reg: 00.006500 000001 A 001340, X 000000, M i t r o c CCG + >>CPU fetch: 00.010344 037777 instruction fetch + >>CPU instr: 00.010343 041100 LOAD DB+100 + >>CPU data: 00.002100 123003 data read + >>CPU reg: 00.006500 000002 A 123003, B 001340, X 000000, M i t r o c CCL + >>CPU fetch: 00.010345 023404 instruction fetch + >>CPU instr: 00.010344 037777 ANDI 377 + >>CPU reg: 00.006500 000002 A 000003, B 001340, X 000000, M i t r o c CCG + >>CPU fetch: 00.010346 002043 instruction fetch + >>CPU instr: 00.010345 023404 MPYI 4 + >>CPU reg: 00.006500 000002 A 000014, B 001340, X 000000, M i t r o c CCG + >>CPU fetch: 00.010347 020320 instruction fetch + + The INSTR option traces instruction executions. Each instruction is printed + before it is executed. The two opcodes of a stack instruction are printed + together before the left-hand opcode is executed. If the right-hand opcode + is not NOP, it is reprinted before execution, with dashes replacing the + just-executed left-hand opcode. + + The DATA option traces reads from and writes to memory. Each access is + classified by the memory bank register that is paired with the specified + offset; they are: dma, absolute, program, data, and stack. DMA accesses + derive their bank addresses from the banks specified in Set Bank I/O program + orders. Absolute accesses always use bank 0. Program, data, and stack + accesses use the bank addresses in the PBANK, DBANK, and SBANK registers, + respectively. + + The FETCH option traces instruction fetches from memory. These accesses are + separated from those traced by the DATA option because fetches usually are of + little interest except when debugging the fetch/execute sequence. Because + the HP 3000 has a two-stage pipeline, fetches load the NIR (Next Instruction + Register) with the instruction after the instruction to be executed from the + CIR (Current Instruction Register). + + The REG option traces register values. Two sets of registers are printed. + After executing each instruction, the currently active TOS registers, the + index register, and the status register are printed. After executing an + instruction that may alter the base registers, the program, data, and stack + segment base registers are printed. + + The OPND option traces memory byte operand values. Some instructions take + memory and register operands that are difficult to decode from DATA or REG + traces. This option presents these operands in a higher-level format. The + memory bank and address values are always those of the operands. The operand + data and values printed are specific to the instruction. For example, the + ALGN instruction prints its source and target operands, digit counts, and + fraction counts, and the EDIT instruction displays its subprogram operations. + + The EXEC option traces the execution of instructions that match + user-specified criteria. When a match occurs, all CPU trace options are + turned on for the duration of the execution of the matched instruction. The + prior trace settings are restored when a match fails. This option allows + detailed tracing of specified instructions while minimizing the log file size + compared to a full instruction trace. + + The PSERV option traces process clock event service entries. Each trace + reports whether or not the CPU was executing on the Interrupt Control Stack + when the process clock ticked. Execution on the ICS implies that the + operating system is executing. As the process clock ticks every millisecond, + enabling PSERV tracing can quickly produce a large number of trace lines. + + The various trace formats are interpreted as follows: + + >>CPU instr: 00.010341 000300 ZROX,NOP + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~ + | | | | + | | | +-- instruction mnemonic(s) + | | +---------- octal data (instruction opcode) + | +------------------ octal address (P) + +----------------------- octal bank (PBANK) + + >>CPU instr: 00.001240 000006 external interrupt + >>CPU instr: 00.023736 000000 unimplemented instruction trap + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- interrupt classification + | | +---------- parameter + | +------------------ octal address (P) at interrupt + +----------------------- octal bank (PBANK) at interrupt + + + >>CPU data: 00.000000 001340 absolute read + >>CPU data: 00.002100 123003 data read + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~ + | | | | + | | | +-- memory access classification + | | +------------ octal data (memory contents) + | +-------------------- octal address (effective address) + +------------------------- octal bank (PBANK, DBANK, or SBANK) + + + >>CPU fetch: 00.010342 020320 instruction fetch + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- memory access classification + | | +------------ octal data (instruction opcode) + | +-------------------- octal address (P + 1) + +------------------------- octal bank (PBANK) + + + >>CPU reg: 00.006500 000002 A 123003, B 001340, X 000000, M i t r o c CCL + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- register values (from 0-4 TOS registers, X, STA) + | | +------------ octal stack register count (SR) + | +-------------------- octal stack memory address (SM) + +------------------------- octal bank (SBANK) + + >>CPU reg: 00.000000 000001 PB 010000, PL 025227, DL 001770, DB 002000, Q 006510, Z 007000 + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- base register values + | | +------------ current code segment number (from STA) + | +-------------------- zero + +------------------------- octal bank (DBANK) + + >>CPU opnd: 00.135771 000000 DFLC '+','!' + >>CPU opnd: 00.045071 000252 target fraction 3 length 6,"002222" + ~~ ~~~~~~ ~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | | | | + | | | +-- operand-specific value + | | +------------ operand-specific data + | +-------------------- octal address (effective address) + +------------------------- octal bank (PBANK, DBANK, or SBANK) + + >>CPU pserv: Process clock service entered not on the ICS + + + The process clock offers a user-selectable choice of calibrated or realistic + timing. Calibrated timing adjusts the clock to match actual elapsed time + (i.e., wall-clock time). Realistic timing bases the process-clock interval + on machine instructions executed, using a mean instruction time of 2.5 + microseconds. Running on a typical host platform, the simulator is one or + two orders of magnitude faster than a real HP 3000, so the number of machine + instructions executed for a given calibrated time interval will be + correspondingly greater. When the process clock is calibrated, the current + simulation speed, expressed as a multiple of the speed of a real HP 3000 + Series III, may be obtained with the SHOW CPU SPEED command. The speed + reported will not be representative if the machine was executing a PAUS + instruction when the simulator was stopped. + + When enabled by a SET CPU IDLE command, execution of a PAUS instruction will + idle the simulator. While idle, the simulator does not use any host system + processor time. Idle detection requires that the process clock and system + clock be set to calibrated timing. Idling is disabled by default. + + + Implementation notes: + + 1. Three timing sources in the simulator may be calibrated to wall-clock + time. These are the process clock, the system clock, and the ATC poll + timer. The process clock is always enabled and running, although the + PCLK register only increments if the CPU is not executing on the ICS. + The system clock and poll timer run continuously if their respective + devices are enabled. If the ATC is disabled, then the process clock + takes over polling for the simulation console. + + The three sources must be synchronized to allow efficient simulator + idling. This is accomplished by designating the process clock as the + master device, which calls the SCP timer calibration routines, and + setting the system clock and ATC poll timer to the process clock wait. + All three devices will then enter their respective service routines + concurrently. + + 2. In hardware, the process clock period is fixed at one millisecond, and + the system clock period, while potentially variable, is set by MPE to one + millisecond with an interrupt every 100 ticks. These periods are too + short to allow the simulator to idle, as the host OS clock resolution is + typically also one millisecond. + + Therefore, the process and system clock simulators are scheduled with + ten-millisecond service times, and the PCLK and counter registers are + incremented by ten for each event service. To present the correct values + when the registers are read, the counts are incremented by amounts + proportional to the fractions of the service intervals that have elapsed + when the reads occur. + + 3. In simulation, the TOS renamer is implemented by permanently assigning + the register names RA-RD to TR [0]-TR [3], respectively, and copying the + contents between registers to pop and push values. An alternate + implementation approach is to use a renaming register, RN, that tracks + the correspondence between registers and array entries, and to assign the + register names dynamically using modular indices, e.g., RA is TR [RN], RB + is TR [(RN + 1) & 3], etc. In lieu of copying, incrementing and + decrementing RN is done to pop and push values. + + Both implementations were mocked up and timing measurements made. The + results were that copying values is much faster than mapping via array + index values, so this is the implementation chosen. +*/ + + + +#include "hp3000_defs.h" +#include "hp3000_cpu.h" +#include "hp3000_cpu_ims.h" +#include "hp3000_mem.h" +#include "hp3000_io.h" + + + +/* Program constants */ + +#define PCLK_PERIOD mS (1) /* 1 millisecond process clock period */ + +#define PCLK_MULTIPLIER 10 /* number of hardware process clock ticks per service */ +#define PCLK_RATE (1000 / PCLK_MULTIPLIER) /* process clock rate in ticks per second */ + +#define UNIT_OPTS (UNIT_EIS) /* the standard equipment feature set */ + +#define CPU_IO_RESET 0 /* reset CPU and all I/O devices */ +#define IO_RESET 1 /* reset just the I/O devices */ + +#define CNTL_BASE 8 /* the radix for the cold dump control byte */ +#define CNTL_MAX 0377 /* the maximum cold dump control byte value */ + +#define SIO_JUMP 0000000u /* Jump unconditionally */ +#define SIO_SBANK 0014000u /* Set bank */ +#define SIO_ENDIN 0034000u /* End with interrupt */ +#define SIO_CNTL 0040000u /* Control */ +#define SIO_WRITE 0060000u /* Write 4096 words */ +#define SIO_READ 0077760u /* Read 16 words */ + +#define MS_CN_GAP 0000005u /* Write Gap */ +#define MS_CN_WRR 0000004u /* Write Record */ +#define MS_CN_RST 0000011u /* Rewind and Reset */ +#define MS_CN_BSR 0000012u /* Backspace Record */ +#define MS_CN_WFM 0000015u /* Write File Mark */ + +#define MS_ST_PROTECTED 0001000u /* write protected */ +#define MS_ST_READY 0000400u /* unit ready */ + +#define MS_ST_MASK (MS_ST_PROTECTED | MS_ST_READY) + + +/* CPU global SCP data definitions */ + +DEVICE cpu_dev; /* incomplete device structure */ + +REG *sim_PC = NULL; /* the pointer to the P register */ + + +/* CPU global data structures */ + + +/* CPU registers */ + +HP_WORD CIR = 0; /* current instruction register */ +HP_WORD NIR = 0; /* next instruction register */ +HP_WORD PB = 0; /* program base register */ +HP_WORD P = 0; /* program counter register */ +HP_WORD PL = 0; /* program limit register */ +HP_WORD PBANK = 0; /* program segment bank register */ +HP_WORD DL = 0; /* data limit register */ +HP_WORD DB = 0; /* data base register */ +HP_WORD DBANK = 0; /* data segment bank register */ +HP_WORD Q = 0; /* stack marker register */ +HP_WORD SM = 0; /* stack memory register */ +HP_WORD SR = 0; /* stack register counter */ +HP_WORD Z = 0; /* stack limit register */ +HP_WORD SBANK = 0; /* stack segment bank register */ +HP_WORD TR [4] = { 0, 0, 0, 0 }; /* top of stack registers */ +HP_WORD X = 0; /* index register */ +HP_WORD STA = 0; /* status register */ +HP_WORD SWCH = 0; /* switch register */ +HP_WORD CPX1 = 0; /* run-mode interrupt flags register */ +HP_WORD CPX2 = 0; /* halt-mode interrupt flags register */ +HP_WORD MOD = 0; /* module control register */ +HP_WORD PCLK = 0; /* process clock register */ +HP_WORD CNTR = 0; /* microcode counter */ + + +/* Condition Code B lookup table. + + Byte-oriented instructions set the condition code in the status word using + Pattern B (CCB). For this encoding: + + CCG = ASCII numeric character + CCE = ASCII alphabetic character + CCL = ASCII special character + + The simplest implementation of this pattern is a 256-way lookup table using + disjoint condition code flags. The SET_CCB macro uses this table to set the + condition code appropriate for the supplied operand into the status word. +*/ + +const HP_WORD cpu_ccb_table [256] = { + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* NUL SOH STX ETX EOT ENQ ACK BEL */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* BS HT LF VT FF CR SO SI */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* DLE DC1 DC2 DC3 DC4 NAK SYN ETB */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* CAN EM SUB ESC FS GS RS US */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* spa ! " # $ % & ' */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* ( ) * + , - . / */ + CFG, CFG, CFG, CFG, CFG, CFG, CFG, CFG, /* 0 1 2 3 4 5 6 7 */ + CFG, CFG, CFL, CFL, CFL, CFL, CFL, CFL, /* 8 9 : ; < = > ? */ + CFL, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* @ A B C D E F G */ + CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* H I J K L M N O */ + CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* P Q R S T U V W */ + CFE, CFE, CFE, CFL, CFL, CFL, CFL, CFL, /* X Y Z [ \ ] ^ _ */ + CFL, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* ` a b c d e f g */ + CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* h i j k l m n o */ + CFE, CFE, CFE, CFE, CFE, CFE, CFE, CFE, /* p q r s t u v w */ + CFE, CFE, CFE, CFL, CFL, CFL, CFL, CFL, /* x y z { | } ~ DEL */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 200 - 207 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 210 - 217 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 220 - 227 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 230 - 237 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 240 - 247 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 250 - 257 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 260 - 267 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 270 - 277 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 300 - 307 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 310 - 317 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 320 - 327 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 330 - 337 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 340 - 347 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 350 - 357 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL, /* 360 - 367 */ + CFL, CFL, CFL, CFL, CFL, CFL, CFL, CFL /* 370 - 377 */ + }; + + +/* CPU global state */ + +jmp_buf cpu_save_env; /* the saved environment for microcode aborts */ +uint32 cpu_stop_flags; /* the simulation stop flag set */ + +POWER_STATE cpu_power_state = power_on; /* the power supply state */ +EXEC_STATE cpu_micro_state = halted; /* the microcode execution state */ +t_bool cpu_base_changed = FALSE; /* TRUE if any base register is changed */ +t_bool cpu_is_calibrated = TRUE; /* TRUE if the process clock is calibrated */ +UNIT *cpu_pclk_uptr = &cpu_unit [0]; /* a (constant) pointer to the process clock unit */ + + +/* CPU local state */ + +static uint32 sim_stops = 0; /* the current simulation stop flag settings */ +static uint32 cpu_speed = 1; /* the CPU speed, expressed as a multiplier of a real machine */ +static uint32 pclk_increment = 1; /* the process clock increment per event service */ +static uint32 dump_control = 0002006u; /* the cold dump control word (default CNTL = 4, DEVNO = 6 */ +static uint32 debug_save = 0; /* the current debug trace flag settings */ +static HP_WORD exec_mask = 0; /* the current instruction execution trace mask */ +static HP_WORD exec_match = D16_UMAX; /* the current instruction execution trace matching value */ + + +/* CPU local data structures */ + + +/* Interrupt classification names */ + +static const char *const interrupt_name [] = { /* class names, indexed by IRQ_CLASS */ + "integer overflow", /* 000 irq_Integer_Overflow */ + "bounds violation", /* 001 irq_Bounds_Violation */ + "illegal memory address error", /* 002 irq_Illegal_Address */ + "non-responding module error", /* 003 irq_Timeout */ + "system parity error", /* 004 irq_System_Parity */ + "address parity error", /* 005 irq_Address_Parity */ + "data parity error", /* 006 irq_Data_Parity */ + "module", /* 007 irq_Module */ + "external", /* 010 irq_External */ + "power fail", /* 011 irq_Power_Fail */ + "ICS trap", /* 012 irq_Trap */ + "dispatch", /* 013 irq_Dispatch */ + "exit" /* 014 irq_IXIT */ + }; + + +/* Trap classification names */ + +static const char *const trap_name [] = { /* trap names, indexed by TRAP_CLASS */ + "no", /* 000 trap_None */ + "bounds violation", /* 001 trap_Bounds_Violation */ + NULL, /* 002 (unused) */ + NULL, /* 003 (unused) */ + NULL, /* 004 (unused) */ + NULL, /* 005 (unused) */ + NULL, /* 006 (unused) */ + NULL, /* 007 (unused) */ + NULL, /* 010 (unused) */ + NULL, /* 011 (unused) */ + NULL, /* 012 (unused) */ + NULL, /* 013 (unused) */ + NULL, /* 014 (unused) */ + NULL, /* 015 (unused) */ + NULL, /* 016 (unused) */ + NULL, /* 017 (unused) */ + "unimplemented instruction", /* 020 trap_Unimplemented */ + "STT violation", /* 021 trap_STT_Violation */ + "CST violation", /* 022 trap_CST_Violation */ + "DST violation", /* 023 trap_DST_Violation */ + "stack underflow", /* 024 trap_Stack_Underflow */ + "privileged mode violation", /* 025 trap_Privilege_Violation */ + NULL, /* 026 (unused) */ + NULL, /* 027 (unused) */ + "stack overflow", /* 030 trap_Stack_Overflow */ + "user", /* 031 trap_User */ + NULL, /* 032 (unused) */ + NULL, /* 033 (unused) */ + NULL, /* 034 (unused) */ + NULL, /* 035 (unused) */ + NULL, /* 036 (unused) */ + "absent code segment", /* 037 trap_CS_Absent */ + "trace", /* 040 trap_Trace */ + "STT entry uncallable", /* 041 trap_Uncallable */ + "absent data segment", /* 042 trap_DS_Absent */ + "power on", /* 043 trap_Power_On */ + "cold load", /* 044 trap_Cold_Load */ + "system halt" /* 045 trap_System_Halt */ + }; + + +/* CPU features table. + + The feature table is used to validate CPU feature changes within the subset + of features supported by a given CPU. Features in the typical list are + enabled when the CPU model is selected. If a feature appears in the typical + list but NOT in the optional list, then it is standard equipment and cannot + be disabled. If a feature appears in the optional list, then it may be + enabled or disabled as desired by the user. + + + Implementation notes: + + 1. The EIS was standard equipment for the Series II and III, so UNIT_EIS + should appear in their "typ" fields. However, the EIS instructions are + not currently implemented, so the value is omitted below. +*/ + +struct FEATURE_TABLE { + uint32 typ; /* standard features plus typically configured options */ + uint32 opt; /* complete list of optional features */ + uint32 maxmem; /* maximum configurable memory in 16-bit words */ + }; + +static const struct FEATURE_TABLE cpu_features [] = { /* features indexed by CPU_MODEL */ + { 0, /* UNIT_SERIES_III */ + UNIT_CIS, + 1024 * 1024 }, + { 0, /* UNIT_SERIES_II */ + 0, + 256 * 1024 } + }; + + +/* CPU local SCP support routine declarations */ + +static t_stat cpu_service (UNIT *uptr); +static t_stat cpu_reset (DEVICE *dptr); + +static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc); +static t_stat set_exec (UNIT *uptr, int32 option, CONST char *cptr, void *desc); +static t_stat set_dump (UNIT *uptr, int32 option, CONST char *cptr, void *desc); +static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc); +static t_stat set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc); +static t_stat set_option (UNIT *uptr, int32 new_option, CONST char *cptr, void *desc); +static t_stat set_pfars (UNIT *uptr, int32 setting, CONST char *cptr, void *desc); + +static t_stat show_stops (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat show_exec (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat show_dump (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat show_speed (FILE *st, UNIT *uptr, int32 val, CONST void *desc); + + +/* CPU local utility routine declarations */ + +static t_stat halt_mode_interrupt (HP_WORD device_number); +static t_stat machine_instruction (void); + + +/* CPU SCP data structures */ + + +/* Unit list. + + The CPU unit holds the main memory capacity and is used to schedule the + process clock events. + + + Implementation notes: + + 1. The unit structure must be global for other modules to obtain the memory + size via the MEMSIZE macro, which references the "capac" field. +*/ + +#define UNIT_FLAGS (UNIT_PFARS | UNIT_CALTIME) + +UNIT cpu_unit [] = { + { UDATA (&cpu_service, UNIT_FLAGS | UNIT_FIX | UNIT_BINK | UNIT_IDLE, 0), PCLK_PERIOD * PCLK_MULTIPLIER } + }; + + +/* Register list. + + The CPU register list exposes the machine registers for user inspection and + modification. User flags describe the permitted and default display formats, + as follows: + + - REG_A permits any display + - REG_B permits binary display + - REG_M defaults to CPU instruction mnemonic display + - REG_T defaults to CPU status mnemonic display + + + Implementation notes: + + 1. All registers that reference variables of type HP_WORD must have the + REG_FIT flag for proper access if HP_WORD is a 16-bit type. + + 2. The CNTR register is set to the value of the SR register when the + micromachine halts or pauses. This allows the SR value to be accessed by + the diagnostics. The top-of-stack registers are flushed to main memory + when the machine halts or pauses, which alters SR. +*/ + +static REG cpu_reg [] = { +/* Macro Name Location Radix Width Offset Flags */ +/* ------ ------- ------------ ----- ----- ------ ------------------------ */ + { ORDATA (CIR, CIR, 16), REG_M | REG_RO | REG_FIT }, /* current instruction register */ + { ORDATA (NIR, NIR, 16), REG_M | REG_RO | REG_FIT }, /* next instruction register */ + { ORDATA (PB, PB, 16), REG_FIT }, /* program base register */ + { ORDATA (P, P, 16), REG_FIT }, /* program counter register */ + { ORDATA (PL, PL, 16), REG_FIT }, /* program limit register */ + { ORDATA (PBANK, PBANK, 4), REG_FIT }, /* program segment bank register */ + { ORDATA (DL, DL, 16), REG_FIT }, /* data limit register */ + { ORDATA (DB, DB, 16), REG_FIT }, /* data base register */ + { ORDATA (DBANK, DBANK, 4), REG_FIT }, /* data segment bank register */ + { ORDATA (Q, Q, 16), REG_FIT }, /* stack marker register */ + { ORDATA (SM, SM, 16), REG_FIT }, /* stack memory register */ + { ORDATA (SR, SR, 3), REG_FIT }, /* stack register counter */ + { ORDATA (Z, Z, 16), REG_FIT }, /* stack limit register */ + { ORDATA (SBANK, SBANK, 4), REG_FIT }, /* stack segment bank register */ + { ORDATA (RA, TR [0], 16), REG_A | REG_FIT }, /* top of stack register */ + { ORDATA (RB, TR [1], 16), REG_A | REG_FIT }, /* top of stack - 1 register */ + { ORDATA (RC, TR [2], 16), REG_A | REG_FIT }, /* top of stack - 2 register */ + { ORDATA (RD, TR [3], 16), REG_A | REG_FIT }, /* top of stack - 3 register */ + { ORDATA (X, X, 16), REG_A | REG_FIT }, /* index register */ + { ORDATA (STA, STA, 16), REG_T | REG_B | REG_FIT }, /* status register */ + { ORDATA (SWCH, SWCH, 16), REG_A | REG_FIT }, /* switch register */ + { ORDATA (CPX1, CPX1, 16), REG_B | REG_FIT }, /* run-mode interrupt flags */ + { ORDATA (CPX2, CPX2, 16), REG_B | REG_FIT }, /* halt-mode interrupt flags */ + { ORDATA (PCLK, PCLK, 16), REG_FIT }, /* process clock register */ + { ORDATA (CNTR, CNTR, 6), REG_HRO | REG_FIT }, /* microcode counter */ + { ORDATA (MOD, MOD, 16), REG_HRO | REG_FIT }, /* module control register */ + + { DRDATA (POWER, cpu_power_state, 2), REG_HRO }, /* system power supply state */ + { ORDATA (WRU, sim_int_char, 8), REG_HRO }, /* SCP interrupt character */ + { ORDATA (BRK, sim_brk_char, 8), REG_HRO }, /* SCP break character */ + { ORDATA (DEL, sim_del_char, 8), REG_HRO }, /* SCP delete character */ + + { NULL } + }; + + +/* Modifier list */ + +static MTAB cpu_mod [] = { +/* Mask Value Match Value Print String Match String Validation Display Descriptor */ +/* ------------ --------------- ------------------- ------------ ----------- ------- ---------- */ + { UNIT_MODEL, UNIT_SERIES_II, "Series II", NULL, &set_model, NULL, NULL }, + { UNIT_MODEL, UNIT_SERIES_III, "Series III", "III", &set_model, NULL, NULL }, + + { UNIT_EIS, UNIT_EIS, "EIS", NULL, &set_option, NULL, NULL }, + { UNIT_EIS, 0, "no EIS", "NOEIS", NULL, NULL, NULL }, + + { UNIT_CIS, UNIT_CIS, "CIS", "CIS", &set_option, NULL, NULL }, + { UNIT_CIS, 0, "no CIS", "NOCIS", NULL, NULL, NULL }, + + { UNIT_PFARS, UNIT_PFARS, "auto-restart", "ARS", &set_pfars, NULL, NULL }, + { UNIT_PFARS, 0, "no auto-restart", "NOARS", &set_pfars, NULL, NULL }, + + { UNIT_CALTIME, UNIT_CALTIME, "calibrated timing", "CALTIME", NULL, NULL, NULL }, + { UNIT_CALTIME, 0, "realistic timing", "REALTIME", NULL, NULL, NULL }, + +/* Entry Flags Value Print String Match String Validation Display Descriptor */ +/* ------------------- ----------- ------------ ------------ ------------- -------------- ---------- */ + { MTAB_XDV, 128 * 1024, NULL, "128K", &set_size, NULL, NULL }, + { MTAB_XDV, 256 * 1024, NULL, "256K", &set_size, NULL, NULL }, + { MTAB_XDV, 384 * 1024, NULL, "384K", &set_size, NULL, NULL }, + { MTAB_XDV, 512 * 1024, NULL, "512K", &set_size, NULL, NULL }, + { MTAB_XDV, 768 * 1024, NULL, "768K", &set_size, NULL, NULL }, + { MTAB_XDV, 1024 * 1024, NULL, "1024K", &set_size, NULL, NULL }, + + { MTAB_XDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle, NULL }, + { MTAB_XDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL, NULL }, + + { MTAB_XDV | MTAB_NMO, 0, "DUMP", "DUMPDEV", &set_dump, &show_dump, NULL }, + { MTAB_XDV, 1, NULL, "DUMPCTL", &set_dump, NULL, NULL }, + + { MTAB_XDV | MTAB_NMO, 1, "STOPS", "STOP", &set_stops, &show_stops, NULL }, + { MTAB_XDV, 0, NULL, "NOSTOP", &set_stops, NULL, NULL }, + + { MTAB_XDV | MTAB_NMO, 1, "EXEC", "EXEC", &set_exec, &show_exec, NULL }, + { MTAB_XDV, 0, NULL, "NOEXEC", &set_exec, NULL, NULL }, + + { MTAB_XDV | MTAB_NMO, 0, "SPEED", NULL, NULL, &show_speed, NULL }, + + { 0 } + }; + + +/* Debugging trace list */ + +static DEBTAB cpu_deb [] = { + { "INSTR", DEB_INSTR }, /* instructions */ + { "DATA", DEB_MDATA }, /* memory data accesses */ + { "FETCH", DEB_MFETCH }, /* memory instruction fetches */ + { "REG", DEB_REG }, /* register values */ + { "OPND", DEB_MOPND }, /* memory operand values */ + { "EXEC", DEB_EXEC }, /* instruction execution states */ + { "PSERV", DEB_PSERV }, /* process clock service events */ + { NULL, 0 } + }; + +/* Debugging stop list */ + +static DEBTAB cpu_stop [] = { + { "LOOP", SS_LOOP }, /* stop on an infinite loop */ + { "PAUSE", SS_PAUSE }, /* stop on a PAUS instruction */ + { "UNDEF", SS_UNDEF }, /* stop on an undefined instruction */ + { "UNIMPL", SS_UNIMPL }, /* stop on an unimplemented instruction */ + { NULL, 0 } + }; + + +/* Device descriptor */ + +DEVICE cpu_dev = { + "CPU", /* device name */ + cpu_unit, /* unit array */ + cpu_reg, /* register array */ + cpu_mod, /* modifier array */ + 1, /* number of units */ + 8, /* address radix */ + PA_WIDTH, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 16, /* data width */ + &mem_examine, /* examine routine */ + &mem_deposit, /* deposit routine */ + &cpu_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + NULL, /* device information block pointer */ + DEV_DEBUG, /* device flags */ + 0, /* debug control flags */ + cpu_deb, /* debug flag name array */ + NULL, /* memory size change routine */ + NULL /* logical device name */ + }; + + + +/* CPU global SCP support routines */ + + +/* Execute CPU instructions. + + This is the instruction decode routine for the HP 3000. It is called from + the simulator control program to execute instructions in simulated memory, + starting at the simulated program counter. It runs until the status to be + returned is set to a value other than SCPE_OK. + + On entry, P points to the instruction to execute, and the "sim_switches" + global contains any command-line switches included with the run command. On + exit, P points at the next instruction to execute (or the current + instruction, in the case of a simulator stop during a PAUS instruction or + after the first of two stack operations). + + Execution is divided into four phases. + + First, the instruction prelude configures the simulation state to resume + execution. This involves verifying that system power is on and there are no + device conflicts (e.g., two devices with the same device number), + initializing the I/O processor and channels, and setting the RUN switch if no + other front panel switches are pressed. These actions accommodate + reconfiguration of the I/O device settings and program counter while the + simulator was stopped. The prelude also responds to one command-line switch: + if "-B" is specified, the current set of simulation stop conditions is + bypassed for the first instruction executed. This allows, e.g., a PAUS + instruction to be bypassed or an unimplemented instruction trap to be taken. + + Second, the microcode abort mechanism is set up. Microcode aborts utilize + the "setjmp/longjmp" mechanism to transfer control out of the instruction + executors without returning through the call stack. This allows an + instruction to be aborted part-way through execution when continuation is + impossible, e.g., due to a memory access violation. It corresponds to direct + microcode jumps out of the execution sequence and to the appropriate trap + handlers. + + Third, the instruction execution loop decodes instructions and calls the + individual executors in turn until a condition occurs that prevents further + execution. Examples of such conditions includes execution of a HALT + instruction, a user stop request (CTRL+E) from the simulation console, a + recoverable device error (such as an improperly formatted tape image), a + user-specified breakpoint, and a simulation stop condition (such as execution + of an unimplemented instruction). The execution loop also polls for I/O + events and device interrupts, and runs I/O channel cycles. During + instruction execution, the CIR register contains the currently executing + instruction, the NIR register contains the next instruction to execute, and + the P register points to the memory location two instructions ahead of the + current instruction. + + Fourth, the instruction postlude updates the simulation state in preparation + for returning to the SCP command prompt. Devices that maintain an internal + state different from their external state, such as the CPU process clock, are + updated so that their internal and external states are fully consistent. + This ensures that the state visible to the user during the simulation stop is + correct. It also ensures that the program counter points correctly at the + next instruction to execute upon resumption. + + If enabled, the simulator is idled when a PAUS instruction has been executed + and no service requests for the multiplexer or selector channels are active. + Execution of a PAUS instruction suspends the fetch-and-execute process until + an interrupt occurs or the simulator is stopped and then resumed with a GO -B + or RUN -B command. + + The HP 3000 is a microcoded machine. In hardware, the micromachine is always + executing microinstructions, even when the CPU is "halted." The halt/run + state is simply a flip-flop setting, reflected in bit 15 of the CPX2 + register and the RUN light on the front panel, that determines whether the + "halt-mode" or "run-mode" microprogram is currently executing. + + In simulation, the "cpu_micro_state" variable indicates the state of the + micromachine, i.e., which section of the microcode it is executing, while + CPX2 bit 15 indicates whether the macromachine is halted or running. The + micromachine may be in one of four states: + + - running : the run-mode fetch-and-execute microcode is executing + - paused : the run-mode PAUS instruction microcode is executing + - waiting : the halt-mode cold load or dump microcode is executing + - halted : the halt-mode front panel microcode is executing + + Simulation provides a variety of stop conditions that break instruction + execution and return to the SCP prompt with the CPU still in run mode. These + have no analog in hardware; the only way to stop the CPU is to press the HALT + button on the front panel, which shifts the micromachine into halt-mode + microcode execution. When any of these conditions occur, the micromachine + state is set to "halted," but the CPX2 run flag is remains set unless the + stop was caused by execution of a HALT instruction. Resuming execution with + a STEP, CONT, GO, or RUN command proceeds as though the hardware RUN switch + was pressed after a programmed halt. This provides the proper semantics for + seamlessly stopping and restarting instruction execution. + + A microcode abort is performed by executing a "longjmp" to the abort handler, + which is outside of and precedes the instruction execution loop. The value + passed to "longjmp" is a 32-bit integer containing the Segment Transfer Table + index of the trap handler in the lower word and an optional parameter in the + upper word. Aborts are invoked by the MICRO_ABORT macro, which takes as its + parameter a trap classification value, e.g.: + + MICRO_ABORT (trap_Privilege_Violation); + MICRO_ABORT (trap_Integer_Zero_Divide); + + Some aborts require an additional parameter and must be invoked by the + MICRO_ABORTP macro, which takes a trap classification value and a + trap-specific value as parameters. The traps that require additional + parameters are: + + MICRO_ABORTP (trap_CST_Violation, segment_number); + MICRO_ABORTP (trap_STT_Violation, segment_number); + MICRO_ABORTP (trap_CS_Absent, label/n/0); + MICRO_ABORTP (trap_DS_Absent, DST_number); + MICRO_ABORTP (trap_Uncallable, label); + MICRO_ABORTP (trap_Trace, label/n/0); + MICRO_ABORTP (trap_User, trap_number); + + trap_User is not usually called explicitly via MICRO_ABORTP; rather, + MICRO_ABORT is used with one of the specific user-trap identifiers, e.g., + trap_Integer_Overflow, trap_Float_Overflow, trap_Decimal_Overflow, etc., that + supplies both the trap classification and the trap parameter value. + + In addition, user traps must be enabled by setting the T-bit in the status + word. If the T bit is not set, a user trap sets the O-bit (overflow) in the + status word and resumes execution with the next instruction instead of + invoking the user trap handler. + + When an abort occurs, an equivalent PCAL to the appropriate STT entry is set + up. Then execution drops into the instruction loop to execute the first + instruction of the trap handler. + + When the instruction loop is exited, the CPU process clock and system clock + registers are updated, the micromachine is halted, and control returns to + SCP. Upon return, P points at the next instruction to execute, i.e., the + instruction that will execute when the instruction loop is reentered. + + If the micromachine is paused, then P is reset to point to the PAUS + instruction, which will be reexecuted when the routine is reentered. If it + is running, then P is reset to point to the current instruction if the stop + allows it to be rerun, or at the next instruction. + + + Implementation notes: + + 1. While the Microsoft VC++ "setjmp" documentation says, "All variables + (except register variables) accessible to the routine receiving control + contain the values they had when longjmp was called," the ISO C99 + standard says, "All accessible objects have values...as of the time the + longjmp function was called, except that the values of objects of + automatic storage duration that are local to the function containing the + invocation of the corresponding setjmp macro that do not have + volatile-qualified type and have been changed between the setjmp + invocation and longjmp call are indeterminate." + + Therefore, after a microcode abort, we cannot depend upon the values of + any local variables. + + 2. In hardware, the NEXT microcode order present at the end of each + instruction transfers the NIR content to the CIR, reads the memory word + at P into the NIR, and increments P. However, if an interrupt is + present, then this action is omitted, and a microcode jump is performed + to control store location 3, which then jumps to the microcoded interrupt + handler. In simulation, the CIR/NIR/P update is performed before the + next instruction is executed, rather than after the last instruction + completes, so that interrupts are handled before updating. + + In addition, the NEXT action is modified in hardware if the NIR contains + a stack instruction with a non-NOP B stackop. In this case, NEXT + transfers the NIR content to the CIR, reads the memory word at P into the + NIR, but does not increment P. Instead, the R bit of the status register + is set to indicate that a B stackop is pending. When the NEXT at the + completion of the A stackop is executed, the NIR and CIR are untouched, + but P is incremented, and the R bit is cleared. This ensures that if an + interrupt or trap occurs between the stackops, P will point correctly at + the next instruction to be executed. + + In simulation, following the hardware would require testing the NIR for a + non-NOP B stackop at every pass through the instruction execution loop. + To avoid this, the NEXT simulation unilaterally increments P, rather than + only when a B stackop is not present, and the stack instruction executor + tests for the B stackop and sets the R bit there. However, by that time, + P has already been incremented, so we decrement it there to return it to + the correct value. + + 3. The System Halt trap has no handler. Instead, the simulator is halted, + and control returns to the SCP prompt. + + 4. The trace display for a trap reports the parameter value supplied with + the microcode abort. This is not necessarily the same as the parameter + that is pushed on the stack for the trap handler. As some traps, e.g., + trap_CST_Violation, can cause a System Halt, the placement of the trace + call is dictated by the desire to report both the original trap and the + System Halt trap, even though the placement results in the display of the + incoming parameter value, rather than the stacked parameter value. + + 5. The execution trace (DEB_EXEC) match test is performed in two parts to + display the register values both before and after the instruction + execution. Consequently, the enable test is done before the register + trace, and the disable test is done after. + + 6. The execution test (exec_test) is set FALSE even though execution tracing + is not specified. This is done solely to reassure the compiler that the + value is not clobbered by a longjmp call. + + 7. The set of debug trace flags is restored in the postlude (and therefore + also saved in the instruction prelude) to ensure that a simulation stop + that occurs while an execution trace is in progress does not exit with + the flags set improperly. +*/ + +t_stat sim_instr (void) +{ +static const char *const stack_formats [] = { /* stack register display formats, indexed by SR */ + BOV_FORMAT " ", /* SR = 0 format */ + BOV_FORMAT " A %06o, ", /* SR = 1 format */ + BOV_FORMAT " A %06o, B %06o, ", /* SR = 2 format */ + BOV_FORMAT " A %06o, B %06o, C %06o, ", /* SR = 3 format */ + BOV_FORMAT " A %06o, B %06o, C %06o, D %06o, " /* SR = 4 format */ + }; + +int abortval; +HP_WORD label, parameter, device; +TRAP_CLASS trap; +t_bool exec_test; +t_stat status = SCPE_OK; + + +/* Instruction prelude */ + +debug_save = cpu_dev.dctrl; /* save the current set of debug flags for later restoration */ + +if (sim_switches & SWMASK ('B')) /* if a simulation stop bypass was requested */ + cpu_stop_flags = SS_BYPASSED; /* then clear the stop flags for the first instruction */ +else /* otherwise */ + cpu_stop_flags = sim_stops; /* set the stops as indicated */ + +if (cpu_power_state == power_off) /* if system power is off */ + status = STOP_POWER; /* then execution is not possible until restoration */ + +else if (hp_device_conflict ()) /* otherwise if device assignment is inconsistent */ + status = SCPE_STOP; /* then inhibit execution */ + +else { /* otherwise */ + device = iop_initialize (); /* initialize the IOP */ + mpx_initialize (); /* and the multiplexer channel */ + sel_initialize (); /* and the selector channel */ + + if ((CPX2 & CPX2_IRQ_SET) == 0) /* if no halt-mode interrupt is present */ + CPX2 |= cpx2_RUNSWCH; /* then assume a RUN command via STEP/CONT */ + } + + +/* Microcode abort processor */ + +abortval = setjmp (cpu_save_env); /* set the microcode abort handler */ + +if (abortval) { /* if a microcode abort occurred */ + trap = TRAP (abortval); /* then get the trap classification */ + parameter = PARAM (abortval); /* and the optional parameter */ + + label = TO_LABEL (LABEL_IRQ, trap); /* form the label from the STT number */ + + dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s trap%s\n", + PBANK, P - 1 & R_MASK, parameter, trap_name [trap], + (trap == trap_User && !(STA & STATUS_T) ? " (disabled)" : "")); + + switch (trap) { /* dispatch on the trap classification */ + + case trap_None: /* trap_None should never occur */ + case trap_System_Halt: + CNTR = SR; /* copy the stack register to the counter */ + cpu_flush (); /* and flush the TOS registers to memory */ + + RA = parameter; /* set RA to the parameter (system halt condition) */ + + CPX2 = CPX2 & ~cpx2_RUN | cpx2_SYSHALT; /* halt the CPU and set the system halt flag */ + status = STOP_SYSHALT; /* and report the system halt condition */ + + label = 0; /* there is no trap handler for a system halt */ + break; + + + case trap_CST_Violation: + if (STT_SEGMENT (parameter) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ + MICRO_ABORT (trap_SysHalt_CSTV_1); /* then the failure is fatal */ + + /* fall into the next trap handler */ + + case trap_STT_Violation: + if (STT_SEGMENT (parameter) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ + MICRO_ABORT (trap_SysHalt_STTV_1); /* then the failure is fatal */ + + /* fall into the next trap handler */ + + case trap_Unimplemented: + case trap_DST_Violation: + case trap_Stack_Underflow: + case trap_Privilege_Violation: + case trap_Bounds_Violation: + parameter = label; /* the label is the parameter for these traps */ + + /* fall into the next trap handler */ + + case trap_DS_Absent: + cpu_flush (); /* flush the TOS registers to memory */ + cpu_mark_stack (); /* and then write a stack marker */ + + /* fall into the next trap handler */ + + case trap_Uncallable: + break; /* set up the trap handler */ + + + case trap_User: + if (STA & STATUS_T) { /* if user traps are enabled */ + STA &= ~STATUS_O; /* then clear overflow status */ + cpu_flush (); /* and flush the TOS registers to memory */ + cpu_mark_stack (); /* and write a stack marker */ + } /* and set up the trap handler */ + + else { /* otherwise in lieu of trapping */ + STA |= STATUS_O; /* set overflow status */ + label = 0; /* and continue execution with the next instruction */ + } + break; + + + case trap_CS_Absent: + if (CPX1 & cpx1_ICSFLAG) /* if the trap occurred while on the ICS */ + MICRO_ABORT (trap_SysHalt_Absent_ICS); /* then the failure is fatal */ + + else if (STT_SEGMENT (STA) <= ISR_SEGMENT) /* otherwise if the trap occurred in segment 1 */ + MICRO_ABORT (trap_SysHalt_Absent_1); /* then the failure is fatal */ + break; /* otherwise set up the trap handler */ + + + case trap_Trace: + if (STT_SEGMENT (STA) <= ISR_SEGMENT) /* if the trap occurred in segment 1 */ + MICRO_ABORT (trap_SysHalt_Trace_1); /* then the failure is fatal */ + break; /* otherwise set up the trap handler */ + + + case trap_Cold_Load: /* this trap executes on the ICS */ + status = STOP_CLOAD; /* report that the cold load is complete */ + + /* fall into trap_Stack_Overflow */ + + case trap_Stack_Overflow: /* this trap executes on the ICS */ + if (CPX1 & cpx1_ICSFLAG) /* so if the trap occurred while on the ICS */ + MICRO_ABORT (trap_SysHalt_Overflow_ICS); /* then the failure is fatal */ + + cpu_setup_ics_irq (irq_Trap, trap); /* otherwise, set up the ICS */ + break; /* and then the trap handler */ + + + case trap_Power_On: /* this trap executes on the ICS */ + cpu_setup_ics_irq (irq_Trap, trap); /* so set it up */ + cpu_power_state = power_on; /* and return to the power-on state */ + + if (CPX2 & cpx2_INHPFARS) /* if auto-restart is inhibited */ + status = STOP_ARSINH; /* then exit and wait for a manual restart */ + break; + } /* all cases are handled */ + + + if (label != 0) { /* if the trap handler is to be called */ + STA = STATUS_M; /* then clear the status and enter privileged mode */ + + SM = SM + 1 & R_MASK; /* increment the stack pointer */ + cpu_write_memory (stack, SM, parameter); /* and push the parameter on the stack */ + + X = CIR; /* save the current instruction for restarting */ + + cpu_call_procedure (label, 0); /* set up PB, P, PL, and STA to call the procedure */ + + cpu_base_changed = TRUE; /* one or more base registers have changed */ + } + + sim_interval = sim_interval - 1; /* count the execution cycle that aborted */ + } + + +/* Instruction loop */ + +while (status == SCPE_OK) { /* execute until simulator status prevents continuation */ + + if (sim_interval <= 0) { /* if an event timeout has expired */ + status = sim_process_event (); /* then call the event service */ + + if (status != SCPE_OK) /* if the service failed */ + break; /* then abort execution and report the failure */ + } + + if (sel_request) /* if a selector channel request is pending */ + sel_service (1); /* then service it */ + + if (mpx_request_set) /* if a multiplexer channel request is pending */ + mpx_service (1); /* then service it */ + + if (iop_interrupt_request_set /* if a hardware interrupt request is pending */ + && STA & STATUS_I /* and interrupts are enabled */ + && CIR != SED_1) /* and not deferred by a SED 1 instruction */ + device = iop_poll (); /* then poll to acknowledge the request */ + + if (cpu_micro_state == running) /* if the micromachine is running */ + if (CPX1 & CPX1_IRQ_SET /* then if a run-mode interrupt is pending */ + && cpu_power_state != power_failing) /* and power is not currently failing */ + cpu_run_mode_interrupt (device); /* then service it */ + + else if (sim_brk_summ /* otherwise if a breakpoint exists */ + && sim_brk_test (TO_PA (PBANK, P - 1 & LA_MASK), /* at the next location */ + BP_EXEC)) { /* to execute */ + status = STOP_BRKPNT; /* then stop the simulation */ + sim_interval = sim_interval + 1; /* and don't count the cycle */ + } + + else { /* otherwise execute the next instruction */ + if (DPRINTING (cpu_dev, DEB_EXEC | DEB_REG)) { /* if execution or register tracing is enabled */ + if (cpu_dev.dctrl & DEB_EXEC) /* then if tracing execution */ + if (STA & STATUS_R) /* then if the right-hand stack op is pending */ + exec_test = /* then the execution test succeeds if */ + (CIR << STACKOP_A_SHIFT /* the right-hand stack op */ + & STACKOP_A_MASK /* matches the test criteria */ + & exec_mask) == exec_match; /* for the left-hand stack op value */ + else /* otherwise */ + exec_test = /* then the execution test succeeds if */ + (NIR & exec_mask) == exec_match; /* the next instruction matches the test criteria */ + else /* otherwise */ + exec_test = FALSE; /* there is no execution test */ + + if (cpu_dev.dctrl & DEB_EXEC /* if execution tracing is enabled */ + && cpu_dev.dctrl != DEB_ALL /* and is currently inactive */ + && exec_test) { /* and the matching test succeeds */ + debug_save = cpu_dev.dctrl; /* then save the current trace flag set */ + cpu_dev.dctrl = DEB_ALL; /* and turn on full tracing */ + } + + if (cpu_dev.dctrl & DEB_REG) { /* if register tracing is enabled */ + hp_debug (&cpu_dev, DEB_REG, /* then output the active TOS registers */ + stack_formats [SR], + SBANK, SM, SR, RA, RB, RC, RD); + + fprintf (sim_deb, "X %06o, %s\n", /* output the index and status registers */ + X, fmt_status (STA)); + + if (cpu_base_changed) { /* if the base registers have been altered */ + hp_debug (&cpu_dev, DEB_REG, /* then output the base register values */ + BOV_FORMAT " PB %06o, PL %06o, DL %06o, DB %06o, Q %06o, Z %06o\n", + DBANK, 0, STATUS_CS (STA), + PB, PL, DL, DB, Q, Z); + + cpu_base_changed = FALSE; /* clear the base registers changed flag */ + } + } + + if (cpu_dev.dctrl & DEB_EXEC /* if execution tracing is enabled */ + && cpu_dev.dctrl == DEB_ALL /* and is currently active */ + && ! exec_test) { /* and the matching test fails */ + cpu_dev.dctrl = debug_save; /* then restore the saved debug flag set */ + hp_debug (&cpu_dev, DEB_EXEC, /* and add a separator to the trace log */ + "*****************\n"); + } + } + + if (!(STA & STATUS_R)) { /* (NEXT) if the right-hand stack op is not pending */ + CIR = NIR; /* then update the current instruction */ + cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ + } + + P = P + 1 & R_MASK; /* point to the following instruction */ + + if (DPRINTING (cpu_dev, DEB_INSTR)) { /* if instruction tracing is enabled */ + sim_eval [0] = CIR; /* then save the instruction that will be executed */ + sim_eval [1] = NIR; /* and the following word for evaluation */ + + hp_debug (&cpu_dev, DEB_INSTR, BOV_FORMAT, /* print the address and the instruction opcode */ + PBANK, P - 2 & R_MASK, CIR); /* as an octal value */ + + if (fprint_cpu (sim_deb, sim_eval, 0, SIM_SW_STOP) == SCPE_ARG) /* print the mnemonic; if that fails */ + fprint_val (sim_deb, sim_eval [0], cpu_dev.dradix, /* then print the numeric */ + cpu_dev.dwidth, PV_RZRO); /* value again */ + + fputc ('\n', sim_deb); /* end the trace with a newline */ + } + + status = machine_instruction (); /* execute one machine instruction */ + + cpu_stop_flags = sim_stops; /* reset the stop flags as indicated */ + } + + else if (cpu_micro_state == paused) { /* otherwise if the micromachine is paused */ + if (CPX1 & CPX1_IRQ_SET) /* then if a run-mode interrupt is pending */ + cpu_run_mode_interrupt (device); /* then service it */ + + else if (sim_idle_enab /* otherwise if idling is enabled */ + && ! sel_request && mpx_request_set == 0) /* and there are no channel requests pending */ + sim_idle (TMR_PCLK, FALSE); /* then idle the simulator */ + } + + else if (CPX2 & CPX2_IRQ_SET) /* otherwise if a halt-mode interrupt is pending */ + status = halt_mode_interrupt (device); /* then service it */ + + sim_interval = sim_interval - 1; /* count the execution cycle */ + } /* and continue with the instruction loop */ + + +/* Instruction postlude */ + +cpu_dev.dctrl = debug_save; /* restore the flag set */ + +cpu_update_pclk (); /* update the process clock */ +clk_update_counter (); /* and system clock counters */ + +if (cpu_micro_state == paused) /* if the micromachine is paused */ + P = P - 2 & R_MASK; /* then set P to point to the PAUS instruction */ + +else if (cpu_micro_state == running) /* otherwise if it is running */ + if (status <= STOP_RERUN) /* then if the instruction will be rerun when resumed */ + P = P - 2 & R_MASK; /* then set P to point to it */ + else /* otherwise */ + P = P - 1 & R_MASK; /* set P to point to the next instruction */ + +cpu_micro_state = halted; /* halt the micromachine */ + +if (cpu_power_state == power_failing /* if power is failing */ + && status == STOP_HALT) /* and we executed a HALT instruction */ + cpu_power_state = power_off; /* then power will be off when we return */ + +dprintf (cpu_dev, cpu_dev.dctrl, BOV_FORMAT "simulation stop: %s\n", + PBANK, P, STA, sim_error_text (status)); + +return status; /* return the reason for the stop */ +} + + +/* Execute the LOAD and DUMP commands. + + This command processing routine implements the cold load and cold dump + commands. The syntax is: + + LOAD { } + DUMP { } + + The is a number that is deposited into the SWCH register + before invoking the CPU cold load or cold dump facility. The CPU radix is + used to interpret the number; it defaults to octal. If the number is + omitted, the SWCH register value is not altered before loading or dumping. + + On entry, the "arg" parameter is "Cold_Load" for a LOAD command and + "Cold_Dump" for a DUMP command, and "buf" points at the remainder of the + command line. If characters exist on the command line, they are parsed, + converted to a numeric value, and stored in the SWCH register. Then the + CPU's cold load/dump routine is called to set up the CPU state. Finally, the + CPU is started to begin the requested action. + + + Implementation notes: + + 1. The RUN command uses the RU_CONT argument instead of RU_RUN so that the + run_cmd SCP routine will not reset all devices before entering the + instruction executor. The halt mode interrupt handlers for cold load and + cold dump reset the simulator as appropriate for their commands (i.e., + the CPU and all I/O devices, or just the I/O devices, respectively). +*/ + +t_stat cpu_cold_cmd (int32 arg, CONST char *buf) +{ +const char *cptr; +char gbuf [CBUFSIZE]; +t_stat status; +HP_WORD value; + +if (*buf != '\0') { /* if more characters exist on the command line */ + cptr = get_glyph (buf, gbuf, 0); /* then get the next glyph */ + + if (*cptr != '\0') /* if that does not exhaust the input */ + return SCPE_2MARG; /* then report that there are too many arguments */ + + value = (HP_WORD) get_uint (gbuf, cpu_dev.dradix, /* get the parameter value */ + D16_UMAX, &status); + + if (status == SCPE_OK) /* if a valid number was present */ + SWCH = value; /* then set it into the switch register */ + else /* otherwise */ + return status; /* return the error status */ + } + +else if (arg == Cold_Dump) /* otherwise if no dump value was given */ + SWCH = dump_control; /* then use the system control panel presets */ + +cpu_front_panel (SWCH, (PANEL_TYPE) arg); /* set up the cold load or dump microcode */ + +return run_cmd (RU_CONT, buf); /* execute the halt-mode routine */ +} + + +/* Execute the POWER commands. + + This command processing routine is called to initiate a power failure or + power restoration. The "cptr" parameter points to the power option keyword; + the "arg" parameter is not used. + + The routine processes commands of the form: + + POWER { FAIL | OFF | DOWN } + POWER { RESTORE | ON | UP } + + In simulation, the "cpu_power_state" global variable indicates the current + state of system power. The simulator starts in the power_on state. The + POWER FAIL command moves from the power_on to the power_failing state if the + CPU is running, or to the power_off state if it is not. Execution of a HALT + in the power_failing state moves to the power_off state. The POWER RESTORE + command moves from the power_off to the power_returning state if the CPU is + running, or to the power_on state if it is not. Execution of the power-on + trap moves from the power_returning to the power_on state. + + The POWER FAIL and POWER RESTORE commands are only valid in the power_on and + power_off states, respectively; otherwise, they print "Command not allowed." + + The four enumeration values model the states of the PON (power on) and PFW + (power-fail warning) hardware signals, as follows: + + PON PFW State Simulator Action + --- --- --------------- ---------------------------- + 1 0 power on executing normally + 1 1 power failing executing with cpx1_PFINTR + 0 1 power off will not execute + 0 0 power returning executing with trap_Power_On + + In microcode, the power-fail routine writes the current value of the CPX2 + register to the word following the last word of the ICS. This value is used + by the power-on routine to decide if the CPU was running (cpx2_RUN bit is + set) or halted at the time of the power failure. A power failure is + indicated by setting the cpx1_PFINTR bit in the CPX1 register; this causes an + interrupt to the power-failure routine in the operating system, which + performs an orderly shutdown followed by a programmed HALT to wait for power + to die. When power is restored, the power-on trap (trap_Power_On) is set up, + and then, if the PF/ARS switch is in the "enable" position, the trap is + taken, which restarts the operating system and any I/O that was in progress + when power failed. If the switch is in the "disable" position, the CPU + remains halted, and the trap is taken when the RUN button is pressed. + + The POWER commands are entered at the SCP prompt. If the machine was running + at the time of power failure, execution is resumed automatically to execute + the power-fail or power-restore OS routines. If the machine was halted when + the POWER commands were entered, the machine remains halted -- just the power + state changes. + + + Implementation notes: + + 1. In hardware, when the power fail interrupt is serviced, the microcode + sets the PWR INH flip-flop, which locks out the RUN switch and inhibits + all other halt-mode (CPX2) and run-mode (CPX1) interrupts until the CPU + is reset. This ensures that the software power-fail interrupt handler + executes unimpeded. In simulation, a POWER FAIL command with the CPU + running sets the power-fail interrupt bit in the CPX1 register and + resumes execution in the power_on state. When the interrupt is detected + by the "cpu_run_mode_interrupt" routine, the state is changed to + power_failing. In this state, run-mode interrupts are not recognized. + Halt-mode interrupts need no special handling, as the power state is + changed to power_off when the CPU halts. Therefore, the CPU is never in + a "halted-and-waiting-for-power-to-fade-away" state. + + 2. The RUN command uses the RU_CONT argument instead of RU_RUN so that the + run_cmd SCP routine will not reset all devices before entering the + instruction executor. The halt-mode interrupt handlers for cold load and + cold dump reset the simulator as appropriate for their commands (i.e., + the CPU and all I/O devices, or just the I/O devices, respectively). + + 3. In order to set up the power-on trap, this routine presses the RUN button + and continues the simulation. After the trap is set up in the + "sim_instr" routine, the halt-mode interrupt handler checks the PF/ARS + switch and stops simulation if auto-restart is disabled. +*/ + +t_stat cpu_power_cmd (int32 arg, CONST char *cptr) +{ +static CTAB options [] = { + { "FAIL", NULL, power_failing }, + { "RESTORE", NULL, power_returning }, + { "OFF", NULL, power_failing }, + { "ON", NULL, power_returning }, + { "DOWN", NULL, power_failing }, + { "UP", NULL, power_returning }, + { NULL } + }; + +char gbuf [CBUFSIZE]; +CTAB *ctptr; +HP_WORD zi, failure_cpx2; +t_stat status; + +if (cptr == NULL || *cptr == '\0') /* if there is no option word */ + return SCPE_2FARG; /* then report a missing argument */ + +cptr = get_glyph (cptr, gbuf, 0); /* parse (and upshift) the option specified */ +ctptr = find_ctab (options, gbuf); /* and look it up in the option table */ + +if (ctptr == NULL) /* if the option is not valid */ + status = SCPE_ARG; /* then report a bad argument */ + +else if (*cptr != '\0') /* otherwise if something follows the option */ + return SCPE_2MARG; /* then report too many arguments */ + +else if (ctptr->arg == power_failing) /* otherwise if a power-fail option was given */ + if (cpu_power_state != power_on) /* but the CPU power is not on */ + status = SCPE_NOFNC; /* then the command is not allowed */ + + else { /* otherwise the failure is valid */ + iop_assert_PFWARN (); /* so send a power-fail warning to all devices */ + + cpu_read_memory (absolute, ICS_Z, &zi); /* get the ICS stack limit */ + cpu_write_memory (absolute, zi + 1, CPX2); /* and save the CPX2 value in the following word */ + + if (CPX2 & cpx2_RUN) { /* if the CPU is currently running */ + CPX1 |= cpx1_PFINTR; /* then set the power-fail interrupt */ + CPX2 |= cpx2_RUNSWCH; /* and assume a RUN command */ + status = run_cmd (RU_CONT, cptr); /* and continue execution */ + } + + else { /* otherwise the CPU is currently halted */ + cpu_power_state = power_off; /* so remain halted in the "power is off" state */ + status = SCPE_OK; /* and return command success */ + } + } + +else if (ctptr->arg == power_returning) /* otherwise if a power-restoration option was given */ + if (cpu_power_state != power_off) /* but the CPU power is not off */ + status = SCPE_NOFNC; /* then the command is not allowed */ + + else { /* otherwise the restoration is valid */ + reset_all_p (0); /* so reset all devices to their power on states */ + + cpu_read_memory (absolute, ICS_Z, &zi); /* get the ICS stack limit */ + cpu_read_memory (absolute, zi + 1, &failure_cpx2); /* and get the value of CPX2 at the time of failure */ + cpu_write_memory (absolute, zi + 1, CPX2); /* and replace it with the current CPX2 value */ + + if (failure_cpx2 & cpx2_RUN) { /* if the CPU was running at the time of power failure */ + cpu_power_state = power_returning; /* then move to the "power is returning" state */ + CPX2 |= cpx2_RUNSWCH; /* and assume a RUN command */ + status = run_cmd (RU_CONT, cptr); /* and continue execution */ + } + + else { /* otherwise the CPU was halted when power failed */ + cpu_power_state = power_on; /* so remain halted in the "power is on" state */ + status = SCPE_OK; /* and return command success */ + } + } + +else /* otherwise a valid option has no handler */ + status = SCPE_IERR; /* so report an internal error */ + +return status; /* return the operation status */ +} + + + +/* CPU global utility routines */ + + +/* Process a run-mode interrupt. + + This routine is called when one or more of the interrupt request bits are set + in the CPX1 register. The highest-priority request is identified and + cleared, the interrupt classification and parameter are established, and the + associated interrupt handler is set up for execution. On return, the CPU has + been configured and is ready to execute the first instruction in the handler. + + On entry, the routine first checks for an external interrupt alone; this is + done to improve performance, as this is the most common case. If some other + interrupt is requested, or if multiple interrupts are requested, the CPX1 + register is scanned from MSB to LSB to identify the request. + + + Implementation notes: + + 1. In hardware, halting the CPU while a PAUS instruction is executing leaves + P pointing to the following instruction, which is executed when the RUN + button is pressed. In simulation, this action occurs only when execution + is resumed with the "-B" option to bypass the pause. Otherwise, resuming + (or stepping) continues with the PAUS instruction. + + If an interrupt occurs while PAUS is executing, the interrupt handler + will return to the instruction following the PAUS, as though HALT and RUN + had been pressed. This occurs because P normally points two instructions + beyond the current instruction, so stacking the value P - 1 during the + interrupt will return to the next instruction to be executed. When + resuming the PAUS instruction from a simulation stop with an interrupt + pending, though, P is set so that P - 1 points to the PAUS instruction, + which is (correctly) the next instruction to execute on resumption. + Stacking the usual P - 1 value, therefore, will cause the interrupt + handler to return to the PAUS instruction instead of to the instruction + following, as would have occurred had a simulation stop not been + involved. + + Therefore, this case is detected when a PAUS instruction is in the CIR + but the micromachine state is "running" instead of "paused", and P is + incremented so that the return will be to the instruction following PAUS. +*/ + +void cpu_run_mode_interrupt (HP_WORD device_number) +{ +HP_WORD request_set, request_count, parameter; +IRQ_CLASS class; + +if (cpu_micro_state == running /* if we are resuming from a sim stop */ + && (CIR & PAUS_MASK) == PAUS) /* into a PAUS instruction */ + P = P + 1 & R_MASK; /* then return is to the instruction following */ +else /* otherwise the micromachine may be paused */ + cpu_micro_state = running; /* but is no longer */ + +request_set = CPX1 & CPX1_IRQ_SET; /* get the set of active interrupt requests */ + +if (request_set == cpx1_EXTINTR) { /* if only an external request is present */ + class = irq_External; /* (the most common case) then set the class */ + parameter = device_number; /* and set the parameter to the device number */ + } + +else { /* otherwise scan for the class */ + request_count = 0; /* where CPX1.1 through CPX1.9 */ + request_set = D16_SIGN; /* correspond to IRQ classes 1-9 */ + + while ((CPX1 & request_set) == 0) { /* scan from left to right for the first request */ + request_count = request_count + 1; /* while incrementing the request number */ + request_set = request_set >> 1; /* and shifting the current request bit */ + } + + class = (IRQ_CLASS) request_count; /* set the class from the request count */ + + if (class == irq_Integer_Overflow) { /* if an integer overflow occurred */ + parameter = 1; /* then set the parameter to 1 */ + STA &= ~STATUS_O; /* and clear the overflow flag */ + } + + else if (class == irq_External) /* otherwise if an external interrupt occurred */ + parameter = device_number; /* then set the parameter to the device number */ + + else if (class == irq_Module) { /* otherwise if the class is a module interrupt */ + parameter = UPPER_BYTE (MOD); /* then the parameter is the module number */ + MOD = 0; /* clear the register to prevent a second interrupt */ + } + + else if (class == irq_Power_Fail) { /* otherwise if a power fail interrupt occurred */ + parameter = TO_LABEL (LABEL_IRQ, class); /* then the parameter is the label */ + cpu_power_state = power_failing; /* and system power is now failing */ + } + + else /* otherwise the parameter */ + parameter = TO_LABEL (LABEL_IRQ, class); /* is the label */ + } + +CPX1 &= ~request_set; /* clear the associated CPX request bit */ + +dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s interrupt\n", + PBANK, P - 1 & R_MASK, parameter, interrupt_name [class]); + +cpu_setup_irq_handler (class, parameter); /* set up the entry into the interrupt handler */ + +return; +} + + +/* Set up a front panel operation. + + This routine sets the SWCH register to the supplied value and then sets the + appropriate bit in the CPX2 register. This will cause a halt-mode interrupt + when simulated execution is resumed. + + + Implementation notes: + + 1. We do this here to avoid having to export the registers and CPX values + globally. +*/ + +void cpu_front_panel (HP_WORD switch_reg, PANEL_TYPE request) +{ +SWCH = switch_reg; /* set the SWCH register value */ + +switch (request) { /* dispatch on the request type */ + + case Run: /* a run request */ + CPX2 |= cpx2_RUNSWCH; /* set the RUN switch */ + break; + + case Cold_Load: /* a cold load request */ + CPX2 |= cpx2_LOADSWCH; /* set the LOAD switch */ + break; + + case Cold_Dump: /* a cold dump request */ + CPX2 |= cpx2_DUMPSWCH; /* set the DUMP switch */ + break; + } /* all cases are handled */ + +return; +} + + +/* Update the process clock. + + If the process clock is currently calibrated, then the service interval is + actually ten times the hardware period of 1 millisecond. This provides + sufficient event service call spacing to allow idling to work. + + To present the correct value when the process clock is read, this routine is + called to increment the count by an amount proportional to the fraction of + the service interval that has elapsed. In addition, it is called by the CPU + instruction postlude, so that the PCLK register will have the correct value + if it is examined from the SCP command prompt. +*/ + +void cpu_update_pclk (void) +{ +int32 elapsed, ticks; + +if (cpu_is_calibrated) { /* if the process clock is calibrated */ + elapsed = cpu_unit [0].wait /* then the elapsed time is the original wait time */ + - sim_activate_time (&cpu_unit [0]); /* less the time remaining before the next service */ + + ticks = /* the adjustment is */ + (elapsed * PCLK_MULTIPLIER) / cpu_unit [0].wait /* the elapsed fraction of the multiplier */ + - (PCLK_MULTIPLIER - pclk_increment); /* less the amount of any adjustment already made */ + + PCLK = PCLK + ticks & R_MASK; /* update the process clock counter with rollover */ + pclk_increment = pclk_increment - ticks; /* and reduce the amount remaining to add at service */ + } + +return; +} + + + +/* CPU global instruction execution routines */ + + +/* Push the stack down. + + This routine implements the PUSH micro-order to create space on the stack for + a new value. On return, the new value may be stored in the RA register. + + If the SR register indicates that all of the TOS registers are in use, then + the RD register is freed by performing a queue down. Then the values in the + TOS registers are shifted down, freeing the RA register. Finally, SR is + incremented in preparation for the store. +*/ + +void cpu_push (void) +{ +if (SR == 4) /* if all TOS registers are full */ + cpu_queue_down (); /* then move the RD value to memory */ + +RD = RC; /* shift */ +RC = RB; /* the register */ +RB = RA; /* values down */ + +SR = SR + 1; /* increment the register-in-use count */ + +return; +} + + +/* Pop the stack up. + + This routine implements the POP micro-order to delete the top-of-stack value. + + On entry, if the SR register indicates that all of the TOS registers are + empty, then if decrementing the SM register would move it below the DB + register value, and the CPU is not in privileged mode, then a Stack Underflow + trap is taken. Otherwise, the stack memory pointer is decremented. + + If one or more values exist in the TOS registers, the values are shifted up, + deleting the previous value in the RA register, and SR is decremented. +*/ + +void cpu_pop (void) +{ +if (SR == 0) { /* if the TOS registers are empty */ + if (SM <= DB && NPRV) /* then if SM isn't above DB and the mode is non-privileged */ + MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ + + SM = SM - 1 & R_MASK; /* decrement the stack memory register */ + } + +else { /* otherwise at least one TOS register is occupied */ + RA = RB; /* so shift */ + RB = RC; /* the register */ + RC = RD; /* values up */ + + SR = SR - 1; /* decrement the register-in-use count */ + } + +return; +} + + +/* Queue a value from memory up to the register file. + + This routine implements the QUP micro-order to move the value at the top of + the memory stack into the bottom of the TOS register file. There must be a + free TOS register when this routine is called. + + On entry, if decrementing the SM register would move it below the DB register + value, and the CPU is not in privileged mode, then a Stack Underflow trap is + taken. Otherwise, the value pointed to by SM is read into the first unused + TOS register, SM is decremented to account for the removed value, and SR is + incremented to account for the new TOS register in use. + + + Implementation notes: + + 1. SR must be less than 4 on entry, so that TR [SR] is the first unused TOS + register. + + 2. SM and SR must not be modified within the call to cpu_read_memory. For + example, SR++ cannot be passed as a parameter. +*/ + +void cpu_queue_up (void) +{ +if (SM <= DB && NPRV) /* if SM isn't above DB and the mode is non-privileged */ + MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ + +else { /* otherwise */ + cpu_read_memory (stack, SM, &TR [SR]); /* read the value from memory into a TOS register */ + + SM = SM - 1 & R_MASK; /* decrement the stack memory register */ + SR = SR + 1; /* and increment the register-in-use count */ + } + +return; +} + + +/* Queue a value from the register file down to memory. + + This routine implements the QDWN micro-order to move the value at the bottom + of the TOS register file into the top of the memory stack. There must be a + TOS register in use when this routine is called. + + On entry, if incrementing the SM register would move it above the Z register + value, then a Stack Overflow trap is taken. Otherwise, SM is incremented to + account for the new value written, SR is decremented to account for the TOS + register removed from use, and the value in that TOS register is written into + memory at the top of the memory stack. + + + Implementation notes: + + 1. SR must be greater than 0 on entry, so that TR [SR - 1] is the last TOS + register in use. + + 2. SM and SR must not be modified within the call to cpu_write_memory. For + example, SR-- cannot be passed as a parameter. +*/ + +void cpu_queue_down (void) +{ +if (SM >= Z) /* if SM isn't below Z */ + MICRO_ABORT (trap_Stack_Overflow); /* then trap with a Stack Overflow */ + +SM = SM + 1 & R_MASK; /* increment the stack memory register */ +SR = SR - 1; /* and decrement the register-in-use count */ + +cpu_write_memory (stack, SM, TR [SR]); /* write the value from a TOS register to memory */ + +return; +} + + +/* Flush the register file. + + This routine implements the PSHA microcode subroutine that writes the values + of all TOS registers in use to the memory stack. As each value is written, + the SM register is incremented and the SR register is decremented. On + return, the SR register value will be zero. + + The routine does not check for stack overflow. +*/ + +void cpu_flush (void) +{ +while (SR > 0) { /* while one or more registers are in use */ + SM = SM + 1 & R_MASK; /* increment the stack memory register */ + SR = SR - 1; /* and decrement the register-in-use count */ + + cpu_write_memory (stack, SM, TR [SR]); /* write the value from a TOS register to memory */ + } + +return; +} + + +/* Adjust SR until it reaches a specified value. + + This routine implements the SRP1-SRP4 microcode subroutines that adjust the + stack until the prescribed number (1-4) of TOS registers are occupied. It + performs queue ups, i.e., moves values from the top of the memory stack to + the bottom of the register file, until the specified SR value is reached. + Stack underflow is checked after the all of the values have been moved. The + routine assumes that at least one value must be moved. + + + Implementation notes: + + 1. SR must be greater than 0 on entry, so that TR [SR - 1] is the last TOS + register in use. + + 2. SM and SR must not be modified within the call to cpu_read_memory. For + example, SR++ cannot be passed as a parameter. + + 3. The cpu_queue_up routine isn't used, as that routine checks for a stack + underflow after each word is moved rather than only after the last word. +*/ + +void cpu_adjust_sr (uint32 target) +{ +do { + cpu_read_memory (stack, SM, &TR [SR]); /* read the value from memory into a TOS register */ + + SM = SM - 1 & R_MASK; /* decrement the stack memory register */ + SR = SR + 1; /* and increment the register-in-use count */ + } +while (SR < target); /* queue up until the requested number of registers are in use */ + +if (SM <= DB && NPRV) /* if SM isn't above DB, or the mode is non-privileged */ + MICRO_ABORT (trap_Stack_Underflow); /* then trap with a Stack Underflow */ + +return; +} + + +/* Write a stack marker to memory. + + This routine implements the STMK microcode subroutine that writes a four-word + marker to the stack. The format of the marker is as follows: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X register value | [Q - 3] X + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | PB-relative return address | [Q - 2] P + 1 - PB + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Status register value | [Q - 1] STA + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Delta Q value | [Q - 0] S - Q + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + After the values are written, the Q register is set to point to the marker. + + This routine is always entered with SR = 0, i.e., with the TOS registers + flushed to the memory stack. It does not check for stack overflow. + + + Implementation notes: + + 1. The PB-relative return address points to the instruction after the point + of the call. Conceptually, this is location P + 1, but because the CPU + uses a two-instruction prefetch, the location is actually P - 1. +*/ + +void cpu_mark_stack (void) +{ +SM = SM + 4 & R_MASK; /* adjust the stack pointer */ + +cpu_write_memory (stack, SM - 3, X); /* push the index register */ +cpu_write_memory (stack, SM - 2, P - 1 - PB & LA_MASK); /* and delta P */ +cpu_write_memory (stack, SM - 1, STA); /* and the status register */ +cpu_write_memory (stack, SM - 0, SM - Q & LA_MASK); /* and delta Q */ + +Q = SM; /* set Q to point to the new stack marker */ + +return; +} + + +/* Calculate an effective memory address. + + This routine calculates the effective address for a memory reference or + branch instruction. On entry, "mode_disp" contains the mode, displacement, + index, and indirect fields of the instruction, "classification" and "offset" + point to variables to receive the corresponding values, and "selector" points + to a variable to receive the byte selection ("upper" or "lower") for byte- + addressable instructions or is NULL for word-addressable instructions. On + exit, "classification" is set to the memory access classification, "offset" + is set to the address offset within the memory bank implied by the + classification, and "selector" is set to indicate the byte referenced if the + pointer is non-NULL. + + The mode and displacement fields of the instruction encode an address + relative to one of the base registers P, DB, Q, or S, as follows: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | memory op | X | I | mode and displacement | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | 0 | 0 | P+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 0 | 1 | P- displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 0 | DB+ displacement 0-255 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 0 | Q+ displacement 0-127 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | 0 | Q- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+---+ + | 1 | 1 | 1 | 1 | S- displacement 0-63 | + +---+---+---+---+---+---+---+---+---+---+ + + The displacement encoded in the instruction is an unsigned value that is + added to or subtracted from the indicated base register. + + If the X and I fields are both 0, the addressing is direct. If the X field + is 1, the addressing is indexed. If the I field is 1, the addressing is + indirect. If both fields are 1, the addressing is indirect indexed, with + indirection performed before indexing. + + To improve execution speed in hardware, a preadder is implemented that sums + the offset contained in certain bits of the CIR with the index register (if + enabled). The primary use is to save a microinstruction cycle during memory + reference instructions, which must add a base register, the offset in the + CIR, and potentially the X register (either directly or shifted left or right + by one place for LDD/STD or LDB/STB, respectively). The preadder also serves + to hold other counts obtained from the CIR, e.g., shift counts, although in + these cases, the addition function is not used. + + This routine simulates the preadder as part of the effective address + calculation. The calculations employed for word addressing are: + + Direct word addressing: + ea = PBANK.(P + displacement) + ea = DBANK.(DB + displacement) + ea = SBANK.(Q,S + displacement) + + Direct indexed word addressing: + ea = PBANK.(P + displacement + X) + ea = DBANK.(DB + displacement + X) + ea = SBANK.(Q,S + displacement + X) + + Indirect word addressing: + ea = PBANK.(P + displacement + M [PBANK.(P + displacement)]) + ea = DBANK.(DB + M [DBANK.(DB + displacement)]) + ea = DBANK.(DB + M [SBANK.(Q,S + displacement)]) + + Indirect indexed word addressing: + ea = PBANK.(P + displacement + M [PBANK.(P + displacement)] + X) + ea = DBANK.(DB + M [DBANK.(DB + displacement)] + X) + ea = DBANK.(DB + M [SBANK.(Q,S + displacement)] + X) + + The indirect cell contains either a self-relative, P-relative address or a + DB-relative address, even for S or Q-relative modes. Indirect branches with + DB/Q/S-relative mode are offsets from PB, not DB. + + The effective address calculations employed for byte addressing are: + + Direct byte addressing: + ea = DBANK.(DB + displacement).byte [0] + ea = SBANK.(Q,S + displacement).byte [0] + + Direct indexed byte addressing: + ea = DBANK.(DB + displacement + X / 2).byte [X & 1] + ea = SBANK.(Q,S + displacement + X / 2).byte [X & 1] + + Indirect byte addressing: + ea,I = DBANK.(DB + M [DBANK.(DB + displacement)] / 2).byte [cell & 1] + ea,I = DBANK.(DB + M [SBANK.(Q,S + displacement)] / 2).byte [cell & 1] + + Indirect indexed byte addressing: + ea,I = DBANK.(DB + (M [DBANK.(DB + displacement)] + X) / 2).byte [cell + X & 1] + ea,I = DBANK.(DB + (M [SBANK.(Q,S + displacement)] + X) / 2).byte [cell + X & 1] + + For all modes, the displacement is a word address, whereas the indirect cell + and index register contain byte offsets. For direct addressing, the byte + selected is byte 0. For all other modes, the byte selected is the byte at + (offset & 1), where the offset is the index register value, the indirect cell + value, or the sum of the two. + + Byte offsets into data segments present problems, in that negative offsets + are permitted (to access the DL-to-DB area), but there are not enough bits to + represent all locations unambiguously in the potential -32K to +32K word + offset range. Therefore, a byte offset with bit 0 = 1 can represent either a + positive or negative word offset from DB, depending on the interpretation. + The HP 3000 adopts the convention that if the address resulting from a + positive-offset interpretation does not fall within the DL-to-S range, then + 32K is added to the address, effectively changing the interpretation from a + positive to a negative offset. If this new address does not fall within the + DL-to-S range, a Bounds Violation trap occurs if the mode is non-privileged. + + The reinterpretation as a negative offset is performed only if the CPU is not + in split-stack mode (where either DBANK is different from SBANK, or DB does + not lie between DL and Z), as extra data segments do not permit negative-DB + addressing. Reinterpretation is also not used for code segments, as negative + offsets from PB are not permitted. + + + Implementation notes: + + 1. On entry, the program counter points to the instruction following the + next instruction (i.e., the NIR location + 1). However, P-relative + offsets are calculated from the current instruction (CIR location). + Therefore, we decrement P by two before adding the offset. + + In hardware, P-relative addresses obtained from the preadder are offset + from P + 1, whereas P-relative addresses obtained by summing with + P-register values obtained directly from the S-Bus are offset from P + 2. + This is because the P-register increment that occurs as part of a NEXT + micro-order is coincident with the R-Bus and S-Bus register loads; both + operations occur when the NXT+1 signal asserts. Therefore, the microcode + handling P-relative memory reference address resolution subtracts one to + get the P value corresponding to the CIR, whereas branches on overflow, + carry, etc. subtract two. + + 2. If the mode is indirect, this routine handles bounds checks and TOS + register accesses on the initial address. + + 3. The System Reference Manual states that byte offsets are interpreted as + negative if the effective address does not lie between DL and Z. + However, the Series II microcode actually uses DL and S for the limits. + + 4. This routine calculates the effective address for the memory address + instructions. These instructions always bounds-check their accesses, and + data and stack accesses are mapped to the TOS registers if the effective + address lies between SM and SM + SR within the stack bank. +*/ + +void cpu_ea (HP_WORD mode_disp, ACCESS_CLASS *classification, HP_WORD *offset, BYTE_SELECTOR *selector) +{ +HP_WORD base, displacement; +ACCESS_CLASS class; + +switch ((mode_disp & MODE_MASK) >> MODE_SHIFT) { /* dispatch on the addressing mode */ + + case 000: + case 001: + case 002: + case 003: /* positive P-relative displacement */ + base = P - 2 + (mode_disp & DISPL_255_MASK); /* add the displacement to the base */ + class = program_checked; /* and classify as a program reference */ + break; + + case 004: + case 005: + case 006: + case 007: /* negative P-relative displacement */ + base = P - 2 - (mode_disp & DISPL_255_MASK); /* subtract the displacement from the base */ + class = program_checked; /* and classify as a program reference */ + break; + + case 010: + case 011: + case 012: + case 013: /* positive DB-relative displacement */ + base = DB + (mode_disp & DISPL_255_MASK); /* add the displacement to the base */ + class = data_mapped_checked; /* and classify as a data reference */ + break; + + case 014: + case 015: /* positive Q-relative displacement */ + base = Q + (mode_disp & DISPL_127_MASK); /* add the displacement to the base */ + class = stack_checked; /* and classify as a stack reference */ + break; + + case 016: /* negative Q-relative displacement */ + base = Q - (mode_disp & DISPL_63_MASK); /* subtract the displacement from the base */ + class = stack_checked; /* and classify as a stack reference */ + break; + + case 017: /* negative S-relative displacement */ + base = SM + SR - (mode_disp & DISPL_63_MASK); /* subtract the displacement from the base */ + class = stack_checked; /* and classify as a stack reference */ + break; + } /* all cases are handled */ + + +if (!(mode_disp & I_FLAG_BIT_5)) /* if the mode is direct */ + displacement = 0; /* then there's no displacement */ + +else { /* otherwise the mode is indirect */ + cpu_read_memory (class, base & LA_MASK, &displacement); /* so get the displacement value */ + + if ((CIR & BR_MASK) == BR_DBQS_I) { /* if this a DB/Q/S-relative indirect BR instruction */ + base = PB; /* then PB is the base for the displacement */ + class = program_checked; /* reclassify as a program reference */ + } + + else if (class != program_checked) { /* otherwise if it is a data or stack reference */ + base = DB; /* then DB is the base for the displacement */ + class = data_mapped_checked; /* reclassify as a data reference */ + } + /* otherwise, this is a program reference */ + } /* which is self-referential */ + +if ((CIR & LSDX_MASK) == LDD_X /* if the mode */ + || (CIR & LSDX_MASK) == STD_X) /* is double-word indexed */ + displacement = displacement + X * 2 & DV_MASK; /* then add the doubled index to the displacement */ + +else if (mode_disp & X_FLAG) /* otherwise if the mode is indexed */ + displacement = displacement + X & DV_MASK; /* then add the index to the displacement */ + +if (selector == NULL) /* if a word address is requested */ + base = base + displacement; /* then add in the word displacement */ + +else if ((mode_disp & (X_FLAG | I_FLAG_BIT_5)) == 0) /* otherwise if a direct byte address is requested */ + *selector = upper; /* then it references the upper byte */ + +else { /* otherwise an indexed or indirect byte address is requested */ + if (displacement & 1) /* so if the byte displacement is odd */ + *selector = lower; /* then the lower byte was requested */ + else /* otherwise it is even */ + *selector = upper; /* and the upper byte was requested */ + + base = base + (displacement >> 1) & LA_MASK; /* convert the displacement from byte to word and add */ + + if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */ + && (base < DL || base > SM + SR)) /* and the word address is out of range */ + base = base ^ D16_SIGN; /* then add 32K to swap the offset polarity */ + } + +*offset = base & LA_MASK; /* set the */ +*classification = class; /* return values */ + +return; +} + + +/* Convert a data- or program-relative byte address to a word effective address. + + The supplied byte offset from DB or PB is converted to a memory address, + bounds-checked if requested, and returned. If the supplied block length is + not zero, the converted address is assumed to be the starting address of a + block, and an ending address, based on the block length, is calculated and + bounds-checked if requested. If either address lies outside of the segment + associated with the access class, a Bounds Violation trap occurs, unless a + privileged data access is requested. + + The "class" parameter indicates whether the offset is from DB or PB and + must be "data", "data_checked", "program", or "program_checked". No other + access classes are supported. In particular, "data_mapped" and + "data_mapped_checked" are not allowed, as callers of this routine never map + accesses to the TOS registers. + + Byte offsets into data segments present problems, in that negative offsets + are permitted (to access the DL-to-DB area), but there are not enough bits to + represent all locations unambiguously in the potential -32K to +32K word + offset range. Therefore, a byte offset with bit 0 = 1 can represent either a + positive or negative word offset from DB, depending on the interpretation. + The HP 3000 adopts the convention that if the address resulting from a + positive-offset interpretation does not fall within the DL-to-S range, then + 32K is added to the address, effectively changing the interpretation from a + positive to a negative offset. If this new address does not fall within the + DL-to-S range, a Bounds Violation trap occurs if the mode is non-privileged. + + The reinterpretation as a negative offset is performed only if the CPU is not + in split-stack mode (where either DBANK is different from SBANK, or DB does + not lie between DL and Z), as extra data segments do not permit negative-DB + addressing. Reinterpretation is also not used for code segments, as negative + offsets from PB are not permitted. + + + Implementation notes: + + 1. This routine implements the DBBC microcode subroutine. +*/ + +uint32 cpu_byte_ea (ACCESS_CLASS class, uint32 byte_offset, uint32 block_length) +{ +uint32 starting_word, ending_word, increment; + +if (block_length & D16_SIGN) /* if the block length is negative */ + increment = 0177777; /* then the memory increment is negative also */ +else /* otherwise */ + increment = 1; /* the increment is positive */ + +if (class == program || class == program_checked) { /* if this is a program access */ + starting_word = PB + (byte_offset >> 1) & LA_MASK; /* then determine the starting word address */ + + if (class == program_checked /* if checking is requested */ + && starting_word < PB || starting_word > PL) /* and the starting address is out of range */ + MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */ + + if (block_length != 0) { /* if a block length was supplied */ + ending_word = /* then determine the ending address */ + starting_word + ((block_length - increment + (byte_offset & 1)) >> 1) & LA_MASK; + + if (class == program_checked /* if checking is requested */ + && ending_word < PB || ending_word > PL) /* and the ending address is out of range */ + MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */ + } + } + +else { /* otherwise this is a data address */ + starting_word = DB + (byte_offset >> 1) & LA_MASK; /* so determine the starting word address */ + + if (DBANK == SBANK && DL <= DB && DB <= Z /* if not in split-stack mode */ + && (starting_word < DL || starting_word > SM)) { /* and the word address is out of range */ + starting_word = starting_word ^ D16_SIGN; /* then add 32K and try again */ + + if (class == data_checked && NPRV /* if checking is requested and non-privileged */ + && (starting_word < DL || starting_word > SM)) /* and still out of range */ + MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */ + } + + if (block_length != 0) { /* if a block length was supplied */ + ending_word = /* then determine the ending word address */ + starting_word + ((block_length - increment + (byte_offset & 1)) >> 1) & LA_MASK; + + if (class == data_checked && NPRV /* if checking is requested and non-privileged */ + && (ending_word < DL || ending_word > SM)) /* and the address is out of range */ + MICRO_ABORT (trap_Bounds_Violation); /* then trap for a bounds violation */ + } + } + + +return starting_word; /* return the starting word address */ +} + + +/* Set up the entry into an interrupt handler. + + This routine prepares the CPU state to execute an interrupt handling + procedure. On entry, "class" is the classification of the current interrupt, + and "parameter" is the parameter associated with the interrupt. On exit, the + stack has been set up correctly, and the PB, P, PL, and status registers have + been set up for entry into the interrupt procedure. + + Run-mode interrupts are classified as external or internal and ICS or + non-ICS. External interrupts are those originating with the device + controllers, and internal interrupts are conditions detected by the microcode + (e.g., a bounds violation or arithmetic overflow). ICS interrupts execute + their handlers on the system's Interrupt Control Stack. Non-ICS interrupts + execute on the user's stack. + + Of the run-mode interrupts, the External, System Parity Error, Address + Parity Error, Data Parity Error, and Module interrupts execute on the ICS. + All other interrupts execute on the user's stack. The routine begins by + determining whether an ICS or non-ICS interrupt is indicated. The + appropriate stack is established, and the stack marker is written to preserve + the state of the interrupted routine. The label of the handler procedure is + obtained, and then the procedure designated by the label is set up. On + return, the first instruction of the handler is ready to execute. + + + Implementation notes: + + 1. This routine implements various execution paths through the microcode + labeled as INT0 through INT7. + + 2. This routine is also called directly by the IXIT instruction executor if + an external interrupt is pending. This is handled as an external + interrupt but is classified differently so that the teardown and rebuild + of the stack may be avoided to improve performance. +*/ + +void cpu_setup_irq_handler (IRQ_CLASS class, HP_WORD parameter) +{ +HP_WORD label; + +if (class == irq_External || class == irq_IXIT) { /* if entry is for an external interrupt */ + if (class == irq_External) /* then if it was detected during normal execution */ + cpu_setup_ics_irq (class, 0); /* then set it up on the ICS */ + else /* otherwise it was detected during IXIT */ + SM = Q + 2 & R_MASK; /* so the ICS is already set up */ + + DBANK = 0; /* all handlers are in bank 0 */ + STA = STATUS_M | STATUS_I; /* enter privileged mode with interrupts enabled */ + + cpu_read_memory (stack, parameter * 4 + 2, &DB); /* read the DB value */ + cpu_read_memory (stack, parameter * 4 + 1, &label); /* and the procedure label from the DRT */ + } + +else if (class >= irq_System_Parity /* otherwise if entry is for */ + && class <= irq_Power_Fail) { /* another ICS interrupt */ + cpu_setup_ics_irq (class, 0); /* then set it up on the ICS */ + + label = TO_LABEL (LABEL_IRQ, class); /* form the label for the specified classification */ + + STA = STATUS_M; /* clear status and enter privileged mode */ + } + +else { /* otherwise entry is for a non-ICS interrupt */ + if (class == irq_Integer_Overflow) /* if this is an integer overflow interrupt */ + label = TO_LABEL (LABEL_IRQ, trap_User); /* then form the label for a user trap */ + else /* otherwise form the label */ + label = TO_LABEL (LABEL_IRQ, class); /* for the specified classification */ + + cpu_flush (); /* flush the TOS registers to memory */ + cpu_mark_stack (); /* and write a stack marker */ + + STA = STATUS_M; /* clear status and enter privileged mode */ + } + +SM = SM + 1 & R_MASK; /* increment the stack pointer */ +cpu_write_memory (stack, SM, parameter); /* and push the parameter on the stack */ + +X = CIR; /* save the CIR in the index register */ + +cpu_call_procedure (label, 0); /* set up to call the interrupt handling procedure */ + +return; +} + + +/* Set up an interrupt on the Interrupt Control Stack. + + This routine prepares the Interrupt Control Stack (ICS) to support interrupt + processing. It is called from the run-time interrupt routine for ICS + interrupts, the microcode abort routine for ICS traps, and from the DISP and + PSEB instruction executors before entering the dispatcher. On entry, "class" + is the interrupt classification, and, if the class is "irq_Trap", then "trap" + is the trap classification. The trap classification is ignored for + interrupts, including the dispatcher start interrupt. + + Unless entry is for a Cold Load trap, the routine begins by writing a + six-word stack marker. This special ICS marker extends the standard marker + by adding the DBANK and DB values as follows: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X register value | [Q - 3] X + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | PB-relative return address | [Q - 2] P + 1 - PB + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Status register value | [Q - 1] STA + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | D | Delta Q value | [Q - 0] S - Q + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | DB-Bank value | [Q + 1] DBANK + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | DB value | [Q + 2] DB + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + D = the dispatcher was interrupted + + After the values are written, the Q register is set to point to the marker. + The stack bank register is then cleared, as the ICS is always located in + memory bank 0. + + If the interrupt or trap occurred while executing on the ICS, and the + dispatcher was running at the time, the "dispatcher running" bit in the CPX1 + register is cleared, and the D-bit is set in the delta-Q value word of the + stack marker. This bit will be used during interrupt exit to decide whether + to restart the dispatcher. + + If the CPU was executing on the user's stack, the "ICS flag" bit in CPX1 is + set, the Q register is reset to point at the permanent dispatcher stack + marker established by the operating system, and the Z register is reset to + the stack limit established by the OS for the ICS; the values are obtained + from reserved memory locations 5 and 6, respectively. The ICS DB value is + read from the ICS global area that precedes the dispatcher stack marker and + is used to write the stack-DB-relative S value back to the global area. + + Finally, the stack pointer is set to point just above the stack marker. + + + Implementation notes: + + 1. This routine implements various execution paths through the microcode + labeled as INT1 through INT5. +*/ + +void cpu_setup_ics_irq (IRQ_CLASS class, TRAP_CLASS trap) +{ +HP_WORD delta_q, stack_db; + +if (class != irq_Trap /* if this is not */ + || trap != trap_Cold_Load && trap != trap_Power_On) { /* a cold load or power on trap entry */ + cpu_flush (); /* then flush the TOS registers to memory */ + cpu_mark_stack (); /* and write a four-word stack marker */ + + cpu_write_memory (stack, SM + 1 & LA_MASK, DBANK); /* add DBANK and DB to the stack */ + cpu_write_memory (stack, SM + 2 & LA_MASK, DB); /* to form a six-word ICS marker */ + } + +SBANK = 0; /* the ICS is always located in bank 0 */ + +if (CPX1 & cpx1_ICSFLAG) { /* if execution is currently on the ICS */ + if (CPX1 & cpx1_DISPFLAG) { /* then if the dispatcher was interrupted */ + CPX1 &= ~cpx1_DISPFLAG; /* then clear the dispatcher flag */ + + cpu_read_memory (stack, Q, &delta_q); /* get the delta Q value from the stack marker */ + cpu_write_memory (stack, Q, delta_q | STMK_D); /* and set the dispatcher-interrupted flag */ + } + } + +else { /* otherwise execution is on the user's stack */ + CPX1 |= cpx1_ICSFLAG; /* so set the ICS flag */ + + cpu_read_memory (stack, ICS_Q, &Q); /* set Q = QI */ + cpu_read_memory (stack, ICS_Z, &Z); /* set Z = ZI */ + + cpu_read_memory (stack, Q - 4 & LA_MASK, /* read the stack DB value */ + &stack_db); + + cpu_write_memory (stack, Q - 6 & LA_MASK, /* write the stack-DB-relative S value */ + SM + 2 - stack_db & DV_MASK); /* which is meaningless for a cold load or power on */ + + SR = 0; /* invalidate the stack registers for a cold load */ + DL = D16_UMAX; /* and set the data limit */ + } + +SM = Q + 2 & R_MASK; /* set S above the stack marker */ + +return; +} + + +/* Set up a code segment. + + This routine is called to set up a code segment in preparation for calling or + exiting a procedure located in a segment different from the currently + executing segment. On entry, "label" indicates the segment number containing + the procedure. On exit, the new status register value and the first word of + the Code Segment Table entry are returned to the variables pointed to by + "status" and "entry_0", respectively. + + The routine begins by reading the CST pointer. The CST is split into two + parts: a base table, and an extension table. The table to use is determined + by the requested segment number. Segment numbers 0 and 192, corresponding to + the first entries of the two tables, are reserved and cause a CST Violation + trap if specified. + + The CST entry corresponding to the segment number is examined to set the + program bank, base, and limit registers (the segment length stored in the + table is number of quad-words, which must be multiplied by four to get the + size in words). The new status register value is set up and returned, along + with the first word of the CST entry. + + + Implementation notes: + + 1. This routine implements the microcode SSEG subroutine. + + 2. Passing -1 as a parameter to trap_CST_Violation ensures that the segment + number >= 2 check will pass and the trap handler will be invoked. + + 3. The Series II microcode sets PBANK and PB unilaterally but sets PL only + if the code segment is not absent. An absent segment entry contains the + disc address in words 3 and 4 instead of the bank address and base + address, so PBANK and PB will contain invalid values in this case. It is + not clear why the microcode avoids setting PL; the microinstruction in + question also sets Flag 2, so conditioning PL may be just a side effect. + In any case, we duplicate the firmware behavior here. + + 4. This routine is only used locally, but we leave it as a global entry to + support future firmware extensions that may need to call it. +*/ + +void cpu_setup_code_segment (HP_WORD label, HP_WORD *status, HP_WORD *entry_0) +{ +HP_WORD cst_pointer, cst_size, cst_entry, cst_bank, segment_number, entry_number; + +segment_number = STT_SEGMENT (label); /* isolate the segment number from the label */ + +if (segment_number < CST_RESERVED) { /* if the target segment is in the base table */ + cpu_read_memory (absolute, CSTB_POINTER, &cst_pointer); /* then read the CST base pointer */ + entry_number = segment_number; /* and set the entry number */ + } + +else { /* otherwise it is in the extension table */ + cpu_read_memory (absolute, CSTX_POINTER, &cst_pointer); /* so read the CST extension pointer */ + entry_number = segment_number - CST_RESERVED; /* and set the entry number */ + } + +if (entry_number == 0) /* segment numbers 0 and 192 do not exist */ + MICRO_ABORTP (trap_CST_Violation, -1); /* so trap for a violation if either is specified */ + +cpu_read_memory (absolute, cst_pointer, &cst_size); /* read the table size */ + +if (entry_number > cst_size) /* if the entry is outside of the table */ + MICRO_ABORTP (trap_CST_Violation, entry_number); /* then trap for a violation */ + +cst_entry = cst_pointer + entry_number * 4; /* get the address of the target CST entry */ + +cpu_read_memory (absolute, cst_entry, entry_0); /* get the first word of the entry */ +cpu_write_memory (absolute, cst_entry, *entry_0 | CST_R_BIT); /* and set the segment reference bit */ + +cpu_read_memory (absolute, cst_entry + 2, &cst_bank); /* read the bank address word */ +PBANK = cst_bank & CST_BANK_MASK; /* and mask to just the bank number */ + +cpu_read_memory (absolute, cst_entry + 3, &PB); /* read the segment's base address */ + +PL = (*entry_0 & CST_SEGLEN_MASK) * 4 - 1; /* set PL to the segment length - 1 */ + +*status = STA & ~LABEL_SEGMENT_MASK | segment_number; /* set the segment number in the new status word */ + +if (*entry_0 & CST_M_BIT) /* if the segment executes in privileged mode */ + *status |= STATUS_M; /* then set up to enter privileged mode */ + +if (! (*entry_0 & CST_A_BIT)) /* if the segment is not absent */ + PL = PL + PB; /* then set the segment limit */ + +return; +} + + +/* Set up a data segment. + + This routine is called to set up a data segment for access. It is called by + the MDS, MFDS, and MTDS instruction executors to obtain the bank and offset + of specified segments from the Data Segment Table. On entry, + "segment_number" indicates the number of the desired data segment. On exit, + the memory bank number and offset of the data segment base are returned to + the variables pointed to by "bank" and "address", respectively. + + The routine begins by reading the DST pointer. Segment number 0, + corresponding to the first entry of the table, is reserved and causes a DST + Violation trap if specified. + + The DST entry corresponding to the segment number is examined to obtain the + bank and base address. If the segment is absent, a Data Segment Absent trap + is taken. Otherwise, the bank and address values are returned. + + + Implementation notes: + + 1. This routine implements the microcode DSEG subroutine. +*/ + +void cpu_setup_data_segment (HP_WORD segment_number, HP_WORD *bank, HP_WORD *address) +{ +HP_WORD dst_pointer, dst_size, dst_entry, entry_0; + +cpu_read_memory (absolute, DST_POINTER, &dst_pointer); /* read the DST base pointer */ + +if (segment_number == 0) /* segment number 0 does not exist */ + MICRO_ABORT (trap_DST_Violation); /* so trap for a violation if it is specified */ + +cpu_read_memory (absolute, dst_pointer, &dst_size); /* read the table size */ + +if (segment_number > dst_size) /* if the entry is outside of the table */ + MICRO_ABORT (trap_DST_Violation); /* then trap for a violation */ + +dst_entry = dst_pointer + segment_number * 4; /* get the address of the target DST entry */ + +cpu_read_memory (absolute, dst_entry, &entry_0); /* get the first word of the entry */ +cpu_write_memory (absolute, dst_entry, entry_0 | DST_R_BIT); /* and set the segment reference bit */ + +if (entry_0 & DST_A_BIT) /* if the segment is absent */ + MICRO_ABORTP (trap_DS_Absent, segment_number); /* then trap for an absentee violation */ + +cpu_read_memory (absolute, dst_entry + 2, bank); /* read the segment bank number */ +cpu_read_memory (absolute, dst_entry + 3, address); /* and base address */ + +*bank = *bank & DST_BANK_MASK; /* mask off the reserved bits */ + +return; /* before returning to the caller */ +} + + +/* Call a procedure. + + This routine sets up the PB, P, PL, and status registers to enter a + procedure. It is called by the PCAL instruction executor and by the + interrupt and trap routines to set up the handler procedures. On entry, + "label" contains an external program label indicating the segment number and + Segment Transfer Table entry number describing the procedure, or a local + program label indicating the starting address of the procedure, and "offset" + contains an offset to be added to the starting address. On exit, the + registers are set up for execution to resume with the first instruction of + the procedure. + + If the label is a local label, the PB-relative address is obtained from the + label and stored in the P register, and the Next Instruction Register is + loaded with the first instruction of the procedure. + + If the label is external, the code segment referenced by the label is set up. + If the "trace" or "absent" bits are set, the corresponding trap is taken. + Otherwise, the Segment Transfer Table length is read, and the STT entry + number is validated; if it references a location outside of the table, a STT + violation trap is taken. + + Otherwise, the valid STT entry is examined. If the target procedure is not + in the designated code segment or is uncallable if not in privileged mode, + the appropriate traps are taken. If the STT entry contains a local label, it + is used to set up the P register and NIR as above. + + The "offset" parameter is used only by the XBR instruction executor. The + PCAL executor and the interrupt handlers pass an offset of zero to begin + execution at the first instruction of the designated procedure. + + + Implementation notes: + + 1. This routine implements the microcode PCL3 and PCL5 subroutines. +*/ + +void cpu_call_procedure (HP_WORD label, HP_WORD offset) +{ +HP_WORD new_status, new_label, new_p, cst_entry, stt_size, stt_entry; + +new_status = STA; /* save the status for a local label */ + +if (label & LABEL_EXTERNAL) { /* if the label is non-local */ + cpu_setup_code_segment (label, &new_status, &cst_entry); /* then set up the corresponding code segment */ + + stt_entry = STT_NUMBER (label); /* get the STT entry number from the label */ + + if (cst_entry & (CST_A_BIT | CST_T_BIT)) { /* if the code segment is absent or being traced */ + STA = new_status; /* then set the new status before trapping */ + cpu_mark_stack (); /* and write a stack marker to memory */ + + if (cst_entry & CST_A_BIT) /* if the code segment is absent */ + MICRO_ABORTP (trap_CS_Absent, label); /* then trap to load it */ + else /* otherwise */ + MICRO_ABORTP (trap_Trace, label); /* trap to trace it */ + } + + cpu_read_memory (program_checked, PL, &stt_size); /* read the table size */ + + if (stt_entry > STT_LENGTH (stt_size)) /* if the entry is outside of the table */ + MICRO_ABORTP (trap_STT_Violation, new_status); /* then trap for a violation */ + + cpu_read_memory (program_checked, PL - stt_entry, &new_label); /* read the label from the STT */ + + if (new_label & LABEL_EXTERNAL) /* if the procedure is not in the target segment */ + MICRO_ABORTP (trap_STT_Violation, new_status); /* then trap for a violation */ + + if ((new_label & LABEL_UNCALLABLE) && NPRV) /* if the procedure is uncallable in the current mode */ + MICRO_ABORTP (trap_Uncallable, label); /* then trap for a violation */ + + if (stt_entry == 0) /* if the STT number is zero in an external label */ + label = 0; /* then the starting address is PB */ + else /* otherwise */ + label = new_label; /* the PB offset is contained in the new label */ + + cpu_base_changed = TRUE; /* the program base registers have changed for tracing */ + } + +new_p = PB + (label + offset & LABEL_ADDRESS_MASK); /* get the procedure starting address */ + +cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the next instruction */ +P = new_p + 1 & R_MASK; /* the bounds are valid, so set the new P value */ + +STA = new_status; /* set the new status value */ + +return; +} + + +/* Return from a procedure. + + This routine sets up the P, Q, SM, and status registers to return from a + procedure. It is called by the EXIT and IXIT instruction executors and by + the cpu_start_dispatcher routine to enter the dispatcher. On entry, "new_q" + and "new_sm" contain the new values for the Q and SM registers that unwind + the stack. The "parameter" value is used only if a Trace or Code Segment + Absent trap is taken. For EXIT, the parameter is the stack adjustment value + (the N field). For IXIT, the parameter is zero. On exit, the registers are + set up for execution to resume with the first instruction after the procedure + call or interrupt. + + The routine begins by reloading register values from the stack marker. The + stack marker format is: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | X register value | [Q - 3] X + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | T | M | PB-relative return address | [Q - 2] P + 1 - PB + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Status register value | [Q - 1] STA + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + | Delta Q value | [Q - 0] S - Q + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + Where: + + T = a trace or control-Y interrupt is pending + M = the code segment is physically mapped + + The T and M bits are set by the operating system, if applicable, after the + stack marker was originally written. + + Stack underflow and overflow are checked, and privilege changes are + validated. If the return will be to a different code segment, it is set up. + Finally, the new P, Q, SM, and status register values are loaded, and NIR is + loaded with the first instruction after the return. + + + Implementation notes: + + 1. This routine implements the microcode EXI1 subroutine. + + 2. We pass a temporary status to cpu_setup_code_segment because it forms the + returned new status from the current STA register value. But for EXIT + and IXIT, the new status comes from the stack marker, which already has + the segment number to which we're returning, and which must not be + altered. + + 3. The NEXT action is modified when the R-bit is set in the status word + being restored. This occurs when an interrupt occurred between the two + stackops of a stack instruction. The main instruction loop does not + alter CIR in this case, so we must set it up here. +*/ + +void cpu_exit_procedure (HP_WORD new_q, HP_WORD new_sm, HP_WORD parameter) +{ +HP_WORD temp_status, new_status, new_p, cst_entry; + +SM = Q; /* delete any local values from the stack */ + +if (new_q > Z || new_sm > Z) /* if either the new Q or SM exceed the stack limit */ + MICRO_ABORT (trap_Stack_Overflow); /* then trap with a stack overflow */ + +cpu_read_memory (stack, Q - 1, &new_status); /* read the new status value from the stack marker */ + +if ((CIR & EXIT_MASK) == EXIT /* if an EXIT instruction is executing */ + && (new_q < DB || new_sm < DB) /* and either new Q or new S are below the data base */ + && (new_status & STATUS_M) == 0) /* and the new mode is non-privileged */ + MICRO_ABORT (trap_Stack_Underflow); /* then trap with a stack underflow */ + +cpu_read_memory (stack, Q - 2, &new_p); /* read the PB-relative return value from the stack marker */ + +if (NPRV /* if currently in user mode */ + && ((new_status & STATUS_M) /* and returning to privileged mode */ + || (new_status & STATUS_I) != (STA & STATUS_I))) /* or attempting to change interrupt state */ + MICRO_ABORT (trap_Privilege_Violation); /* then trap with a privilege violation */ + +STA &= ~STATUS_I; /* turn off external interrupts */ + +cpu_read_memory (stack, Q - 3, &X); /* read the new X value from the stack marker */ + +if (STATUS_CS (new_status) != STATUS_CS (STA)) { /* if returning to a different segment */ + cpu_setup_code_segment (new_status, &temp_status, &cst_entry); /* then set up the new segment */ + + if (NPRV && (temp_status & STATUS_M)) /* if in user mode now and returning to a privileged segment */ + MICRO_ABORT (trap_Privilege_Violation); /* then trap with a privilege violation */ + + if (new_p & STMK_T) /* if the new code segment is being traced */ + MICRO_ABORTP (trap_Trace, parameter); /* then trap to trace it */ + + if (cst_entry & CST_A_BIT) /* if the code segment is absent */ + MICRO_ABORTP (trap_CS_Absent, parameter); /* then trap to load it */ + } + +new_p = PB + (new_p & STMK_RTN_ADDR); /* convert the relative address to absolute */ + +cpu_read_memory (fetch_checked, new_p, &NIR); /* check the bounds and get the next instruction */ +P = new_p + 1 & R_MASK; /* the bounds are valid, so set the new P value */ + +STA = new_status; /* set the new status value */ +Q = new_q; /* and the stack marker */ +SM = new_sm; /* and the stack pointer */ + +if (STA & STATUS_R) { /* if a right-hand stack op is pending */ + CIR = NIR; /* then set the current instruction */ + cpu_read_memory (fetch, P, &NIR); /* and load the next instruction */ + } + +cpu_base_changed = TRUE; /* one or more base registers have changed for tracing */ + +return; +} + + +/* Start the dispatcher. + + This routine is called by the DISP and PSEB instruction executors to start + the dispatcher and by the IXIT executor to restart the dispatcher if it was + interrupted. + + On entry, the ICS has been set up. The "dispatcher running" bit in the CPX1 + register is set, Q is set to point at the permanent dispatcher stack marker + on the ICS, the dispatcher's DBANK and DB registers are loaded, and an "exit + procedure" is performed to return to the dispatcher. +*/ + +void cpu_start_dispatcher (void) +{ +dprintf (cpu_dev, DEB_INSTR, BOV_FORMAT "%s interrupt\n", + PBANK, P - 1 & R_MASK, 0, interrupt_name [irq_Dispatch]); + +CPX1 |= cpx1_DISPFLAG; /* set the "dispatcher is running" flag */ + +cpu_read_memory (absolute, ICS_Q, &Q); /* set Q to point to the dispatcher's stack marker */ +cpu_write_memory (absolute, Q, 0); /* and clear the stack marker delta Q value */ + +cpu_read_memory (stack, Q + 1 & LA_MASK, &DBANK); /* load the dispatcher's data bank */ +cpu_read_memory (stack, Q + 2 & LA_MASK, &DB); /* and data base registers */ + +cpu_exit_procedure (Q, Q + 2, 0); /* return to the dispatcher */ + +return; +} + + + +/* CPU local SCP support routines */ + + +/* Service the CPU process clock. + + The process clock is used by the operating system to time per-process CPU + usage. It is always enabled and running, although the PCLK register only + increments if the CPU is not executing on the ICS. + + The process clock may be calibrated to wall-clock time or set to real time. + In hardware, the process clock has a one-millisecond period. Setting the + mode to real time schedules clock events based on the number of event ticks + equivalent to one millisecond. Because the simulator is an order of + magnitude faster than the hardware, this short period precludes idling. + + In the calibrated mode, the short period would still preclude idling. + Therefore, in this mode, the clock is scheduled with a ten-millisecond + service time, and the PCLK register is incremented by ten for each event + service. To present the correct value when PCLK is read, the + "cpu_update_pclk" routine is called by the RCLK instruction executor to + increment the count by an amount proportional to the fraction of the service + interval that has elapsed. In addition, that routine is called by the CPU + instruction postlude, so that PCLK will have the correct value if it is + examined from the SCP command prompt. + + The simulation console is normally hosted by, and therefore polled by, the + ATC on channel 0. If the console is not hosted by the ATC, due either to a + SET ATC NOCONSOLE or a SET ATC DISABLED command, the process clock assumes + polling control over the console. + + + Implementation notes: + + 1. If the process clock is calibrated, the system clock and ATC poll + services are synchronized with the process clock service to improve + idling. + + 2. The current CPU speed, expressed as a multiple of the hardware speed, is + calculated for each service entry. It may be displayed at the SCP prompt + with the SHOW CPU SPEED command. The speed is only representative when + the process clock is calibrated, and the CPU is not executing a PAUS + instruction (which suspends the normal fetch/execute instruction cycle). +*/ + +static t_stat cpu_service (UNIT *uptr) +{ +const t_bool ics_exec = (CPX1 & cpx1_ICSFLAG) != 0; /* TRUE if the CPU is executing on the ICS */ +t_stat status; + +dprintf (cpu_dev, DEB_PSERV, "Process clock service entered on the %s\n", + (ics_exec ? "ICS" : "user stack")); + +if (!ics_exec) /* if the CPU is not executing on the ICS */ + PCLK = PCLK + pclk_increment & R_MASK; /* then increment the process clock */ + +cpu_is_calibrated = (uptr->flags & UNIT_CALTIME) != 0; /* TRUE if the process clock is calibrated */ + +if (cpu_is_calibrated) { /* if the process clock is tracking wall-clock time */ + uptr->wait = sim_rtcn_calb (PCLK_RATE, TMR_PCLK); /* then calibrate it */ + pclk_increment = PCLK_MULTIPLIER; /* and set the increment to the multiplier */ + } + +else { /* otherwise */ + uptr->wait = PCLK_PERIOD; /* set the delay as an event tick count */ + pclk_increment = 1; /* and set the increment without multiplying */ + } + +sim_activate (uptr, uptr->wait); /* reschedule the timer */ + +cpu_speed = uptr->wait / (PCLK_PERIOD * pclk_increment); /* calculate the current CPU speed multiplier */ + +if (atc_is_polling == FALSE) { /* if the ATC is not polling for the simulation console */ + status = sim_poll_kbd (); /* then we must poll for a console interrupt */ + + if (status < SCPE_KFLAG) /* if the result is not a character */ + return status; /* then return the resulting status */ + } + +return SCPE_OK; /* return the success of the service */ +} + + +/* Reset the CPU. + + This routine is called for a RESET, RESET CPU, or BOOT CPU command. It is the + simulation equivalent of the CPURESET signal, which is asserted by the front + panel LOAD switch. In hardware, this causes a microcode restart in addition + to clearing certain registers. + + If this is the first call after simulator startup, the initial memory array + is allocated, the default CPU and memory size configuration is set, and the + SCP-required program counter pointer is set to point to the REG array element + corresponding to the P register. + + If this is a power-on reset ("RESET -P"), the process clock calibrated timer + is initialized, and any LOAD or DUMP request in progress is cleared. + + The micromachine is halted, the process clock is scheduled, and several + registers are cleared. + + + Implementation notes: + + 1. Setting the sim_PC value at run time accommodates changes in the register + order automatically. A fixed setting runs the risk of it not being + updated if a change in the register order is made. +*/ + +static t_stat cpu_reset (DEVICE *dptr) +{ +if (sim_PC == NULL) { /* if this is the first call after simulator start */ + if (mem_initialize (PA_MAX)) { + set_model (&cpu_unit [0], UNIT_SERIES_III, /* so establish the initial CPU model */ + NULL, NULL); + + for (sim_PC = dptr->registers; /* find the P register entry */ + sim_PC->loc != &P && sim_PC->loc != NULL; /* in the register array */ + sim_PC++); /* for the SCP interface */ + + if (sim_PC == NULL) /* if the P register entry is not present */ + return SCPE_NXREG; /* then there is a serious problem! */ + } + + else /* otherwise memory initialization failed */ + return SCPE_MEM; /* so report the error and abort the simulation */ + } + +if (sim_switches & SWMASK ('P')) { /* if this is a power-on reset */ + sim_rtcn_init (cpu_unit [0].wait, TMR_PCLK); /* then initialize the process clock timer */ + CPX2 &= ~(cpx2_LOADSWCH | cpx2_DUMPSWCH); /* and clear any cold load or dump request */ + } + +cpu_micro_state = halted; /* halt the micromachine */ +sim_activate_abs (&cpu_unit [0], cpu_unit [0].wait); /* and schedule the process clock */ + +PCLK = 0; /* clear the process clock counter */ +CPX1 = 0; /* and all run-mode signals */ +CPX2 &= ~(cpx2_RUN | cpx2_SYSHALT); /* and the run and system halt flip-flops */ + +if (cpu_unit [0].flags & UNIT_PFARS) /* if the PF/ARS switch position is ENBL */ + CPX2 &= ~cpx2_INHPFARS; /* then clear the auto-restart inhibit flag */ +else /* otherwise the position is DSBL */ + CPX2 |= cpx2_INHPFARS; /* so set the auto-restart inhibit flag */ + +CNTR = SR; /* copy the stack register to the counter */ +cpu_flush (); /* and flush the TOS registers to memory */ + +return SCPE_OK; /* indicate that the reset succeeded */ +} + + +/* Set the CPU simulation stop conditions. + + This validation routine is called to configure the set of CPU stop + conditions. The "option" parameter is 0 to clear the stops and 1 to set + them, and "cptr" points to the first character of the name of the stop to be + cleared or set. The unit and description pointers are not used. + + The routine processes commands of the form: + + SET CPU STOP + SET CPU STOP=[;...] + SET CPU NOSTOP + SET CPU NOSTOP=[;...] + + The valid s are contained in the debug table "cpu_stop". If names + are not specified, all stop conditions are enabled or disabled. + + + Implementation notes: + + 1. The CPU simulator maintains a private and a public set of simulator + stops. This routine sets the private set. The private set is copied to + the public set as part of the instruction execution prelude, unless the + "-B" ("bypass") command-line switch is used with the run command. This + allows the stops to be bypassed conveniently for the first instruction + execution only. +*/ + +static t_stat set_stops (UNIT *uptr, int32 option, CONST char *cptr, void *desc) +{ +char gbuf [CBUFSIZE]; +uint32 stop; + +if (cptr == NULL) { /* if there are no arguments */ + sim_stops = 0; /* then clear all of the stop flags */ + + if (option == 1) /* if we're setting the stops */ + for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* then loop through the flags */ + sim_stops |= cpu_stop [stop].mask; /* and add each one to the set */ + } + +else if (*cptr == '\0') /* otherwise if the argument is empty */ + return SCPE_MISVAL; /* then report the missing value */ + +else /* otherwise at least one argument is present */ + while (*cptr) { /* loop through the arguments */ + cptr = get_glyph (cptr, gbuf, ';'); /* get the next argument */ + + for (stop = 0; cpu_stop [stop].name != NULL; stop++) /* loop through the flags */ + if (strcmp (cpu_stop [stop].name, gbuf) == 0) { /* and if the argument matches */ + if (option == 1) /* then if it's a STOP argument */ + sim_stops |= cpu_stop [stop].mask; /* then add the stop flag */ + else /* otherwise it's a NOSTOP argument */ + sim_stops &= ~cpu_stop [stop].mask; /* so remove the flag */ + + break; /* this argument has been processed */ + } + + if (cpu_stop [stop].name == NULL) /* if the argument was not found */ + return SCPE_ARG; /* then report it */ + } + +return SCPE_OK; /* the stops were successfully processed */ +} + + +/* Change the instruction execution trace criteria. + + This validation routine is called to configure the criteria that select + instruction execution tracing. The "option" parameter is 0 to clear and 1 to + set the criteria, and "cptr" points to the first character of the match value + to be set. The unit and description pointers are not used. + + The routine processes commands of the form: + + SET CPU EXEC=[;] + SET CPU NOEXEC + + If the value is not supplied, a mask of 177777 octal is used. The + values are entered in the current CPU data radix, which defaults to octal, + unless an override switch is present on the command line. +*/ + +static t_stat set_exec (UNIT *uptr, int32 option, CONST char *cptr, void *desc) +{ +char gbuf [CBUFSIZE]; +uint32 match, mask, radix; +t_stat status; + +if (option == 0) /* if this is a NOEXEC request */ + if (cptr == NULL) { /* then if there are no arguments */ + exec_match = D16_UMAX; /* then set the match and mask values */ + exec_mask = 0; /* to prevent matching */ + return SCPE_OK; /* and return success */ + } + + else /* otherwise there are extraneous characters */ + return SCPE_2MARG; /* so report that there are too many arguments */ + +else if (cptr == NULL || *cptr == '\0') /* otherwise if the EXEC request supplies no arguments */ + return SCPE_MISVAL; /* then report a missing value */ + +else { /* otherwise at least one argument is present */ + cptr = get_glyph (cptr, gbuf, ';'); /* so get the match argument */ + + if (sim_switches & SWMASK ('O')) /* if an octal override is present */ + radix = 8; /* then parse the value in base 8 */ + else if (sim_switches & SWMASK ('D')) /* otherwise if a decimal override is present */ + radix = 10; /* then parse the value in base 10 */ + else if (sim_switches & SWMASK ('H')) /* otherwise if a hex override is present */ + radix = 16; /* then parse the value in base 16 */ + else /* otherwise */ + radix = cpu_dev.dradix; /* use the current CPU data radix */ + + match = (uint32) get_uint (gbuf, radix, D16_UMAX, &status); /* parse the match value */ + + if (status != SCPE_OK) /* if a parsing error occurred */ + return status; /* then return the error status */ + + else if (*cptr == '\0') { /* otherwise if no more characters are present */ + exec_match = match; /* then set the match value */ + exec_mask = D16_MASK; /* and default the mask value */ + return SCPE_OK; /* and return success */ + } + + else { /* otherwise another argument is present */ + cptr = get_glyph (cptr, gbuf, ';'); /* so get the mask argument */ + + mask = (uint32) get_uint (gbuf, radix, D16_UMAX, &status); /* parse the mask value */ + + if (status != SCPE_OK) /* if a parsing error occurred */ + return status; /* then return the error status */ + + else if (*cptr == '\0') /* if no more characters are present */ + if (mask == 0) /* then if the mask value is zero */ + return SCPE_ARG; /* then the match will never succeed */ + + else { /* otherwise */ + exec_match = match; /* set the match value */ + exec_mask = mask; /* and the mask value */ + return SCPE_OK; /* and return success */ + } + + else /* otherwise extraneous characters are present */ + return SCPE_2MARG; /* so report that there are too many arguments */ + } + } +} + + +/* Set the CPU cold dump configuration jumpers. + + This validation routine is called to configure the set of jumpers on the + system control panel that preset the device number and control value for the + cold dump process. The "option" parameter is 0 to set the device number + and 1 to set the control value. The "cptr" parameter points to the first + character of the value to be set. The unit and description pointers are not + used. + + The routine processes commands of the form: + + SET CPU DUMPDEV= + SET CPU DUMPCTL= + + The device number is a decimal value between 3 and 127, and the control value + is an octal number between 0 and 377. Values outside of these ranges are + rejected. +*/ + +static t_stat set_dump (UNIT *uptr, int32 option, CONST char *cptr, void *desc) +{ +t_value value; +t_stat status = SCPE_OK; + +if (cptr == NULL || *cptr == '\0') /* if the expected value is missing */ + status = SCPE_MISVAL; /* then report the error */ + +else if (option == 0) { /* otherwise if a device number is present */ + value = get_uint (cptr, DEVNO_BASE, /* then parse the supplied value */ + DEVNO_MAX, &status); + + if (status == SCPE_OK) /* if it is valid */ + if (value >= 3) /* and in the proper range */ + dump_control = REPLACE_LOWER (dump_control, /* then set the new device number */ + (uint32) value); /* into the dump control word */ + else /* otherwise the device number */ + status = SCPE_ARG; /* is invalid */ + } + +else { /* otherwise a control byte is present */ + value = get_uint (cptr, CNTL_BASE, /* so parse the supplied value */ + CNTL_MAX, &status); + + if (status == SCPE_OK) /* if it is valid */ + dump_control = REPLACE_UPPER (dump_control, /* then set the new control value */ + (uint32) value); /* into the dump control word */ + } + +return status; /* return the operation status */ +} + + +/* Change the CPU memory size. + + This validation routine is called to configure the CPU memory size. The + "new_size" parameter is set to the size desired and will be one of the + discrete sizes supported by the machine. The "uptr" parameter points to the + CPU unit and is used to obtain the CPU model. The other parameters are not + used. + + The routine processes commands of the form: + + SET [-F] CPU + + If the new memory size is larger than the supported size for the CPU model + currently selected, the routine returns an error. If the new size is smaller + than the previous size, and if the area that would be lost contains non-zero + data, the user is prompted to confirm that memory should be truncated. If + the user denies the request, the change is rejected. Otherwise, the new size + is set. The user may omit the confirmation request and force truncation by + specifying the "-F" switch on the command line. + + + Implementation notes: + + 1. The memory access routines return a zero value for locations beyond the + currently defined memory size. Therefore, the unused area need not be + explicitly zeroed. +*/ + +static t_stat set_size (UNIT *uptr, int32 new_size, CONST char *cptr, void *desc) +{ +static CONST char confirm [] = "Really truncate memory [N]?"; + +const uint32 model = CPU_MODEL (uptr->flags); /* the current CPU model index */ + +if ((uint32) new_size > cpu_features [model].maxmem) /* if the new memory size is not supported on current model */ + return SCPE_NOFNC; /* then report the error */ + +if (!(sim_switches & SWMASK ('F')) /* if truncation is not explicitly forced */ + && ! mem_is_empty (new_size) /* and the truncated part is not empty */ + && get_yn (confirm, FALSE) == FALSE) /* and the user denies confirmation */ + return SCPE_INCOMP; /* then abort the command */ + +MEMSIZE = new_size; /* set the new memory size */ + +return SCPE_OK; /* confirm that the change is OK */ +} + + +/* Change the CPU model. + + This validation routine is called to configure the CPU model. The + "new_model" parameter is set to the model desired and will be one of the unit + model flags. The other parameters are not used. + + The routine processes commands of the form: + + SET [-F] CPU + + Setting the model establishes a set of typical hardware features. It also + verifies that the current memory size is supported by the new model. If it + is not, the size is reduced to the maximum supported memory configuration. + If the area that would be lost contains non-zero data, the user is prompted + to confirm that memory should be truncated. If the user denies the request, + the change is rejected. Otherwise, the new size is set. The user may omit + the confirmation request and force truncation by specifying the "-F" switch + on the command line. + + This routine is also called once from the CPU reset routine to establish the + initial CPU model. The current memory size will be 0 when this call is made. +*/ + +static t_stat set_model (UNIT *uptr, int32 new_model, CONST char *cptr, void *desc) +{ +const uint32 new_index = CPU_MODEL (new_model); /* the new index into the CPU features table */ +uint32 new_memsize; +t_stat status; + +if (MEMSIZE == 0 /* if this is the initial establishing call */ + || MEMSIZE > cpu_features [new_index].maxmem) /* or if the current memory size is unsupported */ + new_memsize = cpu_features [new_index].maxmem; /* then set the new size to the maximum supported size */ +else /* otherwise the current size is valid for the new model */ + new_memsize = (uint32) MEMSIZE; /* so leave it unchanged */ + +status = set_size (uptr, new_memsize, NULL, NULL); /* set the new memory size */ + +if (status == SCPE_OK) /* if the change succeeded */ + uptr->flags = uptr->flags & ~UNIT_OPTS /* then set the typical features */ + | cpu_features [new_index].typ; /* for the new model */ + +return status; /* return the validation result */ +} + + +/* Change a CPU option. + + This validation routine is called to configure the option set for the current + CPU model. The "new_option" parameter is set to the option desired and will + be one of the unit option flags. The "uptr" parameter points to the CPU unit + and is used to obtain the CPU model. The other parameters are not used. + + The routine processes commands of the form: + + SET CPU