Singer System-10

Being designed in the US, this machine wouldn't normally rate a place on this page. However it is so unusual that it deserves mention somewhere on the Web, and no-one else seems to have done so. So here it is. This is put together from memory, so there may be errors. Corrections & additions are welcome. Cover of a Singer-10 manual


Designed in the late 1960's, the S-10 (as it was known) can trace its parentage to the IBM machines of the day. It was designed for commercial EDP work. There was an RPG-II compiler, although the company I worked for used assembler exclusively.


The S-10 had a byte (or "character") length of 6 bits, the internal character code being equivalent to ASCII characters 0x20..0x5F, with 0x20 subtracted, so mapping into the values 0x00..0x3F. Multi-character numbers were processed by repeated CPU cycles.

The 6 bits of a character were referred to as 1,2,4,8,Y,Z, corresponding to the ASCII binary weights 1,2,4,8,16,32 respectively. ASCII bits 32 and 128 were discarded in the conversion.

Number Representation

The S-10 was a big-endian machine, with the most-significant character of a multi-character number stored at the lowest of a contiguous range of addresses. Negative sign was flagged by setting the Z bit on the least-significant digit, so mapping 0..9 to P..Y. For all valid digits, the Y bit is set to 1. It follows that there are two valid representations of zero: '0' and 'P'. The numeric instructions treated these identically (apart from the sign).

Memory Addressing

A hardware switch-panel assigned memory in blocks of 1000 characters, to up to 20 "partitions", plus a common area. There was a 1:1 correspondence between partitions and concurrent tasks. The physical memory ranged up to 65536 characters, rounded down to 65000, since memory was partitioned in 1000-character blocks. Memory addresses were signed decimal numbers, ranging -9999..+9999, with separate -0 and +0 values. Positive memory addresses point into the current task's partition, while negative numbers point into the "common" area, shared by all partitions.

Partitions were of 2 types, user and blind. User partitions were controlled by a human interface, ie a keyboard or video terminal. Blind partitions were controlled indirectly, by commands issued from a user partition. Blind partitions were often used to support shared printers, modem interfaces, etc. They could also be used to run background tasks.

The above applies to both Model-20 and Model-21 CPU's. The Model-21 could address extended memory (above 9999), by setting bits in the 'Y' row of a memory address to zero. The resulting Y fields were complemented, and the result taken as a binary base-value, scaled by 10,000. (This was the only use of binary notation in the machine.)

Memory was ferrite-core, with a cycle time of 3.3uS. With a character-serial architecture, it therefore took 33uS to fetch an instruction. There was no pipelining (since almost every function involved a memory access, there was little opportunity).

Address Indexing

Each partition had a set of dedicated memory locations (in its partition area), which provided 3, 4-character index registers (X1,X2,X3), a condition-code register, and a buffer address and count used by the I/O instructions. If the index field (AX, BX in the tables below) was set to a non-zero value, the index register was added to the corresponding memory address. In the following instruction descriptions, the term "address" includes indexing where appropriate.


Char 0 1 2 3 4 5 6 7 8 9
Y 1 1 1 1 1 1 1 1 1 1
8 LA
A1 A2 A3
B1 B2 B3
All S-10 instructions were 10 characters long, aligned on a 10-character boundary. The format of instructions is shown (Model-20 CPU).

Each instruction comprised two memory addresses (address, sign, index register), two length values (LA, LB, range 0..9, where 0 denotes 10), and the operation code. In general, these are the source and destination, or operand-1 and operand-2 / destination.


Operation Codes
0 0000 R Read from disk/partition
1 0001 W Write to disk/partition
2 0010 AA Add address (S25)
3 0011 MA Move address
4 0100 A Add
5 0101 D Divide
6 0110 M Multiply
7 0111 S Subtract
8 1000 MC Move characters
9 1001 MN Move numeric
10 1010 SM Set mode
11 1011 BC Branch conditional/ Link
12 1100 E Edit (ie output conversion)
13 1101 FN Form numeric (ie input conversion)
14 1110 C Compare
15 1111 X Exchange
Branch Conditions
0 Never (ie NOP)
1 Lower
2 Equal
3 Higher
4 Overflow
5 Always
6 Store link: don't branch
7 Service Request
8 Branch & yield
Read from Disk or Partition From disk, read a fixed block of 100 characters (1 disk sector). Address A specified the "disk address" (ie drive, head, track, sector), while address B was the target address.
From partition, Read IO inputs a character string from the MTIOC. LA & LB are concatenated, allowing up to 100 characters to be input. Input terminated when an ASCII control character was received. If the string supplied was shorter than LA+LB, the buffer was automatically space-filled.
Read Control (IO) operates similarly to Read IO, however the character mapping is altered, so that the punctuation signs "space" .. "/" map to S-10 characters 0..? The only use (afaik) of this instruction is in booting the system.

Write to Disk/Partition: the converse of Read, above.
Write IO: the converse of Read IO.
Write Control (IO) operates similarly to Write IO, however the character mapping from S-10 to ASCII is changed. The characters A..Z now output as the corresponding ASCII control codes. Their effects depend on the IO device selected.

Move Character concatenated the LA, LB values, to get a length of 1..100 characters. This number of characters was copied from address A to B.

Move Numeric takes a numeric string at address A, length LA chars, and copies it (truncating or padding as needed) to address B, new length LB chars. This function serves as the prototype of the arithmetic instructions, below.

In a Branch instruction, the A and B addresses pointed to two alternative branch destinations, and the LA and LB fields designate conditions to be satisfied, for the jump to occur (see table below). If both conditions are satisfied, the A address dominates. S-10 programmers learned a useful instruction: P0PP05nnnn. With the machine in a "Load/Local" state, this instruction could be entered at the keyboard, and the machine would jump to the address nnnn. This could be used to force entry to debugging functions: for example there was a convention that application programs always included a clean-up & exit routine, located at address 1000. The customer's operators (who were not programmers) were taught this dodge: if a program crashes, force Load/Local, then enter P0PP051000 to clean up and exit.

Subroutine calls were made using the LINK mnemonic, which generated condition-code 6 in the instruction. This directs the CPU to store the current instruction address at the given location, rather than branching. The second half of the instruction was generated as an unconditional branch to the subroutine.

Returns were done by moving the link into an index register (if it was not stored there already), then using the index to modify the target of an unconditional branch. Compare tests two character strings, using the same format as Move Character. The result sets the condition flags.

Add uses lengths similarly to Move Numeric, adding the number at A to that at B.

Subtract is similar to Add, but subtracts.

Multiply stored a product at the B address, the length being extended to LA+LB.

Divide I am unsure of the details.

Form Numeric scanned its source string (address A), extracting valid numeric characters (including signs). The result, zero-padded as necessary, was stored at the B address, as a properly-formatted S-10 signed number.

Reference card for S25: a later S-10 derivative. Thanks to Ray Rodrick for this.


The S-10 provided minimal (but sufficient) hardware support for multi-tasking. A hardware timer allocated a time-slice, typically 20mS, when a task was launched. The task then ran until the time-slice was exhausted, and a branch instruction was successfully executed. The need for a successful branch was crucial: this enabled mutexes to be implemented. An example of code to handle a mutex is given below: it would typically have been placed in the Common memory area, and executed as a shared subroutine.

 * MUTEX = -1 IF FREE, ELSE OWNER'S PTN. NO. (0..19)
 .USER                          ?? STORED IN USER'S PARTITION
 .COMMON                        REST IS IN COMMON SPACE
 MUTEX    DM    N2              2-DIGIT NUMBER FOR MUTEX
The routine at GETMTX compares MUTEX to the "empty" value. If it differs, the mutex is in use, and the compare is repeated. Since the branch succeeded, a task-switch will occur, once the time-slot expires.

If the mutex is free, both branches fail, and the code "falls through". In this event, a task-switch cannot occur, since a successful branch is required. Hence the following MC instruction, which claims the mutex, must execute. The succeeding RETURN is implemented as an unconditional branch: this can cause a task-switch, however the mutex has now been taken.

Operating System (LIOCS)

The Logical IO Control System provided disk-files of 3 types. All files were composed of 100-character records, with each record comprising one disk sector. The disk capacity was divided into pools (rather akin to the modern usage of partitions), being contiguous blocks of disk, managed by a single file system. The standard files types were:

  1. Linked Sequential files. These were typically text files, in which each text line corresponded to one record. In text files, the 100 characters were assigned as:
    1. 80 Data (a card image!)
    2. 6 Backward link
    3. 6 Forward link
    4. 8 Date

    This was very inefficient, however it meant that records could be inserted and deleted at will, by manipulating the links. LIOCS provided these functions. The double links made it possible to traverse a file in either direction, besides providing some redundancy which often assisted in repairing damaged files.

    Free disk space was linked into a free-list, using the same format as above. New records were simply pulled from the end of this list, without regard for their physical location on the disk. Files got fragmented quite quickly!

  2. Indexed Linked Sequential files. These were ordinary Linked Sequential files, sorted in record-order. An associated Index File, held a binary tree (?) of record keys and their sector addresses. LIOCS functions traversed this tree, entered the main file at the indicated point, and traversed to the matching record (if any). As records were added & deleted to the main file, the index required periodic rebuilding. Indexed and non-indexed files could co-exist in a single pool.
  3. Random (?) These were contiguous blocks of disk space, which the user could manage in any way desired. They were often used as workspace by sort utilities, etc.

LIOCS also offered IO functions for serial devices (screens, printers etc.), but we normally omitted these to save overhead, and programmed IO directly. This was not hard, given the way the S-10 MTIOC functioned.

LIOCS was distributed as source-code, in the form of a large macro (some 3000 lines, afair). To generate LIOCS, one wrote a short "stub" assembler program, which included a single call to the LIOCS macro. The parameters to this call configured the various LIOCS features. LIOCS resided in the Common memory area, and operated similarly to a DLL.

Multi-Terminal IO Controller (MTIOC)

The MTIOC controlled a multi-drop serial line, which could feed up to 10 devices. The line was a shielded twisted-pair (we used microphone cable), transformer-isolated from each device, and from the MTIOC. The line interface was duplicated to reduce fan-out: the 10-device limit was imposed by the device address format (a single decimal digit).


Technical Details

The following notes were made by me, at the time.

1. Electrical Characteristics.

The System-10 IOC communicates over a transformer-coupled, balanced-pair transmission line, used bidirectionally.

Signal levels are +2.5V (referred to a theoretical centre-tap ground). A double-frequency modulation technique is used, in which the absolute polarity of the lines is immaterial, only polarity reversals are significant. However it was required that every transmission began by driving the lines to a consistent initial state.

The IOC is strappable to operate at two speeds, approximately 28,000 or 2,800 bits/second. This description will cover the high-speed option only. For low-speed working, all timings are increased 10-fold.


2. Sender/Receiver Circuit. (See Fig. 1)

The sender operation is straightforward. Timing is shown in Fig. 2. The signal TXCLOCK cycles at twice the desired bit rate (56.25kHz, i.e. 450kHz divided by 8). A sourced current of l20mA is switched to either side of the line-coupling transformer, Tl.

When receiving, the sender is isolated by CR5 and CR7. Amplifiers Zl and Z2 are connected as Schmitt triggers, each having switching levels of + and -lV, referred to the centre tap of Tl. Before receiving data, the circuit is initialised by a short negative pulse at RXINITIALISE, which sets the Schmitts into standard initial conditions. Signal-pulse trains always commence with a departure from 0V to -2.5V at ST.NEG, which is ignored, since the Schmitts are already in the corresponding polarity. The following positive transition fires the Schmitts, defining the start of Bit 1. Data (if enabled) appears at RXDATA as a train of short +ve pulses.

3. Data Formats.

3.1 Data Character (See Fig. 3)
All data-bit trains begin with a -ve transition from 0V (on the ST-NEG data line). Data characters comprise 9 bit-cells, each of 35.6m S duration. The start of each bit-cell is marked by a transition, and the presence of an additional transition in the centre of the cell defines a "1" bit. Bits 1 to 7 represent the data character, in standard ASCII notation ("DLE" - 0x10 - is shown in Fig. 3). Bits 8 and 9, designated as "E" and "O", are parity bits, computed as even parity over the even and odd data bit positions, respectively.
3.2 Acknowledge Burst (1).
This pulse-train (referred to as ACKl) is used by either the IOC or a terminal, to acknowledge receipt of a character. The ACKl signal comprises a train of 2 or more "1" bits. It may be of indefinite length, and is used on output instructions to synchronise the IOC to slow peripherals. The device receiving ACKl must not send more characters until ACKl has terminated. Typically, the next character may be sent some 60mS after ACKl terminates. ACKl itself should commence some 60mS after the line returns to zero after the end of the data character.
3.3 Acknowledge Burst (2).
ACK2 is issued only by the IOC, never by a terminal. It indicates the receipt of a data character (as does ACKl), and further indicates that the input operation is finished (i.e. that the programmed count is exhausted). ACK2 typically comprises two "1" bits, followed by nine "0"s. Two or more zeros will define ACK2, although the string may be of indefinite length, to achieve synchronisation.

4. Polling.

When no device is selected (see below), the IOC continuously polls device 0 for a Load request, and devices 0 to 9 for Service requests.
4.1 Poll Sequence.
A poll sequence comprises a DLE character, followed by a poll character. Devices do not normally send ACKl in response to a poll sequence. The DLE character will unconditionally deselect all devices.

Bits 765


000 Not Used
001 Not Used
010 Service request offer
011 Load Request offer (device 0 only).
100 Read Data.
101 Write Data.
110 Read Control (sets Load state).
111 Write Control.
4.2 Poll Character Format.
The poll character is used as follows. Bits 4 - 1 give the polled device address (0 - 9); while bits 7 - 5 decode as shown at the right.
4.3 Response.
A device never makes a response to the DLE character, or to any poll character which does not address that device.

When addressed, a peripheral may answer Load Requests or Service Requests by an ACKl sequence, to initiate the corresponding action in the CPU. Load Request will cause the CPU to issue a Read Control instruction, which will set the "LOAD" indicator (if any) on the selected device (always Channel 0).

Select operations (bit 7 set) must be answered by ACKl within 100mS, or the instruction will be aborted, with CC-4 set (absent device).

5. Input (READ) Operations.

5.1 Selection.
The required input device is selected via a poll/select sequence (see above). The poll character will specify Read or Read Control, and must be answered by ACK1 (typically after 60mS).
5.2 Data Transmission.
About 100mS after ending ACKl, the terminal should send the first data character. If no live character is ready, a DEL character may be sent (this will not be stored by the CPU). Characters must be of the System-10 restricted character set (ASCII columns 2, 3, 4 or 5). After some 60mS, the CPU will reply with ACKl if another character is expected, or ACK2 if no more are expected. The terminal may force premature termination of the input operation by sending a US character, which the CPU will not store, and will acknowledge with ACK2.



Condition Code set

7 "0"  
6 "1"  
5 "0"  
4 "0"  
3 FAULT\ (CC-4: overflow) (NB. inverted).
2 FLAG (CC-3: high)
1 ERROR (CC-l: low)
5.3 Status Character.
Following the ACK2 from the IOC, the terminal is required to send a Status character, which will post Condition Codes to the program. As for data input, DEL characters may be used to pad out until the status character is available. Such DEL's, and the Status character itself, will be answered with ACK1 from the IOC.

The format of the Status character is shown at the right.
The Status character is answered by ACKl, and the input operation is terminated. The IOC will then commence a new Poll sequence, testing for Load and Service Requests.

6. Output (WRITE) Operations.

6.1 Selection.
The device selection sequence is as for Input operations. After the terminal sends ACKl to the Select character, the IOC will (after about 100m S), send the first data character.
6.2 Data Transmission.
Each character sent by the IOC is (if valid) answered by AGK1. Inter-character delays, in both directions, are typically 60mS. Write Control operations send characters in the ASCII Control format (Columns 0 and 1 of the ASCII character table).

Finally, the IOC sends an EOT character, defining the end of the string. The terminal responds with ACKl, before proceeding to the Status sequence.

6.3 Status Character.
A Status character (or the first padding DEL) should be sent about 150m S after the ACKl reply to the IOC's EOT. Inter-character delays during the DEL sequence will be about 60m S. The Status character has the same format as for Input operations.

7. Error Control.

An ACK sequence will normally be sent about 60m S after the end of the character it acknowledges. No ACK will be sent to a character of invalid parity, or framing errors.

If ACK is not received within about l00m S, the character will be re-transmitted (by the IOC or the terminal) up to 4 times. After 5 unsuccessful attempts, the terminal should 'lock up' , until another poll sequence is received, which should reset it. This will cause the IOC to abort the operation, setting CC-4 to the program (persistent transmission error).

Service Requests

A user terminal was provided with a service request key, which set a condition-code bit. When it wished, the program could execute a "Branch on Service Request" instruction, which would branch if the user had pressed this key.


Terminals had an Online/Local switch, and a Load key. If Load and Local were pressed simultaneously, the terminal responded to a Load poll from the CPU. This effectively jammed a Read Control instruction into Locations 9990-9999, which was then executed.
This instruction caused 10 characters to load into 0000-0009, which were then executed as an instruction. So a crashed program could be escaped by pressing Load/Local, then entering P0PP051000, for example.


The following question would be put to students in training seminars: I press Load/Local, then simply press Enter, with no data typed. What happens?

The Load/Local initiates a Read Control for 10 characters, however Enter is pressed at once, so no data is supplied. Read instructions fill the unused buffer with spaces, in this case. However, this is a Read Control, so spaces are mapped to zeros. Locations 0000-0009 are hence filled with zeros, and then executed as an instruction. Now "0000000000" is a valid S-10 instruction: Read from disk drive 0, sector 0, to memory 0000-0099. Instruction execution then continues from Location 0010, ie following the disk read instruction (which has now been overwritten). This is, of course, the system bootstrap. From Partition 0, this would reload LIOCS: other partitions reloaded their own start-up code.

HomeBack to Home Page HomeMore System-10 Details HomeAnd More
Valid XHTML 1.0 Transitional