Enter email address here to subscribe to B5500 blog posts...

Saturday, December 15, 2012

Decoding the ESPOL Loader Card

We have been making good progress on the B5500 emulator, and have recently started working on the I/O subsystem. There is quite a bit more in that area to be reported in later posts, but this one concerns some correspondence I had with a reader of this blog a few weeks ago.

The reader had stumbled across something called the "ESPOL Loader Card,"  which was a one-card binary loader used to bootstrap object programs that were written to punched cards by the ESPOL compiler. This bootstrap is described in a couple of Burroughs documents, notably the B5500 Operation Manual and the B5500 MCP Reference Manual, both available as scanned PDFs from bitsavers.org.

I gave the reader those references, but he came back shortly thereafter with some questions on how the loader worked. This was something I had been curious about myself, so decided to dig into it and try to completely understand that program. It took me a few evenings, but I was finally able to satisfy myself that I understood how this loader worked, and replied back to the reader with my findings.

This turned out to be a very interesting exercise, and I thought that others might also find it interesting, so I have reworked that original correspondence into this blog post. This is a highly technical subject, to say the least, so those not used to low-level bit twiddling may want to shield their eyes. Those who want to follow the workings of the individual instructions can find useful descriptions in the B5500 Reference Manual, and truely gruesome ones in the B5281 Processor Training Manual.

The loader is pretty simple, really, but some of the coding is crafty, and it turns out that you have to know something about how the ESPOL compiler generates card-load program decks. I had seen (but not tried to understand) the code that does that generation while working on porting ESPOL to the ESPOLXEM cross-compiler, so that prior experience turned out to be highly advantageous for this exercise.

Before we begin, there are a few significant things to note about the B5500 architecture when looking at a program like the ESPOL Loader:
  • The B5500 could load programs from either punched cards or drum (and later, the Head-per-Track disk). The mode of loading was determined by the Card Load Select switch on the operator's console. When depressed, this switch caused the system to load from cards; in its normal setting, the system loaded from drum or disk. The choice of drum or disk was hardwired in the I/O Units (channels).
  • When the Load button was pressed on the console, most system registers were cleared to zero, particularly A, B, F, L, R, and S in the processor. The exception was C, the instruction word address, which was set to @20. I'll use the ESPOL notation of "@" to indicate octal values. The other major registers are:
    • A -- the first (top) top-of-stack register.
    • B -- the second top-of-stack register.
    • S -- the top-of-stack memory address register, pointing to the word in the stack below the one in the B register. In Character Mode, the S register was repurposed as the destination address register, with the M register being used as the source address register.
    • F -- the stack frame register, which points to the base of the current procedure stack frame (activation record) in the stack.
    • R -- the PRT (Program Reference Table) address register. This pointed to the global environment for a program. In Algol, that would be the outer block; in COBOL, the Working-Storage Section. The R register had only nine bits, with the low-order six bits of the 15-bit PRT address always taken as zero. In Character Mode, R was repurposed as the TALLY register.
    • L -- the instruction syllable register. This was a two-bit register that identified the next syllable within a word to be executed in the code stream. That word was held in the P register. Words were loaded into the P register based on the address in the C register.
  • Each of the four possible I/O Units had separate I/O Finish interrupt vector addresses (@27-@32) and addresses to store a result descriptor word (@14-@17).
  • Subroutine calls worked as follows: 
    1. The program executed a Mark Stack (MKS) instruction which saved a few processor registers (notably R and F) in a Mark Stack Control Word (MSCW) that was pushed into the stack. At completion, F pointed to the MSCW.
    2. Any parameters for the subroutine were typically evaluated and pushed into the stack at this point.
    3. The program then executed an Operand Call (OPDC) or Descriptor Call (DESC) that referenced a Program Descriptor word. The PD principally indicated the address of the subroutine entry point, the code segment's presence in memory, and the mode: Character or Word. The call constructed a Return Control Word (RCW), containing the necessary state not already recorded in the MSCW, including the return address and current F register setting (which was pointing to the MSCW) and pushed that into the stack. At completion, F now pointed to the RCW, and the syllable branched to the entry address.
    4. Parameters were addressed as negative offsets from F, accessing words in the stack below the RCW.
    5. Upon exit, the processor restored the register state from the RCW, including the return address and F register setting. From the MSCW now pointed to by the restored value of F, the exit syllable moved F to S (the top of stack address register), restored the rest of the state from the MSCW and branched to the return address. By restoring the value of S, the stack used by the subroutine was automatically cut back to its original position.
  • The B5500 had a typical set of bitmask instructions (AND, OR), but instead of XOR had EQV, the complement of XOR. It did not, however, have shift or rotate instructions. Instead it had field insert operators, which in Word Mode were closely related to more generalized features in Character Mode. The idea was that the A and B registers were "dialed" to their respective starting bit numbers and then a transfer instruction would move a specified number of bits from A to B, leaving the rest of the bits in B unmolested and setting A to empty (i.e., deleting it from the stack). In Algol and ESPOL, these were used to implement "partial word" constructs, such as X:=Y.[3:17] and X:=Y&Z[24:42:6]. Dial A (DIA) and Dial B (DIB) set the character (G/K) and bit (H/V) number registers directly from the high order six bits of their opcode, so DIA @43 indicated character 4 bit 3, or word-bit number 27 (4*6+3). The count field in the Transfer Bits (TRB) instruction was just the binary number of bits.
  • Bits and characters were numbered starting with zero at the high-order position in a word as far as the hardware operators, Algol, and ESPOL were concerned. Thus, the bits were numbered 0-47 and the characters 0-7 moving from left to right in a word, with everything being big-endian. The circuit designers, however, decided to number the bits in their designs and documentation with 48 at the high end and 1 at the low end. A lot of the hardware documentation (particularly the B5500 Handbook and B5281 Processor Training Manual cited above) uses that latter convention, and sometimes it's not too clear which convention is in use, so watch out for that. (As an aside, in the B6500/6700, the two sides got together and agreed on a common, but still different convention, with the high-order bit as 47 and the low-order bit as 0. So after a lifetime working on that latter architecture, I'm now totally confused when working with the B5500).
  • While an instruction is executing, the C and L registers point to the next instruction in memory. Most branch instructions operate using a relative number of syllables or words from the current location, so those branch offsets are relative to the address of the syllable after the branch instruction.
  • You can think of the B register as the accumulator. Generally, A operated on B, leaving the result in B, although there are a few exceptions to this (such as INX). Typically A gets set to empty (deleted from the stack) at the end of dyadic operations. The hardware automatically adjusts the stack pointer (S register) and moves data between memory and the A and B registers as necessary.
  • Truth for conditional tests is determined by the low-order bit in a word, however you number it, 0=false and 1=true. The rest of the bits in the word are ignored. The bitmask operators worked on the low-order 47 bits of the word, leaving the high-order (flag) bit unchanged in the result (B register). The conditional branch operators branch on false, which is what you want in order to implement an IF statement -- a false condition should branch around the THEN part.
  • The internal character codes for the decimal digits were the same as their binary values. Thus, "0"=@00, "1"=@01, "2"=@02, ... "7"=@07, "8"=@10, and "9"=@11.
With that introduction, the following table presents the ESPOL Loader, syllable by syllable. Code addresses are referred to as ww:s, where ww is the absolute word address, and s is the syllable number (0-3) within the word. The hardware reads one binary card (80 columns = 960 bits = 20 words) starting at location @20 in memory. Since the Load button initializes the C register to @20 and the L register to 0, that is where execution begins after the card is successfully read. The Loader runs entirely in Control State.

Addr Syl Mnemonic Description
20:0 0104 LITC @21 Entry point for the hardware load -- Literal Call: push a literal @21 into the stack (A register) as the absolute address of the card read descriptor
20:1 4411 IIO Initiate the I/O by storing the A register at address @10, signalling Central Control, and setting A to empty. The I/O Unit picks up the descriptor from that stored address and initiates the I/O asynchronously
20:2 0020 LITC 4 Push the number of syllables to branch
20:3 4231 BFW Branch forward 4 syllables to 22:0
21
(data) Card read I/O descriptor: card reader #1 (CRA), 10 words (80 characters), alpha mode, address @44. Note that while the hardware load reads one binary card, the ESPOL Loader reads cards in alpha mode, i.e., the way they would normally be encoded by a keypunch machine.
22:0 4455 DIA @44 Dial A to bit 28 (numbered 20 in the Handbook word diagrams)
22:1 0211 ITI Interrogate interrupts and branch to the vector address for the highest-priority one outstanding, if any. If no interrupts are outstanding, fall through to the next syllable.
22:2 0020 LITC 4 Push branch offset
22:3 4131 BBW Branch back to 22:0 (loop until interrupt occurs)
23
(data) Character mode program descriptor for the subroutine (starting in the next syllable) that will be called from 43:1, present in memory, character mode, F=0, C=@24
24:0 0453 RSA Entry point of character mode subroutine: recall source address at F-4 (four words below the Return Control Word, or RCW, and in this case, three words below the Mark Stack Control Word, or MSCW) into the M and G registers
24:1 0304 RDA Recall destination address at F-3 (two words below the MSCW) into S and K
24:2 0243 CRF Call the repeat field (transfer count) at F-2 (one word below the MSCW)
24:3 0005 TRW Transfer the designated number of words from source to destination addresses
25:0 0000 EXC Exit character mode, in this case returning to 43:2
25:1 0065 TRB 0 Transfer zero bits from A to B (effectively a no-op, except that it sets A empty) [I don't think this and the next two instructions are normally executed, but @24 is the interrupt vector location for Keyboard Request from the SPO, and @23 is the location for I/O Busy, so the branch at 25:3 is probably just a trap for spurious interrupts. Undefined opcodes are no-ops on the B5500, so it's likely that a vector to one of the words above would eventually fall through to the next syllable at 25:2]
25:2 0100 LITC @20 Push branch offset
25:3 4131 BBW Branch back to 22:0 to continue looping for interrupts
26:0 0110 LITC @22 Push branch offset [Don't know of an interrupt that vectors here, but this is also probably a trap for an unexpected vector]
26:1 4131 BBW Branch to 22:0 to continue looping for interrupts
26:2 0055 DIA 0 (traditionally used as a no-op)
26:3 0055 DIA 0 another no-op
27:0 0000 LITC 0 Interrupt vector location for I/O Unit #1 finished: push a zero
27:1 0062 OPDC @14 Operand call: Push a copy of the result descriptor (RD) for I/O Unit #1 at R+@14 = @14 absolute. The R register is still zero after being initialized by the Load button
27:2 0050 LITC @12 Push branch offset
27:3 4231 BFW Branch to 32:2 to handle interrupt
30:0 0000 LITC 0 Interrupt vector location for I/O Unit #2 finished: push a zero
30:1 0066 OPDC @15 Operand call: Push a copy of the result descriptor (RD) for I/O Unit #2 at R+@15 = @15 absolute
30:2 0030 LITC 6 Push branch offset
30:3 4231 BFW Branch to 32:2 to handle interrupt
31:0 0000 LITC 0 Interrupt vector location for I/O Unit #3 finished: push a zero
31:1 0072 OPDC @16 Operand call: Push a copy of the result descriptor (RD) for I/O Unit #3 at R+@16 = @16 absolute
31:2 0010 LITC 2 Push branch offset
31:3 4231 BFW Branch to 32:2 to handle interrupt
32:0 0000 LITC 0 Interrupt vector location for I/O Unit #4 finished: push a zero
32:1 0076 OPDC @17 Operand call: Push a copy of the result descriptor (RD) for I/O Unit #4 at R+@17 = @17 absolute into the stack
32:2 7561 DIB @75 Common point for handling an I/O finish interrupt -- Dial B to bit 47 (the low-order bit)
32:3 0165 TRB 1 Transfer bit 28 from the result descriptor in A (see the DIA at 22:0) to bit 47 in B (which currently holds the zero pushed at each of the interrupt vector entry points) and mark A empty to delete the RD from the stack.
33:0 0010 LITC 2 Push branch offset
33:1 0231 BFC Branch Forward Conditional around the endless loop below to 34:0 if the low-order bit in B is 0 (false), i.e., if bit 28 (20 in the Handbook) in the RD was false, indicating no read-check error occurred on the card read. Since we are reading in alpha mode, false means there were no invalid punches on the card that couldn't be translated to internal character codes
33:2 0010 LITC 2 Push branch offset
33:3 4131 BBW Branch back to 33:2 -- an endless loop. The reason for the loop is that there was no way to halt the processor in control state. We come to 33:2 if there was a read-check error, so in that case the loader just gives up and spins here until someone manually halts the system.
34:0 0004 LITC 1 To here if a successful card read -- push a 1 as an index for the DESC next. This is the beginning of code that builds parameters in the stack for the character-mode procedure that will be called at 43:1.
34:1 0107 DESC @21 Descriptor call: push a copy of the word at R+@21 (@21 absolute) into the stack. Since that word looks like a present data descriptor with a non-zero length field, things get really interesting. The descriptor must be indexed by the value just below it in the stack (the 1 just pushed there by the LITC). The DESC syllable does that automatically First, the index is checked against the length field, but 0 <= 1 < length, so we're okay; otherwise we would get an Invalid Index interrupt set (if we were in Normal State, that is). Next, the base address in [33:15] of the descriptor in B is indexed by adding (modulo 15 bits) the 1 in A to it. The length field [8:10] in the word is set to zero, indicating this copy descriptor points to a specific word address instead of an array of words. This is effectively an absolute address, and it's left in the stack as the operation's result. Note that it points to the second word in the 10-word card read buffer, so I'll refer to it as BUF[1].
Note: This word in the stack becomes the parameter for the source address at F-4 used by the character-mode procedure called at 43:1.
34:2 2025 DUP Duplicate the indexed descriptor in the stack.
34:3 0044 LITC @11 Push @11 (9) into the stack as an index for the OPDC, next
35:0 0106 OPDC @21 Operand call: push another copy of the word at R+@21 into the stack. Since it still looks like a present data descriptor with a non-zero length field, OPDC indexes it by the 9 just below it. Instead of leaving the address in the stack, though, OPDC fetches the word at the indexed address (@44+@11 = @55, the last word in the 10-word buffer) and replaces the descriptor with a copy of that word's contents (I'll refer to this value as BUF[9]). Thus OPDC works somewhat like "load" on other machines and DESC works somewhat like "load address".
35:1 2025 DUP Duplicate the value of BUF[9]
35:2 3355 DIA @33 Dial A to bit 21
35:3 4061 DIB @40 Dial B to bit 24
36:0 2565 TRB @25 Transfer 21 bits from A (the dup of BUF[9]) to B (the original copy of BUF[9]) and delete the dup.
36:1 2025 DUP Duplicate the word just updated
36:2 2265 TRB @22 Transfer 18 bits from the dup word to the updated original word and delete the dup. What these next DUP/TRB sequences do is convert 6-bit character codes to 3-bit octal digits to form an absolute memory address in the low-order end of the word. See below for details.
36:3 2025 DUP Duplicate the newly updated word again
37:0 1765 TRB @17 Transfer 15 bits from the dup word to the updated original word and delete the dup.
37:1 2025 DUP Duplicate the newly updated word again
37:2 1465 TRB @14 Transfer 12 bits from the dup word to the updated original word and delete the dup. This is the last of the octal-to-binary conversion
37:3 5355 DIA @53 Dial A to bit 33 (start of the address field in the updated copy of BUF[9])
40:0 5361 DIB @53 Dial B to bit 33 (start of the address field in the dup of the indexed descriptor pointing to BUF[1])
40:1 1765 TRB @17 Transfer the 15-bit address field from the updated copy of BUF[9] to the dup of the indexed descriptor referencing BUF[1] and delete the updated copy of BUF[9]. The resulting descriptor in the top of stack is now pointing to a memory address that was constructed from fields in BUF[9].
Note: This word in the stack becomes the parameter for the destination address at F-3 used by the character-mode procedure.
40:2 0000 LITC 0 Push a zero into the stack to begin constructing the third parameter, the transfer count.
40:3 0044 LITC @11 Push another 9 into the stack to be used as an index
41:0 0106 OPDC @21 Index the I/O descriptor again and load a copy of BUF[9] into the stack
41:1 2025 DUP Duplicate the copy of BUF[9]
41:2 1555 DIA @15 Dial A to bit 11
41:3 2261 DIB @22 Dial B to bit 14
42:0 0165 TRB 1 Transfer one bit, i.e., B:=B&A[14:11:1] (or equivalently, B.[14:1]:=A.[11:1]), and delete the dup copy of BUF[9]. This is another character-to-octal conversion. There is a two-character count of words on the card in the second and third characters of BUF[9]. The value can't be more than eight, since that's the maximum number of words that fit on a card (see the discussion on ESPOL below), so only the low-order bit of the high-order character can be significant (8=@10).
42:1 2255 DIA @22 Dial A to bit 14
42:2 7261 DIB @72 Dial B to bit 44
42:3 0465 TRB 4 Transfer 4 bits from bit 14 in A (from the last TRB result) to the low-order 4 bits of B (the zero pushed at 40:2).
Note: This word in the stack becomes the the parameter for the transfer count at F-2 used by the character-mode procedure.
43:0 0441 MKS Construct and push a Mark Stack Control Word (MSCW) in preparation for calling a procedure. On entry to the procedure, this word will be at F-1.
43:1 0116 OPDC @23 Construct and push a Return Control Word (RCW) with a return address of 43:2 and call the Character Mode procedure using the program descriptor at R+@23 (@23 absolute). On entry to the procedure, F points to the RCW. Note that the behavior of OPDC is based primarily on the type of word it finds at the relative address, not anything in the opcode it self -- a very B5500 thing to do. Also notice that there is no code to save or restore register state on entering and exiting the subroutine -- all of that is completely automatic.

That procedure (entry point at 24:0) transfers a number of words from a source address to a destination address. The count of words is in the stack just below the MSCW, the destination address below that, and the source address below that. These will be addressed using negative offsets from F, which points to the RCW pushed by the OPDC procedure-call syllable. Typically parameters are pushed into the stack between the MSCW and RCW, but character-mode routines are unusual in that they sometimes address the stack directly below their stack frame.
43:2 0500 LITC @120 Push the branch offset
43:3 4131 BBW Branch to 20:0 to read another card

If you were watching the stack closely, you would have noticed that the parameters for the character-mode procedure are below the MSCW, so they don't get cut back when that procedure exits, as normally happens with parameters that are pushed between the MSCW and RCW. So does the stack just grow? That's problematic, as it starts at address 0, so we'd process only about five cards before the stack ran into our loader routine at @20. This had me puzzled until I recalled that servicing an interrupt (which is what the ITI syllable does, even in control state) unconditionally sets S to @100. Thus, every time we read a card, the top of stack gets reset to address @100.

The reason for the weird sequence of DUPs and TRBs starting at 35:1 has to do with the format of a card load deck. I noticed the code to create this format when I was working on ESPOLXEM, but didn't pay much attention to it. When puzzling over what was going on with BUF[9], that piece of the compiler popped into my mind, and it provided a major clue.

The hardware load function reads this loader card in binary mode, which can hold up to 20 words. The deck produced by ESPOL is in alpha mode, however, and contains a maximum of eight words per card. The first word (8 characters) of the card are zero and are ignored by the loader (I suspect these could have been used to hold sequence numbers or something similar). The payload is in the next eight words. Word 9 (zero relative) holds a control word, as the relevant snippit from ESPOL compiler below shows, building the value of ELBAT[9]:
09432000  BEGIN ELBAT[0]~0; I~16;
09433000        DO BEGIN MOVE(8,SAVINFO[I.LINKR,I.LINKC],ELBAT[1]);
09434000            ELBAT[9]~B2D(I+96)&1[11:47:1]&(I+96)[23:35:1];
09435000                 WRITE(DECK,10,ELBAT[*]);
09436000           END UNTIL I~I+8}SAVNDX;
09437000        FILL ELBAT[*] WITH 0,
09438000             OCT7500000000000012,
09439000             OCT0004535530611765,
09440000             OCT7006000404210435,
09441000             OCT7700000000000015,
09442000             OCT0253010477527705,
09443000             OCT0051000004410046,
09444000             OCT0441070001000062,
09445000             OCT0040413100000000,
09446000             OCT0001000000000101;
09447000        WRITE(DECK,10,ELBAT[*]);
09447010       ELBAT[0] ~0&REAL(DECKTOG)[1:19:17];
09447020       FOR I ~ 0 STEP 1 UNTIL Q DO
09447030            BEGIN K ~ STACKHEAD[I].[23:15];
09447040                 M ~ STACKHEAD[I].[38:10];
09447050                 FOR J ~ 0 STEP 8 UNTIL M DO BEGIN
09447060                      MOVE(8,INFO[(J+K).LINKR,(J+K).LINKC],
09447070                            ELBAT [1]);
09447080                      ELBAT[9] ~ B2D(J)&"310"[1:31:17];
09447090                      WRITE(DECK,10,ELBAT[*]) END;
09447100            END;
09448000  END END END PROGRAM;
This occurs at the very end of code generation for the program. The DO loop in the first five lines writes out the code. Word 9 looks like this:
  • Bits [24:24] (the low-order 24) are the address+96 where this data should be loaded, as four octal characters. The six-bit character codes for the numeric digits are the same as their binary codes (e.g., "0" = @00, "4" = @04, etc.), so to convert from character codes to binary, all we need to do is remove the high-order 0 digit in each character and pack the low-order digits together.
  • Bit [11:1] is set to 1. This appears to be setting the number of words on the card to 8 in the second and third characters of the word, but expressed in octal like the addresses above. "10" = @0100, so just one bit needs to be set.
  • Bit [23:1] contains the value of (address+96).[35:1]. This appears to be the 13th bit of the biased address, so the full address (expressed in characters as octal) is probably in [18:30] (i.e., the low-order five characters of the word).
The address is probably biased by 96 (@140) to allow room for the control-state stack that starts at @100.

One of the smallest ESPOL programs we have is KERNEL, a bootstrap for the MCP that was stored on disk. I've enclosed at the end an octal dump of the DECK output from a compile of that program using ESPOLXEM, so you can see what the data looks like. You can compare this data to the code listing of the program at bitsavers.org.

What the TRBs starting at 36:0 do is compress the leading zero octades out of the character codes of the address to form a binary number. For example, if the address is @03160, this would be represented on the card as "03160" or @0003010600, and the progression of the DUP/TRB sequences would produce the following successive results (only the low-order 30 bits of the word are shown):

0003010600 Initially, as read from word [9] of the card: "03160" in alpha
0000301060 After TRB @25 at 36:0
0000030160 After TRB @22 at 36:2
0000003160 After TRB @17 at 37:0
0000003160 After TRB @14 at 37:2 (the high order character is 0, so there's no apparent change).

The low-order 15 bits of this result are eventually used as the destination address by the character-mode move procedure. That relocates the data from BUF[1] through BUF[8] at memory locations @45-@54 to the address specified by BUF[9] for the number of words also specified by BUF[9].

It helps to draw little pictures of the stack as you are studying code like this. After 40 years of working with stack machines, I still need to do that for all but the simplest cases, and I certainly did for this one.

The one remaining piece of this puzzle is, how does this program terminate? Thus far, we've seen that a read-check exception will stop it cold, but hopefully that doesn't happen. All other I/O exceptions appear to be ignored, so for example, if the card reader goes not ready (jams or is out of cards), the RD will probably contain a "not ready" exception. The loader ignores that and just keeps on truckin', moving the data from the card read buffer (which hasn't changed) to the appropriate destination address memory (which also hasn't changed), until finally the reader starts working again and the loader sees new data. It's crude, but since the processor does not have any way to idle, it's effective.

I think the answer to termination lies in what the ESPOL compiler outputs after the generated code for the program -- an extra card, as shown starting at line 09437000 in ESPOLXEM. The FILL statement just moves literal values into an array, and the WRITE statement outputs it to the card deck. Those octal constants are interesting -- they are the exactly code for the ESPOL Transfer Card, another one-card loader documented in the MCP Operation and MCP Reference manuals cited above. Note in particular the load address in the last word of the array (and the last word of the octal dump below) -- 8 words at address @11. That means the data on that Transfer Card will overlay locations @11-@20 -- that last being the first word of the code for the ESPOL loader, which will now contain a branch to 16:2.

Thus, after the ESPOL Transfer Card is read from the deck and loaded at @11, the ESPOL Loader blithely branches back to 20:0 to initiate the I/O for the next card, only to find the IIO syllable isn't there anymore, having been replaced by a branch into the Transfer Card's code. Very sneaky.

The Transfer Card program is equally sneaky. It calls two procedures, a word-mode one that constructs an MSCW and stores it at address 1 absolute, and also arranges for F to point to address 1 when the routine exits (this is required for proper stack linkage in the just-loaded program). The second is a character-mode procedure that slides 3969 (63*63) words from address @160 to @20 -- a difference of @140 (96) words, matching the bias the ESPOL compiler added to the addresses in BUF[9]. That second procedure is called from address 17:3, so it returns to address 20:0 -- which was just overlaid by the first word of the 3969-word sliding move. Effectively that second procedure exits into the entry point of the program that had just been loaded and relocated downwards in memory.

The technique of having initialization code exit into the entry point of whatever it's initialized is a common one in the B5500, and continues to be used in the Unisys MCP today.

The remainder of the code emitted by the ESPOL compiler after writing out the ESPOL Transfer Card appears to be some sort of stack initialization code, but it apparently isn't effective in the case of KERNEL, so I can't be sure just what is being output there, or (since it's after the Transfer Card) how it would get loaded.

Finally, here is the octal dump of the DECK output for the KERNEL program. Each line of text represents one 10-word card image:

0000000000000000 0740623100000000 0000000000000000 0211101607302231 0004613100000000 0000000000000000 0014613100000000 0020613100000000 0060202102350000 0001000000010600 
0000000000000000 0064202102350000 0070202102350000 0074202102350000 0000000000000000 0050613100000000 0000000000000000 0060613100000000 0064613100000000 0001000000010700 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000020000 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000020100 
0000000000000000 0000000000000000 0534623100000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000020200 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000020300 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000020400 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000020500 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000020600 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000020700 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000030000 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000030100 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000030200 
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000030300 
0000000000000000 5000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 7660000000000022 7560000000000371 7560000000000400 0001000000030400 
0000000000000000 0400001421410230 1003014102301003 0141202111120055 0101102510211003 0141000010250421 0211023010030141 2021111600550225 0024223102301003 0001000000030500 
0000000000000000 0141000010250421 0044613100550055 0000000000010000 0000000000070000 0004101441211020 2021062001411003 0141777462551261 1265101004210204 0001000000030600 
0000000000000000 1003014110102021 5355304510250421 0204100301412021 1003014102001025 0421021010030141 0064102504210441 1642005502041003 0141202154251032 0001000000030700 
0000000000000000 0000442504740231 0224100301410004 1012045510451025 0421020410030141 2021100301411646 0055022410030141 2021745550610265 1025042104411652 0001000000040000 
0000000000000000 0055020410030141 2021542510320051 0204100301412021 1003014116560055 0224100301412021 7455506102651025 0421044116520055 0204100301412021 0001000000040100 
0000000000000000 5425103200510210 1003014102101003 0141202102241003 0141202100240401 0101102504210204 1003014120211003 0141000010250421 0220100301410441 0001000000040200 
0000000000000000 0204100301412021 1662005501411032 1025042102201003 0141202100004425 0024213100041003 0141001010121025 0421021410030141 0004101210250421 0001000000040300 
0000000000000000 0210100301412021 1012100304211002 0004100301412021 4125004022310230 1003014116660055 1025042102341003 0141167200551025 0421024010030141 0001000000040400 
0000000000000000 1676005510250421 0441170200551032 0051000000140131 0200100301410444 1025042104411706 0055023010030141 2021021510360200 1003014100501025 0001000000040500 
0000000000000000 0421044117120055 0230100301412021 0215103602001003 0141000010250421 0441171600550230 1003014120210215 1036023010030141 0230100301412021 0001000000040600 
0000000000000000 0200010110250421 0064100301411722 0055102504210070 1003014117260055 1025042100741003 0141173200551025 0421100200100301 1003042110020224 0001000000040700 
0000000000000000 1003014120217455 2461026502141003 0141202174552261 0265100304210004 0014214100000010 2141006410030141 4231000401042231 0024413100550055 0001000000050000 
0000000000000000 0140004000000000 0000000000004060 0140000100000000 0000000000006060 0140000040100000 3145652143312460 2124245125626260 2646516044234737 0001000000050100 
0000000000000000 0740000000000046 0140000047704235 0140000047700473 0140000041200017 0441023201004441 0253010453527705 3705005101002411 0000102642314006 0001000000050200 
0000000000000000 0235000000000000 0000700744110220 1003014104411022 1025042102201003 0141202141552345 4004042140060024 0415000044250140 0131400602350000 0001000000050300 
0000000000000000 0441100202001003 0141202101017006 5355304544410222 0104106601007006 1003014120210555 1045003402317006 0060715503610565 7004042102201003 0001000000050400 
0000000000000000 0141044170061032 1025042102201003 0141202100004425 0100013104350000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0001000000050500 
0000000000000000 7500000000000012 0004535530611765 7006000404210435 7700000000000015 0253010477527705 0051000004410046 0441070001000062 0040413100000000 0001000000000101