Thursday, February 24, 2022

Running retro-b5500 Without a Browser

When we were first considering designing what became the retro-b5500 emulator back in late 2012 and early 2013, Nigel Williams more or less insisted that it be implemented in JavaScript to be run in a standard web browser. So we did, and that turned out to have been one of the best decisions we could have made.

We recognized from the beginning that there were other JavaScript environments that we might want to support one day, so I carefully separated the core components of the emulator (processor, memory, Central Control, I/O channels) from the user interface and peripheral device implementations, as the latter would be heavily dependent upon the specific environment in which the emulator ran. One of the alternate environments I have always had in mind was Node.js. To make that work, though, would require redesigning and reimplementing all of the user and peripheral device interfaces, which would be a lot of work.

Jim Fehlinger recently wrote to me about a way he has found to host the emulator outside of a browser that takes an entirely different approach and allows you to run the emulator essentially as an ordinary desktop application. Jim has long been a friend of and participant in the retro-b5500 project, particularly in doing yeoman work transcribing several large items of B5500 source code from listings.

I thought Jim's approach was so interesting and useful, and so easy to do, that I asked him to write up his experience as the following guest post for this blog. Jim has subsequently reported that he has used this method to run retro-b5500 on a Raspberry Pi, although you will probably need a Pi 4 to get decent performance.


I'm electrifyin', and I ain't even tryin'...
"You Gotta Get a Gimmick", Gypsy

I am a retired computer programmer, well past the age even of those employees of a certain maturity from a well-known computer firm that an executive recently referred to as "dinobabies." The systems I've worked on have been client-server applications predating the Web and its staggering proliferation of technologies that have sprung up since the mid-90s.

But I have been fascinated by emulators since I first discovered their existence in 2004, starting with SimH and Hercules, and I've been playing with Paul Kimpel's and Nigel Williams' retro-b5500 since I first found out about it in 2014. retro-b5500 is an early example of a trend that seems to have become more common since it first appeared -- the idea of writing the emulator using Web technologies (JavaScript/HTML/CSS) and having it run in a web browser, making it both cross-platform by default and easy to host on a web site so that a user can play with it without having to install anything on the local machine.

This seems like a very attractive idea, though there have been potential drawbacks from the beginning, having to do with lurking incompatibilities among the popular Web browsers (Firefox, Chrome, Safari, etc.) In addition, continual browser updates, and tightening up of security constraints, have always made it possible that the next browser update might break compatibility with a Web-based application like retro-b5500. In particular, local storage (in the form of IndexedDB or similar) has always been something that makes the browser "nervous" and is liable to break. Similarly with pop-up windows. Instead of having a no-fuss experience, a user might have to worry about browser security settings, plug-in incompatibilities, and similar woes.

So I was watching a video on the "Computerphile" YouTube channel the other day, in which David Domminney Fowler sings the praises of a couple of fairly recent technologies that he says have revitalized his interest in building applications in a way he's not experienced since first encountering BASIC on an Acorn microcomputer as a child.

Namely, Node.js and Electron, together with the usual JavaScript/HTML/CSS combo.

JavaScript is usually thought of as a programming language for things intended to be run in a browser, but in 2009 a wunderkind named Ryan Dahl came up with a way to encapsulate the Chrome V8 interpreter and Just-in-Time compiler for JavaScript into a browser-independent wrapper, so that JavaScript programs can be run outside of a browser just like any other desktop application, with the same more-or-less unrestricted access to the host computer's file system as other desktop applications. Therefore, people would be able to write both client-side ("front-end") and server-side ("back-end") programs in the same language. Thus was born Node.js.

That was fine as far as it went, but by itself such a JavaScript program would have no way to interact with a DOM, render HTML in a window, or do any of the other things a browser's runtime environment typically provides.

So then, about four years later, the folks at GitHub decided to marry the Chromium HTML rendering engine with Node.js's existing runtime library, to enable a sort of "upside-down" way to build JavaScript applications -- "upside-down" with respect to the usual inside-the-browser approach. You have Node.js providing JavaScript "directly" on top of the operating system (albeit with V8 sandwiched invisibly between), hosting GUI windows each rendered by their own sort of "browserlet" created by the Chromium engine, and using the same runtime API calls an application would use to manipulate windows inside a browser. The biggest criticism of this approach seems to be that it's a resource hog.

Watching that Computerphile video made me wonder...

And so I found a simple tutorial, written with the Mac in mind, but it worked on my Windows 10 machine, too. Following the tutorial, I downloaded and installed:

  • npm (v8.5.0) -- a command-line package manager for Node.js-related stuff
  • node (v16.14.0) -- Node.js itself, packaged with npm in the Windows installer file node-v16.14.0-x64.msi
  • electron (v17.0.0) -- which can be installed via "npm install -g electron" in a command-line window.

I made a package.json file by running "npm init" in a command-line window and answering the npm-prompted questions. Then in a text editor I made a main.js file and specified an HTML entry point for my application, thus:

const {app, BrowserWindow} = require('electron');
const url = require('url');
const path = require('path');

let win;

function createWindow() {
   win = new BrowserWindow({width: 800, height: 600});
   win.loadURL(url.format ({
      pathname: path.join(__dirname, 'webUI/B5500Console.html'),
      protocol: 'file:',
      slashes: true
   }));
}

app.on('ready', createWindow);

This code was copied directly from the introductory tutorial with only one modification, replacing

pathname: path.join(__dirname, 'index.html'),

with

pathname: path.join(__dirname, 'webUI/B5500Console.html'),

Then I started it up in a command-line window with:

electron ./main.js

... and I could hardly believe my eyes. It just worked! No modifications to the retro-b5500 code were required at all. I was expecting that at least the parts related to persistent storage might have to be changed, but no, the IndexedDB storage creation worked without complaint. Both the configuration database and the emulated disk went into my C:\Users\My Name\AppData\Roaming\Electron\IndexedDB\file__0.indexeddb.leveldb\ directory.

The Electron-based retro-b5500 has no problems such as you might encounter these days with a standard browser -- windows open up smoothly without delay, except for the initial console window, which takes a few seconds longer to appear than you might be used to. There's no problem creating the local storage, and there's no question of it disappearing unexpectedly when you clear your browser history.

There are also ways to package up Electron applications for distribution, so that the target machines don't have to have the Node and Electron development environments. In that case, Electron itself is all you need. You can either recompile Electron itself if you want certain customizations, or (as I did) use a pre-compiled version corresponding to your target platform, which in my case is electron-v17.1.0-win32-x64.zip. There are also packages for Mac (Darwin) and Linux, for both x86-64 (and 32) and ARM64 available at https://github.com/electron/electron/releases/.

You unzip the Electron package into a directory. In the \resources subdirectory, delete default_app.asar. Create a \resources\app subdirectory and copy all the usual retro-b5500 files into it. The easiest way to get those is to go to the GitHub repository at https://github.com/pkimpel/retro-b5500/, click the green Code button, and select Download ZIP from the drop-down menu. From the resulting zip file, extract just the \emulator, \tools, and \webUI directories into the \resources\app subdirectory. Of course, you will also need at least the B5500 SYSTEM tape image in order to initialize the disk and load the system software, as described in the Getting Started wiki page.

Also into the \resources\app subdirectory, copy the files package.json and main.js that you created as described above. Then all you have to do is double-click electron.exe in the top-level directory, and retro-b5500 will start up automatically with the console screen displayed, and works as usual after that -- as far as I know! Even the hyperlinks on the main console screen work -- they open up additional windows for the linked content.

It is also possible to package up the application-specific files into a *.asar archive and run out of that, but I didn't bother with that route.

When you're running an application in a "packaged" environment (as opposed to via the "electron" command installed by npm on your development machine), the application's local storage goes into a directory named according to the application name given in \resources\app\package.json, which in my case looks like this:

{
  "name": "electro-b5500",
  "version": "1.0.6",
  "description": "Electron version of retro-b5500 1.06",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Paul Kimpel, Nigel Williams",
  "license": "MIT"
}

Although of course the details depend on how you answered the questions when you performed "npm init" to create package.json in the first place. Presumably you can just edit package.json directly if you want to change those details afterward.

And because I called the app "electro-b5500", the local storage now goes into:

C:\Users\My Name\AppData\Roaming\electro-b5500\IndexedDB\file__0.indexeddb.leveldb\*

One disappointing limitation I've discovered is that although the IndexedDB files created by the Chrome browser look similar to those created by Electron (with CURRENT, LOCK, MANIFEST-000001, etc.), you can't just copy your old Chrome IndexedDB files into the Electron directory and expect retro-b5500 to use them. It just doesn't "see" them. It doesn't work the other way, either -- you can't copy the Electron-created IndexedDB files into the Chrome directory and expect retro-b5500 running in a browser to see them.

So that's my little adventure with Electron.js, Node.js and retro-b5500. As I say, I was totally blown away by the fact that this "just worked." And I am the furthest thing imaginable from a modern-day Web developer.

Saturday, February 15, 2020

retro-B5500 Emulator Release 1.06

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

Enhancements in Release 1.06


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


Emulator Power-On


The emulator has since it's early days performed two actions as part of its "power-on" initialization sequence:
  1. The SPO types out a brief message showing the emulator version.
  2. The operator console displays a "lamp test" that lights all of the console lamps, waits a few seconds, and then extinguishes them.
I finally got tired of the delay these two behaviors caused and have disabled both of them.

Magnetic Tape Enhancements


The tape drive now examines tape-image file extensions in a case-insensitive manner when determining the format of the image.

The way that a drive stored words in memory during a backward read was incorrect. This has been fixed.

The tape drive will now detect "dropouts" -- areas where there is no flux change indicated by the bytes in the tape image. This can happen only with .bcd tape images, and can be made to occur by writing zero (i.e., all bits off) characters in even parity. If a dropout is detected, it will be reported as a parity error in the result descriptor.

Miscellaneous


When emptying a stacker on the card punch, the progress meter and stacker-full annunciator are now properly reset.

During the Processor's exit from Character Mode, the MSFF and SALF flip-flops were being reset and should not have been. In addition, the way the MSCW address was being obtained was unnecessarily complex. These issues have been corrected.

The delay deviation adjustment algorithm in the setCallback() function has been redesigned and simplified. The "alpha" factor that controls the rate of adjustment decay has been reinstated as part of the redesign.

A few of the utility scripts in the tools/ directory have received minor enhancements and some new scripts added. Most of these scripts are for internal testing and debugging, and are intended as templates to be customized for a particular situation.
  • Added B5500DiskDump.html. This utility will write the contents of the first 20 sectors of EU 0 to a window.
  • Modified B5500DiskPeeker.html to use the standard default name for a disk subsystem data base, "B5500DiskUnit."
  • Added B5500DiskSeg0Fixer.html to correct the MCP and Intrinsics pointers in sector 0 of the halt-load disk unit.
  • Modified B5500LibMaintDir.html to have a new "ShowRows" parameter to control whether header row-address words are displayed on the output.
A new, bootable card-image file, tools/DUMPTAP-XIII.card, has been added. Halt-loading this deck will dump the contents of B5500 memory to a tape drive. The tape can then be analyzed by the DUMP/ANALYZE utility after the system has been halt/loaded to run the MCP. This was a standard utility released by Burroughs, but I had never compiled it to get the deck.

An easier way to obtain a memory-dump tape is to click the NOT READY lamp on the console, but that is an emulator-specific feature.

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.

Sunday, November 20, 2016

Emulator Release 1.04

Nigel and I are pleased to announce that version 1.04 of the retro-B5500 emulator was released on 4 September 2016. All changes have been posted to the repository for our GitHub project. The hosting site has also been updated with this release, and for those of you running your own web server, a zip file of the source can be downloaded from the GitHub repository [updated 2022-05-07].

This is a minor release containing a number of enhancements and corrections for issues we have discovered since version 1.03 was released a little over a year ago.


Enhancements in Release 1.04


This release of the emulator supports the following enhancements.

New Single-Precision Add/Subtract Implementation


The B5500 uses a unified numeric format, which means that it does not have the typical distinction between integer and floating-point numbers that most computer architectures do. Integers are simply values that have been normalized so that their exponent is zero. For exaple, octal 0000000000000001 (the integer representation of 1) and 1141000000000000 (the normalized floating-point representation of 1.0, i.e., octal 1000000000000 × 8-12) are the same value as far as the B5500 numeric operators are concerned.

There is a document informally referred to as "The Flows," which contains a schematic representation of the logic equations for each operator syllable. Alas, we did not have access to this document when initially developing the emulator, and had to rely on its companion, the Processor Training Manual, which is a good, but not-quite-complete narrative guide to The Flows. As a result, the first implementation of single-precision arithmetic was something of a best-guess effort, and as reported previously in this blog, some of my early numeric benchmarks were producing results that had at best one significant digit of agreement with results from a real B5500.

After much frustrating research, that problem turned out to be improper rounding in single-precision Add and Subtract, which are implemented as the same instruction with some small differences in the treatment of signs. I finally fixed the problem empirically by generating results from 64 "interesting" numeric values and comparing the octal results to those from a modern Unisys MCP system, which uses the same numeric word format. That revealed many cases where rounding was being done improperly -- mostly during scaling of the smaller operand to make exponents equal. Fixing those cases allowed the benchmarks to agree with the results from a real B5500.

By the time I got around to implementing the double-precision arithmetic operators about a year later, The Flows had become available on bitsavers.org, and I followed them closely in implementing the double-precision operators. That gave me the idea that at some point I should go back and redo single-precision Add/Subtract with more rigorous attention to implementing them the way The Flows said they worked.

Earlier this year, I had a lengthy and very interesting exchange of correspondence with Reinhard Meyer in Germany, who was contemplating design of a B5500 emulator of his own and perhaps eventually producing an FPGA implementation of the machine. That exchange finally galvanized me to go back and re-implement single-precision Add/Subtract according to The Flows.

Rigorously following The Flows turned out to be considerably more of a challenge than I had originally anticipated. Digital logic tends to work in parallel, with many states changing at each clock pulse. Most of that translates fairly easily to the sequentially-executed code of a traditional programming language, but some of it does not, and you continuously need to be on guard that states set during one clock cycle do not become effective until the start of the next one.

The most frustrating problem that arose during this effort was one where subtracting two numbers of equal value generated a result that was not completely zero. The mantissa would be zero, but the sign or exponent would not. There is explicit logic in The Flows to test for this condition in the J05L block under the secondary control of Q04F/ (see index 1.01.1 page 2 of 2 in the document) and exit the operator early if the sign or exponent do not need to be cleared to zero. That test is based on the logic term W03L, which is defined as "A[39->37] = B[39->37]", meaning the high-order octade of the A-register mantissa (bits 39 through 37) equals the high-order octade of the B-register mantissa. The corresponding narrative in the Processor Training Manual (page 3.17-11) states, "If the 13th octade of the A and B registers do not equal each other (W03L/) it is not possible for the resultant sum of the complement addition to equal all zeros and the operator is terminated."

I implemented the code exactly that way. It didn't work. After staring at that portion of the logic for days, it finally occurred to me that neither the definition of W03L nor the statement in the Training Manual made sense -- it's entirely possible for the two high-order octades to be unequal and still sum to zero -- 7+1 (ignoring the carry), for example. That made me begin to wonder whether "equal" and "not equal" meant what I thought they did. To answer that question, I had to look at the document upon which The Flows are based, the Processor Equation Book, where on page 422.00, I found the term W03L/ to be defined as:

A39F*B39F + A39F/*B39F/ + A38F*B38F + A38F/*B38F/ + A37F*B37F + A37F/*B37F/

This is somewhat simplified from the notation in the Equation Book, which appears to refer to physical circuit elements. For the uninitiated, "*" means logical-AND, "+" means logical-OR, and "/" used as a suffix means logical-NOT. Operator precedence is NOT-AND-OR, highest to lowest. The numbers refer to bits in the registers, with 48 being high-order and 01 being low-order. A suffix of "F" means flip-flop.

That equation clearly does not determine inequality of the high-order A and B octades -- it determines the logical equivalence of the three pairs of bits in the octades and ORs those equivalences together. Logical equivalence is the complement of exclusive-OR. In other words, W03L/ is true if any of the three pairs of bits match. Equivalently, W03L is true if none of the three pairs of bits match. After making that change in the code, results with zero mantissas finally generated words of all zeroes.

After all the hard work to re-implement single-precision Add/Subtract from The Flows, it produced very little improvement over the results of the previous, empirically-corrected implementation. There were a few differences in the way that results are normalized, and a rounding difference if one of the values is octal 0161000000000000 and the other value is one of 0004444444444444, 0005555555555555, 0006666666666666, or 0007777777777777. On that basis, the effort invested in the new implementation was not worth the very slight improvement in results. On the other hand, it was a very interesting experience, and I learned a lot about digital logic and how the B5500 worked at the electronic level.

There is one other significant difference -- the new implementation runs noticeably slower, in terms of emulated clock time, than the prior one. For one heavily numeric benchmark that runs about ten minutes, the new implementation increases the emulated run time by 48 seconds -- a little over 8%.

Card Reader Enhancement


The implementation of the B129 card reader in the emulator allows you to load one or more text files into the reader as if they are card decks. Each line in a text file is treated as an 80-column card image. Thus, in order to read cards, you first must have a text file on your local system to hold the necessary card images.

This has proven to be a little inconvenient. If you have a file of source code you wish to compile, for example, you either need to modify that file and add the necessary B5500 control cards, or create a copy of the file and add the control cards to that.

Starting with this release, the user interface for the card reader now supports the ability to enter card images directly into the reader from your keyboard without creating a text file first. Card images entered this way can be interspersed with card images from text files. This allows you, among other things, to "wrap" existing files with control cards, compiler $-option cards, or anything else you need but don't have (or don't want) in a text file on disk.


The card reader's panel has a new button in the upper right-hand corner labeled KEY IN DECK. This button is disabled unless the reader is in a not-ready state. Clicking the button opens a sub-window that allows you to enter card images directly into the reader's buffer:


While the key-in window is open, the reader's START button is disabled.

The bulk of this window is occupied by a standard text area. You can enter text into this area from your keyboard or copy/paste text from another source. You can edit the text using the normal controls of your operating system. Clicking the Insert button at the bottom of the window appends the contents of the text area to the card reader's buffer and closes the window. Clicking the Cancel button simply closes the window without affecting the reader's buffer. Once text is inserted into the buffer, it cannot be modified or deleted, just as with inserting a text file into the buffer.

There are three white buttons along the bottom of the window with commonly-used control card images. Clicking one of these simply inserts the respective card image into the text area at the current cursor position. From there it can be appended to the reader's buffer by clicking the Insert button.

Miscellaneous Improvements


1. The size of the terminal adapter buffer in B5500DatacomUnit has been reduced from 112 to 56 characters to accommodate the requirements of the R/C interactive editor. Users of the Timesharing MCP (TSMCP) and CANDE will need to update their SYSTEM/DISK file with the new buffer size, e.g.,
?RUN SYSDISK/MAKER
?FILE LINE = LINE BACKUP DISK
?DATA CARD
LINE,0,0,56,0,0,7,0,
LINE,1,0,56,0,0,0,0,
STA,0,0,0,0,"0","0",0,0,
?END
2. We have discovered that the emulator now works with Apple Macintosh OS X using Safari versions 8 and above. Nothing has changed in the emulator to support this -- the issue was waiting for Safari to implement the IndexedDB API used by the emulated disk subsystem.

We continue to be unable to get the emulator to run in either Microsoft Windows Explorer or Edge. If anyone has tried the emulator in Opera or any other GUI browser, please let us know how that works. 

3. Despite a statement in the project wiki that the emulator must be loaded over HTTP from a web server, it appears to run just fine when loaded directly from your local file system, e.g., using a URL like this:
file:///C:/Projects/B5500/WIP-S1/webUI/B5500Console.html
The wiki has been corrected.

4. The alternate B5500SyllableDebugger user interface for the emulator has been enhanced with an Execute Single button. This executes the instruction currently in the T register, but differs from the Step button in that it does not advance the C and L instruction counter registers. This makes Execute Single a better choice than Step for testing and debugging a specific operator.

5. The default height of the SPO and line printer windows has been reduced to about a third of the screen height.

6. The tools/B5500DeleteStorageDB.html script can be used to remove the IndexedDB database for an emulated disk subsystem. Previously, this script could only remove the default data base, named B5500DiskUnit. The script now takes a "?db=" query string parameter with the name of the data base to remove.

7. The Algol Glyphs option for the card punch (CPA) has been set to false by default. Some text editors were corrupting the Unicode characters produced in Cold- and Cool-start decks when this option was set to true, making the decks useless for loading under CARD LOAD SELECT.

8. The exponential moving average computations for Delay Delta and Processor Slack in the Processor object have been adjusted slightly.

Corrections


1. Rounding in Integer Store operators has been corrected to produce a word of all zeroes if the mantissa is zero. Previously, non-zero sign and exponent fields could have been present when mantissa was scaled to zero.

2. BCL translation for alpha-mode (even parity) tape files has been corrected. Previously, the translation was being done twice -- once by the I/O Control Unit and once again by the tape drive. It is now handled entirely within the tape drive.

3. Both EOF and parity error are now reported by the tape units for any attempt to read beyond the end of a tape image.

4. The way that "green-bar" formatting was being done on the line printer was causing a blank line to be inserted every third line when copying text directly from the "paper" area of the printer's window. A different method is now used that does not result in extraneous lines in the copied text.

5. When the emulator was powered on, the browser windows for the individual peripheral devices were being created twice. This behavior was intentional, but dated from very early in the emulator's development when crashes and aborts were a frequent occurrence. The act of opening a window, closing it, and then reopening it served to close any windows left open by a crashed emulator instance. We are long past the need for that now, so the screwy open/close behavior has been removed. As part of this effort, de-registration of event handlers for the various windows has been implemented.

6. After almost five years, we are still finding errors in the transcription of the Mark XVI Algol compiler listing. Most of the errors that have come to light in the past few years have been in comments, but a few months ago we found a serious error in line 06073850 -- a plus sign had been transcribed as a minus. I doubt we are ever going to get all of the errors out of this transcription -- or of any other large transcription like this.


The B5500 Source Code Archive


We are starting to amass a significant amount of source code for the B5500. Most of this has been recovered by transcribing it from listings. Up to this point, we have been including the transcriptions we've done or received from others in the emulator project's repository. Rich Cornwell in North Carolina (US) and Jim Fehlinger in New Jersey (US) have been especially prolific in turning scanned listings into text files.

There are now multiple B5500 emulators, however, either in existence or under development. In addition to Sid McHarg's C++ emulator and this project, Rich Cornwell completed an emulator earlier this year that runs in the SimH framework, and Mark Lloyd in the UK continues to work on his Lazarus/Pascal version.

With multiple emulator projects and multiple sources of recovered source code, it has becoming less and less appropriate for that code to be embedded in a specific project. I have been thinking about this for a while, and a few months ago decided to set up a separate project on GitHub to serve as a common repository for all B5500 emulators:
https://github.com/retro-software/B5500-software
Rather than set this up under someone's personal account, I've created a GitHub "organization" to be the owner. This will make it easier to share and ultimately transfer responsibility for the archive, and also to create additional repositories in the future to maintain recovered source code for other systems. Initially, Nigel and I are the administrators of this organization and the B5500 archive, but hopefully that can be expanded in the future.

To seed this new repository, I have exported the source code transcriptions, and their history, from the retro-b5500 project and made that the initial contribution to the B5500-software project. In the process, I rearranged the directory structure into something that hopefully will be more maintainable over time. The source code in the retro-b5500 project will probably be deleted at some point down the road.

In a future blog post, I will propose some standards and procedures for maintaining the archive, but will be happy to receive comments and recommendations on this from others. I will also be happy to receive new contributions and will consider pull requests for updates. Each transcription should be accompanied by some text that describes it provenance and who did the transcription. See the README.txt files in the repository for examples of what I'd like to have in this regard.

I would like for all of the transcriptions to be faithful to their original sources, so please include sequence numbers if they are present in the original, and please do not correct spelling in comments, or even bugs in the code. Any original errors can be corrected in separate projects or branches from the transcriptions of the originals.