contents
  1. IThe machine
  2. IIMemory & registers
  3. IIIThe SAAL line
  4. IVReference tables
  5. VThe peripherals
  6. VICommon patterns
  7. VIIWorked examples
  8. VIIILimits & gotchas
Programmer's Manual · Plate II

UNIVAC 1005 · programmed in characters.

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.

Manual no.
II · 1st ed.
For emulator
UNIVAC 1005 · stored-program reconstruction
Reading time
~35 minutes end-to-end
Audience
Programmers new to character-addressable assembly
§ I

The machine.

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.

§ II

Memory & registers.

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 memory map

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.

SymbolRangeLengthUse
CARD000 – 07980 charsCard-read input buffer. The RDC instruction reads one card from the hopper into these eighty characters.
PRT080 – 211132 charsPrint line buffer. The PRT instruction ejects this buffer to the drum printer as one printed line, then clears it to spaces.
PCH212 – 29180 charsCard-punch output buffer. The PCH instruction punches one card from this region, then clears it.
WORK292 – 960669 charsFree 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.

Instructions are twelve characters wide

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 four registers

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.

RegisterWidthRole
A1 charThe 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.
BinternalWorking 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.
X3 charsThe 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.
P3 charsThe program counter. Visible on the console. After every instruction, advances by twelve to the next instruction word; jumps overwrite it.

The five flags

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.

FlagLampSet byTested by
OVOVERFLOWAn ADD or SUB that exceeds a single BCD digit. Latches until tested.JOV (which clears it).
EQEQUALCMP when the two compared fields match exactly.JEQ, JNE.
LTLESSCMP when the first operand is less than the second.JLT.
GTGREATERCMP when the first operand is greater than the second.JGT.
EOFEND FILEThe 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.

§ III

The SAAL line.

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.

LOOP ADF CARD+5,SUM,5 * sum 5-digit field LABEL OPCODE OPERANDS COMMENT resolves to an address add-field instruction source, dest, length ignored by assembler Read as: "Label this address LOOP. Add the 5-character field at CARD+5 into SUM."
Fig. 1 The four columns of a SAAL source line.

Labels

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

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.

Comments

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.

Convention

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.

§ IV

Reference tables.

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.

The twenty-one instructions

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.

Load & store · accumulator

OpOperandsEffect
LDAaddrLoad accumulator. A ← M[addr]. One BCD character.
STAaddrStore accumulator. M[addr] ← A.

Single-character arithmetic · accumulator

OpOperandsEffect
ADDaddrA ← A + M[addr] as a single BCD digit. Sets OV if the sum exceeds 9.
SUBaddrA ← A − M[addr] as a single BCD digit. Underflow wraps without flagging.

Field arithmetic · multi-character BCD

OpOperandsEffect
ADFsrc,dst,lenAdd field. M[dst..dst+len] ← M[dst..dst+len] + M[src..src+len] as a multi-digit BCD field. Right-justified; carries propagate.
SBFsrc,dst,lenSubtract field. Same shape; underflow wraps.
INCaddr,,lenIncrement a len-character BCD field at addr by one. The middle operand is unused.
DECaddr,,lenDecrement a len-character BCD field by one.
CMPa,b,lenCompare two len-character fields. Sets exactly one of EQ, LT, GT. Does not modify either field.

Memory utilities

OpOperandsEffect
MVCsrc,dst,lenMove len characters from src to dst. Source and destination may overlap; copy is left-to-right.
CLRaddr,,lenClear (fill with spaces) len characters at addr. Middle operand unused.

Control transfer

OpOperandsEffect
JMPaddrUnconditional jump. P ← addr.
JEQaddrJump if the EQ flag is set.
JNEaddrJump if the EQ flag is not set.
JLTaddrJump if the LT flag is set.
JGTaddrJump if the GT flag is set.
JOVaddrJump if the OV flag is set; clears the flag.
JEFaddrJump if the EOF flag is set (the read past the last card).

I/O & control

OpOperandsEffect
RDCRead one card from the hopper into CARD. Sets EOF if the hopper is empty at read time.
PRTEject the print buffer to the drum printer as one printed line, then clear it to spaces.
PCHPunch one card from the punch buffer, then clear it.
HLTHalt the processor. The HALT lamp lights. Reload the hopper and press RUN to start over.

Assembler directives

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.

DirectiveFormEffect
ORGORG addrSet the assembly origin to addr. Subsequent instructions and data place at this address. Default is 292 (the start of WORK).
EQULBL EQU nDefine the symbol LBL to mean the literal numeric value n. Used for constants, lengths, fixed addresses.
DCLBL 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.
DSLBL DS nDefine storage. Reserves n characters at the current origin (typically initialized to spaces) and binds LBL to that address.
ENDENDEnd of source. Required as the final line. The assembler stops processing here.

Operand forms

FormExampleResolves to
literal address080The numeric address itself. 1- to 3-digit decimal.
symbolCARD, SUM, LOOPThe address bound to the symbol by a label, an EQU, a DC, or a DS. Predefined symbols: CARD, PRT, PCH, WORK.
symbol + offsetCARD+5, PRT+25, SUM-1The symbol's address with the literal integer added or subtracted at assembly time.
emptyCLR PRT,,132Position is intentionally blank. Used for instructions that take fewer operands than the comma layout suggests.
§ V

The peripherals.

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.

The card reader · 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:

READ LOOPidiom
LOOP RDC * read next card JEF DONE * branch out at EOF ... * process the card in CARD..CARD+79 JMP LOOP * back for another DONE ...

Most shipped samples use exactly this shape.

The drum printer · 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.

The card punch · 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.

§ VI

Common patterns.

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 read loop

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.

READ LOOPpattern-1
LOOP RDC * read next card JEF DONE * EOF? exit ... * per-card work JMP LOOP DONE ... * trailer logic HLT

The accumulator

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.

SUM A FIELDpattern-2
LOOP RDC JEF DONE ADF CARD,SUM,5 * sum 5-digit field at start of card JMP LOOP DONE MVC SUM,PRT+10,5 * print total PRT HLT SUM DC 00000 * 5-digit BCD accumulator

The card counter

Tally cards by incrementing a counter once per RDC. The trailer prints the count.

CARD COUNTpattern-3
LOOP RDC JEF DONE INC COUNT,,5 * count++ JMP LOOP DONE MVC COUNT,PRT+10,5 PRT HLT COUNT DC 00000

The header / detail / trailer report

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.

REPORTpattern-4
START MVC HEAD,PRT+10,20 * lay down header PRT * eject header line LOOP RDC JEF DONE MVC CARD,PRT+10,40 * detail line PRT JMP LOOP DONE MVC TAIL,PRT+10,20 PRT HLT HEAD DC 'PAYROLL REPORT 1966' TAIL DC 'END OF REPORT '

The compare-and-branch

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.

SORT TWO CARDSpattern-5
CMP A,B,5 * compare 5-char keys JLT A_LO * A < B JGT A_HI * A > B ... * fall through = equal

The multiply via repeated add

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.

HOURS × RATEpattern-6
MVC ZERO,GROSS,5 * gross = 0 MVC HRS,LOOP_N,3 * loop counter = hours LOOP CMP LOOP_N,ZERO,3 JEQ DONE * exit when counter = 0 ADF RATE,GROSS,5 * gross += rate DEC LOOP_N,,3 * counter-- JMP LOOP DONE ... ZERO DC 00000 GROSS DS 5 LOOP_N DS 3
§ VII

Worked examples.

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.

HELLO — print a literal message

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.

HELLOsample-1
ORG 292 MVC MSG,PRT+10,12 * lay string into print buffer PRT * eject the line HLT MSG DC 'HELLO, 1005!' END

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.

ECHO — print every card

The "hello world" of card programs. Read a card, print it, branch back. Demonstrates the read-loop pattern in its simplest form.

ECHOsample-2
ORG 292 LOOP RDC * read next card JEF DONE * EOF? quit MVC CARD,PRT,80 * copy card image to print PRT * print the line JMP LOOP * read another DONE HLT END

ADDEM — sum a numeric field

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.

ADDEMsample-3
ORG 292 LOOP RDC JEF DONE ADF CARD,SUM,5 * SUM += card cols 1-5 JMP LOOP DONE MVC SUM,PRT+10,5 * print total PRT HLT SUM DC 00000 END

With the shipped deck of cards bearing values 00125, 00250, 00075, 01000, 00500, the trailer reads 01950.

COUNT — tally cards

The card-counter pattern (Pattern 3). Increments COUNT by one on every successful read, prints the count after EOF.

COUNTsample-4
ORG 292 LOOP RDC JEF DONE INC COUNT,,5 * count++ JMP LOOP DONE MVC COUNT,PRT+10,5 PRT HLT COUNT DC 00000 END

SORTEM — bubble-sort three cards

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.

SORTEMsample-5 (excerpt)
ORG 292 RDC MVC CARD,REC1,80 RDC MVC CARD,REC2,80 RDC MVC CARD,REC3,80 * — bubble sort: REC1 vs REC2 — CMP REC1,REC2,5 JLT CHK23 MVC REC1,TEMP,80 MVC REC2,REC1,80 MVC TEMP,REC2,80 CHK23 ... * REC2 vs REC3 ... * — print the sorted records — MVC REC1,PRT,80 PRT MVC REC2,PRT,80 PRT MVC REC3,PRT,80 PRT HLT REC1 DS 80 REC2 DS 80 REC3 DS 80 TEMP DS 80 END

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.

PAYROLL — hours × rate via repeated add

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.

PAYROLLsample-6 (core loop)
PROC MVC ZERO,GROSS,5 * gross = 0 MVC CARD+20,LCNT,3 * loop counter = hours field LOOP CMP LCNT,ZERO,3 JEQ EMIT * done multiplying ADF CARD+25,GROSS,5 * gross += rate field DEC LCNT,,3 JMP LOOP EMIT MVC CARD,PRT+5,20 * name MVC GROSS,PRT+30,5 * gross PRT JMP READ * back for next card ZERO DC 00000 GROSS DS 5 LCNT DS 3
§ VIII

Limits & gotchas.

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.

Nine hundred and sixty-one characters is the entire universe

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.

BCD decimal, not binary

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.

Single-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.

The print buffer auto-clears, but the punch buffer also auto-clears

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.

The 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.

Symbol resolution is two-pass and forward references work

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.

Differences from the historical 1005

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.

A historical note

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.

This manual

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

Sibling manuals

Manual no. II · first edition · pre-microprocessor computing