Tuesday, May 29, 2018

The CUBE Library Tapes

Nigel and I are very pleased to report that three magnetic tapes in the collection of the Computer History Museum have been successfully read, and the resulting image files made available to several of us in the community working on Burroughs B5500 emulators and software restoration.

These tapes represent version 13 of the B5500 CUBE Library from February 1972. The files from these tapes are now available in the B5500-software repository:
https://github.com/retro-software/B5500-software/tree/master/CUBE-Library-13
CUBE, the Cooperating Users of Burroughs Equipment, was a U.S. based user organization, active from the 1960s through the early 1990s. After the merger of Burroughs and Sperry Univac in 1986, CUBE and USE (the Sperry user group) eventually merged to form the UNITE organization.

One of the functions that CUBE performed during the 1960s and early '70s was to maintain a library of programs for the B5000 and B5500 computer systems. These programs were donated both by Burroughs and by CUBE members. The library was freely available on magnetic tape to CUBE members and Burroughs support staff. The library eventually grew to occupy three reels of 7-track, odd-parity magnetic tape.

The remainder of this post describes the acquisition and preparation of the files from these tapes for the B5500-software repository.


Acquisition and Imaging


Tapes for version 13 of the library were acquired several years ago by Jim Haynes from the B5500 site at the University of California at Santa Cruz. He donated them to the Computer History Museum in Mountain View, California. The CHM was finally able to read these tapes in May 2018, producing binary images in .tap (taput) format. For information on the .tap format, see [1].

The .tap format was not originally designed to support the even- and odd-parity encoding available on 7-track tapes, and there is some difference of opinion on how it should be used for 7-track images. The CUBE Library tape images produced by the CHM do not record the parity. Each 6-bit character frame is stored right-justified in 8-bit bytes with two leading zero bits. The data is encoded in B5500 Internal Code (BIC). See Appendix A in [2].

The .tap format is not presently supported by the retro-b5500 emulator. Using a program I wrote for the modern Unisys MCP systems [3], I converted the .tap files to the .bcd format [4] used by retro-b5500 and several other emulators.

The tape images are identified as follows:
  • CUBE_LBR: labeled CASTC, 56-word (448-character) blocks, 2.8MB
  • CUBEA13: labeled CUBEA13/FILE000, B5500 Library/Maintenance, 12.0MB
  • CUBEB13: labeled CUBEB13/FILE000, B5500 Library/Maintenance, 10.4MB

The CUBE_LBR Tape Image


CUBE_LBR is in CAST format. The tape label has a creation date of 1976-06-10, but that is probably the date the tape was last copied. It does not appear that any of the files had been updated since at least the late 1960s. In fact, it appears that most of programs were originally written for the B5000, as they have dates that precede first customer shipment of the B5500 in February 1965. See the appendix on CAST tapes below for more information on this format.

Imaging of the CUBE_LBR tape detected errors in two areas of the tape. The first error was in the PTS041A module, in the block containing sequence numbers "KPKP0024" through "KPKP0028". These records are part of a large comment. The second error was in the PTS051 module, in the block containing sequence numbers "ANTP  11" through "ANTP  13", and including the two blank records on either side of those sequence numbers. These records occurred at the end of another large comment.

There were actually three blocks with errors in this second area of the tape, but two appear to be duplicates of the middle one, probably introduced by positioning errors during tape error retry, either when the tape was written or when it was recently imaged. It was not unusual for tape drives to detect erroneous blocks differently reading in the reverse direction, so this could explain how the duplication occurred. The two duplicated blocks caused module boundaries to be offset by 10 records in all the modules after that point on the tape.

Some research showed that identical comments were part of similar routines in other modules on the tape. I was able to correct the .tap image using a hex editor, drop the two duplicated blocks, and reconstruct the corrupted records from the comments elsewhere on the tape image. The .bcd image and extracted files discussed below were then generated from that corrected .tap image.

The CUBEA13 and CUBEB13 Tape Images


CUBEA13 and CUBEB13 are standard B5500 Library/Maintenance tapes. These two tapes were imaged without error. Their tape labels indicate creation dates of 1972-08-07 and 1972-08-05 respectively, but none of the files on these tapes has a creation date later than February 1972. Files from these tapes can be loaded to disk using the B5500 ?LOAD and ?ADD control card commands. See page 4-15ff in [5].

The individual files have been extracted from these tape images and converted to standard text file format in the Files/ subdirectory of the repository discussed below. Directories of the files on each tape are also discussed below.


Library Contents


CUBEA13 and CUBEB13 contain identical copies of an index file for the library (CUBELIB/INDEX) and an Algol program that can sort and format the index in multiple ways (CUBELST/Q000007). This program was used to generate listings of the index that you can view in the  CUBELIB.LIST.txt and CUBELIB.LIST-1.txt files in the repository. These files appear to match the scans of listings [6] and [7] on bitsavers, respectively.

The CUBE-Library-13 node of the B5500-software repository contains the .tap and .bcd tape images, directories of the files on each tape, and the two index listings cited above. The Files/ subdirectory of that node in the repository contains the individual files from the tape images, converted to standard text file format and encoded in ASCII. The following substitutions used by the retro-b5500 emulator were made for the five B5500 character glyphs that do not have ASCII equivalents:
  • ~  left-arrow (Algol assignment operator)
  • |  small-cross (Algol multiply operator)
  • {  less-than-or-equal operator
  • }  greater-than-or-equal operator
  • !  not-equal operator
The disk file system for the B5500 used a two-part name, the Multi-file identifier (MFID) and the File Identifier (FID), written MFID/FID. In the CUBE Library index, the MFID is the name of the program or package. The FID is a seven-character string termed the CUBE ID. The first two characters of this ID are a letter-number code denoting a classification scheme. The remaining characters (usually numeric) are a unique value within the classification code. The classifications are shown in the index listing CUBELIB.LIST.txt.

On the B5500, these files would be named MFID/FID. In Windows and Unix-like file systems, however, treating the MFID as a directory name would result in a large number of directories containing a single file. This would make the library files a little tedious to navigate, and would make it more difficult to find files by the CUBE ID in their second name. Therefore, the text files in the Files/ subdirectory have been named in the form MFID-FID.ext, where ".ext" attempts to identify the files by language or usage:
  • .alg: B5500 Extended Algol
  • .cob: B5500 COBOL (not COBOL-68)
  • .dat: Data or text in ASCII (no binary encoding)
  • .for: B5500 FORTRAN IV
  • .gtl: GTL (Georgia Tech Language)
  • .mca: Westinghouse Research MCALGOL
  • .sno: SNOBOL
  • .wipl: WIPL (Wisconsin Interactive Problem-Solving Language)
  • .xal: B5500 XALGOL (Compatible Algol)
Most files on the CUBEA13 and CUBEB13 tapes use the CUBE ID as the FID, but quite a few of the files (especially data files) do not. These are noted within the file descriptions in the index. To add to the confusion, files on the CUBE_LBR CAST tape have only one name. The CUBE ID, where available in the library index, has been appended as the FID to the names of these in the Files/ subdirectory of the repository.

In preparing the files for this repository I noticed a number of discrepancies between the files actually on the tape images and the entries in the index:

The following files are on CUBE_LBR but are not in the index:
MRS115MRS117MRS125MRS138MSS002,
ORS023ORS029ORS033ORS036PTS074URS046
The following files are in the index but not on any tape: 
PTS061/T200023
SYSX-O400001 (noted in the index as available separately)
The following files have different names between CUBE_LBR and the index (as noted above, several other files have different names on disk but they are identified as such in the index):
DSS029 on tape is DS029/T300001 in index
DSS030
on tape is DS030/T300002 in index
DSS031 on tape is DS031/T300003 in index
PTS024 on tape is PTS024R/E200008 in index

Library Highlights


The CUBE Library contains quite a bit of interesting software. The CUBE_LBR CAST tape image contains the Burroughs Mathematical Library, written entirely in Extended Algol. This tape has both individual subroutines that can be included in a program at compile time (see the discussion on CAST tapes and "$$" cards below) and full programs. Some of the modules contain sample data.

CUBE_LBR contains routines for special functions, differentiation, integration, interpolation, curve and surface fitting, matrix operations, statistical analysis, and a rather daunting set of programs for civil and chemical engineering (e.g., PTS051/T200019, "Non-Adiabatic Flash Calculations for Hydrocarbon Mixtures"), plus more.

As mentioned earlier, most of the programs on this tape appear to have been written for the B5000, as most have dates in the index that precede the introduction of the B5500 (and its Head-per-Track disks) in February 1965. I have been unable to locate any references to disk files in these programs. If this assertion of their origin is correct, this is the only B5000 software we have found to date.

I tried compiling the PTS051 program mentioned above. It produced syntax errors due to declaring procedure array parameters by value, something neither the B5000 nor B5500 supported. It is likely that early versions of the Algol compiler simply ignored call-by-value specifications for array parameters, and enforcement of that restriction was implemented in a later version. Removing the call-by-value specification for those arrays allowed the program to compile and run. See the compile deck and listing in the CAST-Examples subdirectory of the repository.

The CUBEA13 and CUBEB13 tapes contain many more mathematical programs and procedures, including double-precision transcendental functions written by NASA, a complex-number pre-processor for Algol programs, polynomial operations, Fourier approximation, matrix operations, statistical analysis, simulation, and a variety of miscellaneous utilities. I was particularly pleased to see a couple of disk directory utilities I had last used in 1970 at the University of Delaware.

The following programs and packages on these two tapes are of particular note:

MCALGOL -- An enhancement to Extended Algol by Larry McGuown at Westinghouse Research in Pittsburgh, Pennsylvania.

SNOBOL -- An implementation of the string-processing language by John Chambers at the University of Wisconsin. This had previously been transcribed by Richard Cornwell from the scan of a listing on bitsavers.org. That transcription is now in the versioned history of MCALGOL-L200009.alg SNOBOL-L200010.alg [corrected 2019-01-31] in the repository.

APL -- An APL interpreter for the B5500, written by Gary Kildall (of CP/M fame) and others at the University of Washington. A slightly earlier version of this program had also been previously transcribed by Hans Pufal and Fausto Saporito from a listing sent to me by Ed Vandergriff. Since that transcription has a separate provenance, it is being maintained separately in the repository. Documentation for this version is available on bitsavers.org.

GTL -- Georgia Tech Language. Another clone of Extended Algol by Martin Alexander at the Georgia Institute of Technology in Atlanta, Georgia. This compiler has significant extensions for strings, records, complex arithmetic, list processing, plex processing, and extended I/O features. Documentation is also available on bitsavers.org.

ALTRAN -- A program to translate Burroughs Algol to FORTRAN! This was written in GTL, probably at Georgia Tech. The author is unknown, but it appears to be based on an eponymous B5500 Algol program by Wayne Wilner. Wilner went on to become one of the architects, with Bob Barton, for the Burroughs B1000-series interpreter-based computer systems.

OMNITAB -- A command-driven program for statistical manipulations, originally written by the U.S. National Bureau of Standards (now the National Institute for Standards and Technology) in Washington, D.C., and converted for the B5500 by the Naval Air Test Center in Patuxent River, Maryland.

R/C -- REMOTE/CARD, a remote text editing and job submission program, more like RJE than timesharing, written by Ron Brody at the Burroughs Research Center in Paoli, Pennsylvania. This program and its documentation were also previously transcribed by Richard Cornwell from the scan of a listing on bitsavers.org. That transcription is now in the versioned history of RCSY94-Z100006.alg in the repository.

WIPL -- Wisconsin Interactive Problem-Solving Language, written by Ed Harris and Bob Janoski at the University of Wisconsin. This is a somewhat BASIC-like interactive programming language.

ELIZA -- The famous (or infamous) robotic psychiatrist. This version is written in GTL by Charles Fricks, probably at Georgia Tech.

XREF/JONES -- A documentation and cross-reference utility written by Glen Jones and Joan Dunshee at Burroughs in Pasadena, California. This is an earlier version of the same program on the Mark XIII SYSTEM tape. Documentation for the program can be generated by running the program against its own source file.

There's lots more -- the library is 320 files in all, so this collection of software is going to keep us busy for a while.

Appendix: CAST Tapes


The CUBE_LBR tape image in the library is in CAST format, a sequential source archive originating from the days of the B5000. The B5000 did not have the large Head-per-Track disks that were introduced with the B5500, only two relatively small drums, so source programs had to be maintained either as card decks or on tape.

The CAST format allows multiple source modules to be maintained as a single file. CAST files were originally on tape, but on the B5500, could also be stored on disk. These files are maintained by a standard Burroughs utility program, MAKCAST/DISK. The Algol and COBOL compilers understand this format and can compile programs and include individual routines directly from CAST tapes or disk files.

CAST tapes have a directory on the front of the tape that identifies the files stored on that tape. The directory includes the relative record number of the start of each source module. This allows MAKCAST/DISK and the compilers to use the Algol SPACE statement to position the tape to individual files relatively efficiently. If the CAST file is on disk, SPACE provides random access to the modules. If the file is on tape, it can take up to five minutes to traverse a full reel.

Here is what I have deduced for the format of CAST tapes:
  1. The tape is labeled with standard B5500 tape labels.
  2. Tapes are written with fixed-length 448-character (56 word) blocks.
  3. The first three blocks on the tape contain a directory of the files on the tape:
    • The first word of the first directory block appears to be a binary count of the number of blocks in the directory. This appears to be a fixed value of 3, however, and is hard-wired into the MAKCAST/DISK utility program.
    • Entries in the tape directory are variable length, consisting of N+4 characters, where N is the number of characters in the library module name.
    • The first character in an entry is the binary length of the module name. This length is followed immediately by the characters of the name.
    • Following the name are three characters that specify a big-endian 18-bit binary number -- the 1-relative logical record number on the tape where the module starts. This number is relative to the first non-directory block on the tape (i.e., the block following the directory blocks).
    • Directory entries are not split across tape blocks. If there is insufficient room at the end of a block for the next entry, a zero-length entry is inserted at the end of that block and the entry is stored at the beginning of the next block.
  4. The remainder of the tape after the directory blocks consists of blocks containing the text of the library modules.
  5. The first word of each of these text blocks is the big-endian binary value of the 1-relative record number of the first logical record in the block, using the same relative basis as in the 18-bit directory record numbers.
  6. The remainder of the block consists of five logical records of 88 characters (11 words) each (thus 5x88+8=448). The first 80 characters of a logical record hold a card image. The last eight characters of a logical record do not appear to be used and are zero.
  7. The library on tape is terminated by a physical tape mark and ending tape label.
  8. A 2400-foot reel of tape could hold almost 110,000 records at 800 BPI. The maximum capacity of a library is limited by the three directory blocks and the size of the 18-bit record number in the directory entries.
The MAKCAST/DISK program is described on page 5-5ff in [5].

The Algol and COBOL compilers use "$$" cards to include source modules from a CAST tape or disk file into the program being compiled. These cards are described on page 4-41ff in [5].

Sample card decks showing basic use of the MAKCAST/DISK utility program and use of "$$" cards during compilation can be found in the CAST-Examples subdirectory of the repository.


References


[1] TAP Tape Image Format: http://simh.trailing-edge.com/docs/simh_magtape.pdf
[2] B5500 Internal Code Table: http://bitsavers.org/pdf/burroughs/B5000_5500_5700/1021326_B5500_RefMan_May67.pdf, Appendix A.
[3] TAPBCD Tape Image Conversion Program: https://github.com/retro-software/B5500-software/tree/master/Unisys-Emode-Tools/TAPBCD.alg_m
[4] BCD Tape Image Format: http://www.piercefuller.com/oldibm-shadow/tool.html
[5]B5500/B5700 Operations Manual: http://bitsavers.org/pdf/burroughs/B5000_5500_5700/1024916_B5500_B5700_OperMan_Sep68.pdf
[6] CUBE Library Index Listing: http://bitsavers.org/pdf/burroughs/B5000_5500_5700/listing/CUBE_13_Library_Feb72.pdf
[7] CUBE Library Index Listing: http://bitsavers.org/pdf/burroughs/B5000_5500_5700/listing/CUBE_Library_Listing.pdf

Monday, April 2, 2018

retro-B5500 Emulator Release 1.05

Nigel and I are pleased to announce that version 1.05 of the retro-B5500 emulator was released on 18 March 2018. All changes have been posted to the repository for our GitHub project. The hosting site has also been updated with this release.

This is a primarily a bug-fix release, containing the collection of corrections and enhancements that have accumulated since 1.04 was released in September 2016.

Enhancements in Release 1.05


Apple Safari Pop-up Restrictions


This first item could be qualified as a bug fix, but it's really a new feature. The emulator had been operating quite well in the three major modern workstation browsers -- Google Chrome, Mozilla Firefox, and Apple Safari. I recently updated my seldom-used MacBook Air to macOS 10.13 (High Sierra), and with that update came Safari 11.0.

When I tried to run the retro-B5500 emulator in Safari after this update, the emulator crashed after opening its first pop-up window. I quickly discovered that my retro-205 and retro-220 emulators suffered the same fate. I had Safari configured to allow pop-ups, so what was the problem?

Some research revealed that this new version of Safari places additional restrictions on pop-up windows. Specifically, with pop-ups enabled it would always allow the first pop-up, but if subsequent pop-ups were opened before some amount of time had passed, Safari would inhibit the pop-up. Reports varied on the amount of time that had to elapse between opens, ranging upward from 500ms.

The emulator opens pop-ups for the Console Panel and each of its peripheral devices during its initialization. All of these elements are implemented as Javascript objects, and as part of their instantiation, each one opens, sizes, and positions its own pop-up window. Trying to schedule appropriate delays in the initialization process that instantiates these objects was going to be difficult, so I decided instead to centralize the mechanism for opening pop-ups.

Now, each object calls a common "open pop-up" function in the B5500Util.js module, passing the parameters necessary to open and configure the window. The new mechanism maintains a queue of these open requests, and inserts delays between servicing of the queued requests. If an open request fails (i.e., the Javascript window.open() function returns null), the request is reinserted at the head of the queue and the servicing delay is increased.

This new scheme works extremely well. It automatically adjusts the delay between opens based on the individual browser's behavior. The initial timeout value is 500ms and increments by 250ms on each iteration of a failed open. My tests indicate that Safari 11.0 starts making nice when the timeout reaches 1000ms. Chrome and Firefox both cruise through pop-up opens, because their open attempts do not fail; thus nothing ever gets queued and their opens proceed without delay.

Deimplement the Application Cache


The Application Cache (or AppCache) is a feature that allows a browser to download and store all of the scripts, images, and other resources for a web-based application and store them on your workstation. You can then run the application locally, without access to a network connection to the host web server. When a network connection is available, the browser checks during application initialization for updates. You would see messages in blue displayed briefly at the top of the emulator's home page reflecting this activity.

This feature seemed ideally suited to the emulator, since once it is loaded by the browser, the emulator needs no further communication with the host web server. Alas, this feature ran afoul of a segment of the developer community who thought it should be something different than what it was, some of whom were quite nasty about it. As a result, the Application Cache has been withdrawn as a draft web standard. Both Google Chrome and Mozilla Firefox have deprecated the feature in their browsers and announced that it will be removed in a future release.

In anticipation of this, I have removed from the emulator the things that enabled the Application Cache. The home page now displays a brief message indicating the feature has been deimplemented. This message will be removed in a future release. The first time you load the emulator's home page for this new release (assuming you have an active network connection), the AppCache will be deleted in your browser, but you will still be running the prior release. That is normal AppCache behavior. Simply reload the home page to use the new release.

For those who would still like to be able to run the emulator off-line, the only remaining option is to run a web server on your workstation. You only need a minimal web server in order to do this. As described in the Getting Started wiki page, I have had good success with the free, open-source mongoose web server, which is available in versions for Windows, Mac OS, and Linux/Unix.

Emulator Release Zip Archives



For those of you running your own web server, I have decided to discontinue creating zip archives of the emulator files and placing them on the project's Google Drive. Generating such an archive can be done directly on GitHub. On the project home page, look for the green Clone or download Code [updated 2022-05-07] button:

Download Source ZIP Archive


Click that button and then the Download ZIP link. You can also browse the complete list of commits by clicking the small clock-face icon with the number on its right, just below the green Code button on the project home page and download an archive from a specific release from that list.


Console Panel Annunciator Lamps


The small annunciator lamps along the bottom of the emulator's Console Panel that show the system's I/O channel, interrupt, and peripheral device activity have a slightly different look. Before, the lamps simply appeared in white when they were lit and disappeared when they weren't. Now each annunciator shows in dark gray when not lit. This allows you to see what all of the annunciators are, and it more closely approximates the back-lit effect the annunciators are intended to emulate.

New Console Panel Annunciators
 Note that these annunciators did not exist on a real B5500 console. They are an artifact of the emulator, present simply to give you an idea of the system's activity. Their presence can be toggled on or off by clicking the Burroughs logo on the panel. The annunciator mnemonics are explained on the Using the Console wiki page.

Asynchronous Callback Management


There has always been a significant exchange of ideas, techniques, and code among my Javascript-based emulator projects. One common module among the projects is something called the setCallback() function. It it part of the mechanism that regulates performance, trying to make the emulated Processors and peripheral devices operate at the speed of a real B5500.

Like all Javascript applications running in a web browser, the emulator executes on a single thread of the Javascript run-time system. The appearance of multiple, simultaneous activities is achieved in the same way that most operating systems appear to run multiple programs at once -- by giving each activity a short time slice and switching rapidly among the slices. The way you do that in Javascript is through callback functions that are executed asynchronously. The run-time system assures that only one of these callbacks executes at a time.

User interface events (e.g., mouse clicks) and some DOM APIs use callback functions to signal the emulator, but the primary way in which the emulator regulates performance is by keeping track of when Processor activity and I/O completions should be occurring in real time and inserting delays to allow real time to catch up with emulator time. The way it does this is through the Javascript setTimeout() function. You pass it a reference to a callback function, a number representing a delay in milliseconds, and an optional list of parameters. The run-time calls that function, passing any parameters, after the specified delay. The emulator typically has several of these asynchronous function calls scheduled at a time, and their interleaved execution is what gives the appearance of multiple simultaneous activities in the system.

There are two big problems with setTimeout(), though:
  1. The delay is almost always at least a little longer than you asked for. Since everything is single-threaded, some other function may be executing when your delay expires, and there may be other functions whose delay has expired ahead of yours, waiting in the queue for their turn on the single thread. In most browsers, background activities such as screen painting and memory garbage collection also run on the single thread, so these can also inflate the delay before a callback is actually called.
  2. Modern browsers limit the minimum delay to at least four milliseconds when callbacks are scheduled out of callbacks. There are a couple of reasons for this, but the original one was to inhibit poorly-behaved web pages from engaging in intensive CPU activity that drained the batteries of laptops.
The emulator tries to be well-behaved, but both of these problems interfere with its goal of running the Processors and peripheral devices at real B5500 speeds. The 4ms limit was particularly bedeviling for Processor performance throttling, which requires delays of less than 4ms. There is no mechanism for scheduling shorter delays, but I did find one that could produce a near-zero delay -- what in operating systems is termed a yield. This is based on the Javascript postMessage() API. It involves the emulator sending a small message to itself and attaching a callback function to the onMessage event.

This discovery lead to the development of setCallback(), which is used wherever you would normally use setTimeout().  If a particular call requires a delay of at least 4ms, settCallback() uses setTimeout(); if the delay is less than 4ms, it uses postMessage/onMessage instead. In the first case, the actual delay will be somewhat longer than requested, and in the second case it will usually be much shorter. To compensate for that, once the delay expires, the mechanism measures the actual delay that occurred and accumulates the deviation between the actual delay and the one originally requested.

A portion of that cumulative deviation is then applied to the next call on setCallback(), adjusting the requested delay up or down depending on the sign and magnitude of the deviation. If the cumulative deviation is positive (delays are running longer than requested), future delays will be reduced by some amount; if the deviation is negative (delays are running too short), future delays will be increased by some amount. The magnitude of the cumulative deviation is also reduced by these adjustments, with the goal of driving it toward zero.

Thus, instead of trying to make the individual callback delays accurate, the goal of this mechanism is to have the deviations offset each other to produce delays that are accurate in the average.

This mechanism was originally developed for the retro-B5500 emulator, and has been ported more or less as is to the other emulator projects. The portion that adjusts requested delays has required a lot of tuning to make it stable and reduce jitter, however, and that has been an on-going effort in all of the emulators. Last year, the retro-205 emulator exposed an instability in the adjustment algorithm, and I ended up completely redesigning it for that emulator. This release of retro-B5500 now includes that redesign.

Sending a message to yourself always seemed to me to be a bit of a hokey way to generate a yield, and there are potential cross-site security issues with postMessage(). It worked well enough, though, and it was the only approach I had been able to find. Then, while working on the tape drive implementation for the retro-220 emulator last Fall, I started using a relatively new mechanism for handling asynchronous function calls, Promises. Basically, a Promise is an object to which you can attach one or more callback functions. At some point, the Promise is "resolved" (i.e., the thing that was "promised" is reported as being fulfilled) by calling one of the Promise's methods. The Promise then calls each of the attached callbacks, one at a time, in the order they were attached.

This may not seem like much, but Promises turn out to be a very useful abstraction in asynchronous programming. They have two features that really caught my attention:
  1. After attaching a callback function to a Promise, the function will not be called until after the attaching code has returned to the Javascript run-time dispatcher.
  2. It is possible to create a Promise that is in resolved immediately upon creation. Any callbacks that are attached to a resolved Promise will still be called, but only after the code exits back to the Javascript dispatcher.
The fact that a Promise's callbacks are only called after returning to the Javascript run-time means that any other queued callback functions would be called first. That in turn means that a Promise should be able to generate a minimum-delay yield. Some experiments proved this to be true, and apparently with lower overhead and fewer security implications than postMessage()/onMessage(). Thus, the setCallback() implementation in this retro-B5500 release also incorporates the new Promise-based mechanism from the retro-220 project.

Minor Refinements

  • Rotational latencies for the Head-per-Track disk units are now computed from the high-precision performance.now() timer instead of the random number generator used previously.
  • The emulator's internal bindMethod() function, used to apply an object context to functions, has been replaced by the standard Javascript Function.bind() method.
  • The emulator's custom utility routines for manipulating a DOM element's className attribute have been replaced by standard DOM methods for the attribute's classList object.

Corrections and Bug Fixes


Two Nasty Bugs



The retro-B5500 emulator has always had a problem running a heavy mix of jobs. We first noticed this when we tried to recompile the Mark XIII system software, running several large compilations at once. The symptoms were Flag Bit and Invalid Address errors. Usually these errors aborted jobs in the mix. The Invalid Address error would occasionally result in an MCP "punt," typing "INVALD ADRSS" on the SPO and halting the system. The errors occurred at random times and in random locations in random programs.

Since this problem seemed to exhibit itself only during a heavy mix, and a heavy mix usually implies significant memory segment overlay activity due to Presence Bit interrupts, it seemed likely that the errors were somehow related to memory overlay or the handling of Presence Bit interrupts. I had tried several times over the past few years to trap these errors as they occurred and to isolate their cause, but without success.

Since I had to dig into the emulator to fix the Safari pop-up window problem anyway, I decided to make yet another run at resolving this long-standing problem. In previous attempts, I had tried to trigger memory dumps that captured the state of the system, but had been unable to generate one that revealed anything useful. Determining the source of the Flag Bit errors proved to be especially elusive.

The Flag Bit is the high-order bit in a B5500 word. Flag Bits mean nothing special in Character Mode, but in Word Mode they distinguish data words (operands) from control words. If the Flag Bit is a 1, it's a control word; if 0, it's a data word. The Processor generates a Flag Bit Interrupt in Word Mode when it fetches a word from memory that has a Flag Bit different from what it is expecting. This occurs in two cases:
  1. In the Operand Call (OPDC) instruction, if an indexed data descriptor references a non-operand word, i.e., one with its Flag Bit set.
  2. When exiting a procedure, if the Return Control Word addressed by the F register has its Flag Bit reset.
Interrupts for condition #1 are definitely errors. In researching this, however, I learned something about the B5500 architecture that I hadn't realized before -- interrupts for condition #2 are most often both intentional and beneficial.

Virtual memory on the B5500 (there's actually nothing virtual about it, but that is a subject for another post) is implemented using control words. Data areas are addressed through data descriptors, and code is addressed through program descriptors, also known as Program Control Words (PCWs). Data descriptors contain the physical memory address of the start of a data area, and PCWs contain the physical memory address of a procedure entry point or a cross-segment branch destination. Both words also contain a Presence Bit that indicates whether the associated data or code segment is physically present in memory or not.

Attempting to access memory through a descriptor having a zero Presence Bit generates a Presence Bit interrupt, which is the B5500 equivalent of a page fault. The MCP reacts to this interrupt by allocating space in memory (overlaying other areas if necessary to make room), making the referenced data or code present in memory, fixing up the descriptor with the physical memory address, setting the descriptor's Presence Bit to 1, and restarting the interrupted instruction. All of this is well known.

There are two other control words, however, that also address code, the Return Control Word (RCW) and Interrupt Return Control Word (IRCW). Both are stored in the stack by the hardware and hold return addresses -- the RCW as a result of a procedure call and the IRCW as a result of an interrupt. Both have a similar format, but neither one has a Presence Bit. So what will happen when a procedure exits back to its caller, but the caller's code segment has been overlaid while the called procedure has been executing?

This seems to be an aspect of the architecture that is less-well understood -- I certainly had not appreciated it before digging into the problem at hand, and have not found any mention of it in the documentation. It turns out that the MCP treats the Flag Bit of an RCW or IRCW somewhat like a Presence Bit. When a code segment is overlaid, the MCP not only resets the Presence Bit in any PCW that references that segment, it also resets the Flag Bit in any active RCW or IRCW that references that segment. If the hardware attempts to return to the address in one of these "absent" RCW or IRCW words, a Flag Bit Interrupt will occur. If the syllable that caused the interrupt is one of the return operators, the MCP will handle the interrupt similar to a Presence Bit Interrupt; otherwise the MCP will DS (abort) the job with a Flag Bit error.

Thus, my first attempts to capture the source of the problem, by trapping every Flag Bit Interrupt, led to somewhat comical results -- a tsunami of memory dumps caused by perfectly normal and legitimate page-fault activity. What I needed was a way to trap the problem at the point where the MCP has determined the Flag Bit Interrupt was not a pseudo Presence Bit Interrupt. I did not want to modify the Mark XIII MCP to do this, so the approach I chose was to modify the emulator to detect entry into the MCP's TERMINALMESSAGE procedure, which generates the program abort message that gets typed on the SPO. TERMINALMESSAGE is a so-called "save" procedure, meaning that it has a fixed memory address and is not overlayable. For the Mark XIII MCP as released, its entry point is physical address 999 decimal.

That approach worked. After running my standard heavy mix batch of jobs a few times and analyzing the resulting Flag Bit dumps, it became apparent that all of the interrupts were occurring at the point a program returned from a Character Mode procedure back into Word Mode.

With that clue in hand, the problem was easy to find -- the emulator was handling both Character and Word Mode exits using the same common routine, but a closer inspection of the Processor Flows and the B5281 Processor Training Manual showed that while Character and Word Mode returns are very similar, they are not the same. The common routine was handling Word Mode returns properly, but not Character Mode returns. In particular, it was not invalidating the A register before checking the Flag Bit in the RCW. Leaving the A register occupied could result an extra word in the stack if the interrupt was triggered, which could throw off the MCP's addressing of the stack while handling the interrupt. Of course, the interrupt was most likely to be triggered during a heavy mix when the probably was high that many code segments would have been rolled out.

Rewriting the procedure return mechanism in the emulator so that Word Mode and Character Mode returns were handled separately completely eliminated the problem with Flag Bit aborts. That left the Invalid Address aborts, which had been occurring a lot less frequently than the Flag Bit aborts, but were still taking place.

Invalid Address Interrupts occur under two conditions:
  1. When the Processor attempts to access a memory location that does not exist. This could be due to a memory module that was not installed or one which had been powered off or switched to local status.
  2. When a Normal State program attempts to access a physical address less than 1000 octal. The first 512 decimal words of memory are reserved for the MCP, and can be accessed only when the Processor is in Control State.
I was running a configuration with a full complement of 32K words, so condition #1 did not apply. In reviewing how the emulator handles condition #2, I realized that the original implementation distributed responsibility for detecting this condition between the Processor and Central Control. In fact, that detection was a matter entirely internal to the Processor. In the process of correcting that, it suddenly struck me that while the emulator was checking for Normal State in the detection of condition #2, it wasn't checking for it in other cases -- an invalid address occurring in Control State should be ignored and not generate an interrupt. It was clear the emulator had been generating interrupts in Control State, and that was the problem, not a bad address.

After fixing that really dumb error, my heavy mix batch now runs successfully and repeatedly. It has taken almost five years to nail these two nasty bugs, but finally it's done.

Miscellaneous Corrections

  1. Disable the word count field in I/O Descriptors for card reader/punch operations. I/Os for both devices did not use this field -- they always access 10 words of memory to transfer 80 characters of data.
  2. Fix a bug when using the RUNOUT button on the card punch to empty the stackers. The code was making an invalid property reference that caused a Javascript error to be thrown.
  3. Implement separate jump mechanisms for Word and Character Mode to correct edge-case errors in branch logic.
  4. Remove extraneous white space from B5500FramePaper.html. This file provides the initial content for the <iframe> elements that represent "paper" in peripheral devices such as the line printer, card punch, and SPO. The extraneous white space produced additional blank lines of text when the contents of the <iframe> were saved to disk or copied to the clipboard.
  5. Correct top-of-form handling in the line printer. Certain combinations of carriage control would interfere with top-of-form handling.
  6. Correct the animation of the tape reel in the tape drive for backward motion.
  7. Make a slight correction to clock counting in Single-Precision Add.
  8. Add P1 S and F register values to the dump caption record in tape images for internal memory dumps to tape (i.e., those generated by clicking the NOT READY lamp on the Console Panel). Also update and improve the dump tape's label records.
  9. Correct the method to "focus" the Console Panel window after the SPO initializes and becomes ready.
The problem resolved in #1 above was causing the Cold- and Cool-Start decks to function improperly. These decks are used to initialize and repair the disk filesystem and to load MCP code files. Both decks consist of the Cold- or Cool-Start program deck, followed by their parameter cards, followed by the MCP Tape-to-Disk loader program deck, followed by its parameter cards.

The Cold- and Cool-Start programs are designed to bootstrap the Tape-to-Disk Loader program automatically when they finish. I had not realized this, and thought you had to halt/load each program individually. The only way I could get that to work was to insert a blank card just before the Tape-to-Disk Loader program.

What was really happening was that the Cold- or Cool-Start program was attempting to read the first card of the loader program into memory, but unlike the MCP, was not setting the word count field in the I/O Descriptor. Thus the card was physically being read, but because the emulator was incorrectly relying on the word-count field, none of the card's data got transferred to memory, so there was no code to execute when the Cold- or Cool-Start program branched to it. Adding the blank card to the deck caused the Cold- or Cool-Start program simply to "eat" that card, allowing a subsequent manual halt/load to boot the loader program starting with its first card.

With this correction in place, that blank card now gets in the way. In this release it has been removed from the COLDSTART-XIII.card and COOLSTART-XIII.card decks available in the tools/ directory of the project repository. Any decks you may have that are based on these two should be checked. Any card between the STOP card that terminates the Cold- or Cool-Start program's parameters and the binary bootstrap card for the Tape-to-Disk Loader should be removed.