Monday, June 3, 2013

It's Alive...

After almost six months of silence, this blog is back. You can code or you can blog, and since the last post in December, we have been coding -- and then, of course, debugging what we have been coding. The central components of the B5500 emulator are essentially finished and are starting to work. There is more to be done for I/O, we have ideas for a richer user interface and display of system state, and there are certain to be more bugs that have not yet been discovered, but in a technical sense, this project is over the hump -- the emulator is running, it has successfully halt/loaded (i.e., booted) a version of the B5500 Datacom MCP, and it is able to run a few programs natively, including the Algol compiler.

 

A Brief Background

As we have reported in previous posts, Nigel Williams and I started this project in early 2012, after having talked about it for several months previously. When we first started talking about it, I thought we would program the emulator in Java, or perhaps something like Python. Nigel stunned my by suggesting that we implement the emulator within a web browser, and do the programming in Javascript. To be honest, I initially didn't think this would be possible, but Nigel pointed to some existing browser-based emulators, and eventually won me over. The emulator is 100% Javascript and currently runs within a couple of standard web browsers.

We started with pretty good references for the hardware architecture, but no machine-readable source or object code for the Burroughs system software. Having an emulator for a system but no software to run on it is not much fun. There were, however, a number of scans of listings of B5500 source code available on the bitsavers.org web site, so it appeared that our only option was to transcribe the source code manually from those scans and then somehow figure out how to bootstrap that into the emulator environment.

Nigel had hand-transcribed the Mark XVI Extended Algol compiler source from such a listing the year before we started, and Hans Pufal in France had made a similar transcription of the Mark XVI ESPOL compiler and given it to Nigel. A little more than a year ago, I decided that I needed to do my part, so started transcribing the Mark XVI Datacom MCP source. I'm still at it.

Thinking that the only way we were going to be able to have system software for the B5500 was to transcribe the source from listings, we were going to need a way to bootstrap object code from that source. Thus, last year I took the manually-transcribed Extended Algol and ESPOL compiler sources and ported them to the Algol variant used by the modern successor to the B5500, Unisys ClearPath MCP systems. That effort was successful, producing what we term the ALGOLXEM and ESPOLXEM cross-compilers, but we were going to need more source code -- lots of it. Alas, it appeared it was going to take us a couple of years' work to prepare enough source code from the scanned listings to be able to make our emulator run.

Despite not having system software in a usable condition, and little hope for having much for a long time, we nonetheless started coding for the emulator itself exactly a year ago, over the (U.S.) Memorial Day weekend at the end of May 2012.

 

And Then the Most Amazing Thing Happened...


Sid McHarg of Seattle, Washington contacted me in early July 2012. I knew Sid slightly from the annual Unisys user conferences. It turns out that Sid is an old B5500 hand, and over the prior year had been working on a B5500 emulator of his own, which he had recently gotten working. Our surprise at finding that someone else was crazy enough to try to emulate this old machine was matched by Sid's surprise that our project existed. That was surpassed only by our surprise to learn that not only did Sid have a working emulator, he had software!

Sid started out in the same position with respect to software (or rather, the lack thereof) that we were in, but Sid had something we didn't -- a set of 7-track Burroughs Mark XIII release tapes from 1971 that had been sitting on a shelf for 40 years. Amazingly, Sid found someone who had a 7-track drive that still worked, and even more amazingly, his tapes proved to be readable after all this time. He was in possession of a complete set of machine-readable source and object files for the B5500 system software, as they existed just past the apogee of the B5500's productive life.

Sid generously started working with Unisys on a license to share this data with others. We felt obliged to keep quiet about this while Sid was negotiating with Unisys, and since we did not know when or if that software might be available to us, we kept trudging along on our original path. We were eventually able to obtain raw images of Sid's tapes in late October 2012, and have recently acquired our own license for the Mark XIII software from Unisys.

 

The Road to First Halt/Load

With usable system software now in hand, the priority in the project shifted late last year from source transcription to getting the emulator working. I started working on I/O in early December, and by January of this year had initial implementations for the Input/Output Units, Head-per-Track disk, and SPO (supervisory keyboard/printer). I also built a web-based testbed to exercise individual instructions, which eventually evolved into the B5500SyllableDebugger, a basic debugging environment that could load and run whole programs.

Using our ESPOLXEM cross-compiler, I wrote a program to exercise the Character Mode instruction syllables for the system, used the SyllableDebugger to test those instructions, and found and fixed many bugs in the process. I am not very good at writing these kinds of tests, however, so started casting about for an existing program I could use to debug more of the Word Mode side of the instruction set. I settled on the KERNEL, a small bootstrap program that was typically stored on disk. The hardware load mechanism would load and initiate KERNEL, which in turn would bring in the MCP's initialization code to finish the system boot process.

Booting the MCP is termed a "halt/load," after the two buttons on the operator's console that had to be pressed in series to accomplish the act. That term is still used in the modern Unisys MCP systems, even though the physical buttons disappeared long ago.

The KERNEL proved to be a very good vehicle for testing out a significant part of the instruction set. I compiled its source with ESPOLXEM to get an object file and a listing that included the generated machine instructions. I would then load the object file into the SyllableDebugger and single-step through the code, inspecting the processor registers and memory at each step for proper operation. Working this way I was able to identify and correct several problems in the processor portion of the emulator.

As part of the implementation of disk I/O earlier, I had written a standalone utility, B5500ColdLoader, that would initialize the disk subsystem, prepare a skeleton directory structure, and load files from the binary images of the tapes we had obtained from Sid. The standard B5500 software release had a couple of standalone programs that were loaded from cards to perform these functions -- COLD to initialize the disk subsystem and create an empty directory, and TAPEDSK to copy files from Library/Maintenance tapes (an MCP tape format used to dump and restore disk files, somewhat similar to Unix tar). We did not have the card reader working yet, though, and the HTML5 IndexedDB mechanism we were using to implement persistent storage within the browser for the B5500 disk subsystem required some initialization and setup of its own, so building a custom tool to do these initialization functions seemed like the best idea.

The ColdLoader and SyllableDebugger are just HTML files with a bunch of Javascript embedded in them, and you run these utilities from within a standard web browser, just as with the emulator itself.

Thus, by the time in mid-February I was single-stepping through KERNEL with the SyllableDebugger, we already had a disk subsystem in place, and some of the system files, including the Datacom MCP, loaded into it. Over a period of several days, I was able to progress through the instructions in KERNEL, finding and fixing problems as I went, eventually arriving at the point where the program reads sector 0 from EU (disk Electronics Unit) 0 to get the MCP bootstrap address, which had been placed there (correctly, I hoped) by the ColdLoader utility. That eventually worked, so I continued stepping into the code that actually reads the MCP initialization segment.

Hardware load operations finish by branching to memory location @20 (@ indicating an octal literal in ESPOL), so the ESPOL compiler generates object code with the assumption that their execution will start at that address. The MCP cannot be read directly into that address, however, since KERNEL (which is about 250 words in size) was loaded there and is still running in that area of memory. Therefore, KERNEL reads the MCP code into the next available 4KW memory module, generally at address @10000. Disk reads are limited to 63 sectors (1890 words) each, so the MCP initialization segment is read in three chunks into addresses @x4236, @x0474, and @x0020, in that order. Disk I/Os take their disk address from the first word of the memory buffer, thus the backwards sequence allows each read to overlay the disk address word of the prior one.

After reading the MCP initialization code into the higher memory addresses, KERNEL deposits a small in-line Character Mode routine, in raw machine code, into addresses @15-17, and branches to it. That routine simply slides 4042 words starting at address @x0040 down to address @20, overwriting the memory used by the rest of KERNEL. The routine exits back to Word Mode while executing the last syllable at address @17, thus cleverly falling into the first syllable of the MCP initialization code that now resides at address @20.

So there I was, having started out intending just to debug some instructions, but now sitting there with the first word of actual Mark XIII MCP code loaded into the emulator's processor registers, poised to execute a LITC @704 syllable that had been generated in 1971. I thought, why not? and kept right on stepping into the MCP. The date was Saturday, 2 March 2013.

Much of my spare time over the next couple of weeks was taken up with continuing to step through the MCP initialization segment, which is quite large. That consists of the INITIALIZE procedure itself, at about 570 words, plus the kernel MCP routines for allocating memory and doing disk I/O, plus DIRECTORYBUILDER, which implements the complex process of "complementing" the disk directory to determine which areas of disk are not allocated and to build the tables that manage available disk space. Not surprisingly, there were quite a few more emulator bugs resolved during this process.

On 13 March, I saw the first halt/load message to come out of the emulator. This screen shot captured the moment, with the SyllableDebugger and emulated SPO shown running in Mozilla Firefox:
retro-B5500 first halt/load message -- click to enlarge
This message comes out at the point after most memory tables have been initialized and just before initialization of the disk tables begins. The text may be difficult to make out clearly. It reads:
-H/L WITH MCP/DISK MARK XIII MODS RR@@RR7#H13!4:7#H13!4:@@-
The underlined characters are junk, and should not be there. They were due to a bug in Character Mode that did not reset the BROF flip-flop when a memory transfer ended on a word boundary. That bug was easily found and fixed. There were several more emulator bugs as I got further into the disk initialization code, but by the 18th, it appeared I had gotten all the way through it, and the SPO now produced this:
-H/L WITH MCP/DISK MARK XIII MODS RR@@RRRR-
 SYSTEM/LOG REMOVED
DKA EU0 SU 1,2,3,4,5, EU1 SU 1,2,3,4,5 WENT READY
 TIME IS 0000
 DATE IS WEDNESDAY, 6/29/10
#DT PLEASE
This was very encouraging. The date was not what it was supposed to be, but alas, the SPO would not respond to any input requests so I could change it. It turned out that the MCP was looping endlessly in its NOTHINGTODO loop, and the reason for that eventually turned out to be a bug in handling the F register during accidental entries (thunks). That was easily fixed, and on the 19th I was finally able to see a reasonable halt/load completion with the MCP responding to SPO commands. The underlined text represents my inputs to the system:

-H/L WITH MCP/DISK MARK XIII MODS RR@@RRRR-
 TIME IS 0026
 DATE IS WEDNESDAY, 6/29/10
#DT PLEASE
#TR PLEASE
DT 3/19/83
INV KBD DT 3/19/83
DT 03/19/83
INV KBD DT 03/19/83
DT 3-19-83
INV KBD DT 3-19-83
DT 031983
INV KBD DT 031983
DT 3/19/73
INV KBD DT 3/19/73
TR 2128
 TIME IS 2128
DT 3/19/83
INV KBD DT 3/19/83
PD =/=
 INT/DISK
 DUMP/ANALYZE
 PBD/SYSNOTE
 ALGOL/DISK
 LDCNTRL/DISK
 MCP/DISK
 ESPOL/DISK
 LIBMAIN/DISK
 PRNPBT/DISK
MX
 NULL MIX

CU
0:MCP/DISK= 0:SAVE=13485 OLAY=3649
TOTAL MEM IN USE= 17134

OL DKA
 DKA SCRATCH
OL SPO
 SPO SCRATCH
OL LPA
 LPA NOT READY
The problem with the DT command (which sets the system date) turned out to be another Character Mode bug, which was fixed the next day. The PD command lists the files in the disk directory (these were all put there by the ColdLoader utility). The MX command lists the tasks in the "mix" -- the set of currently-running jobs. CU prints the memory in use by all tasks in the mix. OL reports on the status of a specified peripheral unit -- DK for a disk controller, SPO for the SPO, and LP for a line printer.

I had long wanted Nigel to share in the experience of the emulator's first successful halt/load, but was feeling a little guilty about that at this point, because I had been slowly, manually, doing the first halt/load all by myself inside the SyllableDebugger. I had been keeping him posted on my progress by email, but there wasn't much else to be done about it, as Nigel is in Hobart, Tasmania, while I am in San Diego, California -- 12,840 km (almost 8,000 miles) away. At that time of the year we are 18 time zones apart, so most days he is going to bed just as I am getting up. This does not make for close collaboration.

It was time to try halt/loading the emulator on its own, though, so Nigel and I arranged to meet on Skype the following weekend. I wired up the Load button on the console UI to the load function in the emulator's CentralControl module, and on 22 March we watched together as our emulator booted the Mark XIII MCP all by itself for the first time.

Life After Halt/Load

This project has been an exercise in deferred gratification, and that first completely autonomous halt/load was certainly both deeply gratifying and a significant milestone for the project, but things were far from perfect. Some SPO commands (e.g., CD) crashed the system, and our initial attempts to run programs under the MCP were completely unsuccessful.

I was busy with work and travel during April, so spent far less time on the project that month than in the ones previously. I managed to chip away at several problems, however, and by the end of the month was able to see one program (XREF/JONES) run to a successful completion. There were more bugs in the emulator, including a very serious one where the Program Release (PRL) operator was looking at the wrong bit in the I/O Descriptor word to determine whether to cause a Program Release or a Continuity Bit interrupt. The consequence of that was that I/Os would not get initiated against empty buffers, which in turn caused programs to wait for I/O completion interrupts that would never occur. There were a couple of problems with the way that the ColdLoader utility was initializing the disk, one of which was the cause of the problem with the CD command. Then there were two problems for which the root cause was determined to be that Paul was no longer a competent B5500 operator. The solution for that was to RTFM, only more carefully this time.

XREF/JONES was an interesting program -- sort of a Swiss Army knife for B5500 source code -- and I had been very happy to see it included in the files on Sid's tape images. As its name implies, it could generate an identifier cross-reference, but could also do flowcharting and generate a block/procedure structure outline. It was a basic text formatter, along the lines of nroff, and could even extract and format documentary comments from source programs, making it a very early form of Javadoc processor.

XREF/JONES was controlled by "$" pragmas embedded in the source code, but since we did not yet have any way to input data into the system (the SPO is an operations console, not a user terminal), the program was running in its default mode of simply listing its input file, which in our initial tests was its own 14K-line source.

Once we got past the initial post-halt/load problems noted above (especially the one concerning the incompetent operator), XREF/JONES started to show very bizarre behavior. Sometimes it would finish successfully. Sometimes it would abort with a disk addressing error. Other times it would abort with an Integer Overflow or Invalid Address interrupt. The remaining times the system simply crashed, sometimes with an Invalid Address message as its last gasp, and sometimes with no indication at all.

Resolving this took three long, agonizing weeks in May. There were a number of problems, including some missing or misplaced break statements and missing "this." prefixes on method calls -- all really bad news when programming in Javascript. I also reworked the implementation for IP1 (Initiate Processor 1), which is what does all of the register setup to assign a task to a processor, as I had never been happy with the way I had coded it originally.

The biggest fly in the ointment, though, which I noticed entirely by accident while looking for something else, was an error in the way the processor traced MSCWs (Mark Stack Control Words) during procedure exit. Those who are familiar with the B5500 will recall that the MSCW stored the state of the F register, MSFF (Mark Stack Flip-Flop) and SALF (Subprogram-Level Flip-flop). During procedure exit, the processor had to follow the F-register links in the MSCWs backwards in the stack until it found the first one where MSFF was not set, in order to restore that word at address R+7 in memory.

Alas, I had written this mechanism to stop when either the MSFF or SALF bits in the word were not set, which sometimes resulted in the wrong word being restored (especially when a procedure was called from MCP global code in Control State), and consequently messing up the (R+7) relative addressing mode within the processor. That relative addressing mode is used infrequently, but the results were catastrophic and extremely difficult to trace back to the source. This wasn't actually a bug, but a misreading of how procedure exit was supposed to work, not that it made me feel any better about it once I discovered what the problem was.

With that problem resolved, the emulator suddenly started running much more reliably. Not only was I able to run XREF/JONES to a successful completion, but last weekend all of the following worked:
  • Compile a smaller Algol source file, SYMBOL/TAPCOPY, used to copy tapes. The compiled code is bit-identical to the version on Sid's tape image.
  • Compile XREF/JONES with the Algol compiler. The 14K-line source compiled in about 13 minutes elapsed time with default compile options, including generation of a listing.
  • Run the newly-compiled XREF/JONES against another small source file, SYMBOL/DSKDUMP.
  • Compile the Extended Algol compiler with itself. This source is 11K lines and compiled in about nine minutes elapsed time.
  • Compile SYMBOL/TAPCOPY with the new version of the compiler. The output of that is also bit-identical to the original object file.
This is hardly a scientific test, but it demonstrated an order of magnitude more capability and reliability than we had seen before. We are now ready to submit the emulator to more demanding tests, but in order to do that we need to implement more I/O capability.

 

Current State and Next Steps

The emulator consists of a number of Javascript objects:
  • CentralControl, which serves as the node through which all inter-component communication takes place. It has the memory exchange, allowing multiple processors and I/O Units (channels) to access memory. It also prioritizes interrupts, hosts the interval timer, initiates I/Os, and controls the system-load functions.
  • Memory Modules. These are actually implemented as a part of CentralControl. There can be up to eight 4K-word mods, for a total of 32K words on a system. We are currently running six mods, with a hole in the @2xxxx-@3xxxx address range to test the MCP's ability to configure around missing mods. The MCP handles that just fine.
  • Processor, which implements the instruction set. There can be two processors, but we have only tested the system with one thus far. The mechanisms to support a second processor have been implemented, but never tested.
  • I/O Unit, which is basically a DMA device that asynchronously manages input/output operations. There can be up to four of these on a system. We are currently running with two units, but need to test varying numbers. CentralControl is designed to adapt to the actual number automatically.
  • Peripheral controls, or drivers. With the exception of disk and datacom, these were implemented within the I/O cabinet and connected to the I/O Units through a peripheral exchange.
At present, we consider CentralControl and Memory to be functionally complete. Some additional work may be needed to support the maintenance display panels, but otherwise they are done.

The Processor is complete except for the double-precision multiply and divide operators. At present those have been stubbed out with their single-precision equivalents. It is likely some debugging may be necessary to get a second processor to work, but we won't know that until we try.

We currently have the SPO and Head-per-Track disk peripherals working. A small amount of coding needs to be done in the I/O Unit object to support new peripheral types, and a separate object must be created to support each peripheral type.

Nigel has taken on the task of implementing a line printer control, and I am ready to start work on one for the card reader. With those two in place, we will have a basic way to interact with the system in batch mode. That will be much nicer than typing control card images on the SPO, as we must do now, and will give us the ability to compile and run programs other than those available on Sid's tape images.

Next after that will be a control for tape drives. This will be a bit of a challenge. Getting data from the local file system into a web browser is easy, but getting data out of a browser into the local file system is, by design, extremely difficult. Tapes are input/output devices, and a reel of tape could be read in both forward and backward directions (of which the MCP sort intrinsic took advantage). We may need to import tape reels into a persistent disk mechanism as we currently have for the Head-per-Track disk, and find a way to export data out of that for output tape reels that need to be saved.

Datacom will also be a challenge. Not only is the datacom control complex (and from today's perspective, somewhat bizarre), but web browsers are by their nature client environments, and for datacom, the emulator needs to act as a server. Ideally we would like to implement something that current networking technology can connect to, e.g., Telnet NVT, but that may prove to be difficult. The Web Sockets protocol is worth looking into. At worst, we could probably implement some sort of network interface in a web server, and connect to that from the browser-hosted emulator environment using AJAX techniques.

The remaining peripheral types -- card punch, paper tape reader, and paper tape punch -- are much lower on the priority list. Unless we find a compelling reason, we may not implement paper tape devices at all.

We have the small operator console working, but would like to implement a full set of maintenance panels for the system. That is a lot of lights, and one concern is whether the performance will suffer from trying to refresh all of that state in real time.

Also on the horizon are ideas to implement the emulator in non-browser environments, such as Mozilla Rhino and Node.js. Those will probably offer richer I/O capabilities, but the user interfaces will need to be completely rethought. We have also discussed implementing the emulator in other languages. Nigel has expressed interest in Google's Dart, and no doubt to his dismay, I am still interested in Java.

It is too early to say much about the performance of the emulator. One of our goals has been to make it run at the speed of a real B5500. We have some rough indications that it is running slower than that, but within 50%. Once we can get our own programs compiled and running within the emulator, we will be in a better position to judge the actual performance, and to understand where some tuning and optimization may be required.

Parts of the foregoing may read as if we think the existing emulator components are fully debugged, but we know that is not the case. Based on the most recent problems we have seen, we suspect we are entering the stage where the easy bugs have been found, and the rest are going to be subtle, difficult to reproduce, difficult to trace, and otherwise downright nasty. The only way to flush those out is to push the system harder, and that is what we intend to do.

As I told Nigel, we have a couple of careers' worth of things that we can do yet with just this emulator, provided we aren't locked away first, muttering incoherently about MSCW F-register linkages, interrupt-driven T-register syllable injection, C-register update during Inhibit Fetch, and the like.