IBM 704 Punched Card Reading Part 5

Now I will complete this look into the IBM 704 and the UA-SAP assembler by presenting the main body of the RDBCD subroutine. The subroutine C1 which was presented in the last part is the heart of the card reading code. It is responsible for taking two words whose bits represent an accumulated logical sum of card rows (that is, a set bit means that at least one hole was punched in the corresponding column of the card) and spreading them out into six bit fields, so that the 72 bits of the accumulated logical sum are spread out across a 12 word line buffer. By repeated addition of these spread out field, the six bit encodings of the input character will be contructed. The main task of RDBCD, which calls C1 repeatedly, is to make sure the correct left and right logical sums provided to C1, and to provide callback routines that manipulate the spread-out bit masks produced by C1 in such a way as to produce the desired character values when they are summed arithmetically. Let’s begin:

 RCD   RCD                RESTART AFTER ERROR                           SAP10003
       LXD B2,1           X                                             SAP10004
       LXD B3,2           X                                             SAP10005

These first three lines are a re-entry point provided to allow restarting RDBCD after a card error, such as failure to feed a card, a jammed card, etc. As we will see, when such an error occurs, the program halts to allow the operator to fix whatever problem exists. After correcting the problem, when the operator restarts the program, the computer will jump to the instruction labeled RCD above. It selected the card reader for input, reloads index registers 1 and 2 with saved values, and then drops into the main body of RDBCD.

 RDBCD CPY L              COPY 9 LEFT ROW                               SAP10006
       TXL B1             X                                             SAP10007
       TRA 2,4            END OF FILE EXIT                              SAP10008

This is the real beginning of the card reading subroutine of UA-SAP. It is assumed that the card reader has already been selected for input, so the first instruction is CPY L, which attempts to retrieve the first word of data from the card readerand store it in variable L. If the CPY succeeds, control is returned to the first instruction after the CPY instruction, which is just an unconditional branch to the rest of the subroutine. If the CPY fails because there are no more cards to read (an end of file condition), then control returns to the second instruction after the copy, which just immediately returns to the caller of RDBCD. It returns to the second word after the TSX that was used to call RDBCD. The calling code will arrange for an instruction to be there that handles the end of file condition. The first word after the subroutine call is an argument word used by subroutine C1. The third word after the subroutine call is the return location that indicates the successful reading of one card into the supplied 12-word line buffer.

 B1    STQ LS             SET LEFT SUM                                  SAP10009
       SXD B2,1           SAVE INDEX REGISTERS                          SAP10010
       SXD B3,2           X                                             SAP10011
       LXD B4,1           SET DIGIT ROW COUNT                           SAP10012
       CPY R              COPY 9 RIGHT ROW AND                          SAP10013
       STQ RS             SET RIGHT SUM                                 SAP10014

The word just read from the card reader is the left half of row 9 of a card. It was read into variable L, but it is also stored in variable LS, which is an input to subroutine C1. Index registers 1 and 2 are saved, as they will be used in RDBCD. They will be restored before RDBCD returns to its caller. Then the decrement field of the instruction labeled B4, which contains the number 8, is loaded into index register 1, which will be used to countdown the rows as they are read in. Lastly, the right half of row 9 is read from the card reader with the CPY R instruction and also stored in variable RS, which is another input to C1. Row 9 has thus been stored in both the variable pairs L/R and LS/RS.

       TSX C1,2           ENTER CONVERSION LOOP                         SAP10015
 B2    TXL B5             LEAVE CONVERSION LOOP                         SAP10016
       ALS 1                                                            SAP10017
 B3    TXL C2             INITIALIZE BCD RECORD                         SAP10018

After reading both words for row 9 from the card reader, subroutine C1 is called to spread out the bits into the line buffer (the address of which is provided by the caller of RDBCD). the line labled B2 is the normal return of C1. The next two lines are the callback routine. This callback routine shifts A left by one bit before jumping to label C2, which is the entry to subroutine C1 that just stores A into the appropriate word of the line buffer. This serves to initialize the line buffer. Normally, we would expect the variables LS and RS to contains an accumulated logical sum (OR) of all of the cards rows. By repeatedly adding this accumulated logical sum to itself in each character position of the line buffer, we can generate the digit and character encodings. Because row 8 will be handled specially, the callback here goes ahead and multiplies the bits coming out of the row 9 by 2, to account for the accumulation of these row 9 bits for both row 9 and row 8.

Note that it is assumed that the CPY instruction for the right half must succeed. There is no provision made for failure. That is, there are no instructions in the second and third words after the CPY that could handle an error result.

 B5    CPY 8L             COPY 8 ROW AND                                SAP10019
       STQ LS             USE AS SUM                                    SAP10020
       CPY 8R             X                                             SAP10021
       STQ RS             X                                             SAP10022
       TSX C1,2           ENTER CONVERSION LOOP                         SAP10023
 B4    TXL B6,0,8         LEAVE CONVERSION LOOP                         SAP10024
       ALS 3              ADD 8 TIMES 8 ROW                             SAP10025
       TXL C3             X                                             SAP10026

CPY instructions for row 8 are executed. The data words are read from the card reader into the variable pair 8L/8R and also stored in LS and RS for use by subroutine C1. Subroutine C1 is called. The callback routine immediately adds the full value of the 8 row to the line buffer by shifting A to the left three positions and then branching to the instruction labeled C3 (so each set bit in the 8 row contributes the value 8 to the corresponding character position).

 B6    CAL L              USE 9 ROW AS SUM                              SAP10027
       SLW LS             X                                             SAP10028
       CAL R              X                                             SAP10029
       SLW RS             X                                             SAP10030

Having taken care of the special 8 row, the 9 row is retrieved and placed in the variable pair LS/RS to begin building the cumulative logical sum of the digit rows.

 B13   TXL B7,1,1         TEST FOR ZERO ROW                             SAP10031
 B14   CPY L              COPY LEFT ROW AND                             SAP10032
       TXL B8             TEST FOR END OF RECORD                        SAP10033
 B17   HTR RCD            ERROR                                         SAP10034
       TXL B9             END OF RECORD                                 SAP10035

The value of index register 1 is compared to 1. If equal (or less), then row 1 has already been processed so this must be row 0. Index register 1 will also contain the value 1 if we are reading the other zone rows (rows 11 and 12), but in that case, the instruction labeled B13 will never be executed. Instead control will jump directly to B14 for those rows, as we will see below.

Assuming this is not row 0, the left word of the row is retrieved into variable L by the CPY instruction. If the CPY is successful, the unconditional branch to B8 is taken. If the card has already been completely read, there is an end of record condition and control passes to the instruction in the third word after the CPY, which is an unconditional branch to B9. Any other error will transfer control to the instruction at B17. That instruction is HTR, which halts the computer, after placing its operand in the program counter. The computer operator should resolve the error and restart the computer. Upon restart, control will be tranferred to the restart code labeled RCD, as explained above.

 B8    CAL L              TEST LEFT ROW FOR                             SAP10036
       ANA LS             ILLEGAL DOUBLE PUNCH                          SAP10037
       TNZ B17            X                                             SAP10038
 B10   CAL L              FORM LOGICAL SUM                              SAP10039
       ORS LS             OF LEFT ROWS                                  SAP10040

Now the code checks for an illegal double punch by ANDing the (left half of the) row just read with the accumulated logical sum so far. If the result is not zero, it means that two rows were punched in some column, which is illegal if one of those rows is not the row 8. This is why row 8 was handled specially above and the row 8 data was not included in the logical sum. In case of an illegal double punch, the branch to B17 is taken, which is the code above that halts the computer to allow the operator to resolve an error. If there is no illegal double punch, the logical sum is updated by ORing the data from the current row into the logical sum. The ORS instruction used here stores the result of the OR operation into the operand location.

       CPY R              COPY RIGHT ROW AND                            SAP10041
       CAL R              TEST FOR ILLEGAL                              SAP10042
       ANA RS             DOUBLE PUNCH                                  SAP10043
       TNZ B17            X                                             SAP10044
 B11   CAL R              FORM LOGICAL SUM OF                           SAP10045
       ORS RS             RIGHT ROWS                                    SAP10046

This code for the right half row is almost identical to that above for the left half row. The only difference is that it is assumed that no error condition can occur when executing the CPY for the right half.

       TNX B12,1,1        TEST FOR ZONE ROWS                            SAP10047
       TSX C1,2           ENTER CONVERSION LOOP                         SAP10048
       TXL B13            LEAVE CONVERSION LOOP                         SAP10049
       TXL C3             ADD TO BCD RECORD                             SAP10050

A check is made to see if the current row is a zone row (row 11 or 12). This check is made by conparing the value of index register 1 to 1. Remember that the value is greater than one for the digit rows. If the current row is a zone row, the branch to B12 is taken. Otherwise, index register 1 is decremented and then subroutine C1 is called. Here, which is the code path used for rows 7 through 1, and 11 and 12, the callback routine does nothing except return to C3, which adds the bits in A to the corresponding characters in the line buffer.

 B7    CAL 8L             ADD 8 LEFT ROW TO                             SAP10051
       ORA LS             LEFT LOGICAL SUM                              SAP10052
       SLW LDS            X                                             SAP10053

This code, at label B7, is the destination of the branch above when the current row is row 0. Now, working on the left half, the data from row 8 is retrieved and ORDed into the accumulated logical sum and the result, the accumulated OR of all digit rows from 9 to 1, is stored in the variable LDS.

       CPY LZ             COPY ZERO LEFT AND                            SAP10054
       ANA LZ             FORM INDICATOR FOR                            SAP10055
       SLW LS             BOTH DIGIT AND ZERO                           SAP10056

Now the data for the left half of row 0 is read from the card reader into variable LZ. It is also ANDed with the left digit sum, LDS, which is still in A to form a word which contains bits corresponding to every column in the left side of the card in which there is both a punch in a digit row and a punch in the row 0. This is true for 11 different valid character, but not true for the digit zero. Columns containing only a zero punch, representing the digit zero, must be excluded here because the digit zero is encoded by the six bit sequence 000000, so bits corresponding to a punch in the row 0 without an additional punch in a digit row cannot be allowed to contribute to the value of any characters.

This logical product of row 0 with the accumulated logical sum of the digit rows is stored in LS, eliminating its previous value. Therefore, the accumulated logical sum is being restarted for the zone rows, with the initial value having only those bits set which correspond to columns which have both a zero punch and some digit row punch.

       CAL 8R             ADD 8 RIGHT ROW TO                            SAP10057
       ORA RS             RIGHT LOGICAL SUM                             SAP10058
       SLW RDS            X                                             SAP10059
       CPY RZ             COPY ZERO RIGHT AND                           SAP10060
       ANA RZ             FORM INDICATOR FOR                            SAP10061
       SLW RS             BOTH DIGIT AND ZERO                           SAP10062

Now the right half of row 0 is handled identically to the left half.

 B12   TSX C1,2           ENTER CONVERSION LOOP                         SAP10063
       TXL B14            LEAVE CONVERSION LOOP                         SAP10064
       ALS 4              SHIFT TO ZONE POSITION                        SAP10065
       TXL C3             X                                             SAP10066

This is the call to subroutine C1 for all zone rows, including row 0. It is very similar to that for the digit rows. The only difference is that the callback routine shifts the bits to the left by four positions so that they accumulate in the high two bits of each character in the line buffer. After subroutine C1 returns, an unconditional branch to B14 is taken, which as mentioned above is the first instruction in the sequence of code that reads and processes rows 11 and 12.

 B9    CAL LS             SAVE LEFT ZONE SUM                            SAP10067
       SLW L              X                                             SAP10068
       CAL LDS            FORM INDICATOR FOR                            SAP10069
       COM                ZERO AND X AND / OR Y                         SAP10070
       ANA LZ             IN LEFT ROWS                                  SAP10071
       ANS LS             X                                             SAP10072

Control is transferred to B9 at the end of a record. That is, after all rows on a card have been read. At this point, most characters in the line buffer are complete. However, there are a few special cases that need to be cleaned up. First, there are two characters composed only of double punches in zone rows. One is a formed by a 0-11 punch and the other is formed by a 0-12 punch. These characters cannot be printed by the IBM 704’s line printer, but they are used as special marks on tape. We would not expect to see them in assembly source code, but the subroutine RDBCD has been written for more general purposes and takes care to handle these characters properly.

These characters are recognized by ANDing the complement of the digit sum with row 0 and the logical sum of the zone rows. This yields a word in which bits set indicate that the corresponding column of the card was punched in row 0 and at least one other zone row, while not being punched in any digit row. Although the comments attached to the code above indicate that a 0-11-12 punch could be a valid character here, an 11-12 double punch would actually be caught as an illegal double punch in the code above between labels B8 and B11.

       CAL RS             SAVE RIGHT ZONE SUM                           SAP10073
       SLW R              X                                             SAP10074
       CAL RDS            FORM INDICATOR FOR                            SAP10075
       COM                ZERO AND X AND/OR Y                           SAP10076
       ANA RZ             IN RIGHT ROWS                                 SAP10077
       ANS RS             X                                             SAP10078

The equivalent bit product is produced for the right half of the card.

       TSX C1,2           ENTER CONVERSION LOOP                         SAP10079
       TXL B15            LEAVE CONVERSION LOOP                         SAP10080
       SLW T              MULTIPLY INDICATOR                            SAP10081
       ALS 2              BITS BY TEN                                   SAP10082
       ACL T              X                                             SAP10083
       ALS 1              X                                             SAP10084
       TXL C3             X                                             SAP10085

Subroutine C1 is then called with a callback routine that multiplies each set bit in A by 10, adding the result into the line buffer. This produces these two special characters which have encodings of 011010 and 101010 (binary). It does this by storing the original word in the temporary variable T, shifting to multiply it by four, adding the original value back in, and shifting again to multiply by two, producing the product 2 * (4 + 1) = 10.

 B15   CAL LDS            FORM INDICATOR FOR                            SAP10086
       SWT 2                                                            SAP10087
       WTD 1                                                            SAP10088
       ORA LZ             BLANK COLUMNS IN                              SAP10089
       ORA L              LEFT HALF OF CARD                             SAP10090
       COM                X                                             SAP10091
       SLW LS             X                                             SAP10092

The last thing to do is to properly handle blank columns, which should be interpreted as spaces in the input. Spaces are encoded as 110000, but at this point, all characters in the line buffer corresponding to blank columns have the value zero, because they never had any bits set in the words read from the card reader. The code above forms the logical sum of all rows of the card by ORing together the accumulated bits of rows 9 to 1 (LDS), row 0 (LZ), and rows 11 and 12 (L). The complement of this word yields bits set corresponding to every column that had no punch at all.

In the middle of doing this, the code prepares to write the line buffer to tape by first executing an SWT 2 instuction to see if the operator is requesting the card input to be written to tape. If sense switch 2 is up (closed), the instruction WTD 2 is executed, which selected tape drive 2 for writing. If sense switch 2 is down (open), the WTD instruction is skipped.

       CAL RDS            FORM INDICATOR FOR                            SAP10093
       ORA RZ             BLANK COLUMNS IN                              SAP10094
       ORA R              RIGHT HALF OF CARD                            SAP10095
       COM                X                                             SAP10096
       SLW RS             X                                             SAP10097

The indicator for blank columns is now formed for the right half of the card just as it was for the left half above.

       TSX C1,2           ENTER CONVERSION LOOP                         SAP10098
       TXL B16            LEAVE CONVERSION LOOP                         SAP10099
       SLW T              MULTIPLY INDICATOR                            SAP10100
       ALS 1              BITS BY 3 AND                                 SAP10101
       ACL T              SHIFT TO ZONE POSITION                        SAP10102
       ALS 4              X                                             SAP10103
       TXL C3             X                                             SAP10104

Subroutine C1 is called with a callback routine that produce 11 in the two high bits of every character in the line buffer that corresponds to a blank column on the card, thus producing the encoding of the space character as desired.

 B16   LXD B2,1           RESTORE INDEX REGISTERS                       SAP10105
       LXD B3,2           AND RETURN TO MAIN                            SAP10106
       TRA 3,4            PROGRAM                                       SAP10107

At this point, the input line buffer is complete. Index registers are restored and RDBCD returns to its caller at the third word after the subroutine call, which is the return for successful reading of one line without an end of file condition.

This concludes the presentation of the card reading subprogram of the assembler UA-SAP for the IBM 704 computer. Just as last time, I leave by giving the code unbroken in its original format:

 RCD   RCD                RESTART AFTER ERROR                           SAP10003
       LXD B2,1           X                                             SAP10004
       LXD B3,2           X                                             SAP10005
 RDBCD CPY L              COPY 9 LEFT ROW                               SAP10006
       TXL B1             X                                             SAP10007
       TRA 2,4            END OF FILE EXIT                              SAP10008
 B1    STQ LS             SET LEFT SUM                                  SAP10009
       SXD B2,1           SAVE INDEX REGISTERS                          SAP10010
       SXD B3,2           X                                             SAP10011
       LXD B4,1           SET DIGIT ROW COUNT                           SAP10012
       CPY R              COPY 9 RIGHT ROW AND                          SAP10013
       STQ RS             SET RIGHT SUM                                 SAP10014
       TSX C1,2           ENTER CONVERSION LOOP                         SAP10015
 B2    TXL B5             LEAVE CONVERSION LOOP                         SAP10016
       ALS 1                                                            SAP10017
 B3    TXL C2             INITIALIZE BCD RECORD                         SAP10018
 B5    CPY 8L             COPY 8 ROW AND                                SAP10019
       STQ LS             USE AS SUM                                    SAP10020
       CPY 8R             X                                             SAP10021
       STQ RS             X                                             SAP10022
       TSX C1,2           ENTER CONVERSION LOOP                         SAP10023
 B4    TXL B6,0,8         LEAVE CONVERSION LOOP                         SAP10024
       ALS 3              ADD 8 TIMES 8 ROW                             SAP10025
       TXL C3             X                                             SAP10026
 B6    CAL L              USE 9 ROW AS SUM                              SAP10027
       SLW LS             X                                             SAP10028
       CAL R              X                                             SAP10029
       SLW RS             X                                             SAP10030
 B13   TXL B7,1,1         TEST FOR ZERO ROW                             SAP10031
 B14   CPY L              COPY LEFT ROW AND                             SAP10032
       TXL B8             TEST FOR END OF RECORD                        SAP10033
 B17   HTR RCD            ERROR                                         SAP10034
       TXL B9             END OF RECORD                                 SAP10035
 B8    CAL L              TEST LEFT ROW FOR                             SAP10036
       ANA LS             ILLEGAL DOUBLE PUNCH                          SAP10037
       TNZ B17            X                                             SAP10038
 B10   CAL L              FORM LOGICAL SUM                              SAP10039
       ORS LS             OF LEFT ROWS                                  SAP10040
       CPY R              COPY RIGHT ROW AND                            SAP10041
       CAL R              TEST FOR ILLEGAL                              SAP10042
       ANA RS             DOUBLE PUNCH                                  SAP10043
       TNZ B17            X                                             SAP10044
 B11   CAL R              FORM LOGICAL SUM OF                           SAP10045
       ORS RS             RIGHT ROWS                                    SAP10046
       TNX B12,1,1        TEST FOR ZONE ROWS                            SAP10047
       TSX C1,2           ENTER CONVERSION LOOP                         SAP10048
       TXL B13            LEAVE CONVERSION LOOP                         SAP10049
       TXL C3             ADD TO BCD RECORD                             SAP10050
 B7    CAL 8L             ADD 8 LEFT ROW TO                             SAP10051
       ORA LS             LEFT LOGICAL SUM                              SAP10052
       SLW LDS            X                                             SAP10053
       CPY LZ             COPY ZERO LEFT AND                            SAP10054
       ANA LZ             FORM INDICATOR FOR                            SAP10055
       SLW LS             BOTH DIGIT AND ZERO                           SAP10056
       CAL 8R             ADD 8 RIGHT ROW TO                            SAP10057
       ORA RS             RIGHT LOGICAL SUM                             SAP10058
       SLW RDS            X                                             SAP10059
       CPY RZ             COPY ZERO RIGHT AND                           SAP10060
       ANA RZ             FORM INDICATOR FOR                            SAP10061
       SLW RS             BOTH DIGIT AND ZERO                           SAP10062
 B12   TSX C1,2           ENTER CONVERSION LOOP                         SAP10063
       TXL B14            LEAVE CONVERSION LOOP                         SAP10064
       ALS 4              SHIFT TO ZONE POSITION                        SAP10065
       TXL C3             X                                             SAP10066
 B9    CAL LS             SAVE LEFT ZONE SUM                            SAP10067
       SLW L              X                                             SAP10068
       CAL LDS            FORM INDICATOR FOR                            SAP10069
       COM                ZERO AND X AND / OR Y                         SAP10070
       ANA LZ             IN LEFT ROWS                                  SAP10071
       ANS LS             X                                             SAP10072
       CAL RS             SAVE RIGHT ZONE SUM                           SAP10073
       SLW R              X                                             SAP10074
       CAL RDS            FORM INDICATOR FOR                            SAP10075
       COM                ZERO AND X AND/OR Y                           SAP10076
       ANA RZ             IN RIGHT ROWS                                 SAP10077
       ANS RS             X                                             SAP10078
       TSX C1,2           ENTER CONVERSION LOOP                         SAP10079
       TXL B15            LEAVE CONVERSION LOOP                         SAP10080
       SLW T              MULTIPLY INDICATOR                            SAP10081
       ALS 2              BITS BY TEN                                   SAP10082
       ACL T              X                                             SAP10083
       ALS 1              X                                             SAP10084
       TXL C3             X                                             SAP10085
 B15   CAL LDS            FORM INDICATOR FOR                            SAP10086
       SWT 2                                                            SAP10087
       WTD 1                                                            SAP10088
       ORA LZ             BLANK COLUMNS IN                              SAP10089
       ORA L              LEFT HALF OF CARD                             SAP10090
       COM                X                                             SAP10091
       SLW LS             X                                             SAP10092
       CAL RDS            FORM INDICATOR FOR                            SAP10093
       ORA RZ             BLANK COLUMNS IN                              SAP10094
       ORA R              RIGHT HALF OF CARD                            SAP10095
       COM                X                                             SAP10096
       SLW RS             X                                             SAP10097
       TSX C1,2           ENTER CONVERSION LOOP                         SAP10098
       TXL B16            LEAVE CONVERSION LOOP                         SAP10099
       SLW T              MULTIPLY INDICATOR                            SAP10100
       ALS 1              BITS BY 3 AND                                 SAP10101
       ACL T              SHIFT TO ZONE POSITION                        SAP10102
       ALS 4              X                                             SAP10103
       TXL C3             X                                             SAP10104
 B16   LXD B2,1           RESTORE INDEX REGISTERS                       SAP10105
       LXD B3,2           AND RETURN TO MAIN                            SAP10106
       TRA 3,4            PROGRAM                                       SAP10107

— ReallyOld

IBM 704 Punched Card Reading Part 5

IBM 704 Punched Card Reading Part 4

It’s time to show some code. To start out, I will present an important subroutine of the card reading code. This subroutine is imaginatively titled “C1”. Comments in the calling code refer to it as the “conversion loop”. It’s basic function is to build an input line of characters, given bits representing holes punched in a row of a card. It is called once for every row of a card being read.

The basic idea is that if a hole is punched in a given row on a card, a corresponding bit will be set in one of the words input for that row when the card is being read. By remembering that bit, and successively adding it to itself for every row that follows the row on which it is first seen, the character it represents can be constructed. For example, remember that digits are represented by a hole punched in row corresponding to their value. The card is read row by row starting with the 9-row. If we remember that a given bit was set in the ninth row, and add it to itself for each row from row nine down to row one, then it will be added to itself nine times, yielding the value 9 as a result. Looking back at the encoding of characters in memory, we can see that the six-bit binary value 0010012 = 910 is, in fact, the encoding of the digit nine.

In a more general way, if we accumulate a “logical sum” of all the rows, by ORing successive rows together, and then successively add that logical sum to itself each time we read a row, then the bits in the 9-row will be added to themselves nine times, the bits in the 8-row will be added to themselves 8 times, and so on, thus yielding the correct encoding of the digits. This doesn’t account for letters, which require “zone punches” in the rows 0, X (11), and Y (12) in addition to punches in digit rows, and ignores 8-3 and 8-4 double punching of digit rows, but the basic approach is valid and is used in the card reading code below. The complications just mentioned are handled by allowing the caller of C1 to customize its behavior as will soon be explained.

There is no clear calling interface to C1. It’s inputs are variables and registers shared with the larger card reading subroutine. These inputs are as follows:

Index Register 1: When C1, this subroutine, is called, index register 1 contains the number of the row currently being read (counting from 9 down to 1). This infomation is checked and then saved for later retrieval as index register 1 is used as a counter locally in this subroutine.

Index Register 2: Index register 2 contains the (two’s complement of the) address of the TSX instruction that was used to call this subroutine. That is, C1 was called with an instruction of the form TSX C1,2. To return to the caller, this subroutine will execute TRA 1,2.

Index Register 4: Index register 4 contains the (two’s complement of the) address of the TSX instruction that was used to call the main card reading subprogram. C1 needs this information because the caller of the card reading code has to provide a 12-word space for the input line which is to be read from a card (6 characters per word × 12 words = 72 characters). This is provided by putting the address of the 12-word space in the address field of the word immediately following the subroutine call like this:

       TSX RDBCD,4        CALL READ-A-CARD USING INDEX REGISTER 4
       ??? BCD            ADDRESS OF 12-WORD SPACE FOR CHARACTER DATA

Where ??? is any opcode, or indeed no opcode at all. The opcode is irrelevant, because all we care about is the address field. We can return from the subroutine, skipping this argument, with a TRA 2,4 instruction. The “2” in the address field causes us to return to the second instruction after the call, thus skipping the argument.

LS, RS: LS and RS are two memory words representing the “logical sums” of the left and right halves of the input card respectively. As explained above, they are the accumulated OR of the card rows read so far. Space for these variables is reserved in the assembly code with the two lines:

 LS                       LEFT SUM                                      SAP10145
 RS                       RIGHT SUM                                     SAP10146

Note that there is no pseudo-opcode for reserving one word of space. Each line of the assembly code source file automatically represents one word of memory, so merely labelling a line is sufficient to name and reserve one word of memory.

Now let us consider the constants and variable used locally by C1:

BCD: As stated above, the caller of the larger card reading subroutine must supply the address of a 12-word space in which the 72 character line punched into an input card will be stored. This space is not directly named in the card reader source code, but it is referred to as the “BCD Record”, where “BCD” refers to character data in general, not merely Binary Coded Decimal numbers.

SIX, T: The constant value six is necessary as a starting point for indexing through the BCD space (there are six words of BCD for each of the left and right halves of the card). Apparently no good way was found to insert this constant into some unused part of an instruction because a separate word is reserved for it. T is a word reserved for use as a temporary variable.

 SIX       6              CONSTANT 6                                    SAP10143
 T                        TEMPORARY                                     SAP10144

Index Register 1: As stated above, upon entry to C1, index register 1 contains the number of the row currently being read. However, it is also used in this subroutine to step through the words of the BCD space.

Lastly before looking at the code, the caller-customization feature mentioned above needs to be explained. When C1 exits, it returns to the instruction following the TSX instruction that called it. However, it also calls back to its caller by returning to the second instruction after the TSX once for each of the twelve words of the BCD space. The caller should have code in that position to perform any necessary adjustments on each word of BCD before passing control back to C1. C1 itself provides two entry points to which the callback code can return. Entry point C2 simply stores the word, as adjusted by the callback code, in the BCD space. Entry point C3 adds the adjusted word to the current contents of the appropriate word of BCD space. C2 is used for the 9-row, when the BCD space contains no valid data for the current card, and C3 is used for subsequent rows, to accumulate the character data as described as the beginning of this post.

In the general case, no adjustment will need to be done, and the callback code will return immmediately to entry point C3. In that case, the calling code looks like this:

       TSX C1,2           ENTER CONVERSION LOOP, USING INDEX REGISTER 2
       TXL CONT           LEAVE CONVERSION LOOP, CONTINUE
       TXL C3             CALLBACK - NO ADJUSTMENT, JUST ADD TO BCD RECORD
 CONT  ...

The Code

Now we are finally ready to look at the entire C1 subroutine:

 C1    SXD C4,1           SAVE ROW COUNT                                SAP10108

This first line, saves index register 1 in the decrement field of a TXL instruction at the end of the subroutine. As described in the last part, this does not affect the TXL at all, which continues to be executed as an unconditional branch.

       SLN 1              INDICATE LEFT ROW                             SAP10109

Sense Light one is used as a bit flag to figure out if the subroutine has processed both the left and right sides of the current row. It is turned on upon entry to indicate that the left side is being processed.

       CAL 1,4            INITIALIZE ADDRESS                            SAP10110
       ADD SIX                                                          SAP10111

Load the base address of the BCD space from the word immediately following the call to the larger card reading subroutine. Index register four was used to call the card reading subroutine and so contains the address of the calling instruction. Then add six to that base address. Remember indexing on the IBM 704 is subtractive, so a pointer is built to the end of the space the code will be working with.

       LDQ LS             OBTAIN LEFT SUM                               SAP10112
 C8    STA C2             SET BCD RECORD ADDRESS                        SAP10113
       STA C3             X                                             SAP10114

The left side logical sum is brought into MQ, and the pointer into the BCD space calculated above is plugged into the address fields of the instructions that will use it. Self-modifying code for the win! The label C8 is the beginning of the loop for processing one of the sides, so a branch will be made to C8 after preparing the right side.

       TXH C5,1,1         SKIP TEST IF DIGIT ROW                        SAP10115
       STQ T              TEST FOR NO SUM                               SAP10116
       CAL T              X                                             SAP10117
       TZE C6             X                                             SAP10118

If no bits are set in the logical sum, there’s really nothing to do, so we can skip the accumulation of bits in the BCD space and branch directly to C6 . We can check for this condition by moving the logical sum (the accumulated OR of rows) through a temporary into A, where it is possible to check for zero. However, this check is not done while processing rows 9 through 2. If normal processing of row 9 is skipped, the BCD space won’t be initialized. I dont think it would hurt to jump directly to C6 if the logical sum is zero on rows 8 through 2, but it’s also extremely unlikely to be the case that the logical sum is zero because almost every character has a digit row punch.

 C5    LXA SIX,1          SET WORD COUNT                                SAP10119

Load a 6 into index register 1 to step through the BCD space. When the current row is not a zone row, control comes immediately to label C5 from the TXH above. Since indexing is subtractive on the 704, the first word touched in the BCD space will be the address computed above: base + 6, minus the contents of index register 1, which is 6, for base + 6 – 6 = base. As the index register is decremented, the effective addresses increases and the code steps forward through the BCD space.

 C7    PXD 12                                                           SAP10120

This very tricky line has two purposes. As an instruction, it clears A and then loads the decrement field of A with the decrement field specified in this instruction. However the decrement field is omitted in the instruction, so it is zero, and A is just cleared to zero. The 12 is in the unused address field of the instruction, and is nothing more than a constant. It will be used below to build the end address of the BCD space when the right logical sum is being processed.

       LGL 1              X                                             SAP10121
       ALS 5              X                                             SAP10122
       LGL 1              X                                             SAP10123
       ALS 5              X                                             SAP10124
       LGL 1              X                                             SAP10125
       ALS 5              X                                             SAP10126
       LGL 1              X                                             SAP10127
       ALS 5              X                                             SAP10128
       LGL 1              X                                             SAP10129
       ALS 5              X                                             SAP10130
       LGL 1              X                                             SAP10131

This is the core of the subroutine. It spreads out the bits in the logical sum into six bit fields. When successively added, these fields will become the character data. The logical sum is in MQ, it is shifted into A such that 6 bits of MQ become six 6-bit fields in A.

       TRA 2,2            EXIT FOR ROW PROCEDURE                        SAP10132
 C3    ACL 0,1            ADD TO BCD RECORD                             SAP10133
 C2    SLW 0,1            STORE IN BCD RECORD                           SAP10134

This is the call back to the calling code. The callback code has the opportunity to adjust A as necessary. If the callback code returns by jumping to C2, then the adjusted A is merely stored in the BCD space. If it returns by jumping to C3, then the current contents of the BCD space are added to A and then A is stored back into the BCD space.

Remember that the end address of the six-word part of BCD space currently being manipulated was previously stored into the address fields of the instructions at C2 and C3, replacing the zeros in the code above. Index register 1 counts down from 6 to 1, thus causing this code to operate on successive locations of the BCD space.

       TIX C7,1,1         COUNT WORDS                                   SAP10135

If index register 1 is greater than 1, decrement and loop back to shift out the next six bits from MQ to A. Otherwise, this half has been done.

       LXD C4,1           RESTORE ROW COUNT                             SAP10136
 C6    SLT 1              TEST FOR RIGHT ROW                            SAP10137
       TRA 1,2            RETURN                                        SAP10138

Retrieve the current row from where it was saved, and test to see if sense light one is on. If sense light one is on, then we just processed the left half. Sense light one will be turned off by the test and the TRA instruction will be skipped. If sense light one is already off, then we just processed the right half, the TRA will not be skipped. TRA 1,2 is the subroutine return instruction which causes control to return to the caller.

       LDQ RS             SETUP RIGHT ROW                               SAP10139
       CAL 1,4            X                                             SAP10140
       ADM C7                                                           SAP10141
 C4    TXL C8             X                                             SAP10142

Here the sense light was on, so the code prepares to process the right half sum, by loading the right sum into MQ, then (again) retrieving the base address from where it was provided by the calling code. This time, a constant 12 is added, taken from the address field of the PXD instruction labeled by C7. Then branch unconditionally to the beginning of the code to process the logical sum at C8.

And that concludes the presentation of the subroutine C1. In the next installment we will begin looking at the larger card reading subprogram. Lastly, I will show the entire subroutine unbroken:

 C1    SXD C4,1           SAVE ROW COUNT                                SAP10108
       SLN 1              INDICATE LEFT ROW                             SAP10109
       CAL 1,4            INITIALIZE ADDRESS                            SAP10110
       ADD SIX                                                          SAP10111
       LDQ LS             OBTAIN LEFT SUM                               SAP10112
 C8    STA C2             SET BCD RECORD ADDRESS                        SAP10113
       STA C3             X                                             SAP10114
       TXH C5,1,1         SKIP TEST IF DIGIT ROW                        SAP10115
       STQ T              TEST FOR NO SUM                               SAP10116
       CAL T              X                                             SAP10117
       TZE C6             X                                             SAP10118
 C5    LXA SIX,1          SET WORD COUNT                                SAP10119
 C7    PXD 12                                                           SAP10120
       LGL 1              X                                             SAP10121
       ALS 5              X                                             SAP10122
       LGL 1              X                                             SAP10123
       ALS 5              X                                             SAP10124
       LGL 1              X                                             SAP10125
       ALS 5              X                                             SAP10126
       LGL 1              X                                             SAP10127
       ALS 5              X                                             SAP10128
       LGL 1              X                                             SAP10129
       ALS 5              X                                             SAP10130
       LGL 1              X                                             SAP10131
       TRA 2,2            EXIT FOR ROW PROCEDURE                        SAP10132
 C3    ACL 0,1            ADD TO BCD RECORD                             SAP10133
 C2    SLW 0,1            STORE IN BCD RECORD                           SAP10134
       TIX C7,1,1         COUNT WORDS                                   SAP10135
       LXD C4,1           RESTORE ROW COUNT                             SAP10136
 C6    SLT 1              TEST FOR RIGHT ROW                            SAP10137
       TRA 1,2            RETURN                                        SAP10138
       LDQ RS             SETUP RIGHT ROW                               SAP10139
       CAL 1,4            X                                             SAP10140
       ADM C7                                                           SAP10141
 C4    TXL C8             X                                             SAP10142
 SIX       6              CONSTANT 6                                    SAP10143
 T                        TEMPORARY                                     SAP10144
 LS                       LEFT SUM                                      SAP10145
 RS                       RIGHT SUM                                     SAP10146

— ReallyOld

IBM 704 Punched Card Reading Part 4

IBM 704 Punched Card Reading Part 3

An IBM 704 instruction can have up to three operands. The most common case is one operand: an address. The next most common is two operands: an address and an index register. The third operand is a “decrement”, a 15-bit immediate value stored in the decrement field of the instruction. It is used for various purposes, but is usually used to increment or decrement an index register, or as a value to which an index register is compared. If an operand otherwise would be zero, it can be omitted, although the second operand (index register) cannot be omitted without also omitting the third operand. If the index register specification is omitted in an instruction that allows indexed addresses, it is as if an index register with contents of zero had been specified.

In UA-SAP assembly code for the IBM 704, instructions are written in the form:

      INS {ADDRESS{,INDEX{,DECREMENT}}}

where brackets indicate that an syntactic element is optional. Note, that the left-to-right order of these operands is the opposite of their position in the instruction word. The address is actually the fifteen lowest-order bits of the word. The index is the three bits to the left of the address, and the decrement, when present, is the fifteen bits to the left of the index. However, most instructions don’t have a decrement field, and that part of the word is partly used for opcode bits, and is partly unused.

As stated in the last part, the index registers allow the programmer to modify the address operand of an instruction. This can be done in two ways. First, the address can be treated as a base pointer, and the index register can be used to refer to data relative to that base. This is done when the programmer wants to step through an array or some other kind of structured data that is composed of multiple memory objects offset from a base. Alternatively, the contents of the index register itself can be considered a base pointer, and the address can be used as a (usually small) fixed offset from that base. Both of these types of addressing are done in the card reading code, but the latter is more common.

Before getting into the specifics of individual instructions, here is a table of most of the opcodes used in the card reader subroutine:

STA Store Accumulator
ADD ADD Accumulator (standard signed addition)
ANA AND Accumulator (AND A, storing result in A)
ANS ANd to Storage (AND A, storing result in the operand location)
ORA OR Accumulator (OR A, storing result in A)
ORS OR to Storage (OR A, storing result in the operand location)
COM COMplement Accumulator
ALS Arithmetic Left Shift (shift A to the left some number of bits)
LGL LonG Left shift (shift both A and MQ left some number of bits, shifting bits from MQ into A)
LDQ LoaD mQ (load a 36 bit word into MQ)
STQ STore mQ (store MQ into a 36 bit word)
CAL Clear and Add Logical (load a word into A as a 36 bit magnitude.)
SLW Store Logical Word (store A as a 36 bit magnitude.)
ACL Add and Carry Logical (add A as a 36 bit magnitude)
LXD Load indeX Decrement (load an index register from the decrement field of some word)
SXD Store indeX Decrement (store an index register in the decrement field of some word)
TSX Transfer and Set indeX (store the program counter in an index register and branch)
TRA TRAnsfer (unconditional branch)
TZE Transfer if ZEro (branch if A is zero)
TNZ Transfer if Not Zero (branch if A is not zero)
TXL Transfer on indeX Low (branch if an index register <= a specified decrement)
TXH Transfer on indeX High (branch if an index register > a specified decrement)
TIX Transfer on IndeX (if an index register > a specified decrement, subtract the decrement from the register and branch, otherwise continue)
TNX Transfer on No indeX (if an index register > a specified decrement, subtract the decrement from the register and continue, otherwise branch)
RCD Read CarDs (prepare to input from card reader)
CPY CoPY (Do I/O – here it will be input from the card reader)
SLN Sense Light Number (turn on a sense light)
SLT Sense Light Test (skip an instruction if the specified sense light is on)
SWT Switch Test (skip an instruction if the specified sense switch is closed)

Many of these instructions are mostly self-explanatory, but there are a few that require further explanation. To begin with, let’s examine the Transfer instructions.

TSX : This is the IBM 704 subroutine call instruction. The two’s complement of the address of the TSX instruction itself is placed in a specified index register and then an jump to the specified absolute target address is performed.

TRA : The table above states that TRA is an unconditional branch. Although this is true, it is not often used where the programmer wishes merely to branch unconditionally. This is because TRA can used indexed addressing. It is most commonly used to jump to an address stored in an index register. Assume for example, that a subroutine call is made with an instruction of the form TSX A,X, where A is the subroutine address, and X is the index register in which the address of the TSX is placed. By executing a TRA 1,X instruction, control is returned to the instruction following the subtroutine call. In fact, this is the most common use of the TRA instruction. So, for the most part, TRA can be thought of mainly as the IBM 704 Return from Subroutine (RTS) instruction.

TXL : Nominally, TXL allows the comparison of the contents of an index register with a value specified in the decrement field. The branch will be taken if the contents of the index register are less than or equal to the specified decrement. This comparison is unsigned. Therefore, if the specified index register contains zero, the branch will always be taken. And if the index register is omitted in the instruction, the effect is the same as if an index register containing zero were specified, which makes TXL an unconditional branch, no matter what value the decrement field contains. The decrement field then becomes available to store useful constants, saved values, temporary variables, etc. That’s right, IBM 704 code commonly stores data inside branch instructions. This is horrifying to the modern programmer, but typical programming practice on early machines.

TIX, TNX : TIX is a very normal iteration looping instruction. The index register is compared to a value specified in the decrement field. If the contents of the index register are greater than the decrement, the contents of the index register are reduced by the decrement and the branch is taken. TNX is more unusual. Conceptually, it is the opposite of TIX. The same comparison is made, but in this case, the branch is taken if the contents of the index register are less than or equal to the decrement. Otherwise, the contents of the index register are reduced by the decrement and execution continues with the sequentially next instruction.

Some other instructions that deserve some extra explanation are the “logical” operations on A — CAL, SLW, and ACL.

CAL : Although CAL is an abbreviation for Clear and Add Logical, this is actually a load instruction. A is cleared and then, the operand is added to the zero value in A, thus loading the operand into A. What makes this instruction “logical”, is that the sign bit of the operand is not placed in the sign bit of A, but rather is placed in the first overflow bit of A, called the P-bit.

SLW : This store instruction is the counterpart of CAL. It stores A into an specified memory word. As with CAL, the P-bit takes the place of the sign of A. It is stored into the sign bit of the destination location.

ACL : ACL stands for Add and Carry Logical. As usual, “logical” means here that this instructions involves the P overflow bit rather than the sign of A. The sign bit of the operand adds into the P-bit of A. The sign bit of A remains unaffected. If there is a carry out of the P-bit, it is added to the lowest order bit of A. In other words, the carry wraps around from the high end of A to the low end of A. This may seem strange, but it is useful in certain cases, such as one’s complement arithmetic. This feature is not used in the card reading code.

A general feature of I/O instructions on the IBM 704 is that their execution can result in the advancement of the program counter by one, two, or even three instructions, depending on the result of executing the instruction. The most important I/O instruction is CPY, but SLT, and SWT are also used in the card reading code.

SLT : SLT tests whether a specified sense light is on or off. If the sense light is off, the instruction following SLT is executed, otherwise the sense light is turned off and the instruction following SLT is skipped.

SWT : SWT is very similar to SLT. It tests whether a specified sense switch is in an up or down position on the front panel of the IBM 704. If the switch is up (off — open circuit), the instruction following SWT is executed, otherwise if the switch is down (on — closed circuit), the instruction following SWT is skipped.

CPY : COPY is the primary I/O instruction of the IBM 704. After setting up a particular peripheral with an appropriate instruction (such as RCD), each CPY instruction will either input or output a 36-bit word to or from MQ. If a CPY instruction succeeds in inputting or outputting a word, execution continues with the instruction sequentially following the CPY. If a CPY fails because there are no more words to input or output in the current “record” (e.g., an entire card, tape block, etc. has been read or written), two instructions are skipped. If a CPY fails because no more data can be input or output on the peripheral device (e.g., end of tape, card hopper empty, etc.), then one instruction is skipped.

With these instructions defined, we are now ready to begin looking at the card reader code in the next installment.

— ReallyOld

IBM 704 Punched Card Reading Part 3

IBM 704 Punched Card Reading Part 2

In the last part, I explained how symbols are encoded on punched cards using Hollerith Code. In order to understand Roy Nutt’s card reading subroutine in the UA-SAP assembler, it is now necessary to know something about the architecture and instruction set of the IBM 704 computer. In order to read and understand the code in its original form it is necessary to understand these topics and also to be familiar with the somewhat bizarre (by today’s standards) assembly language. I will cover this information as to the extent necessary to understand the card reading code, but I will not attempt to give a full description of IBM 704 programming. For those who do want to know all of the details, The IBM 704 EDPM Manual of Operation documents the machine architecture and instruction set, and the UA-SAP Operator’s Notes documents the assembler.

The IBM 704 was a word-oriented machine, as all early computers were. Each word was 36 bits long. Addresses were 15 bits long, so the machine was able to address 215 = 32768 36-bit words, equivalent to 144 kB. While some installations were equipped with the full 32768 words of memory, 4096 or 8192 words were more typical. Notice that each memory word has sufficient space to store two addresses plus an extra 6 bits of storage. In fact, the instruction set of the 704 explicitly foresaw this possibility and allowed a 15 bit field plus an extra three bits to be easily stored in, and retrieved from, each half of a machine word. When used in this way, the low-order end of a machine word was called the address part, and the high-order end of a machine word was called the decrement part. These names relate to uses of these fields in certain instructions, but the fields themselves could be used to store any 15 bit value. The creators of Lisp, which was first implemented on the IBM 704, used a machine word as a cons cell, each of the 15-bit fields as values or pointers, and the three extra bits in each half of the word as tag bits. To retrieve the Contents of the Address Register of a word, Lisp provided the CAR form, and similarly, to retrieve the Contents of the Decrement Register of a word, Lisp provided the CDR form.

An IBM 704 word with address and decrement fields.[Figure 1]

The IBM had one general purpose register, called the Accumulator, A. It was 36+2 bits long and was used for all mathematical operations and almost all load, store, and comparison operations. There was another 36 bit register called MQ (for Multiplier/Quotient) which acted as an extension of the Accumulator for various mathematical operations. For example, as you might expect by its name, it was used in multiplications and divisions. It was also used in floating point operations, which I won’t say more about in this article. Lastly, it was used as an I/O buffer. Words being input appeared in MQ and words being output were placed in MQ. We will see this use of MQ in the card reading code. There were also three 15 bit index registers numbered 1, 2, and 4. A three bit field was used in instructions to select an index register and the register numbers correspond to the bit used to select them. As you might expect, later computer models in the same family expanded the number of index registers to 7 in order to more fully utilize this three bit field. There were also “sense lights” and “sense switches”. The sense lights were actual lights on the front panel of the 704 that could be switched on and off under program control. It was also possible to read their current state, so they were often used as one bit registers or flags. The sense switches were physical switches located on the front panel of the 704. Programs could also read the state of the sense switches, so they were often used for run-time input to programs, selection of program options, and the like.

The IBM 704 represents numbers using a sign-magnitude representation. Both registers A and MQ consist of a sign bit in addition to a magnitude field. Both positive and negative zeros are possible. Positive and negative zeros both act as a zero in arithmetic computations, but positive zeros are considered to be greater than negative zeros, and the sign of a zero can affect the sign of the result of an arithmetic operation in which it is involved. In MQ, the magnitude field is, as expected, 35 bits wide. In the Accumulator, the magnitude field is 35+2 bits long. The two extra bits are the highest order bits of the magnitude. They are used for detecting overflow and are usable in certain operations, such as shifts and rotates. When storing the arithmetic value of A into a memory word, however, they are lost, so the effective length of A for arithmetic operations is limited to 36 bits. Interestingly, the index registers do not have a sign bit and represent negative numbers using two’s complement.

An IBM 704 word with address and decrement fields.[Figure 2]

The instruction set of the IBM 704 was typical for computers of the time, but fairly odd by today’s standards. The only place modern programmers are likely to have come across something similar is in The Art of Computer Programming by Donald Knuth. Knuth’s original MIX computer and MIXAL assembly language, used to present algorithms in the first three volumes of The Art of Computer Programming is quite similar to the 704. The instruction encoding on the 704 is very sparse, meaning that there are often large numbers of unused bits in an instruction word. This allows for simpler decoding hardware, which was important in the 1950s. It also allows programmers to use unused parts of instructions as temporary variables or to store constants (!) The relative simplicity of the instruction set made the use of self-modifying code sometimes necessary and fairly common, although the introduction of index registers on the 704 removed the most common use case for self-modifying code — updating an address to step through an array.

There are operations to load and store both A and MQ. Index registers can be loaded and stored from both the address- and decrement-parts of memory words and A. Arithmetic instructions operate on the Accumulator and a memory operand. Logical and shifting operations on the Accumulator and MQ are also available. Many of these instructions treat A and MQ as a single 72-bit register. MQ is shifted left into A and A is shifted right into MQ. All references to memory can be indexed by one of the three index registers. The index registers themselves can be incremented and decremented by arbitrary 15 bit values. Oddly, IBM 704 index registers did not contain an offset to be added to an address, but rather an offset to be subtracted from an address, presumably for reasons of hardware design simplicity. For this reason, the use of negative numbers in two’s complement form was common when indexing.

Branches can be taken based on the status of index registers, A, or MQ. There are not really any condition codes per se, but it is possible to branch on certain overflow or error conditions. It is also possible to branch based on the state of the sense lights and sense switches located on the console of the 704. There is no hardware stack, or concept of a stack pointer. Instead, there is a jump instruction that deposits the current program counter into one of the index registers before loading the new value into the program counter. People familiar with IBM 360 (Z-Series) assembly will recognize this concept as it carried over into the BALR/BASR instructions of that computer family. Finally, there was a very general purpose I/O instruction called CPY (Copy), that could read or write data from any of the IBM 704’s peripherals. I/O was not memory mapped, specific instructions were used to initiate I/O to the various pieces of peripheral equipment such as tape drives, magnetic drum unit, line printer, CRT display, and card reader.

Since punched cards have 80 columns, there 80 bits of information in each row. Since, as already stated, I/O occurred through the 36 bit MQ, only 36 bits at a time could be read by the 704. For this reason, the 704 actually read only 72 columns of each row, requiring two CPY instructions for each row — one for the “left” half of the row and one for the “right” half of the row. As usual with IBM equipment of the time, exactly which rows were read in each case could be configured by the use of a suitable control panel (plugboard) installed in the card reader, but generally, things were set up so that the first 72 columns were read and the last eight columns were ignored. This allowed the last eight columns to be used for sequence numbers or other information without impacting their use in the 704 in any way.

In the next installment, I will dive more into the specifics of the instructions used in the card reading subprogram so that some of the subtleties and tricks of the code will be more easily understood when presented.

— ReallyOld

IBM 704 Punched Card Reading Part 2

IBM 704 Punched Card Reading Part 1

I’ve been meaning to write something about software for the IBM 704 computer for a long time now. I’ve been studying old IBM 704 code on and off for years now – but only for very short periods of time, so there is still a lot I haven’t looked at. In particular, I want to eventually dig into the original Fortran Compiler.

Anyway, one thing I can talk about is SAP (Symbolic Assembly Program), the historically important 704 assembler written by the legendary programmer Roy Nutt, while he was at United Aircraft Corporation. In particular, I want to take a look at the punched card reading subprogram of the assembler. This was one of the primary input routines of the assembler, as programs under development were often assembled directly from punched cards.

Before I can start to talk about the code, though, I probably have to say something about punched cards themselves. It is likely that most people who might read this don’t know anything about punched cards and how they were used with computers. To start off, look at the picture in Figure 1 below.

an IBM punched card[Figure 1]

This figure shows a punched card. It has 80 columns and 12 rows. Holes can be punched in certain rows of a given column to represent one character from a very limited character set. At first glance, it would seem that 12 rows in each column would give us the ability to encode one of 212 = 4096 different characters in each column. However, this was not actually possible because too many punches would weaken the card such that it would flex or crumple inside the card processing equipment and jam the machine. Also, the encoding of characters on a punched card was constrained by historical considerations, as all of the various card processing equipment had been around long before the advent of digital computers.

So what was the encoding used to represent characters by holes in a card? First, lets name the rows of the card so we can refer to the locations where holes are punched. The bottom row on the card in Figure 1 is the 9-row, the next 9 rows above the 9-row are the 8-row, the 7-row, … , 1-row, 0-row. For early punched cards, that was it. There were only 10 rows. Punching a hole in a given row represented that number. So a hole punched in the 6-row (a “6 punch”) of the 10th column meant that the character in the 10th column was a ‘6’.

By the time computers came around though, the card-code had been extended to include letters (uppercase only) and a few useful punctuation marks. The card in Figure 1 has 12 rows, not 10. The two rows above the 0-row were called variously the y-row and the x-row, or simply the 11-row and the 12-row. When counting by numbers, this means that the rows were numbered from bottom to top as rows 9,8,7,6,5,4,3,2,1,0,11,12. This gives us twelve possible punching positions in each column, rather than ten, but clearly being able to represent twelve characters in twelve rows is not a sufficient improvment over the previous ability to represent ten digits in ten rows. Instead these two new rows were meant to be punched in addition to some other row.

These two new rows — along with the zero row, which was slightly repurposed — are called the “zone rows”. A hole punched in the 0-row, the 11-row, or the 12-row selected a “zone”. In other words, a different part of the character set. A 6 punch alone represents a ‘6’, but a 6 punch together with a 0 punch (a 6-0 punch) represents something else, and a 6 punch together with an 11 punch (a 6-11 punch) again something different, and similarly with a 6-12 punch. It was not permitted to punch two different zone rows in a given column, probably for reasons of the mechanical stability of the card.

So at this point, we’re looking at four possible zones (a 0 punch, 11 punch, 12 punch, or no zone punch) with ten available punch combinations in each zone: a punch in a zone row by itself, or a punch in a zone row together with a punch in one of the rows 1 to 9. In the case of no zone punch, there could be a punch in one of the rows 1 to 9 or no punch at all. But this only gives us 40 characters, which isn’t quite enough, so eight more characters were added to the character set by means of what was called “double punching”.

In double punching, two holes were punched in a column in addition to any zone row punch. Four characters (one in each zone) were added by an 8-3 punch (that is, a hole punched both in row 8 and in row 3) and the last four were added by an 8-4 punch. So, the total number of characters in the punched card character set was 48. The original character set represented by these 48 punch combinations included the digits 0-9, uppercase letters A-Z, space, and the eleven punctuation characters:  # @ / , % $ * – . ⌑ & . For computer purposes, that character set was replaced by a character set more suitable for computing:  = ‘ / , ( $ * – . ) + . This was done by changing the key caps on the keypunch machines and by changing the print wheels on the printers used with computers. A diagram of the character set organized by the punches corresponding to each character is displayed in Figure 2.

IBM punched card character code[Figure 2]

Lastly, it is important to understand how the cards were read by card processing equipment, including the card reader used as a computer input device. A card reader had an input hopper, into which a stack of cards could be placed with the edge of the card next to the 9-row (the 9 edge) aligned towards the card feeder. The cards were then drawn one at a time into the reader, 9 edge first, and brought to a reading station. At the reading station, metal pins called brushes made contact with the card. If there was a punched hole beneath the brush, the brush made contact with a charged metal plate behind the card, a current flowed, and the hole was detected. Where a brush rested on the card itself, no contact was made, and no hole was detected.

In the IBM 704 computer, this card information was presented to the computer one row at a time upon execution of a special I/O instruction. As I will explain in more detail in my next post, the IBM 704 computer had two 36-bit machine registers used for general arithmetic. Since the two 36-bit machine registers combined had only 72 bits in total, it was only possible to read 72 of the card’s columns. It was possible to configure the card reader to choose any 72 of the 80 possible columns to input, but the default configuration of most card readers was to read the first 72 columns of each card.

The presence or absence of holes in each row of the punched card were successively presented to the computer in these two machine registers. When a row was read from a card, a one in a given bit position in one of the two registers represented a hole in a given column. Since the cards were drawn into the card reader 9 edge first, the twelve rows of the punched card were presented to the 704 in the same order they appeared on the card, beginning with the 9 row.

Since the purpose of the card reading code is to bring the character data stored on punched cards into the computer’s main memory, we also need to know how the characters are represented in memory. As stated, the IBM 704 was a word-oriented machine with 36 bit words. Given that there are only 48 characters in the card character set, 6 bits per character in memory is sufficient to represent all characters, and since 6 conveniently divides 36, a 6 bit representation of characters was chosen, as shown in Figure 3.

IBM 704 in-memory character code[Figure 3]

In the next part I will explain enough of the architecture of the IBM 704 computer to hopefully make the card reading code understandable.

— ReallyOld

IBM 704 Punched Card Reading Part 1