In chapter 13 we saw how modifying data using mutators required the concept of state.

We introduced the notion of a Scheme vector to control the changing state of stacks and queues.

In this chapter we look at the state of a computer system in general by asking more specifically:

This chapter will provide an introduction to computer architecture and assembly language programming.
Computer architecture is a structural and functional view of computers. Computer architecture is not the physical structure of a computer at the circuit level (like the layout of a chip or motherboard).
The core of a computer refers to its processor and memory, omitting input and output devices like the keyboard, mouse, monitor, and disk drive. To access data values, the processor sends the memory unit the address of a memory location.

We will describe the architecture of SLIM (Super-Lean Instruction Machine).

SLIM is a stored program computer. In such a computer:
Recall the role of memory within the computer's core:

Recall the role of the processor within the computer's core:

Recall the role of the registers in the processor:

Registers, like memory, can hold data, but: The SLIM architecture has 32 registers, referred to by number:

Recall the role of the arithmetic logic unit in the processor:

The ALU can perform:
Arithmetic Operations Comparison Operations
Addition =
Subtraction <>
Multiplication <
Division >
Quotient <=
Remainder >=
Recall the role of the control unit in the processor:

Instructions, being patterns of bits, must be decoded before they can be carried out.

Decoding circuitry figures out:

Once the instruction is decoded, the control unit sends signals:
SLIM instructions have two notations: Assembly language instructions are translated into machine language instructions by programs called assemblers.

This section describes the SLIM assembly language.

SLIM assembly language instructions have the form:
	opcode operand1, ...
      
where: Most operand specifiers in SLIM are register numbers.
	add 17, 2, 5
      
means:
"Add the contents of registers 2 and 5 and store the sum into register 17."

The general form of all arithmetic instructions is:
	opcode destreg, sourcereg1, sourcereg2
      
where: Other operations will have these kinds of operands:
Arithmetic instruction opcodes fall into two groups:

Operations Comparisons
add seq (set equal)
sub sne (set not equal)
mul slt (set less than)
div sgt (set greater than)
quo sle (set less or equal)
rem sge (set greater or equal)
Arithmetic comparison instructions compare values in the source registers and set the destination register to 1 (true) or 0 (false) depending on the result of the comparison.

Example. Suppose:

Then the instruction
	slt 4, 6, 5
      
will compare the contents of register 6 to the contents of register 5 to determine if the first is less than the second.

Since it is, then register 4 will be set to 1 (true).

If the instruction were

	slt 4, 5, 6
      
then register 4 will be set to 0 (false).
There are two ways of moving values between registers and memory:

	ld 3, 7
      

	st 4, 6
      
There are two input/output operations available in SLIM: Example:
  read 1  ; reads a value, say 314, typed at the keyboard into register 1
  write 1 ; writes the value in register 1 to the display
      
These instructions are able to read or write numeric values, but real machines would only have instructions for reading or writing individual characters (e.g. '3', '1', '4', etc.).
Another way of getting values into registers is through program constants.

SLIM uses a load immediate opcode for this:

	li destreg, const
      
Example:

Suppose SLIM executes the following instructions:
  li 1, 314
  write 1  
      
The machine would display 314 because it loads that value into register 1 and then writes out the contents of register 1 to the display.

But nothing would stop the machine from going to the third location in instruction memory and attempting to execute any code contained there.

To halt the machine, use the halt instruction:

  li 1, 314
  write 1  
  halt
      
Recall that the program counter PC is used to hold the address of the current instruction being executed.

After the instruction whose address is in PC is executed, the value in PC is incremented by one.

Registers with truth values along with conditional jump instructions are used to determine whether control should jump to an instruction other than PC + 1.

Suppose we want a program to exhibit the following behavior:
If the value in register 6 is greater than or equal to the value in register 5, then execute the instruction whose location is in register 8
We would use a combination of an arithmetic comparison instruction and a conditional jump instruction:
  slt 4, 6, 5  ; set reg4 to 1 if reg6 < reg5
  jeqz 4, 8    ; jump to the location in reg8
               ; if reg4 = 0, i.e. reg6 >= reg5
      
General form of the conditional jump:
	jeqz sourcereg, addressreg
      
Meaning: Jump to the location in addressreg if sourcereg contains false (0).

The jeqz instruction can be interpreted as "jump on false."

If you want to do something X on a certain condition, then: In the previous example:
Suppose instruction memory, the PC, and certain registers are in the following state:

Suppose that before the slt instruction is executed, registers 5 and 6 have the values as shown:

The PC will automatically be incremented to 99.

Since register 6 < register 5, register 4 will be set to 1:

The PC will automatically be incremented to 100.

Since register 4 is not 0, the PC stays at 100:

Suppose that before the slt instruction is executed, registers 5 and 6 have their values reversed:

The PC will automatically be incremented to 99.

Since register 6 ≥ register 5, register 4 will be set to 0:

The PC will automatically be incremented to 100.

Since register 4 is 0, the PC gets the contents of register 8 (200):

Sometimes, "unconditional" jumps are used:
	j addressreg
      
Meaning: "Jump to the location in addressreg no matter what."
This section explains how symbolic names make assembly language programs easier to write and understand.

Consider a program that reads in two numbers and then uses conditional jumping to display the larger of the two.

The flow chart below shows the program's structure:

  read 1      ; read input into registers 1 and 2
  read 2
  sge 3, 1, 2 ; set reg 3 to 1 if reg 1 ≥ reg 2, otherwise 0
  li 4, 7     ; 7 is address of the "write 2" instruction, for jump
  jeqz 3, 4   ; if reg 1 < reg 2, jump to instruction 7 (write 2)
  write 1     ; reg 1 ≥ reg 2, so write reg 1 and halt
  halt
  write 2     ; reg 1 < reg 2, so write reg 2 and halt
  halt

These difficulties are significant for large programs that are edited frequently.
Many assembly languages (including SLIM) allow names for: In SLIM,
  allocate-registers input-1, input-2
  allocate-registers comparison, jump-target

  read input-1
  read input-2
  sge comparison, input-1, input-2
  li jump-target, input-2-larger
  jeqz comparison, jump-target

  write input-1
  halt

input-2-larger:
  write input-2
  halt

Note that blank lines and spaces can be used to emphasize program structure.
Recall the Recursion Strategy:
Do nearly all the work first on a self-similar, smaller problem; then there will only be a little left to do.
And the Iteration Strategy:
Progressively reduce a problem to another problem that gives the same result.
At the beginning of this course, recursion was presented before iteration because recursion is easily implemented in Scheme.

When programming in assembly language (SLIM), iteration is easier to implement due to the availability of jump instructions.

The flow chart below shows the control structure for a program that prints the numbers from 1 to 10.

To implement the program in SLIM, we will use registers for three purposes:
Note how the SLIM program and the flow chart parallel each other:
  allocate-registers count, one, ten
  allocate-registers loop-start, done

  li count, 1
  li one, 1
  li ten, 10
  li loop-start, the-loop-start

the-loop-start:
  write count
  add count, count, one
  sgt done, count, ten
  jeqz done, loop-start

  halt

Recall the Scheme program for iteratively computing factorial:
(define factorial-product
  (lambda (a b) ; computes a * b!, provided
    (if (= b 0) ; b is a nonnegative integer
        a
        (factorial-product (* a b) (- b 1)))))

(define factorial
  (lambda (n)
    (factorial-product 1 n)))

The SLIM program for factorial will use registers for similar purposes: One of the registers used for jumping is called a continuation register because it holds an address of code to execute when the iteration is complete.
Suppose register b has a non-negative integer.

A flow chart for computing b! in register a, where end is a continuation register appears to the right.

Note that the flow chart includes algorithmic "pseudocode" that is not executable but indicates common arithmetic operations and data movements. For example, if a and b are registers, then

  a ← a * b
  
is pseudocode for the SLIM instruction
  mul a, a, b
  
  allocate-registers a, b, one, factorial-product, end

  li a, 1
  read b
  li one, 1
  li factorial-product, factorial-product-label
  li end, end-label

factorial-product-label:
  ;; computes a * b! into a and then jumps to end
  ;; provided that b is a nonnegative integer;
  ;; assumes that the register named one contains 1 and
  ;; the factorial-product register contains this address;
  ;; may also change the b register’s contents
  jeqz b, end ; if b = 0, a * b! is already in a

  mul a, a, b         ; otherwise, we can put a * b into a
  sub b, b, one       ; and b - 1 into b, and start the
  j factorial-product ; iteration over

end-label:
  write a
  halt

This section presents a more interesting use of continuation registers in SLIM.

Consider the following computation of n! + (2n)! in Scheme:

  (define factorial-product ; unchanged from before
    (lambda (a b) ; computes a * b!, given b is a nonnegative integer
      (if (= b 0)
          a 
          (factorial-product (* a b) (- b 1)))))

  (define two-factorials
    (lambda (n)
      (+ (factorial-product 1 n)
         (factorial-product 1 (* 2 n)))))

  read n

  b ← n
  end ← after-first 

  loop to compute b! in a

after-first:
  result ← a

  b ← 2n
  end ← after-second 

  loop to compute b! in a

after-second:
  result ← result + a
  write result
  halt
  allocate-registers a, b, one, factorial-product
  allocate-registers end, n, result, zero  ; note new registers

  li one, 1
  li zero, 0
  li factorial-product, factorial-product-label
  read n
  li a, 1
  add b, zero, n      ; copy n into b by adding zero
  li end, after-first ; note continuation is after-first

factorial-product-label:  ; same loop as before
  jeqz b, end   

  mul a, a, b   
  sub b, b, one 
  j factorial-product 

after-first:
  add result, zero, a   ; save n! away in result
  li a, 1
  add b, n, n           ; and set up to do (2n)!,
  li end, after-second  ; continuing differently after
  j factorial-product   ; this 2nd factorial-product,

after-second:           ; namely, by
  add result, result, a ; adding (2n!) in with n!
  write result          ; and displaying the sum
  halt
  
  
Recall the Scheme procedure for recursively computing factorial:
(define factorial
  (lambda (n)
    (if (= n 0)
        1
        (* (factorial (- n 1)) n))))

And the Recursion Strategy:
Do nearly all the work first on a self-similar, smaller problem; then there will only be a little left to do.
Q: How to implement recursion in SLIM?
Recall the Scheme trace of (factorial 5):
  > (factorial 5)
  >(factorial 5)        ; remember 5, compute 4!
  > (factorial 4)       ; remember 4, compute 3!
  > >(factorial 3)      ; remember 3, compute 2!
  > > (factorial 2)     ; remember 2, compute 1!
  > > >(factorial 1)    ; remember 1, compute 0!
  > > > (factorial 0)   ; base case
  < < < 1               ; 0! = 1
  < < <1                ; 1! = 1 × 0! = 1
  < < 2                 ; 2! = 2 × 1! = 2
  < <6                  ; 3! = 3 × 2! = 6
  < 24                  ; 4! = 4 × 3! = 24
  <120                  ; 5! = 5 × 4! = 120
  120      
  
  
A SLIM program must represent the data values: and the program locations: While other values and locations will be required, these are critical to a recursive implementation.
Not only must n be remembered when a recursive invocation is executed, but the program location to continue with after must also be remembered:
  > (factorial 5)
  >(factorial 5)        ; remember after-top-level
  > (factorial 4)       ; remember after-recursive-invocation
  > >(factorial 3)      ; remember after-recursive-invocation
  > > (factorial 2)     ; remember after-recursive-invocation
  > > >(factorial 1)    ; remember after-recursive-invocation
  > > > (factorial 0)   ; remember after-recursive-invocation
  < < < 1               ; 0! = 1, continue at after-recursive-invocation
  < < <1                ; 1! = 1 × 0!, continue at after-recursive-invocation
  < < 2                 ; 2! = 2 × 1!, continue at after-recursive-invocation
  < <6                  ; 3! = 3 × 2!, continue at after-recursive-invocation
  < 24                  ; 4! = 4 × 3!, continue at after-recursive-invocation
  <120                  ; 5! = 5 × 4!, continue at after-top-level
  120      
  
  
Two values must be remembered across recursive invocations of SLIM factorial: To compute 5!, we store 5 in n and ATL in cont, and we create an empty stack:

This section describes the use of the stack to recurse down to the base case.
5! will be computed as 5 × 4!.

To compute 4!, the values in n (5) and cont (ATL) will be saved on the stack.

Now n can be loaded with new value n - 1 (4).

After computing 4!, computation must continue at after-recursive-invocation (ARI), so cont is set to ARI.

4! will be computed as 4 × 3!.

To compute 3!, the values in n (4) and cont (ARI) will be saved on the stack.

Again, new values will be loaded into n and cont and pushed.
The pushing process continues as long as n does not equal 0.
When n = 0, the push loop stops and 0! = 1 is stored in a register, val, that will accumulate products.
If the top-level call had been 0!, then cont would contain ATL (after-top-level), and we would be done.

Instead, we were in the process of a recursive invocation (computing 1! as 1 × 0!), so we must complete the computation by executing the following instructions at the location ARI (after-recursive-invocation):

This process is repeated any time the location stored in cont is ARI (after-recursive-invocation).
The first time we enter the code at ARI:
The second time we enter the code at ARI:
The third time we enter the code at ARI:
The fourth time we enter the code at ARI:
The fifth time we enter the code at ARI: Since cont contains ATL, stack processing is complete and the result of the top-level call 5! is stored in val.
A top-level flow chart for the recursive SLIM implementation of factorial is shown below.

Most of the work is performed by the push and pop loops, shown next.

Note that the test cont = ATL, after the initial setting of val to 1, is necessary in case the original value read into n is 0. (What would happen if the test were omitted and control passed immediately to the pop loop?)

Since SLIM has a (relatively) limited number of registers, a stack will be implemented in data memory.
We visualize the bottom of the stack at location 0, with memory addresses increasing from bottom to top.

To access the stack, we use a register sp that acts as a stack pointer, holding the address of the next available location in the stack.

A representation of an empty stack is shown to the right.

To push onto the stack, we use the store instruction:
	st sourcereg, addressreg
      
To prepare for the first recursive invocation in the computation of 5!:

To pop from the stack, we use the load instruction:
	ld destreg, addressreg
      
Suppose we have reached the base case in factorial and are ready to begin the pop loop:

The SLIM code for factorial divides into code for:
   allocate-registers n, cont ; the argument, continuation,
   allocate-registers val     ; and result of factorial procedure
   allocate-registers factorial, base-case ; hold label values
   allocate-registers sp      ; the stack pointer
   allocate-registers one     ; the constant 1, used in several places

   li one, 1                  ; set up the constants
   li factorial, factorial-label  ; top of push loop
   li base-case, base-case-label  ; base case code
   
   li sp, 0                   ; initialize the stack pointer

   read n                     ; the argument, n, is read in
   li cont, after-top-level   ; the continuation is set
factorial-label:   ; computes the factorial of n into val 
   jeqz n, base-case          

   st n, sp        ; push n onto stack
   add sp, sp, one
   st cont, sp     ; push cont
   add sp, sp, one
   sub n, n, one   ; using n-1 as the new n argument
   li cont, after-recursive-invocation 
   j factorial     ; continue the push loop
  
base-case-label:   ; this is the n = 0 case
   li val, 1
   j cont
after-recursive-invocation:   
   sub sp, sp, one
   ld cont, sp     ; pop stack into cont
   sub sp, sp, one
   ld n, sp        ; pop stack into n
   mul val, val, n ; compute n! as (n-1)! * n, 
                   ; i.e. val * n,
   j cont          ; jump to the continuation
  
after-top-level:              ; after top-level call,
   write val                  ; display the result
   halt
   allocate-registers n, cont ; the argument, continuation,
   allocate-registers val     ; and result of factorial procedure
   allocate-registers factorial, base-case ; hold label values
   allocate-registers sp      ; the stack pointer
   allocate-registers one     ; the constant 1, used in several places

   li one, 1                  ; set up the constants
   li factorial, factorial-label
   li base-case, base-case-label
   
   li sp, 0                   ; initialize the stack pointer

   read n                     ; the argument, n, is read in
   li cont, after-top-level   ; the continuation is set

factorial-label:              ; computes the factorial of n into val 
   jeqz n, base-case          

   st n, sp                   ; push n onto stack
   add sp, sp, one
   st cont, sp                ; push cont
   add sp, sp, one
   sub n, n, one              ; using n-1 as the new n argument
   li cont, after-recursive-invocation 
   j factorial                ; continue the push loop
   
after-recursive-invocation:   
   sub sp, sp, one
   ld cont, sp                ; pop stack into cont
   sub sp, sp, one
   ld n, sp                   ; pop stack into n
   mul val, val, n            ; compute n! as (n-1)! * n, i.e. val * n,
   j cont                     ; jump to the continuation

base-case-label:              ; this is the n = 0 case
   li val, 1
   j cont

after-top-level:              ; after top-level call,
   write val                  ; display the result
   halt