CS311 Lecture: Introduction to the MIPS ISA                     Revised 9/11/03


1. To introduce the MIPS architecture
2. To introduce MIPS R-Type, immediate, and load-store instructions


1. MIPS ISA Handout (will have been distributed before class)
2. Connection to MIPS to demo gcc

I. Introduction
-  ------------

   A. For the next few weeks, we will be studying the Machine Language level
      of system description, focussing on a particular instruction set
      architecture (ISA) known as MIPS.

      1. It is not the goal of these lectures that you should become
         proficient MIPS assembly or machine language programmers.

      2. Rather, we want to use MIPS as an example of a typical instruction
         set architecture.

         a. The MIPS architecture belongs to the general category of
            Reduced Instruction Set Computer (RISC) architectures.  

            i. As such, it is easier to learn than a Complex Instruction Set
               Computer (CISC) architecture such as the IA32 architecture
               used by the Pentium.  

           ii. Moreover, RISC seems to be the direction of new architectures -
               e.g. Intel's 64-bit architecture (IA64)  that will eventually 
               replace the current IA32 architecture is a RISC architecture.  
               (The reasons for this have to do with RISC architectures
               facilitating producing higher-performance systems - a subject
               we will discuss later.)

          iii. In fact, all new instruction set architectures defined since 
               1985 have been RISC!  (The first RISC architectures were
               defined in the early 1980's.)

         b. When we get to the actual details of implementing a CPU
            (computer organization), the implementation of a RISC architecture
            like MIPS is more comprehensible - and, indeed, we will discuss
            the implementation of the MIPS architecture in later lecturs.

      3. Becoming familiar with this particular architecture has practical
         usefulness in its own right, as well.

         a. The MIPS architecture is widely used in a variety of
            applications ranging from high-end graphics workstations 
            to laser printers, Cisco routers and Nintendo and Sony videogame 

         b. Once you have become familiar with one instruction set
            architecture, it is much easier to learn another.  (Once you
            learn to drive a Ford, driving a Chevy is easy.)
   B. A bit of history

      1. The MIPS architecture grows out of an early 1980's research
         project at Stanford University.  

      2. In 1984, MIPS computer corporation was founded to commercialize
         this research.  However, CPU chips based on the MIPS architecture
         have been produced by a number of different companies, including
         LSI Logic, Toshiba, Philips, NEC, IDT, and NKK.

      3. The MIPS architecture has passed through a series of evolutions,
         known as MIPS I, MIPS II, MIPS III, and MIPS IV.

         a. Each successive ISA is a superset of the preceeding one - so 
            anything found in MIPS I is also found in MIPS II, III, and IV,

         b. The MIPS I and II ISA's were 32 bit architectures.  MIPS III
            added 64 bit capabilities - but with the core 32 bit
            architecture as a subset.

         c. We will confine our coverage to the core MIPS I architecture.

      4. Note that the MIPS architecture itself is nearly 20 years old.
         That may seem surprising, given the rapid progress in the field of
         CPU performance.  However, the changes have mostly come at the
         implementation level, not the architectural level.

         (Compare: Today's cars are much safer, longer lasting, and 
          environmentally friendly than those of decades ago - however, the 
          basic architecture of gasoline engine, four wheels, a steering wheel, 
          gas, brake and (optionally) clutch pedals has remained unchanged for 

   C. Note that we are going to study the MIPS ARCHITECTURE.  As is true of
      most successful architectures, There have been many IMPLEMENTATIONS of 
      this architecture - e.g.   

      1. R2000 - the original implementation, and the one whose implementation
         we will discuss later in the course

      2. R3000, R3051

      3. MIPS R6000 (implemented MIPS II ISA) *

      4. MIPS R4000, Vr4300, R4400, R4600 (implemented MIPS III ISA)

      5. MIPS R5000 (implemented MIPS IV ISA)

      6. MIPS R10000

         * The R6000 preceeded the R4000 because the R4000 took longer
           than planned to develop, but was quickly superseded by the R4000

      7. Various specialized implementations used in embedded systems
         (printers, routers, game consoles)

      Note: the system we will use in lab uses the R5000 implementation.
II. Basic MIPS-I Architecture
--  ----- ------ ------------

   A. Although MIPS implementations differ in internal organization, they can
      all be regarded as having the same basic architecture.

      (Go over diagram in handout.  Note: IO Devices will be discussed later 
      in the course and vary widely from installation to installation.)

   B. Discuss handout material on CPU registers

      1. The CPU has 35 user-visible REGISTERS (plus several typically used
         only by the operating system).  Each register holds one word (32 bits).

      2. Registers can be thought of as a very special kind of memory cell.

         a. Registers are part of the CPU, mot the memory system.

         b. A register is referred to by name (e.g. $31, pc) instead of by

         c. Information in a register can be accessed in much less than one
            clock cycle (e.g. much less than 1ns on a 1GHz machine).  
            Information in memory requires 10's of ns to access.

      3. Discuss

         a. 32 general registers

         b. pc

         c. hi and lo

         d. Note that - in contrast to the VonNeumann machine and MARIE, there
            is no IR.  That's because MIPS uses a pipelined implementation in
            which several instructions are at different stages of processing 
            at any one time.  There are several "IR's" that are part of the
            pipeline registers, as we shall see later.

   C. Discuss Handout material on Memory

      1. The amount of physical memory installed will vary from system to

         Special hardware and software gives the user the appearance of a 
         much larger VIRTUAL MEMORY by using disk as an extension of main 
         memory to hold regions not currently being used.

      2. Addresses whose leftmost bit is 1 (0x80000000 to 0xffffffff) are
         handled in special ways by the memory management hardware, and so
         are not used by ordinary user programs (though they are used by the
         operating system).  

      3. Individual regions of memory may be PROTECTED, so that a user program
         may be prohibited from writing it or reading or writing it.  (This
         allows the system to protect multiple users of the same system
         from one another, and to protect itself from them.)
         (Memory management and protection are topics we will consider
          toward the end of the course.  They are only an issue when writing
          user programs if one accidentally or intentionally uses an invalid
          address.  The familiar "segmentation fault - core dumped" that you
          may have gotten due to pointer error in a C++ program is the
          operating system's typical response to attempted access to illegal
          memory addresses.)

      4. Thus, regardless of the mount of physical memory actually installed,  
         the application program view of memory is 2 Gigabytes, with addresses 
         ranging from 00000000 to 0x7fffffff (on a 32-bit version of MIPS).  

III. Basic MIPS R-Type Instructions
---  ----- ---- ------ ------------

   A. The Basic Execution Cycle

      1. The CPU fetches and executes instructions from memory.  

         a. Each instruction is one word (32 bits) long.

         b. The leftmost six bits of each instruction are the OPCODE
            which specifies what operation is to be performed.  (Some
            instructions use additional bits elsewhere in the instruction
            to further specify the operation.)

         c. The remainder of the instruction specifies the OPERANDS -
            what values the operation is to be performed upon.

         d. The precise format of the rest of the instruction (what bits have
            what meaning) follows one of three patterns, depending on the 
            opcode.  (We will briefly introduce two today.)

      2. Like all Von Neumann machines, the CPU repeatedly executes the
         following "fetch - execute" cycle:

            while not halted
                fetch an word from the memory location specified by pc
                update pc (pc <- pc + 4 since instructions are one word long)
                decode instruction
                execute instruction

      3. MIPS instructions have one of three formats:

         a. R-Type

         b. I-Type

         c. J-Type

   B. The add instruction

      1, The MIPS add instruction can be used to add the contents of two SOURCE
         registers, placing the result in some DESTINATION register (which can
         be the same as one of the source registers, or different.)
      2. It looks like this (all values given in decimal)

         # of bits      6       5       5       5       5       6       

         field name     op      rs      rt      rd      shamt   funct

         contents       op = 0  1st     2nd     dest    (not    arith/logical
                        for     source  source  reg     used -  function =
                        most R  reg     reg              0)     32 for add
         This general format of instruction is called R format (where R
         stands for "register", because all operands are in registers)

      3. Thus, the instruction to add the contents of register 8 and register 
         9, placing the results in register 10, would look like this:

         bits           31..26  25..21  20..16  15..11  10..6   5..0     
                        (6)     (5)     (5)     (5)     (5)     (6)

         field          0       8       9       10      0       32

         binary         000000  01000   01001   01010   00000   1000000

                        = 0000 0001 0000 1001 0101 0000 0010 0000

         hexadecimal    =  0x01095020

      4. Clearly, writing instructions in machine language is an error-prone
         and tedious process.  For this reason, we normally use assembly
         language as a symbolic representation, relying on a program (the
         assembler) to translate into machine language for us.

         Ex: The above instruction in assembly language

                add $10, $8, $9

         Three things to note:

         a. The symbolic op code (add) represents values both in the op
            and the function fields

         b. Order of specifying registers

            i. Machine language: source1, source2, destination

            2. Assembly language: destination, source1, source2

               (corresponds to the way we would write a HLL assignment 
                statement: destination = source1 + source2.  The order of
                writing is not an issue, since we use a program to translate
                the symbolic form to machine language.)

            3. The shamt field that is not used by add (and many other 
               instructions) is not specified at all in the assembly language
   C. Other R-Type instructions

      1. Go over list in handout.

         a. Discuss distinction between add/sub and addu/subu

         b. Note two kinds of shift instructions (fixed amount and variable

         c. Note two kinds of right shift instruction (arithmetic, logical)

         d. We will see user for slt, sltu later.

      2. You might think that MIPS would have multiply and divide instructions
         that look similar to add and subtract - but this is not the case.

         a. Multiply and divide are much more complex operations.  An add or
            subtract can be done in one machine cycle, but a multiply or divide
            will take many cycles.  

         b. For this reason, some RISC architectures do not include multiply
            and divide instructions - they must be synthesized by a software
            subroutine when needed.

         c. MIPS does have hardware multiply and divide instructions, but
            they differ from most other R-type instructions in two ways

            i. They do not specify a destination register - the result of
               multiplication is placed as a double-length value in hi and lo;
               and division produces two results - quotient in lo, remainder
               in hi.

           ii. These instructions START the execution of the operation,
               which is performed by the multiply divide unit in parallel with
               further ordinary computation.

          iii. The result is fetched from hi and lo by mfhi, mflo - which are
               interlocked - i.e. further execution of instructions by the
               CPU is suspended until the needed value is available.
IV. Working with Constants
--- ------- ---- ---------

   A. The add immediate instruction.

      1. Many times, it is necessary to work with integer constants in
         a program - e.g.

         C/C++:         i ++

         translates into "add 1 to i"

      2. One way to handle this would be to store the value 1 in a known
         location in memory, and then treat it like a memory variable when
         its value is needed.
      3. However, because constants are needed so often, MIPS provides a
         special form of the add instruction for dealing with them, called
         add immediate.  These instructions are called I-Format instructions,
         because the instruction contains an IMMEDIATE VALUE as part of the

         # of bits      6               5       5               16      

         field name     op              rs      rt              immediate value

         contents       op =            source  destination     value to add
                        8 for addi      reg     reg             (two's 
                                                                 signed number)

      4. Example: to add 1 to register 8, and put the result in register 9,
         we could use the following instruction 

         bits           31..26  25..21  20..16  15..0
                        (6)     (5)     (5)     (16)

         field          8       8       9       1

         binary         001000  01000   01001   0000000000000001

                        = 0010 0001 0000 1001 0000 0000 0000 0001 
         hexadecimal    =  0x21090001

      5. The assembly-language way of writing the above would be

         addi   $9, $8, 1

         (By now you're used to the fact that the machine language has
          the order source then destination, while the assembly language
          puts the destination first!)

      6. Actually, the addi instruction can be used for much more than just
         adding a value to a register.

         a. Suppose we wanted to SUBTRACT 1 - e.g. to do something like

                i --

            We can do this with addi, using a negative value.  (Assume i
            is in register 8):

                addi $8, $8, -1

         b. Suppose we wanted to LOAD 1 into a register - e.g. to do
            something like:

                i = 1;

            We can do this with addi, taking advantage of the fact that
            register 0 always contains 0.  (Assume i is in register 8)

                addi $8, $0, 1
         c. Other architectures might include several different instructions -
            e.g. add immediate, subtract immediate, and load immediate.  In
            keeping with the RISC philosophy, MIPS has just one that can be
            used to perform multiple jobs.

   B. Other Immediate instructions - Handout

   C. The load upper immediate Instruction

      1. The I-Format instructions allocate 16 bits in the instruction to hold
         the immediate value to be used.  For those instructions which sign 
         extend the immediate value, we can represent any value between -32768 
         and + 32767; for those which don't sign extend, any value between
         0 and + 65535.

      2. What do we do if we need a value outside this range?  The MIPS
         architecture includes a "load upper immediate" instruction (lui),
         that can be used to place a 16 bit value into the UPPER half of a
         32 bit register.  When followed by an ordinary immediate instruction
         (typically ori to avoid sign extension), this can be used to put
         any 32 bit value in a register.

         # of bits      6               5       5               16      

         field name     op              0       rt              immediate value

         contents       op =            (not    destination     value to load
                        15 for lui      used)   reg             into upper 16

      3. Example:

         C:     x = 0x12345678

         MIPS:  lui     $2,0x1234
                ori     $2,$2,0x5678
                sw      $2,x

V. MIPS Load and Store Instructions
--- ---- ---- --- ----- ------------

   A. An important architectural characteristic of RISCs is that all
      computational instructions operate on values contained in registers,
      and put their result in a register.  

      1. If we want to do computation on variables contained in memory, we
         need to first load them into registers, do the computation there,
         and then (if necessary) store the result back into memory.
         (NOTE: in many ISA's, the term LOAD is used to mean "copy a value from 
          a location in memory into a register", and STORE is used to mean 
          "copy a value from a register into a location in memory" - but these
         terms are not used with 100% consistency!  We will always use them
         in this way.)


         Assume that the variables x, y, and z are stored in memory.  Then

                x = y + z
         Would be translated by four MIPS instructions

                Load y into some register (say $8)
                Load z into some other register (say $9)
                Add the two registers, putting the result into some
                 register (say re-use $8)
                Store the result register into x

         In assembly language

                lw $8, --- address of y
                lw $9, --- address of z
                add $8, $8, $9
                sw $8, --- address of x

      2. There are major two reasons why RISCs use this approach (known as load
         store architecture).

         a. It facilitates using a speed-improving technique known as
            PIPELINING (to be discussed in detail later.)

         b. It allows an arithmetic instruction to be represented in a single
            word - note that it takes only 5 bits to specify a register, but
            could take as many as 32 to specify a memory address (so if we
            could do x = y + z in one instruction, the instruction could need
            96 bits just to specify the addresses of the three operands, plus
            more for the opcode!)

      3. Obviously, when a value is stored in a register it is much easier
         to manipulate than when it is stored in memory.  For this reason,
         good compilers (and smart human programmers) try to take advantage
         of the large number of available registers to store frequently-used
         variables in registers, rather than memory.

         a. For example, if a function contains a few local variables, these
            will most likely be kept in registers, and will never exist in
            memory - since they come into existence when the function is
            entered, and cease to exist when it terminates.

         b. The C langauge includes a register directive which can be used,
            when declaring a variable, to tell the compiler that the variable
            should "live" in a register if at all possible.


            register int i;

            The compiler will try to set aside a register to hold the value
            of i, and will not put it in memory (unless it is unable to
            reserve a register.)

         c. However, good compilers incorporate register allocation algorithms
            that accomplish the same result - but often more efficiently
            than humans can do with register "hints" - so most programmers
            leave it to the compiler to handle this issue.

         d. Nonetheless, in any program having more than a very small number
            of variables, there will be a need to keep many variables in
   B. This leads us to a consideration of the basic load and store 

      1. As indicated in the example above, each load or store must specify the
         operation to be performed, a register to be loaded or stored, and
         a memory address.

      2. An astute observer will note that this appears to need more than
         the 32 bits available in an instruction word: some number of bits
         to specify the operation, 5 to specify the register, and 32 to
         specify the memory address!

      3. To avoid this problem, MIPS uses a format for these instructions
         that specifies the address in terms of a BASE REGISTER and a
         16 bit OFFSET.  The address is computed by adding the base register
         and the offset together.  The instruction format used is I-Format,
         similar to that of the immediate instructions we looked at earlier.

         # of bits      6               5       5               16      

         field name     op              rs      rt              immediate value

         contents       op =            source  transfer        offset
                        35 for lw       (base)  (to load)       (16 bit two's
                        43 for sw       reg     reg              complement
                                                                 signed number)

      4. Example: to load the contents of memory location 100 (decimal) into
         register 8, we could use the following instruction - taking advantage
         of the fact that $0 always contains zero:

         bits           31..26  25..21  20..16  15..0
                        (6)     (5)     (5)     (16)

         field          35      0       8       100

         binary         100011  00000   01000   0000000001100100

                        = 1000 1100 0000 1000 0000 0000 0110 0010 
         hexadecimal    =  0x8c080062

      5. The assembly-language way of writing the above would be

         lw     $8, 100($0)

         (Note, once again, that the order of the two register operands
          is the opposite of the machine-language order.  The load and
          store instructions always specify the transfer register first,
          then the offset and base register.  As usual, the assembler
          takes care of the order issue for us.)

      6. It might seem that the limitation to using a 16 bit offset would
         "cramp our style" in terms of accessing memory - i.e. a 16 bit
         offset can assume values in the range -32768 .. + 32767 if we
         regard the offset as a signed number.
         a. If the address we wish to access is in low memory (up to 32767), 
            we can specify it directly, using $0 as the base register.

         b. It is common for programs to group variables into regions of
            memory, and to use a register to point to the beginning of
            that region.

            i. The fields of an object are allocated storage in successive
               locations of memory, and the "this" pointer of methods
               is set to point to the first such location.

               Example: Suppose we have a declared as follows:

                class SomeClass
                        int x, y, z;


                        // Various methods including constructor ...

                        void foo();
                void SomeClass::foo()
                    x = y + z;

               When foo() is executing, the following situation might exist
               in memory:

                        | x     | <--- this is address of start of
                        ---------      this area
                        | y     |
                        | z     |

               Assuming that the value of this is placed in register 2, the
               code for the assignment statement in foo might translate
               as follows (actual code generated by g++ on our MIPS machine)

                lw      $3, 4($2)
                lw      $4, 8($2)
                add     $3, $3, $4
                sw      $3, 0($2)

               (Since ints are stored as words (4 bytes long), y is at an
                offset of 4 relative to this, and z is at an offset of 8.)

          iii. Wherever possible, local variables of a function are kept in
               registers, rather than memory.  However, if variables needs
               to be in memory, most compilers put the local variables of a 
               function into a single region of memory called the "stack frame" 
               of the function, and set register 29 (known by the special name 
               $fp) to point to it.
               Example: a function might declare local variables as follows:

                int a, b;

               Assume, for the sake of discussion, that these need to be kept
               in memory.  Then the compiler might generate code that would 
               create the following environment when the function is called:

                        | a     | <--- $fp holds address of beginning of
                        ---------      this area
                        | b     |

               Then we might load b into register 8 by the following code:

                        lw $8, 4($fp)

               (Since ints are one word (4 bytes) long, b is at an offset
                of 4 from the beginning of the frame area.)

               Note: actual compilers store additional information in the
                     frame, so if you looked at actual compiled code the
                     offset would be more than 4, reflecting this.

           iv. Many compilers put global variables into a single region of 
               memory, and set register 28 (known by the special name $gp) to 
               refer to point to it.  This allows global variables to
               be referenced by loads of the form:

                        lw register, some-offset($gp)

               Note: The gnu compilers we have on our mips machine doesn't 
                     actually do this.

         c. If all else fails, it is always possible to access a variable in
            memory by putting its address into a register, and then using
            an address of the form

                0(the register)

            The assember is capable of generating code to accomplish this,
            and uses a specific register that is set aside by software
            convention for this: register 1 - known by the special name
            at (assembler temporary.)

   C. In addition to accessing scalar variables, it is also possible to
      use load/store to access elements of an array.  In this case,
      two approaches are possible:

      1. If the element number is a constant, we can put the address
         of the array in a register and encode the element number in
         the offset.

         Example: Given an array of integers x, load x[4] into register 9,
                  assuming that register 8 holds the address of the
                  array (the address of x[0])

         lw $9, 16($8)  # 16 because each element is 4 bytes long

      2. If the element number is a variable, we can compute the address
         of the desired element in a register and then use it with
         offset 0.

         Example: Given an array of integers x, load x[i] into register 9,
                  assuming that register 8 holds the address of the
                  array, and register 10 holds the value of i.  Use
                   egister 2 as a temporary.


         add    $2, $10, $10    # $2 = 2 * i
         add    $2, $2, $2      # $2 = 4 * i
         add    $2, $2, $9      # $2 = address of x[i]
         lw     $9, 0($2)

      3. Note that in C/C++ and Java it is possible to use very similar
         statements to allocate storage for an array:

                C/C++   int * x = new int [10];

                Java    int [] x = new int [10];

         In both languages, x now is a variable that holds the ADDRESS of
         the first element of the array (x[0]), and access ot an array
         element x[i] is obtained by adding the value of x and the value
         of i (times the size of an element).

         This directly mirrors the way that the underlying hardware accesses

VI. The Assembler
--  --- --------

   A. From the examples above, it should be clear that writing MIPS programs
      in machine language would be tedious and error prone, and that such
      programs could be very hard to modify.  This is true of any machine
      language.  Thus, from the earliest days of computing (the 1950's), when
      it is necessary to program at this level it has been common to write
      programs in a symbolic ASSEMBLY LANGUAGE and then use a special program
      called an ASSEMBLER to translate the symbolic program into actual
      binary machine code.

   B. Today, of course, it is relatively uncommon to write programs in
      assembly language - though assembly language must still be used for
      writing some low-level components of system software such as operating
      systems.  Today, most assembly language is actually written by compilers -
      many compilers translate a HLL into assembly language, which then
      translates it into machine language - though some compilers compile
      directly to machine language.

      1. Example: The C and C++ compilers on most Unix systems work this way.

                  E.g. if we compile a C++ program called foo.cc into an object
                       program foo.o using the command
                    g++ -c foo.cc

                  We actually get an intermediate file called foo.s which
                  is then translated to get foo.o.

      2. Normally, the assembly code is deleted after it is assembled.
         However, you can use the -S command line switch to stop the process
         after the assembly code is produced.


         Create the following simple program demo.cc:

                int x, y, z;

                void foo()
                    x = y + z;

         Compile using gcc -S demo.cc

         Show demo.s.

         - Note that there is a _lot_ of overhead code associated with
           entering and exiting a function.

         - Note several lines that are immediately recognizable:

                lw      $2,y
                lw      $3,z
                addu    $2,$2,$3

          (Note: the addu instruction is very similar to the add instruction
           we have considered, except that it does not do any checking for
           overflow.  We will consider how add handles overflow later; note
           that C programs simply ignore overflow!)
   C. Historically, there has been a one-to-one correspondence between
      lines of assembly language code and machine instructions - e.g. the
      symbolic operation code corresponds directly to a machine language
      op code, and each line of assembly language produces exactly one machine 

      However, on RISCs this is not necessarily true - many RISC assemblers 
      will accept some constructs that assemble into more than one machine 
      instruction, and will synthesize certain RISC instructions from
      assembly language instructions that do not correspond directly to a
      machine instruction.

      1. Example: in the above - the various load and store instructions
         may require more than one machine instruction.

         Ex: lw $2,y

         If y's address is <= 32767, this can be encoded in a single I
         format lw instruction.
         However, if y's address is >= 32768, it may be necessary to
         synthesize a sequence of instructions that put the address of y
         into some temporary register (at), and then the instruction
         lw $2, 0(at).  Thus, this one line could translate into as many
         as three machine language instructions.

      2. Example: The MIPS assemblers recognize certain pseudo-instructions that
         can be synthesized from other actual machine instructions.

         a. Example: Suppose we want to copy the value in $2 into $3.  How can
                     we do this using MIPS instructions we already know?


                     add $3, $2, $0

            The MIPS assembler will accept the pseudo instruction:

                     move $3, $2

            which does not directly correspond to any MIPS machine instruction,
            and will synthesize an instruction like the above when it occurs.

         b. Example: Suppose we want to load a constant (say 42 = 2a hex) into 
                     register 2.  How can we do this using instructions we

                     ori $2, $0, 42

            The MIPS assembler will accept the pseudo instruction:

                     li $2, 42

            which does not directly correspond to any MIPS machine instruction,
            and will synthesize an instruction like the above when it occurs.
      3. The pipelined implementation of MIPS requires that we cannot use a
         value loaded from memory on the very next instruction.  (This has to
         do with the fact that execution of successive instructions is 
         overlapped in time.)  Sometimes the assembler needs to insert a "do 
         nothing" instruction to ensure that values are valid.  (We will 
         discuss this later in the course, and will ignore it for now.)

   D. You will get some experience working with the MIPS assembler in lab.

VII. An Example
---  -- -------

   A. To put everything together, consider the compilation of the
      following C/C++ assignment:

        int answer;
        int x, y; 
        int a[10];
        int i;

        answer = (x + a[i]) - (y + 1);
   B. MIPS assembly language (ignoring load delays).  Assume, for
      simplicity, that all variables are in a low address region of
      memory, so they can be accessed by a 16-bit address.  (A more
      realistic situation would involve 32-bit addresses, but that's
      not the point here.)

        lw $8, i        # $8 = value of i
        sll $8, $8, 2   # $8 = 4*i
        lw $8, a($8)    # $8 = word at address of a + 4 * i = a[i]
        lw $9, x        # $9 = x
        add $8, $9, $8  # $8 = x + a[i]
        lw $9, y        # $9 = value of y
        addi $9, $9, 1  # $9 = y + 1
        sub $8, $8, $9  # $8 = (x + a[i]) - (y + 1)
        sw $8, answer

   C. MIPS machine language.  Assume, for simplicity, that answer is at
      100, x at 104, y at 108, a at 112, and i at 152 - all decimal.

      ASK class to develop

        lw $8, i        # $8 = value of i

                35      0       8       152             (values in decimal)
                100011  00000   01000   0000000010011000

                1000 1100 0000 1000 0000 0000 1001 1000 = 0x8c080098

        sll $8, $8, 2   # $8 = 4*i

                0       0       8       8       2       0
                000000  00000   01000   01000   00010   000000

                0000 0000 0000 1000 0100 0000 1000 0000 = 0x00084080

        lw $8, a($8)    # $8 = word at address of a + 4 * i = a[i]

                35      8       8       112
                100011  01000   01000   0000000001110000

                1000 1101 0000 1000 0000 0000 0111 0000 = 0x8d080070

        lw $9, x        # $9 = x

                35      0       9       104
                100011  00000   01001   0000000001101000

                1000 1100 0000 1001 0000 0000 0110 1000 = 0x8c090068

        add $8, $9, $8  # $8 = x + a[i]

                0       8       9       8       0       32
                000000  01000   01001   01000   00000   100000

                0000 0001 0000 1001 0100 0000 0010 0000 = 0x01094020
        lw $9, y        # $9 = value of y

                35      0       9       108
                100011  00000   01001   0000000001101100

                1000 1100 0000 1001 0000 0000 0110 1100 = 0x8c09006c

        addi $9, $9, 1  # $9 = y + 1

                8       9       9       1
                001000  01001   01001   0000000000000001

                0010 0001 0010 1001 0000 0000 0000 0001 = 0x21290001

        sub $8, $8, $9  # $8 = (x + a[i]) - (y + 1)

                0       8       9       8       0       34
                000000  01000   01001   01000   00000   100010

                0000 0001 0000 1001 0100 0000 0010 0010 = 0x01094022

        sw $8, answer

                43      0       8       100
                101011  00000   01000   0000000001100100

                1010 1100 0000 1000 0000 0000 0110 0100 = 0xac080064

Copyright ©2003 - Russell C. Bjork