Orthogonal instruction set
In computer engineering, an orthogonal instruction set is an instruction set architecture where all instruction types can use all addressing modes. It is "orthogonal" in the sense that the instruction type and the addressing mode vary independently. An orthogonal instruction set does not impose a limitation that requires a certain instruction to use a specific register[1] so there is little overlapping of instruction functionality.[2]
Orthogonality was considered a major goal for processor designers in the 1970s, and the VAX-11 is often used as the benchmark for this concept. However, the introduction of RISC design philosophies in the 1980s significantly reversed the trend towards more orthogonality. Modern CPUs often simulate orthogonality in a pre-processing step before performing the actual tasks in a RISC-like core.
Basic concepts
At its core, all general purpose computers work in the same underlying fashion; data stored in a main memory is read by the central processing unit (CPU), acted on, and then written back to memory. Memory consists of a collection of data values, encoded as numbers[a] and referred to by their address, also a numerical value. This means the same operations applied to the data can be applied to the addresses themselves. While being worked on, data can be temporarily held in processor registers, scratchpad values that can be accessed very quickly. Registers are used, for example, when adding up strings of numbers into a total.[3]
In early computers, the instruction set architecture (ISA) often used a single register, in which case it was known as the accumulator. Instructions included an address for the operand. For instance, an ADD address
instruction would cause the CPU to retrieve the number in memory found at that address and then add it to the value already in the accumulator. This very simple example ISA has a "one-address format" because each instruction includes the address of the data.[4]
One-address machines have the disadvantage that even simple actions like an addition require multiple instructions, each of which takes up scarce memory,[b] and requires time to be read. Consider the simple task of adding two numbers, 5 + 4. In this case, the program would have to load the value 5 into the accumulator with the LOAD address
instruction, use the ADD address
instruction pointing to the address for the 4, and finally SAVE address
to store the result, 9, back to another memory location.[4]
Further improvements can be found by providing the address of both of the operands in a single instruction, for instance, ADD address 1, address 2
. Such "two-address format" ISAs are very common. One can further extend the concept to a "three-address format" where the SAVE
is also folded into an expanded ADD address 1, address 2, address of result
.[4]
It is often the case that the basic computer word is much larger than needed to hold just the instruction and an address, and in most systems, there are leftover bits that can be used to hold a constant instead of an address. Instructions can be further improved if they allow any one of the operands to be replaced by a constant. For instance, ADD address 1, constant 1
eliminates one memory cycle, and ADD constant 1, constant 2
another.[4]
Further complexity arises when one considers common patterns in which memory is accessed. One very common pattern is that a single operation may be applied across a large amount of similar data. For instance, one might want to add up 1,000 numbers. In a simple two-address format, there is no way to change the address, so 1,000 additions have to be written in the machine language. ISAs fix this problem with the concept of indirect addressing, in which the address of the next point of data is not a constant, but itself held in memory. This means the programmer can change the address by performing addition on that memory location. ISAs also often include the ability to offset an address from an initial location, by adding a value held in one of its registers, in some cases a special index register. Others carry out this addition automatically as part of the instructions that use it.[4]
The variety of addressing modes leads to a profusion of slightly different instructions. Considering a one-address ISA, for even a single instruction, ADD
, we now have many possible "addressing modes":
Immediate (constant): ADD.C constant 1
- adds the constant value to the result in the accumulator
Direct address: ADD.A address 1
- add the value stored at address 1
Memory indirect: ADD.M address 1
- read the value in address 1, use that value as another address and add that value
Many ISAs also have registers that can be used for addressing as well as math tasks. This can be used in a one-address format if a single address register is used. In this case, a number of new modes become available:
Register direct: ADD.R register 1
- add the value stored in the address held in register one
Displacement: ADD.D constant 1
- add the constant to the address register, then add the value found in memory at that resulting location
Index: ADD.I register 1
- add the value in register 1 to the address register to make a new address and then adds the value at that location to the accumulator
Autoindex: ADD.AI register 1
- as in the Index case, but automatically increments the address
Orthogonality
Orthogonality is the principle that every instruction should be able to use any supported addressing mode. In this example, if the direct addressing version of ADD
is available, all the others should be as well. The reason for this design is not aesthetic, the goal is to reduce the total size of a program's object code. By providing an array of addressing modes, the ISA allows the programmer to chose the one that precisely matches the need of their program at that point, and thereby reduce the need to use multiple instructions to achieve the same end. This means the total number of instructions is reduced, both saving memory and improving performance. Orthogonality was often described as being highly "bit efficient".[5]
As the ultimate end of orthogonal design is simply to allow any instruction to use any type of address, implementing orthogonality is often simply a case of adding more wiring between the parts of the processor. However, it also adds to the complexity of the instruction decoder, the circuitry that reads an instruction from memory at the location pointed to by the program counter and then decides how to process it.[5]
In the example ISA outlined above, the ADD.C
instruction using direct encoding already has the data it needs to run the instruction and no further processing is needed, the decoder simply sends the value into the arithmetic logic unit (ALU). However, if the ADD.A
instruction is used, the address has to be read, the value at that memory location read, and then the ALU can continue. This series of events will take much longer to complete and requires more internal steps.[5]
As a result, the time needed to complete different variations of an instruction can vary widely, which adds complexity to the overall CPU design. Therefore, orthogonality represents a tradeoff in design; the computer designer can choose to offer more addressing modes to the programmer to improve code density at the cost of making the CPU itself more complex.[5]
When memory was small and expensive, especially during the era of drum machines or core memory, orthogonality was highly desirable. However, the complexity was often beyond what could be achieved using current technology. For this reason, most machines from the 1960s offered only partial orthogonality, as much as the designers could afford. It was in the 1970s that the introduction of large scale integration significantly reduced the complexity of computer designs and fully orthogonal designs began to emerge. By the 1980s, such designs could be implemented on a single-chip CPU.[5]
In the late 1970s, with the first high-powered fully-orthogonal designs emerging, the goal widened to become the high-level language computer architecture, or HLLCA for short. Just as orthogonality was desired to improve the bit density of machine language, HLLCA's goal was to improve the bit density of high-level languages like ALGOL 68. These languages generally used an activation record, a type of complex stack that stored temporary values, which the ISAs generally did not directly support and had to be implemented using many individual instructions from the underlying ISA. Adding support for these structures would allow the program to be translated more directly into the ISA.[5]
Orthogonality in practice
The PDP-11
With the exception of its floating point instructions, the PDP-11 was very strongly orthogonal.[6] Every integer instruction could operate on either 1-byte or 2-byte integers and could access data stored in registers, stored as part of the instruction, stored in memory, or stored in memory and pointed to by addresses in registers. Even the PC and the stack pointer could be affected by the ordinary instructions using all of the ordinary data modes. I"mmediate" mode (hardcoded numbers within an instruction, such as ADD #4, R1 (R1 = R1 + 4) was implemented as the mode "register indirect, autoincrement" and specifying the program counter (R7) as the register to use reference for indirection and to autoincrement.[7]
Since the PDP-11 was an octal-oriented (3-bit sub-byte) machine (addressing modes 0–7, registers R0–R7), there were (electronically) 8 addressing modes. Through the use of the Stack Pointer (R6) and Program Counter (R7) as referenceable registers, there were 10 conceptual addressing modes available.[7]
The VAX-11
The VAX-11 extended the PDP-11's orthogonality to all data types, including floating point numbers.[5] Instructions such as 'ADD' were divided into data-size dependent variants such as ADDB, ADDW, ADDL, ADDP, ADDF for add byte, word, longword, packed BCD and single-precision floating point, respectively. Like the PDP-11, the Stack Pointer and Program Counter were in the general register file (R14 and R15).[8]
The general form of a VAX-11 instruction would be:
opcode [ operand ] [ operand ] ...
Each component being one byte, the opcode a value in the range 0–255, and each operand consisting of two nibbles, the upper 4 bits specifying an addressing mode, and the lower 4 bits (usually) specifying a register number (R0–R15).[8]
Unlike the octal-oriented PDP-11, the VAX-11 was a hexadecimal-oriented machine (4-bit sub-byte). This resulted in 16 logical addressing modes (0–15), however, addressing modes 0–3 were "short immediate" for immediate data of 6 bits or less (the 2 low-order bits of the addressing mode being the 2 high-order bits of the immediate data, when prepended to the remaining 4 bits in that data-addressing byte). Since addressing modes 0-3 were identical, this made 13 (electronic) addressing modes, but as in the PDP-11, the use of the Stack Pointer (R14) and Program Counter (R15) created a total of over 15 conceptual addressing modes (with the assembler program translating the source code into the actual stack-pointer or program-counter based addressing mode needed).[8]
The MC68000 and similar
Motorola's designers attempted to make the assembly language orthogonal while the underlying machine language was somewhat less so. Unlike PDP-11, the MC68000 (68k) used separate registers to store data and the addresses of data in memory. The ISA was orthogonal to the extent that addresses could only be used in those registers, but there was no restriction on which of the registers could be used by different instructions. Likewise, the data registers were also orthogonal across instructions.[9]
In contrast, the NS320xx series were originally designed as single-chip implementations of the VAX-11 ISA. Although this had to change due to legal issues, the resulting system retained much of the VAX-11's overall design philosophy and remained completely orthogonal.[10] This included the elimination of the separate data and address registers found in the 68k.[11]
The 8080 and follow on designs
The 8-bit Intel 8080 (as well as the 8085 and 8051) microprocessor was basically a slightly extended accumulator-based design and therefore not orthogonal. An assembly-language programmer or compiler writer had to be mindful of which operations were possible on each register: Most 8-bit operations could be performed only on the 8-bit accumulator (the A-register), while 16-bit operations could be performed only on the 16-bit pointer/accumulator (the HL-register pair), whereas simple operations, such as increment, were possible on all seven 8-bit registers. This was largely due to a desire to keep all opcodes one byte long.
The binary-compatible Z80 later added prefix-codes to escape from this 1-byte limit and allow for a more powerful instruction set. The same basic idea was employed for the Intel 8086, although, to allow for more radical extensions, binary-compatibility with the 8080 was not attempted here. It maintained some degree of non-orthogonality for the sake of high code density at the time). The 32-bit extension of this architecture that was introduced with the 80386, was somewhat more orthogonal despite keeping all the 8086 instructions and their extended counterparts. However, the encoding-strategy used still shows many traces from the 8008 and 8080 (and Z80). For instance, single-byte encodings remain for certain frequent operations such as push and pop of registers and constants; and the primary accumulator, the EAX register, employs shorter encodings than the other registers on certain types of operations. Observations like this are sometimes exploited for code optimization in both compilers and hand written code.
RISC
A fully orthogonal architecture may not be the most "bit efficient" architecture. In the late 1970s research at IBM (and similar projects elsewhere) demonstrated that the majority of these "orthogonal" addressing modes were ignored by most programs. Perhaps some of the bits that were used to express the fully orthogonal instruction set could instead be used to express more virtual address bits or select from among more registers.
Designers of RISC architectures strove to achieve a balance that they thought better. In particular, most RISC computers, while still being highly orthogonal with regard to which instructions can process which data types, now have reverted to "load/store" architectures. In these architectures, only a very few memory reference instructions can access main memory and only for the purpose of loading data into registers or storing register data back into main memory; only a few addressing modes may be available, and these modes may vary depending on whether the instruction refers to data or involves a transfer of control. Conversely, data must be in registers before it can be operated upon by the other instructions in the computer's instruction set. This trade off is made explicitly to enable the use of much larger register sets, extended virtual addresses, and longer immediate data (data stored directly within the computer instruction).
Notes
- ^ See digitization.
- ^ Even in modern computers, performance is maximized by keeping data in the cache, a limited resource.
References
- ^ Null, Linda; Lobur, Julia (2010). The Essentials of Computer Organization and Architecture. Jones & Bartlett Publishers. pp. 287–288. ISBN 1449600069.
- ^ Tariq, Jamil (1995), "RISC vs CISC: Why less is more" (PDF), IEEE Potentials (August/September), retrieved 7 May 2019
- ^ "Basic Computer Organization & Design" (PDF). Computational Sensory-Motor Systems Laboratory.
- ^ a b c d e Tullsen, Dean. "Instruction Set Architecture" (PDF). UCSD.
- ^ a b c d e f g Hennessy, John; Patterson, David. Computer Architecture: A Quantitative Approach. p. 151.
- ^ "Introduction to the PDP-11". University of Sydney.
- ^ a b "PDP-11 instruction reference" (PDF). University of Toronto.
- ^ a b c "Another Approach to Instruction Set Architecture—VAX" (PDF). University of Bremen.
- ^ Veronis, Andrew. The 68000 Microprocessor. p. 54.
- ^ Tilson, Michael (October 1983). "Moving Unix to New Machines". BYTE. p. 266. Retrieved 31 January 2015.
- ^ "NS32532". Datormuseum.