IBM PC Architecture - TheIntel 8088 "base" architecture


Although no longer common, the processor used in the orginal IBM PC, the Intel 8088, provides the basis for understanding all subsequent processors in the Intel 80x86 familly (and thus for all later versions of the IBM PC). Some of the restrictions of the 8088 have been removed and some additional registers have been provided, but all the features of the original 8088 apply to all subsequent members of the Intel 80x86 familly and thus the 8088 provides a convenient basis for studying this familly.

We will start with an overview of the 8088's memory (greatly simplified when compared with subsequent 80x86 processors). We will then discuss the various registers available within the 8088. Finally we will combine these two elements in a discussion of stack processing as it is performed on the 8088.


Memory




General Purpose Registers

Note

The general purpose registers can each be used as a single 16-bit register or as two 8-bit registers. As a 16-bit register, a general purpose register's name ends in an "X"; e.g. AX. As an 8-bit register, its name ends in either an "H", if it is the "high order" half of the 16-bits, or an "L", if it is the "low order" half; e.g. AH or AL.
AX
- Arithmetic register (while any of the general purpose registers can be used for arithmetic, arithmetic is optimized for AX -and its two halves, AH and AL.)
BX
- Base register (the only general purpose register whose contents can be used by instructions as a 16-bit offset address, normally within DS:.)
CX
- Count register (used by several instructions to control the number of times a looping process is performed.)
DX
- Data register (often used to hold character data - when referenced as DH or DL; also combines with AX to form a 32-bit register for some operations.)
Data Latch
The data latch is a register used to give the appearance of an ability to perform arithmetic and similar operations directly on a byte or word in memory; the data latch can not be explicitly referenced by a programmer.


Address Registers



Flags




The Stack

The stack is a temporary LIFO (Last In, First Out) area for storage; when words are stored in or "pushed on to" the stack and then retieved from or "popped off" the stack, the last value "pushed" becomes the first value "popped".

Instructions which push word values on to the stack are: CALL, INT and PUSH. The corresponding instructions which pop word values off the stack are RET, IRET and POP.

CALL and RET are associated with modular programming and subroutine usage. A subroutine is a sequence of instructions which is "called" into execution by instructions somewhere else and which, when finished its task, should "return" to the place from which it was called. It is therefore necessary, when "calling" a subroutine, to save the address from where the call is taking place; in this way when the subroutine is completed, it will be able to return to the correct location. This is further complicated by the fact that, very often, a subroutine will "call" another subroutine. For example consider the following instruction sequence:

    address:    code:
      .           .
      .           .
      .           .
    [a1]        CALL [b1]
    [a2]          .
      .           .
      .           .
    [b1]          .  (start of subroutine "B")
      .           .
      .           .
    [b2]        CALL [c1]
    [b3]          .
      .           .
    [b4]        RET
      .           .
      .           .
    [c1]          .  (start of subroutine "C")
      .           .
      .           .
    [c2]        RET
Instructions are executed sequentially until [a1] is reached.

The "CALL" at [a1] causes the next address (namely [a2]) to be pushed on to the stack and then "goes to" [b1].

Instructions are executed sequentially from [b1] until [b2] is reached. The "CALL" instruction at [b2] causes the next address (namely [b3]) to be pushed on the stack above [a2].

                             +--------+
           top of stack ---> |  [b3]  |
                             +--------+
                             |  [a2]  |
                             +--------+
                             |    .   |
Instructions are executed sequentially from [c1] until [c2] is reached. The "RET" instruction removes the value, [b3], from the top of the stack (so [a2] is again at the top of the stack) and then "jumps" to the address just removed from the stack (namely [b3]).

                             +--------+
           top of stack ---> |  [a2]  |
                             +--------+
                             |    .   |
N.B. Technically the top value is not removed from the stack; a pointer (SS:SP) to the "top of the stack" is moved down one position. However, the logical effect is the same.

With the completion of subroutine "C", execution has returned to the statement following the "CALL" to subroutine "C" (namely to [b3]). Instruction execution continues sequentially from [b3] until the "RET" (return) instruction at [b4] is reached. As before, the "RET" instruction removes the top value (address [a2]) from the stack and jumps to this address.

Instruction execution now continues with the instruction following the original "CALL" (to subroutine "B").

As previously noted addresses are derived from a combination of the CS and IP registers, that is, from a combination of a 16-bit segment and a 16-bit offset address. Therefore, addresses saved by the CALL on the stack for use by the RET must be two (16-bit) words in order to represent a "complete" address. This type of two word address is called a "far" address. If the CALL instruction and the subroutine "called" are in the same segment (have the same CS value), then only the IP or offset address needs to be saved. A single word (offset) address is called a "near" address. Subroutines are therefore classified as "near" or "far" subroutines depending upon whether or not they are in the same code segment as the statement which "calls" them.

-

The "PUSH" instruction can be used to save the contents of a 16-bit register on the top of the stack. This is convenient when we need to use and change the contents of a register but don't want to lose its current value. We "PUSH" its current value on to the stack, use the register for something else, and then use the "POP" instruction to restore its original value.

This temporary storage and reloading of register contents is a frequent requirement and is especially common within a subroutine. We "call" a subroutine to perform some specific requirement. A subroutine should behave as a "blackbox"; it should perform its function without any observable side-effects to the higher level code. Specifically, all registers should have the same values when we "get back" from a subroutine as when we "called" it (unless the explicit function of the subroutine is to modify a register). Since it is normal for a subroutine to need to use and modify the contents of registers, a subroutine should save the contents of all registers to be used (normally by "pushing" them on to the stack) before using them; before "returning" to the calling code, the subroutine should restore the original values to these registers.

    address:    code:
      .           .
      .           .
      .           .
    [m1]        CALL [s1]
    [m2]          .
      .           .
      .           .
      .           .
    [s1]        PUSH AX ;start of subroutine
    [s2]        PUSH BX
    [s3]          .     ;subroutine is now free to modify AX
                        ;     and BX
      .           . 
      .           .
      .           .
    [s4]        POP BX  ;restore old values before
    [s5]        POP AX  ;   returning to mainline
    [s6]        RET     ;return to 
      .           .
      .           .

Note that, with the above "code skeleton", when program execution reaches the instruction at address [s3], the stack looks like:

                   +----------+
                   | original |
 top of stack ---> |    BX    |
                   |  value   |
                   +----------+
                   | original |
                   |    AX    |
                   |  value   |
                   +----------+
                   | address  |
                   |   [m2]   |
                   |          |
                   +----------+
                   |     .    |
                   |     .    |

Therefore, in restoring the registers, BX must be restored ("popped") before AX. Note also, failure to "pop" one of these registers would result in the RET instruction attempting to use the original value in AX as its "return address" (instead of using [m2]).

-

As far as stack usage is concerned, the INT (interrupt) and IRET (interrupt return) do exactly the same thing as CALL and RET (with a "far" subroutine), except that the flag register is also saved on the stack by INT and restored by IRET.

                   +----------+
                   |  return  |
 top of stack ---> |  offset  |
                   | address  |
                   +----------+
                   |  return  |
                   | segment  |
                   | address  |
                   +----------+
                   | original |
                   |   flag   |
                   |  values  |
                   +----------+
                   |    .     |