The 1005 is what happens when a unit-record machine grows up. The card path stays. The drum printer stays. The 6-bit BCD character set stays. What goes is the plug-board: in its place, nine hundred and sixty-one characters of magnetic core, four registers, and twenty-one instructions of a real assembly language. This manual is a complete guide to writing, assembling, and debugging programs for the 1005's SAAL — single-address assembler — through every shipped sample, with every flag, register, and quirk documented along the way.
The UNIVAC 1005 was introduced by Sperry-Rand in 1966 as the stored-program successor to the 1004. It kept everything the unit-record shops were already trained on: the eighty-column card hopper, the four-hundred-card-per-minute reader, the one-hundred-thirty-two-column drum printer, and the same 6-bit BCD character set the 1004 used. What it added was a processor: a small, slow, character-addressable stored-program machine with twenty-one instructions, four registers, and a magnetic-core memory holding nine hundred and sixty-one characters of program and data combined.
For the operators trained on plug-boards, this was new. The 1004's program was a
pattern of patch cords; you installed it by physically slotting in a different board.
The 1005's program was a string of characters in core memory; you installed it by
reading a stack of source-listing cards through the same hopper that fed it data, by
letting the assembler turn that source into a memory image, and by pressing
RUN. There was no plug-board. There were no patch cords. There was source
code, and there was an assembler, and there was a debugger, and the workflow began to
look — for the first time on this product line — like programming in the modern sense
of the word.
The 1004 stored its program in the wiring. The 1005 stored its program in the same kind of memory it stored data in. This is the entire difference between the two machines.
The assembly language is called SAAL, the Sperry-Rand Assembly
Language. It is a single-address assembler — most instructions name one memory address
and operate against the implicit accumulator A — with field-oriented
variants for moving and arithmetic on multi-character runs of BCD data. Programs are
tight: a complete production payroll might fit in six hundred characters of core,
because every instruction occupies twelve characters of memory and you only have nine
hundred and sixty-one to spend, minus the buffers the I/O subsystem reserves at the
bottom of the address space.
The emulator gives you the whole stack. A SAAL source editor with sample programs.
A working two-pass assembler that resolves symbols, computes addresses, and reports
errors with source-line context. A character-addressable virtual machine that you can
step one instruction at a time. A live memory display showing all nine hundred and
sixty-one cells, color-coded by region. A console with the same flags the real machine
had — Overflow, End-File, Equal, Less, Greater — and a register panel showing the
program counter P, the accumulator A, and the index
X as the program runs.
The 1005 is character-addressable. Memory is addressed one BCD character at a
time, not one word, not one byte — one six-bit decimal-coded character. There are nine
hundred and sixty-one of them, numbered 000 through 960. An
instruction that names address 080 is referring to the first character of
the print buffer; an instruction that names address 292 is referring to
the first character of working storage; an instruction that names 080+5
is referring to print column six.
The bottom of memory is reserved for the I/O subsystem. The card reader, the drum
printer, and the card punch each have a buffer at a fixed address. Your program lives
and runs in the working-storage region above them, which the assembler treats as the
default origin for the ORG directive.
| Symbol | Range | Length | Use |
|---|---|---|---|
CARD | 000 – 079 | 80 chars | Card-read input buffer. The RDC instruction reads one card from the hopper into these eighty characters. |
PRT | 080 – 211 | 132 chars | Print line buffer. The PRT instruction ejects this buffer to the drum printer as one printed line, then clears it to spaces. |
PCH | 212 – 291 | 80 chars | Card-punch output buffer. The PCH instruction punches one card from this region, then clears it. |
WORK | 292 – 960 | 669 chars | Free for program code, constants, and working storage. The assembler defaults ORG to 292. |
The four symbols CARD, PRT, PCH, and
WORK are predefined in the assembler, so you can write
LDA CARD+10 or MVC CARD,PRT,80 without ever computing the
raw address yourself. Almost no SAAL program references the bottom-of-memory addresses
numerically; the symbols are how you talk to the I/O subsystem.
Every instruction occupies twelve characters of core, regardless of how many operands it has. This is important for two reasons. First, when you place a label on an instruction, the label resolves to that twelve-character cell — so a jump to the label lands on the first character of the instruction word. Second, when you sit down to budget your memory, you can do so in instructions: the working-storage region holds at most fifty-five instructions plus whatever data you reserve. Real programs are usually thirty to fifty instructions long.
The processor has four named registers. Three of them — A, X,
and P — are visible in the console. The fourth, B, is used
internally by the field instructions and is not directly exposed but can be referenced
symbolically in some assembler contexts.
| Register | Width | Role |
|---|---|---|
A | 1 char | The accumulator. Single-address instructions like LDA, STA, ADD, SUB operate on the accumulator. It holds one BCD digit (or one BCD character) at a time. |
B | internal | Working register used by the field-form arithmetic and move instructions. The assembler manages it; you do not normally store to or load from it directly. |
X | 3 chars | The index register. Used to address memory by an offset that varies at run-time — for stepping through a card buffer one character at a time, or indexing a table. |
P | 3 chars | The program counter. Visible on the console. After every instruction, advances by twelve to the next instruction word; jumps overwrite it. |
In addition to the registers, the processor carries five condition flags, all visible
as console lamps. Most of them are set by the comparison and arithmetic instructions
and consumed by the conditional branches. EOF is the exception: it is set
by the card reader when the hopper empties and the program asks for one more card.
| Flag | Lamp | Set by | Tested by |
|---|---|---|---|
OV | OVERFLOW | An ADD or SUB that exceeds a single BCD digit. Latches until tested. | JOV (which clears it). |
EQ | EQUAL | CMP when the two compared fields match exactly. | JEQ, JNE. |
LT | LESS | CMP when the first operand is less than the second. | JLT. |
GT | GREATER | CMP when the first operand is greater than the second. | JGT. |
EOF | END FILE | The card reader on the read attempt that follows the last card. | JEF. |
Three flags — EQ, LT, GT — share state. A
CMP sets exactly one of them. The conditional branches read this state
until the next CMP or arithmetic instruction overwrites it. OV
is independent and latches until JOV tests and clears it. EOF
latches once and stays set until HLT.
A SAAL source line is the smallest unit of input the assembler understands. It is a line of text with up to four named columns: a label, an opcode, a comma-separated operand list, and a comment. Whitespace separates the columns; commas separate the operands inside the operand list.
A label is an identifier in column one — that is, the very first character of the line
is non-blank and not the asterisk that introduces a comment line. The assembler binds
the label to the address of whatever follows it on the same line: an instruction, a
DC directive that places data, a DS directive that reserves
space, or an EQU directive that gives the symbol a numeric value.
Labels are global to a single source file and may be referenced before they are
defined — the assembler is two-pass.
Operands take three forms. A bare number — 080 — is a literal address.
A symbol — CARD, SUM, LOOP — is resolved to its
address at assembly time. A symbol-plus-offset — CARD+5, PRT+25,
SUM-1 — is a base symbol with a constant offset added or subtracted at
assembly time. The offset must be a literal integer; expressions involving other
symbols are not supported.
Some instructions take fewer operands than others, and a missing operand is written as
an empty position between commas. CLR PRT,,132 has an empty middle
operand because CLR only needs a destination address and a length. This
convention is important because the position of an operand carries meaning;
omitting one without leaving a placeholder can shift the others.
A line that begins with an asterisk in column one is a whole-line comment. Comments that follow the operand field on the same line as an instruction are introduced by either an asterisk or by sufficient whitespace and an asterisk; both are accepted. Comments compile to zero bytes; they exist only in the source.
Conventional 1966 SAAL listings used four-space columns: label in 1–8, opcode in 10–14, operands starting at 16, comment starting at column 40. The emulator's editor is column-flexible and any whitespace pattern that produces valid tokens will assemble; the conventional layout exists to make six-month-old listings readable to someone who has never seen them before. Worth following.
These four tables enumerate every instruction, every assembler directive, and every operand form the SAAL assembler understands. Bookmark this section. You will scroll back to it constantly while you write your first half-dozen programs.
Instructions fall into five rough groups: load and store for the accumulator, single-character arithmetic on the accumulator, field arithmetic on multi-character BCD runs, memory utilities for moving and clearing regions, control transfer for branches, and I/O for the peripherals. Each instruction occupies twelve characters of core regardless of operand count.
| Op | Operands | Effect |
|---|---|---|
LDA | addr | Load accumulator. A ← M[addr]. One BCD character. |
STA | addr | Store accumulator. M[addr] ← A. |
| Op | Operands | Effect |
|---|---|---|
ADD | addr | A ← A + M[addr] as a single BCD digit. Sets OV if the sum exceeds 9. |
SUB | addr | A ← A − M[addr] as a single BCD digit. Underflow wraps without flagging. |
| Op | Operands | Effect |
|---|---|---|
ADF | src,dst,len | Add field. M[dst..dst+len] ← M[dst..dst+len] + M[src..src+len] as a multi-digit BCD field. Right-justified; carries propagate. |
SBF | src,dst,len | Subtract field. Same shape; underflow wraps. |
INC | addr,,len | Increment a len-character BCD field at addr by one. The middle operand is unused. |
DEC | addr,,len | Decrement a len-character BCD field by one. |
CMP | a,b,len | Compare two len-character fields. Sets exactly one of EQ, LT, GT. Does not modify either field. |
| Op | Operands | Effect |
|---|---|---|
MVC | src,dst,len | Move len characters from src to dst. Source and destination may overlap; copy is left-to-right. |
CLR | addr,,len | Clear (fill with spaces) len characters at addr. Middle operand unused. |
| Op | Operands | Effect |
|---|---|---|
JMP | addr | Unconditional jump. P ← addr. |
JEQ | addr | Jump if the EQ flag is set. |
JNE | addr | Jump if the EQ flag is not set. |
JLT | addr | Jump if the LT flag is set. |
JGT | addr | Jump if the GT flag is set. |
JOV | addr | Jump if the OV flag is set; clears the flag. |
JEF | addr | Jump if the EOF flag is set (the read past the last card). |
| Op | Operands | Effect |
|---|---|---|
RDC | — | Read one card from the hopper into CARD. Sets EOF if the hopper is empty at read time. |
PRT | — | Eject the print buffer to the drum printer as one printed line, then clear it to spaces. |
PCH | — | Punch one card from the punch buffer, then clear it. |
HLT | — | Halt the processor. The HALT lamp lights. Reload the hopper and press RUN to start over. |
Directives are not instructions. They tell the assembler something about the source itself: where to place code, what symbols mean, what data to lay down, where the source ends. They consume no instruction-cycle time at run-time, though they do place data into memory.
| Directive | Form | Effect |
|---|---|---|
ORG | ORG addr | Set the assembly origin to addr. Subsequent instructions and data place at this address. Default is 292 (the start of WORK). |
EQU | LBL EQU n | Define the symbol LBL to mean the literal numeric value n. Used for constants, lengths, fixed addresses. |
DC | LBL DC 'TEXT'LBL DC 12345 | Define a constant. The character form 'TEXT' places the literal characters at the assembly origin and binds LBL to that address. The numeric form places the BCD digits. |
DS | LBL DS n | Define storage. Reserves n characters at the current origin (typically initialized to spaces) and binds LBL to that address. |
END | END | End of source. Required as the final line. The assembler stops processing here. |
| Form | Example | Resolves to |
|---|---|---|
| literal address | 080 | The numeric address itself. 1- to 3-digit decimal. |
| symbol | CARD, SUM, LOOP | The address bound to the symbol by a label, an EQU, a DC, or a DS. Predefined symbols: CARD, PRT, PCH, WORK. |
| symbol + offset | CARD+5, PRT+25, SUM-1 | The symbol's address with the literal integer added or subtracted at assembly time. |
| empty | CLR PRT,,132 | Position is intentionally blank. Used for instructions that take fewer operands than the comma layout suggests. |
The 1005 has the same three peripherals the 1004 had: a card reader, a drum printer, and a card punch. They are addressed not as devices but as buffers in low memory, and programs interact with them by moving characters into or out of those buffers and then issuing the I/O instruction that ejects or fills them.
RDC
The reader runs at four hundred cards per minute. RDC takes the next card
from the hopper and places its eighty BCD characters into CARD through
CARD+79. There are no other operands; the read is always into the same
fixed location. If the hopper is empty when RDC is issued, the
EOF flag is set, the contents of CARD are unspecified
afterwards, and the program is expected to detect this with the next JEF
and dispatch to its end-of-file routine.
The standard read-loop idiom is therefore:
Most shipped samples use exactly this shape.
PRT
The drum printer accepts a one-hundred-thirty-two-character line buffer at
PRT through PRT+131. PRT ejects the buffer as
one printed line, advances the paper one line, and clears the buffer to spaces. There
is no notion of column-formatting beyond what you do with MVC and
CLR before issuing the eject. To print HOURS: at column ten
and the value of SUM at column twenty, you MVC the literal
'HOURS:' into PRT+9, MVC SUM into
PRT+19, and then issue PRT.
The buffer auto-clears after each PRT, so subsequent lines start from
spaces and you do not need to CLR between them. You do need to CLR
at program start if anything assembled into PRT as initial data.
PCH
The punch is the inverse of the reader: lay an eighty-character output card into the
PCH buffer with MVC instructions, then issue PCH
to punch the card. Buffer auto-clears after each punch. None of the shipped samples
use the punch, but PCH is wired up and works; it is the standard way to
pass intermediate results between two passes of a multi-pass program.
Six idioms cover most of what 1005 programmers wrote in 1966. Each is short. Each composes with the others. Internalize these and the rest of SAAL programming is mostly arrangement.
The fundamental shape of any program that processes a deck. Read, branch on EOF,
process, jump back. Variations differ only in what goes between the JEF
and the JMP.
Sum a numeric field across the entire deck into a working-storage cell, then print
the total in the trailer. The 1005's ADF instruction makes this nearly
trivial — one line per card.
Tally cards by incrementing a counter once per RDC. The trailer prints
the count.
The classic three-part report. Print a header line at program start. Print one detail line per card. Print a trailer line after EOF. The header is printed before the read loop begins; the trailer is printed in the post-EOF block.
CMP sets one of EQ, LT, GT; the
following branch consumes it. The two-card-into-one pattern that the SORTEM
sample uses is built from this primitive.
Like the 1004, the 1005 has no MUL instruction. Multiplication is
repeated addition: zero a result, load the multiplier into a counter, loop adding the
multiplicand to the result and decrementing the counter, exit when the counter
reaches zero. The PAYROLL sample uses this pattern and the implementation
is slightly more verbose than the 1004's because the 1005 expresses each step as a
separate instruction.
Six sample programs ship with the emulator. Each is short enough to read in full and
each demonstrates one or two of the patterns from the previous section. Load any of
them with the Load Sample dropdown and step through them with the
STEP key on the console.
The minimal program. No card reading at all: lay a literal string into the print
buffer with MVC, eject the line with PRT, halt. Three
instructions and one constant.
Loading HELLO, pressing LOAD, then RUN prints
a single line at column ten of the printer paper. The program stops at HLT
and the HALT lamp lights.
The "hello world" of card programs. Read a card, print it, branch back. Demonstrates the read-loop pattern in its simplest form.
The accumulator pattern (Pattern 2). Each card has a five-digit field at columns
one through five; the program reads the deck, adds each card's field into a running
total in SUM, and prints the total at column ten of the trailer line
after EOF.
With the shipped deck of cards bearing values 00125, 00250,
00075, 01000, 00500, the trailer reads 01950.
The card-counter pattern (Pattern 3). Increments COUNT by one on every
successful read, prints the count after EOF.
The most ambitious sample. Reads three cards into three reserved buffers, performs
a bubble sort by repeated CMP-and-swap, then prints the three cards
in sorted order. Demonstrates the compare-and-branch pattern (Pattern 5) and shows
how to use working storage as fixed-position arrays. The keys are the five-character
fields at the start of each card.
The full program is a few instructions longer because a real bubble sort over three records needs two compare-swap passes to be correct. The sample in the emulator includes both passes.
The headline sample. Each card carries a name, an hours value, and a rate value. The program computes gross = hours × rate by repeated addition (Pattern 6), then prints a stub line bearing the name and the gross. With the shipped deck (forty hours at twenty-five, thirty-five hours at thirty, twenty hours at fifteen) the gross-pay column reads 01000, 01050, 00300.
This is real 1966 math: forty additions to compute one multiplication, executed at roughly thirty thousand instructions per second. The sample executes in a fraction of a second on the emulator; on the historical machine it would have completed in a few milliseconds.
A small list of things to know before they bite. The 1005 is forgiving in some ways (it has source code, after all, and an assembler that catches syntax errors) but strict in others, and most of its strictness is a direct consequence of the small memory and the BCD character architecture.
Memory is finite and small. Subtracting the I/O buffers leaves six hundred and
sixty-nine characters of working storage, of which an instruction occupies twelve.
A program of fifty-five instructions plus no data exhausts the address space. Real
programs of the era ran twenty to forty instructions plus a handful of constants and
buffers. If your program won't fit, you must factor it: punch intermediate results
via PCH, then run the punched deck through a second assembled program
that completes the work. This is how 1966 programmers handled large jobs, and it is
also why PCH is a first-class instruction.
All arithmetic is BCD on signed decimal digits. There is no binary, no floating-point,
no bit manipulation, and no shift instruction. A "field" is a run of BCD characters,
typically right-justified, treated as a multi-digit number. Overflow on an
ADD sets the OV flag; the wider ADF propagates
carries within the field but wraps without flagging at the field's leftmost digit.
You cannot store a Boolean in one bit of a character; the smallest unit of state is
one whole character.
ADD is rarely what you want
ADD and SUB operate on one BCD digit at a time. They are
not how you sum a column of dollar amounts; ADF is. The single-character
forms exist for the kinds of digit-shuffling that show up inside conversion routines
and packed-decimal arithmetic. New SAAL programmers often reach for ADD
first; they almost always meant ADF.
Both PRT and PCH clear their buffers after ejection. This
is convenient for sequential output but it means you cannot rely on previously-laid
content surviving an eject. If a detail line and a trailer line share a constant
label at column one, you must lay it down twice — once before the detail
PRT and again before the trailer PRT. The
FORMAT-style report (Pattern 4) handles this correctly by re-laying the
header text into the buffer for each line that uses it.
EOF flag latches
Once the card reader has signaled EOF on a read past the end of the
hopper, the flag stays set until the next HLT resets the machine. If
your post-EOF code re-issues RDC, the read will complete (with whatever
stale data the reader returns) and EOF will remain set; JEF
will continue to branch true. Always treat EOF as a terminal state.
You can JMP DONE on line three even though DONE isn't
defined until line forty. The assembler runs two passes: pass one collects all
labels and assigns addresses; pass two emits instructions with addresses substituted.
What does not work is using a forward-defined symbol's value in an
EQU expression: EQU resolves at the moment the directive
is encountered.
The emulator simplifies a few things. The historical 1005 had additional instructions for tape and disk I/O that this emulator does not implement; it had memory protection registers that the emulator omits; and its peripheral I/O was overlapped with computation, where the emulator runs sequentially. The instruction semantics, memory map, register set, flag conventions, and SAAL syntax are faithful to the documented 1966 reference.
The 1005 was sold into 1004 shops as a drop-in replacement: same hopper, same printer, same operator console, same training. The pitch was that programmers could rewrite a 1004 plug-board as a SAAL program in roughly the same number of steps, using roughly the same conceptual primitives, and end up with a program that ran at the same speed but could be edited as text instead of rewired by hand. The transition from plug-boards to source code was therefore a transition of medium more than of method: programmers used the new machine to write programs that looked, for the most part, like the plug-boards they were replacing. The interesting changes — multiplication done other than by repeated addition, sorts that exceeded the deck's natural order, programs that called subroutines — came years later and on different machines.
One of three companion manuals for the field-guide emulators. Single-file HTML, no build step, no JavaScript. Print-friendly: most browsers will produce a clean twelve-to-fourteen-page printout suitable for keeping by the keyboard.
↑ Field guide (catalog)
UNIVAC 1005 emulator