Simulator for the IBM 1620 (AKA CADET for Can't Add Didn't Even Try, because it uses memory table lookup)

src Make a Map class so I can go back to ES5 output 1 year ago
.gitignore ignore generated and downloaded 1 year ago
gulpfile.js Make a Map class so I can go back to ES5 output 1 year ago
package.json working commit. I think the framework is mostly sorted out, just putting together a quick mneumonic assembler. Then all I need to do is the instructions and writeup 1 year ago
poster.jpg I stole this poster from the internet. I kinda want to get it printed out. It came from here (http://www.bitsavers.org/pdf/ibm/1620/1620_Poster_1960.jpg), and should be public domain cuz the (C) date is 1960 1 year ago
readme.md typo, errors, and optimizations to the README 1 year ago
readme.md

IBM 1620 Simulator

IBM 1620

The IBM 1620 is a computer created in 1959 by IBM and produced in two models until 1970. This will focus on the 1620-1 which is the more interesting version, since it did not have its own addition logic circuitry. The 1620 has a variable word length and stores digits in base 10.

Base 10 representation of a single digit is as follows. Each Digit has six bits, a check bit which is the parity of the entire digit. The check bit is automatically set such that the number of high bits is always an odd number. The Flag bit has a few purposes:

  1. at the beginning of a "number field", it indicates a negative number
  2. at the end of an number field, it indicates the end of a variable length feild.
  3. It is used in special values.
--------------------------------
| check | flag | 8 | 4 | 2 | 1 |
--------------------------------

A Field is a variable length number, and a record is series of fields. A field is stored with the high end first, and is referenced by the low-order digit. When processing a field, the 1620 decrements the memory address to move from right-to-left.

--> memory addresses increase -->
----------------------------------------------------------------------------------------------
| check | flag | 8 | 4 | 2 | 1 | check | flag | 8 | 4 | 2 | 1 | check | flag | 8 | 4 | 2 | 1 |
----------------------------------------------------------------------------------------------
<-- order of process on a field <--

In my simulator, I have implemented the Digit as a class in TypeScript to represent an individual digit. An individual digit may be incremented, decremented, twice incremented, or nine's complemented, as these are the only options performed in actual hardware on the 1620.

Core Memory Structure

The 1620 used magnetic core memory. The operating principle of core memory is that tiny magnetic rings can hold a direction of magnetism and that by applying a sufficient current through the center of the ring, the direction can be changed. In a plane of core memories, wires are woven together like fabric, with a magnetic core at each intersection. Since each core has two wires going through it (one in the horizontal and vertical directions of the plane), it can be addressed by by activating both wires simultaneusly, each carrying half the required current. Thus the addressed core is affected, but the others in it's row and column are not affected.

The 1620 has 12 planes of memory, each carrying 10,000 cores. The first 6 hold even digits and the remaining hold odd digits. the each bit of of a digit is stored at its particular address in a different plane. Each successive bit in a digit is stored in the plane above its predecessor.

--------------
| Even Check |
--------------
| Even Flag  |
--------------
| Even Eight |
--------------
| Even Four  |
--------------
| Even Two   |
--------------
| Even One   |
--------------
[Odds Omitted]

Having 12 planes of 10,000 cores, the 1620 could hold 20,000 digits of 6 bits on its own. 10,000 even digits and 10,000 odd digits. IBM also sold an additional memory cabinet which could hold another 12 or 24 core planes for 40,000 or 60,000 total digits.

In my simulator I chose to represent the memory cores as a large XML File which the simulator downloads at startup. There are 12 Core Plane nodes, each holding 10,000 individual core nodes. This file is downloaded by the main application (Hence the DownloadMoreRam function), and transformed into the HMTL DOM.

TypeScript (transformed to JavaScript) can then use the document object to access them.

The registers

The 1620 has a number of registers that store data within the processor while it acts on the memory. They do things like store the memory addresses of digits within a field.

I have categorized them into the following categories of registers: Indicators, Digit Registers, and Address Registers. An indicator holds one bit and is typically set automatically when certain conditions are met in the processor. A Digit Register is one digit (6 bits) and holds the four data bits, the flag, and the parity check. These are used for holding calculations and results. Address Registers are used to hold memory addresses.

Registers in my simulator are represented as individual TypeScript classes

The Memory Buffer Register (MBR)

The Memory Buffer Register stores memory as it is read in from the core memory. Since there are two stacks of memory, both are read at the same time, and the Memory Buffer Register is split into an even section and on odd section. These are often considered as individual digit registers: the MBR-E and the MBR-O.

The Memory Data Register (MDR)

The Memory Data Register is a single digit register. It is used to hold a single digit between memory accesses. One example of this is to hold an operand during arithmetic. The first operand is read into MBR and placed into the MDR, then the second can be read into the MBR and the operation can proceed.

The Memory Address Register (MAR)

The memory address register is the first Memory Register. It is directly connected to the Core Memory, and its value is used to select digits. The numeric bits, except for the lowest bit of the lowest digit, are used to select addresses from the Core Memory Planes. The last bit is used to select the MBR-E or the MBR-O when transferring values to the MDR.

The Instruction Registers

There are two Instruction Registers (Three with optional hardware, however I have disregarded the third). These both hold memory addresses. The first (IR-1) is used as the program counter, and is automatically incremented when an instruction completes. The second (IR-2) holds the return address when a sub-routine is called.

The Operand Registers

There are two operand registers, OR-1 and OR-2. They serve the purpose of holding two operands while an instruction is processed. Since digit fields are processed from right to left, memory locations are read into the Operation registers, and decremented to get higher order digits of the operand.

The Operation Register

This register holds the instruction which is being executed. It is two digits wide. It is decoded by the 1620, and it selects which operation is performed.

The Indicators

There are three main indicators used by the 1620: High/Positive, Equal/Zero, and Overflow. The High/Positive (HP) indicator is set if an instruction produces a result which is greater than zero. The Equal/Zero (EZ) indicator is set if an instruction produces a result which is less than zero. The Overflow (OFLOW) indicator is set if an instruction produces a result which is larger than the field pointed to by P.

Operations

Operations in the 1620 are always 12 digits long, and must start on an even bit. A unique feature at the time, it used two operands in every instruction, P and Q. For arithmetic operations, P and Q are addresses of operands, except in immediate operations where Q is used as directly as a value.

The first two bits describe the operation, and the remaining ten bits are split between P and Q, typically pointers to an area in memory. As an operation is executed, the program counter (IR-1) is incremented to the 12th next digit for the beginning of the next operation. None of the literature directly states this, however I suspect that the P.C. is incremented as P and Q are read into OR-1 and OR-2.

O0, O1, P2, P3, P4, P5, P6, Q7, Q8, Q9, Q10, Q11
< Op >  <   P address    >  <     Q address    >
 Code

The following is a table of a few operation codes. It does not list all possible operations. My simulator will strive to implement the operations listed here, however this is not a complete list of all operations which the 1620 could perform. Immediate operations are identical to their regular counterparts, with the exception that Q is used as directly as an operand instead of the address of an operand.

|-----------|----------|------|-----------------------------------|------------------------------|
| Operation | Mnemonic | Code |            Description            |            Action            |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Addition |     A    |  21  | Adds two fields together          | P field + Q field -> P field |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Addition |    AI    |  11  | Adds the P field to the Q Data    | P field + Q data -> P field  |
| Immediate |          |      |                                   |                              |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Branch   |     B    |  49  | Changes the P.C. to the P field   | P field -> P.C.              |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Branch   |    BI    |  46  | Changes the P.C. to the P field   | if (Q indicator)             |
| Indicator |          |      | if the desired indicator is set   |     P field -> P.C.          |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Branch   |    BNI   |  47  | Changes the P.C. to the P field   | if (not Q indicator)         |
|    Not    |          |      | if the desired flag is not set    |     P field -> P.C.          |
| Indicator |          |      |                                   |                              |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Branch   |    BT    |  27  | Saves the P.C.                    | Save P.C.                    |
|  Transmit |          |      | copies the Q field to one before  | Q field -> P field - 1       |
|           |          |      | the P address                     | Pfield -> P.C.               |
|           |          |      | Changes the P.C. to the P field   |                              |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Branch   |     BB   |  42  | Changes teh P.C. back to the      | Saved value -> P.C.          |
|  Back     |          |      | Saved value                       |                              |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Compare  |     C    |  24  | Compare and set indicators        | Pfield ? Qfield -> indicator |
|-----------|----------|------|-----------------------------------|------------------------------|
|   Halt    |     H    |  48  | Stops execution                   |                              |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Transmit |    TF    |  26  | Copys the Q field to the P field  | Qfield -> Pfield             |
|   Field   |          |      |                                   |                              |
|-----------|----------|------|-----------------------------------|------------------------------|
|  Write    |    WN    |  38  | Writes the P field to the         | Pfield prints on Q device    |
| Numeric   |          |      | Output Device specified by Q      |                              |
|-----------|----------|------|-----------------------------------|------------------------------|

Addition

The Addition operation does not perform logical addition, instead it uses a lookup table, in memory to add digit by digit. The Addition table is stored inbetween locations [300 and 400). If the addition of two digits produces a carry, then the flag bit is set in the table. The tens place is selcted by the left operand and the rights place is selected by the right operand.

Overflow occurs when the Q field is has more digits than the P field, or when the addition produces a carry in the highest order digit. If overflow occurs, then the Oferflow indicator is set after the instruction completes. If there are more digits in the P field than the Q field, then leading zeros are used for computation of the result. After an addition, if the sum is greater than 0, the H/P indicator is set. If the sum is exactly zero, then the E/Z flag is set.

The following table demonstrates, along the left is the address up to the tens place. Along the top is the ones place. The 1620 places the left operand in the 10s position of the MAR and the right operand of the MAR in the ones place. The leading ones in the table represent carry bits, and are stored as in the flag bit.

|-------||----|----|----|----|----|----|----|----|----|----|
| index ||  0 |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 |
|=======||====|====|====|====|====|====|====|====|====|====|
|  0030 ||  0 |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0031 ||  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0032 ||  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0033 ||  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0034 ||  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 | 13 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0035 ||  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 | 13 | 14 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0036 ||  6 |  7 |  8 |  9 | 10 | 11 | 12 | 13 | 14 | 15 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0037 ||  7 |  8 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0038 ||  8 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
|-------||----|----|----|----|----|----|----|----|----|----|
|  0039 ||  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 |
|-------||----|----|----|----|----|----|----|----|----|----|

Variations of addition such as subtraction or addition of a single negative operand are performed using the 9's complement (digit = 9 - digit). The 9's complement of a digit is implemented in hardware, and I suspect that it uses the following algorithm, which I determined using truth table analysis: (using C-style boolean operators)

newEight = !(eight || four || two);
newFour  = (four != two);
newTwo   = two;
newOne   = !one;

The 9's complement subtraction operation is performed by complementing the second operand, performing addition with the table, ignoring any carry produced, and then adding one. There is some additional logic, implemented by the 1620 for handling edge cases such as crossing zero, but the following example should suffice.

To subtract the following:
 237
 136

First we complement the second operand:
 237
 863

And perform the table look up addition, to come up with the following sum:
1100

Next the leading carry is disregarded:
 100

And one is added
 101
 
 Which is the correct result.

Comparison

Comparison is implemented using subtraction logic, except that, that the result is not stored in the P value. Essentially the subtract operation is performed but not saved. Only the indicators are set, which allows compare to be comibined nicely with conditional branch operations.

If the result is positive, then P is less than Q, and the HP indicator is set. If the result is zero, then the P is equal to Q, and the E/Z indicator is set. If the result is negative, then P is greater than Q, and neither indicator is set.

Branch Instructions

Branch instructions change the value of the program counter (IR-1). The value of program counter is set to the P value, and execution continues at the location pointed to by P.

Branch Indicator

The branch indicator instruction will branch only if selected indicator is set. Indicators are selected using the second two Q8 and Q9 digits. Q7, Q10, and Q11 are unused. The indicators are selected using the following table:

|-----------|----------|--------|
| Indicator | Selector | Q Data |
|-----------|----------|--------|
| OFLOW     | 14       | 01400  |
| EZ        | 12       | 01200  |
| HP        | 11       | 01100  |
| HP or EZ  | 13       | 01300  |
|-----------|----------|--------|

the Branch Indicator (BI) instruction will branch if the indicator is set, and the Branch No Indicator (BNI) will branch if the indicator is unset.

Subroutines, and the Branch Transmit, and Branch Back instructions

The branch transmit and branch back instructions were designed to be used together to create subroutines. During branch transmit, a single field is passed and the program counter is saved before the branch occurs. Branch back will return to the saved program counter. The saved program counter, does not stack, meaning that the a subroutine cannot itself call another subroutine without losing its own branch back address. (As a side note, I am curious if the 1620 not having stackable subroutines is connected to FORTRAN II not having stackable subroutines.)

In the Branch Transmit, the Q field is copied into the field indicated by (immediate) P - 1. Then the program counter (IR-1) is incremented and saved into the return address register (IR-2). Then the 1620 branches to the address indicated by P.

In the Branch Back instruction, the saved address (IR-2) is placed in the Program Counter (IR-1), and program execution is continued where it left off before the subroutine call.

Transmit Field

The Transmit field (TF) instruction copies the field at Q to the field at P. If the Q field has more digits than the P field, then the OFLOW indicator is set.

Write Numeric

The write numeric instruction writes the field at P to the indicated output device. The output device is chosen by the Q8 and Q9 (disregarding Q7, and Q10; Q11 is used by add-on units). The only devices I currently plan to implement are the typewriter, card reader, and card puncher, however the full device table is shown.

----------------------------
| Device        | Selector |
----------------------------
| TypeWriter    | 01       |
| Tape Punch    | 02       |
| Plotter       | 02       |
| Tape Reader   | 03       |
| Card Puncher  | 04       |
| Card Reader   | 05       |
| Disk Storage  | 06       |
| Printer       | 09       |
----------------------------

Note that the Tape Punch and Plotter are both selected by two. The Tape Reader and Tape Writer cannot be installed at the same time as the plotter is installed.

Halt

This instruction stops execution. P and Q are disregarded.

Example

Earlier in this course, we did an assignment modelling how Charles Babbage attempted to produce tables of quadratics using sucsessive differences. I will attempt to replicate that code using the 1620 now.

The following is a selection from it in LolCode (I promise I actually took this class seriously).

IM IN YR loop UPPIN YR i TIL BOTH SAEM i AN BIGGR OF i AN DIFF OF count AN 1
    series R SUM OF series AN diffs1
    diffs1 R SUM OF diffs1 AN diffs2
    
    I HAS A iAnd2 ITZ SUM OF i AN 2
    
    VISIBLE ":{iAnd2}:>:{series}:>:{diffs1}:>:{diffs2}"
IM OUTTA YR loop

The original program used three variables, series, diffs1, and diffs2. series is the value of the quadratic at any given iteration. diffs1 is the difference between the current value of series and the previous value of series. diffs2 is the difference between the current value of diffs1 and the previous value of diffs1. Inductively, using the properties of polynomial differentiation, diffs1 is also the difference between the current value of series and the next value of series. And the same of diffs2 and diffs1.

To calculate series, all the programmer needs to do is to add diffs1 to series, and then add diffs2 to diffs1.

I have implemented my simulator for the 1620, such that it loads a program from an XML file, and uses XML as its assmbly also, since that allows me to use a COTS parser (although I can't claim that my assembler is functioning yet).

The first thing we will need in our program is the data fields to store series, diffs1, and diffs2. To calculate perfect squares, they start off with the values 1, 3, 2.

    <line type="data" name="series" digits="10" value="1" />
    <line type="data" name="diffs1" digits="10" value="3" />
    <line type="data" name="diffs2" digits="10" value="2" />

In order to create the successive summing, we will start with simply producing one sum:

    <line type="op" code="A" p="series" q="diffs1" />
    <line type="op" code="A" p="diffs1" q="diffs2" />

The first line will add series and diffs1 using table addition, storing the sum in series. The second line will add diffs1 and diffs2, storing the sum in diffs1. This calculates one value in the series, and prepares us to calculate the next.

In order to calculate the next one, we need to branch back to the beginning. In order to do that, we need to introduce a branch instruction and a label for the branch to go to.

    <line label="calculate" type="op" code="A" p="series" q="diffs1" />
    <line type="op" code="A" p="diffs1" q="diffs2" />
    <line type="op" code="B" p="calculate" q="" />

Now we are performing the calculation for ever, but we probably do not want that. Lets say we only want the first 100 squares. To get the first 100, lets introduce a counter variable that will increment and then compare after each calculation.

    <line label="calculate" type="op" code="A" p="series" q="diffs1" />
    <line type="op" code="A" p="diffs1" q="diffs2" />
    <line type="op" code="AI" p="count" q="1" />
    <line type="op" code="C" p="count" q="max" />
    <line type="op" code="BNI" p="calculate" q="HPEZ" />
    
    <line type="data" name="count" digits="3" value="1" />
    <line type="data" name="max" digits="3" value="100" />

Now the code will increment count by one using the Addition Immediate (AI) operation, and compare count and max. Note that count and max both only have three digits, because they only need to go up to 100. Lastly, the program conditionally branches to calculate if count is less than or equal to 100. HPEZ is an alias for 01400, which is the code to check either the High/Positive or Equal/Zero flag is set. BNI branches if both indicators are unset. Essentially the program, increments count, and branches if count is less than or equal to max.

There is however a few problems still:

  1. we can't get the result, because it is never printed.
  2. when the program completes it will continue running and attempt to execute its data.

Lets solve the most glaring issue first: it doesn't stop and attempts to execute it's own data. We'll use the halt instruction to do this:

    <line type="op" code="C" p="count" q="max" />
    <line type="op" code="BNI" p="calculate" q="HPEZ" />
    <line type="op" code="H" p="" q="" />

Since the halt instruction disregards P and Q, I've left them blank, however it does not matter what is placed there, and one could maybe put values there if they were really low on memory.

Next step is to print out the data. We can use the Write Numeric instruction to output series to the typewriter.

    <line label="calculate" type="op" code="A" p="series" q="diffs1" />
    <line type="op" code="A" p="diffs1" q="diffs2" />
    <line type="op" code="WN" p="series" q="TYPEWRITER" />

TYPEWRITER is an alias for 00100 which is the Q data for printing things to the type writer.

At this point we should be ready to assemble this code: Next step is to print out the data. We can use the Write Numeric instruction to output series to the typewriter.

<Program start="400">
    <line address="400" label="calculate" type="op" code="A" p="series" q="diffs1" />
    <line type="op" code="A" p="diffs1" q="diffs2" />
    <line type="op" code="WN" p="series" q="TYPEWRITER" />
    <line type="op" code="AI" p="count" q="1" />
    <line type="op" code="C" p="count" q="max" />
    <line type="op" code="BNI" p="calculate" q="HPEZ" />
    <line type="op" code="H" p="" q="" />
    
    <line type="data" name="series" digits="10" value="1" />
    <line type="data" name="diffs1" digits="10" value="3" />
    <line type="data" name="diffs2" digits="10" value="2" />
    <line type="data" name="count" digits="3" value="1" />
    <line type="data" name="max" digits="3" value="100" />
</Program>

I've added the root tags <Program>, with a start attribute of 400, and also added an address attribute with value 400 to the first line. 400 is the first position after the add table, which makes it a logical place to start the program data. While the program could start at 0, it does not make sense to do so, since there would not be a lot of space for the program (the 1620 has a multiplication table before the addition table which I have disregarded for the time being). The <start> attribute signals to the assembler that when the machine starts it should branch directly to 400. The <address> attribute of the first instruction indicates that it should place the lines starting at memory address 400.

As we assemble this program, lets keep in mind that the operations are read left to right: starting at the lowest numbered address, and moving to the highest number address. The data is the opposite, it is addressed at the highest numbered address, and the lowest ordered digit, and is read until it reaches the lowest numbered address and the highest order digit, which is indicated by the flag bit on the highest order bit.

TODO: assemble the the thingey.

Instructions for Running the simulator

The simulator is written in TypeScript, XML, XSLT, HTML. It is a combination of a few technologies which I wanted to try out together for a while now, but had not had the time. In order to build it, the user must have Node JS and Gulp which is a build automation tool for Node JS. Gulp can be installed with Node Package Manager using npm install --global gulp-cli (maybe needs root permission), this will allow the user to use the gulp command.

To build the program run the following:

> npm install # this downloads all the dependencies ./node_modules
> gulp build

The program is then built in the ./target/ directory and ready to be run:

> gulp serve

That command will run a static http server on localhost:8080 which serves files from ./target/. Then point your favorite web browser to localhost:8080/index.html, and the simulator should take a little while to download and install the ram. I tested it on Firefox ESR 52, YMMV with other browsers, but it will definitely not work on Internet Explorer < 8, because the XSLT API is different in IE8.

simulator link