CS220 - Design of a Complete CPU and Memory System 
November 1, 1999

This document summarizes what will serve as a basic design of a complete CPU and memory system that we can aim toward implementing in LogicWorks.  The single-bus architecture shown on page 113 is a model for this design.
All registers shown here are 8-bit (1-byte) registers. The memory is a 256-byte memory and is byte-addressible, so that a memory address is also 8 bits and the range of memory addresses (the "address space") is 0-255.

Instruction Set

A machine instruction is either 1 byte long or 2 bytes long, depending on whether or not it addresses memory or has an immediate operand. If it is an add, subtract, multiply, divide, and, or, compare, or halt instruction it is 1 byte long and loads into the IR using a standard 3-step instruction fetch (as illustrated in chapter 3). The 1-byte machine instruction's fields are defined as follows:

Here, Source and Target identify the two registers (any of r0 - r3) that contain the two operands to be added, subtracted, etc. and Target identifies also the register where the result will be stored.  The assembly language mirrors this instruction layout.  Thus, when writing an assembly language instruction like

add r1,r2

the interpretation is that r2 is the Target of the operation and gets the sum of its contents and the contents of Source register r1.  Below is a summary of the one-byte instructions:

Symbol    Op      Meaning
------    ----    -------
halt     0000    Halt execution
add      0001    Integer addition (8-bit)   Target <- [Source] + [Target]
sub      0010       "    subtraction               "           -    
mul      0011       "    multiplication            "           *
div      0100       "    division                  "           /
and      0101    Logical AND (8-bit)               "           &&
or       0110       "    OR (8-bit)                "           ||
cmp      0111    Compare registers; set LT GT EQ and CO accordingly
Note that the Op code for these instructions always 0 as its first bit, so this group is easy to distinguish from the 2-byte instructions.  According to his table, the complete machine coding for the add r1,r2 instruction is given as follows (its hex version is shown on the right):
0001 01 10       (16)
If the instruction is a load, store, or branching instruction, it will require two bytes in memory, the second of which is the IROffset field for computing an address or an immediate value. These instructions have the following layout:

Because these instructions occupy two bytes, their instruction fetch cycles will require 6 steps rather than 3 in order to fetch the second byte and load it into the IROffset register. The 4-bit op codes for these instructions and their meanings are summarized as follows (notice that their first bit is always 1):

Symbol   Op      Meaning
------   ----    -------
lbz      1000    Load from memory to register    Target <- [[Source] + IROffset]
la       1001    Load an address to a register   Target <-  [Source] + IROffset
li       1010    Load immediate to register      Target <-  IROffset
stb      1011    Store to memory from register   [Target] + IROffset <- [Source]
b<cc>    1100    Branch conditionally (the four bits of Source and Target define 
                     the branch condition <cc>):  b = 0000, blt = 1000,
                     beq = 0010, ble = 1010, bgt = 0101, bne = 1101,
                     BOV (branch on overflow) = 0001.  If the values of LT GT EQ SO 
                     match the condition <cc>, then PC <- [Source] + IROffset
bl       1101    Branch and link (procedure call): LR <- [PC],                                                  
                                                   PC <- [PC] + IROffset
blr      1110    Return from procedure             PC <- [LR]
nop      1111    Continue to the next instructuction  (do nothing)
For example, the instruction
   lbz  12(r3), r2
loads register r2 with the byte at the memory address which is 12 bytes beyond the address stored in register r3. This instruction has the following binary coding (its hex version is shown on the right):
1000 11 10 0000 1100     (8E0C)
The instruction
   li   -1, r2
loads the constant -1 into register r2. Its binary coding (hex on the right) is:
1001 00 10 1111 1111     (92FF)
The branch instruction
   bne  *+7
branches to the instruction that is 7 bytes after it, but only if the EQ condition code <cc> is not set to 1. Its binary representation (hex on the right) is:
1100 11 01 0000 0111     (CD07)

Example 1: Capitalizing a String

Suppose we want to write a program that converts a string of characters to upper case, as identified in the following pseudo-Java code ("%11011111" designates a binary string):
          String Msg = "Hello";
          for (int i=0; i<Msg.length(); i++)
              Msg[i] = Msg[i] && '%11011111';
The Assembly language code for this example looks like what you've been writing for the PowerPC assembler, except that each instruction has two operands rather than 3.  Another main difference is that instructions are byte operations and integer and character data values are stored in single bytes of memory (numbers are in 2's complement notation):
 Addr     Instruction
 ----     ----------------------------
  00              la      Msg, R0          ; R0 <- address of 1st character in Msg
  02              li      1, R1            ; R1 <- constant 1
  04              la      4(R0), R3        ; R3 <- address of last character in Msg
  06      Loop:   cmp     R0, R3           ; see if done
  07              bgt     Done             ; this operand could be written *+12
  09              lbz     0(R0), R2        ; R2 <- a character in Msg
  0B              li      %11011111, R1    ; put the mask into R1
  0D              and     R1, R2           ; convert it to upper case
  0E              stb     R2, 0(R0)        ; put it back in Msg
  10              li      1, R1            ; put constant 1 back into R1
  12              add     R1, R0           ; increment to address next character
  13              b       Loop             ; this operand could be written *-15
  15      Done:   halt
  16-1A   Msg:    dc      'Hello'
Note that the hex addresses 'Addr' shown on the left are for this program's machine instructions, once it is assembled (by hand!).  The machine code for this program is given below:
                   Machine Instruction
  Addr  Hex        Op   Source Target IROffset  Notes
  ----  ----       ---------------------------  -------------------------------
  00    9010       1001 00     00     00010000  ; R0 <- address of 1st character
  02    A101       1010 00     01     00000001  ; R1 <- constant 1
  04    9304       1001 00     11     00000100  ; R3 <- address of last char
  06    73         0111 00     11               ; see if done
  07    C30C       1100 01     01     00001100  ; this operand could be *+12
  09    8200       1000 00     10     00000000  ; R2 <- that character from Msg
  0B    A1DF       1010 00     01     11011111  ; put the mask into R1
  0D    56         0101 01     10               ; convert it to upper case
  0E    B800       1011 10     00     00000000  ; put it back into Msg
  10    A101       1010 00     01     00000001  ; put constant 1 back into R1
  12    14         0001 01     00               ; increment to address next char
  13    C0F1       1100 00     00     11110001  ; this operand could be *-15
  15    00         0000
  16-1A 48656C6C6F 0100 1000 0110 0101 0110 1100 0110 1100 0110 1111 ; 'Hello'


Example 2: Summing a List

To illustrate the "versatility" of this machine, here is another problem. Suppose we want a program that computes the sum of the elements in a 7-element list, reminiscent of the following pseudo-Java program fragment.

    int [] List = {2, 3, -1, 9, 4, 6, 7};
    Sum = 0;
    for (int i=0; i<List.length; i++)
        Sum = Sum + List[i];

The assembly code for this program might look like this:

Addr     Instruction
----     --------------------------
00       la List, R0                     ; R0 <- address of an integer
02       li 1, R1                        ; R1 <- constant 1
04       li 0, R2                        ; R2 <- temporary Sum
06       la 6(R0), R3                    ; R3 <- address of last integer
08 Loop: cmp R0, R3                      ; see if sum is complete
09       bgt Done
0B       lbz 0(R0), R1                   ; if not, load next integer
0D       add R1, R2                      ; R2 <- [R2] + [[R0]]
0E       li  1, R1                       ; put 1 back into R1
10       add R1, R0                      ; address of next integer
11       b   Loop 
13 Done: stb R2, Sum                     ; store the result
15       halt
16 List: DC 2, 3, -1, 9, 4, 6, 7 
1D Sum:  DC 0
Note that R1 serves double-duty here (for the constant 1 and for an integer to be added to the sum.  This is due to the small number of registers we have in this machine.  Try hand-coding this program into machine code:
                    Machine Instruction
Addr  Hex           Op   Source Target IROffset  Notes
----  ----          ---------------------------  -------------------------------
00                                               ; R0 <- address of an integer
02                                               ; R1 <- constant 1
04                                               ; R2 <- temporary Sum
06                                               ; R3 <- address of last integer
08                                               ; see if sum is complete
09 
0B                                               ; if not, load next integer
0D                                               ; R2 <- [R2] + [[R0]]
0E                                               ; put 1 back into R1
10                                               ; address of next integer
11 
13                                               ; store the result
15
16 0203FF09040607 00000010 00000011 11111111 00001001 00000100 00000110 00000111 
1D 00