MIX is a computer architecture created by Donald Knuth. Knuth is writing a grand monography The Art of Computer Programming, and in older editions of that book, he used to present example subroutines and programs in this architecture.
The size of a MIX byte depends on the machine: a byte stores a natural number less than 64 on binary machines, 100 on decimal machines, or any limit between 64 and 100 in general.
A machine word in MIX is an integer made of a sign and five bytes. These are called byte 5 for the least significant byte to byte 1 for the most significant, and the sign counts as byte 0 (even though it's not a full byte, it can only store plus or minus).
The memory of a MIX computer holds 4000 words. Data and instructions are stored in the same memory. Since MIX is an old computer, there are no caches, all memory accesses take the same time.
MIX has eight main registers: rA (accumulator), rI1, rI2, rI3, rI4, rI5, rI6 (index registers), rX (extension registers). Of these, rA and rX are full words, but the index registers rI1..rI6 are limited to a sign and the two lower bytes, and trying to assign any value that does not have zero in bytes 1..3 has an undefined behavior. In addition, there is a program counter, an rJ register that contains the return address after the last jump, an arithmetic overflow flag, and comparison flags.
There is one instruction per word, and are executed sequentially unless a branch instruction changes the program counter. An instruction has the format ±AAIFC, where the opcode C is byte 5. Byte 3 called I, which must be less than 7, allows indexed mode for any instruction. Execution of the instruction starts by computing a value called M by indexing the first three bytes ±AA with an index register: M = ±AA + rI_I if I is nonzero, or M = ±AA if I is zero. The resulting M must also fit in two bytes and a sign, or else its value is undefined. All instructions then care only about the value of M, F and C, rather than the original ±AAI.
Most instructions come in series of eight, where the source or destination operand is one of the eight main registers, decided by the low three bits of the opcode C. The series of instructions like this are as follows: here x is one of A, 1, 2, 3, 4, 5, 6, X, which use the register rA, rI1..rI6, rX respectively.
- LDx loads the word with address M from memory to the register.
- LDxN loads the word with address M from the memory to the register but then flips the sign of the register.
- STx stores the register to memory address M.
- CMPx compares the value of the register with the word at address M in memory, and sets the comparison flags to the result, indicating equal, less, or greater.
- Jxcc is a conditional branch to address M, depending on the value of the register. Here, cc is a condition: one of N (negative), Z (zero), P (positive), NN (nonnegative), NZ (nonzero), NP (nonpositive), .
- ENTx loads the (immediate or indexed value) M into the register.
- ENNx is the same, but then flips the sign of the register.
- INCx adds the (immediate or indexed value) M to the register, putting the destination to the same register.
- DECx subtracts M from the register, putting the destination to the same register.
Other general purpose instructions, which don't come in such series of eight, are:
- JMP is an unconditional jump to address M.
- Jcc are conditional jumps to address M depending on the comparison flags, where cc a condition code chosen from L (less), E (equal), G (greater), GE (less or equal), NE (not equal), LE (less or equal). For example, JL branches if the register had a lower value than the memory at the last CMPx instruction.
- JOV and JNOV are conditional jumps if the overflow flag is set or not set respectively.
- STJ stores the rJ register to a memory cell. All of the above jump instructions put one plus the address of the jump instruction to rJ before jumping, thus rJ contains the return address after a subroutine call. Subroutines usually store this return value to the ±AA field of a jump instruction with STJ before they do another jump. This results in self-modifying code.
- JSJ is an unconditional jump, but unlike all other jump instructions, it does not modify the rJ register.
- ADD adds the memory word at address M to the accumulator rA, putting the result in rA.
- SUB subtracts the memory word at address M from the accumulator rA, putting the result in rA.
- MUL multiplies the memory word at address M with the accumulator rA, and puts the high word of the two-word result in rA and the low word in rX.
- DIV performs a division. The divisor is two words long, the high word and sign is in register rA, the low word is in rX (whose sign is ignored). The divisor is the memory word at address M. The instruction puts the quotient (truncated towards zero) in rA and the remainder in rX, or an undefined value into both of those registers if the division overflows or if the divisor is zero.
- STZ stores +0 to the memory word at address M.
- SLA and SRA shift the magnitude bytes of the accumulator rA by M bytes to the left or right (the most significant bytes are on the left), shifting in zeros from the side.
- SLAX and SRAX shifts the magnitude bytes of the registers rA and rX taken together as a ten-byte word (rA is on the left), shifting in zeros from the side.
- SLC and SRC cyclically shifts the magnitude bytes of the registers rA and rX taken as a ten-byte word left or right cyclically. None of the shift instructions modifies the signs of the registers.
- MOVE copies several bytes from memory to memory. The word count is F, the first source word is at address M, the first destination word at address rI1. The copies are done one word at a time, so if M < rI1 < M + C, then the first rI1-M bytes are repeated cyclically. As a side effect, rI1 is incremented by F.
- NOP does nothing.
- NUM converts a ten-digit decimal string, one digit per byte, to an unsigned single word integer. The input string is in rA (five most significant digits) and rX (five least significant digits), and the remainder divided by 10 of each byte of the input gives a digit. The output is in the mantissa bytes of rA, the sign is unchanged.
- CHAR converts an unsigned single word integer rA to decimal. The output is ten digits, stored in individual bytes of rA and rX, but 30 is added to each digit. Signs are unchanged. These last three instructions ignore the value M.
In addition to the above section, MIX has IO instructions, floating-point instructions, and some versions have nonstandard extensions too.
Many instructions can operate on just a field of a word, where a field can be any non-empty range of the six bytes (including the sign). For some instructions, byte F is interpreted as a field specification to choose such a field. In that case, F has the format h+8*l, which selects the range from byte h to byte l inclusive, and 0≤h≤l≤5. Very often, F=5, meaning to operate on a full word.
- The LDx and LDxN instructions extract the specified field of the memory word, and loads only that to the register. The magnitude bytes in the field chosen are shifted to the least significant bytes of the register, the bytes above them filled with zero. If the field specification includes the sign, that is loaded to the sign of the register, otherwise the sign is filled with plus.
- In the ADD, SUB, MUL and DIV instructions, the second operand is the specified field of the memory word, but the instructions operate on the whole register or registers as the second operand and output.
- The STx and STJ instruction puts the whole register to the specified field of the memory word, and doesn't modify the other bytes of the memory word. If the field doesn't include byte 5, then the less significant magnitude bytes of the register are thus shifted to the left, and the more significant ones are ignored.
- Similarly, STZ modifies the specified field of the memory word only.
- However, the CMPx instructions use F to select the same field both from the register and the memory word, comparing those fields without shifting anything.
Other instructions use the byte F for other purposes, the most common of which is distinguishing between sub-instructions with the same opcode.
- Specifically, for a fixed register x, the immediate instructions INCx, DECx, ENTx, ENNx are distinguished only by F.
- For a fixed register x, in the conditional branches Jxcc, the condition code cc is decided by F.
- Opcode 39 multiplexes the branch instructions Jcc, JMP, JOV, JNOV, JSJ by the value F.
- Opcode 5 multiplexes the instructions NUM, CHAR, HLT and some nonstandard extensions.
- Opcode 6 multiplexes between the shift instructions.
The overflow flag is set when an arithmetic operation on rA or rX (ADD, SUB, INCx, DECx, DIV, NUM) overflows. Recall that overflowing an index register or the indexed value M has undefined behavior though, so overflow need not be detected there. The overflow flag is only cleared by the JOV and JNOV conditional branches, not by other instructions. The shift instructions don't affect the overflow flag.
As MIX uses sign-magnitude representation, the sign of a zero is defined in all operations, and can be read by field access or shifting, but in practice this rarely matters. The rules for the sign of a zero in arithmetic operations is as follows. When computing M for the ENTx or ENNx instructions, if I is nonzero and M is zero, then M has the sign of the instruction. In the ADD, SUB, INCx, DECx instructions, if the result is zero, the sign of the destination register is unchanged. Comparisons and conditional branches ignore the sign of a zero value. The MUL and DIV instructions act on the signs and magnitudes independently. For MUL, both output registers get a plus sign if the sign of the two operands were the same, or a minus sign if they were different. For DIV, the sign of the quotient output is plus if the divisor and the dividend have the same sign, or minus if they have different sign, and the remainder output has the sign of the dividend.
MIX computers can vary in what IO devices they're connected to.
Text input and output can use either 80-column Hollerith punched cards, or a teletype and tape reader. Text input is one character per byte using a custom MIX character code, which defines 55 characters, although some devices may support only some of those characters. (This is necessary because EBCDIC doesn't fit into a MIX byte.)
More expensive versions of MIX may use magnetic tape or hard disk for storing larger amounts of data. Both of these do binary IO of blocks full MIX words.
IO is memory-mapped, reading or writing a block at a time. IO may happen in the background while the CPU continues executing other instructions. Background output may continue to access the data from memory. Only one operation per device can run in the background, when you start a second operation for the same device, the CPU will first wait until the previous operation completes. There is no interrupt support though, so the CPU has to poll the IO devices for when the operation is completed.
Blocks are fixed size, the block size is determined implicitly by the device. The block is one card (80 characters) for card reader and card punch, one line of fixed 70 characters for the teletype, one line of a fixed 160 characters for the printer, and 100 word for tape or disk drives. Text is represented as five characters per word, one in each byte, sign unused.
Devices are identified by a number: the teletype is 19, line printer is 18, Hollerith card reader and card punch are 16 and 17 respectively, magnetic tape drives are numbered 0..7, disk drives 8..15.
IO instructions are:
- HLT halts the machine, but the operator can continue it with no state changed.
- JRED, JBUS conditional jump to address M if the IO device number F is ready (i.e. has completed the previous operation so all input is available in memory or output buffer in memory reusable) or not ready respectively
- IN starts to read a block from IO device number F to memory starting at address M. For disks, rX gives the sector number.
- OUT starts to write a block to IO device number F from memory starting at address M. For disks, rX gives the sector number.
- IOC does an IO control operation on IO device number F, the operation determined by M and the device. If M 0, a line printer starts on the top of a new page, a disk tries to pre-seek to sector rX, a magnetic tape rewinds to the start of the tape. If M is positive or negative, a magnetic tape skips M blocks of the tape forwards or -M blocks of the tape backwards.
Startup and assembly
For startup, a MIX computer loads a single block from the punch card reader or ticker tape. TAOCP defines a text-based executable format for programs, in which memory words are stored as text in decimal numbers. This format can be then loaded with a simple bootstrap program (which is on the first two cards), which can use the NUM instruction to convert the words from decimal. The format cannot be binary because cards or paper tape cannot represent all MIX words.
TAOCP also defines an assembly language format MIXAL for MIX prorgams, and gives most program listings in that assembly language. This can then be assembled to the above mentioned executable formats. While assembly programs are often independent of the byte size, programs in the executable format work only for a specific byte size.
TAOCP suggests some optional extensions to MIX, both in the main text and exercises. Extensions include:
- Floating point arithmetic operations. Floating-point numbers are a single word long. The floating-point format uses the byte size as the base, so the exponent shifts the value by an entire byte. Byte 1 of the word contains the biased exponent, bytes 2..5 contain the mantissa. The additional instructions for MIX with a floating point unit are:
- FADD: floating point addition, FSUB subtraction, FMUL multiplication, FDIV division, all of which operate on rA and the memory word at address M as inputs and rA as output.
- FCMP: tolerant comparison of rA with the memory word at address M, the tolerance is given by the word at address 0, output to the comparison flags.
- FLOT: convert integer to floating point, in place in rA.
- FIX: convert floating point to integer, in place in rA, with the overflow flag set and an undefined output in rA on overflow.
- Bitwise operations for binary machines (bitwise shift left/right, jump if even/odd, AND, XOR).
- An interrupt system for IO and timers, together with memory accessible only in system mode.
- Advanced indexing by values of the I field of instructions greater than 7.
- Attaching the rX register to the traffic lights at an intersections, as well as the overflow counter for a pedestrian crossing button.
The extensions listed here are nonstandard and are not part of TAOCP nor mentioned by Knuth.
MIXPC does not implement floating point, but does implement XOR and jump if even/odd (the latter works even in decimal mode, and are available for index registers too). There is also no paper tape in MIXPC (unit 19 is only the typewriter), and there is the command for the operator (but not the program) to rewind the card reader.
In addition there are four new devices:
- 32 (Music): IOC will stop any sound that is currently playing. To play a time, use OUT, where rX stores the frequency of the tone in decihertz and M is the duration in PC clock ticks.
- 33 (Lights): There are four lights which can be turned on/off independently. Use IOC to control them; the rX register tells which lights to turn on/off. Only fields 2:5 are used; any byte which is nonzero means light on, and zero means light off.
- 34 (Random number generator): IOC will seed the random number generator; M is the seed. If M is zero, use the time of day to seed.
- 35 (Clock): IN reads one record, which contains the current time of day. The sign of this record is always positive and fields 1 and 2 are zero, while field 3 stores hours, field 4 stores minutes, and field 5 stores seconds.
- 36 (Television): Is a monochrome green CRT with 80x40 resolution. The pixels go left to right, starting on the top row, and each MIX word represents five pixels from field 1 to field 5 (sign is ignored).
The assembler included with MIXPC also uses a bootstrap program that fits on only one card, although decks from other assemblers can still be used with MIXPC too, and decks from MIXPC's assembler can still be used with other MIX implementations too. However, MIXPC is using PC character set, so if using the format from the book with MIXPC if the program contains any negative numbers, you must use character code 233 to represent MIX character code 10 (in the PC character set, 233 is the code of theta). The reverse is unnecessary though; MIXPC's assembler does not punch any Greek alphabets.
A variant of MIX (that isn't quite MIX, because MIX requires a byte size in range 64 to 100), where a byte has 8-bits, so can have 256 values.
The CHAR instruction works a bit differently, because numbers can now be larger than 1010. If the number fits in ten digits then the effect is the same as in normal MIX. Otherwise, it encodes the magnitude in some unambiguous way where the first character is always a letter from S to Z inclusive and the other nine characters are always letters from A to Z or digits from 0 to 9, sets the overflow flag. (TODO: Specify details of the encoding)
There is a new NUME instruction that can be used to convert extended numbers. It works like NUM if the second byte isn't a letter, but if it is a letter then it undoes the transformation specified above. The sign of rA and rX, the contents of rX, and the overflow flag, are unaffected, regardless of the result. (The encoding of the NUME instruction needs to be specified)
MIX is defined in The Art of Computer Programming volume 1 (chapter 1.3), of which the first edition is from 1969, but I believe MIX is slightly older than that. MIX is used to write example subroutines and programs in the first three volumes of TAOCP in the first three editions (first two editions of volume 3).
Being a typical 60s computer however, Knuth has found that by the late 1990s, MIX has become obsolete. Knuth has thus designed the modern architecture MMIX to replace it. MMIX is used for the examples in volume 4A, and will be using it in future volumes (4B, 4C, 5). Eventually he will update all the examples in the first three volumes too, so the final and glorious edition of TAOCP will use only MMIX.
The encoding of the registers for sets of instructions is:
The encoding of instructions, including some extensions, is:
The AND nonstandard instruction is defined in 4.5.4 and is used in an assembly listing, but the encoding doesn't seem to be specified anywhere in the books.
00 01 02 03 04 05 06 07 08 09 A B C D E F G H I 10 11 12 13 14 15 16 17 18 19 Θ J K L M N O P Q R 20 21 22 23 24 25 26 27 28 29 Φ Π S T U V W X Y Z 30 31 32 33 34 35 36 37 38 39 0 1 2 3 4 5 6 7 8 9 40 41 42 43 44 45 46 47 48 49 . , ( ) + - * / = $ 50 51 52 53 54 55 < > @ ; : '
Note: Zero indicates a space, 10 and 20 and 21 are Greek alphabets, and character codes higher than 55 are not valid. The characters above are for the old version. The new version has some Greek alphabets difference: Δ instead of Θ and Σ instead of Φ. Character code 10 is also used to represent a "negative zero" on a card (positions 0 and 11 punched for Hollerith cards).