Comp 210 Lab 13: JAM

JAM simulator, JAM


JAM simulator

First we will discuss some of the basics of using the JAM simulator.

To start the Jam2000 simulator, enter the Unix command jam (or /home/comp210/bin/jam). (Note: The simulator is written in Scheme, of course.)

You'll see:

     [00000]:  00000000 (halt) %
The first number in brackets is the address of the "current" memory location (0 in this case), also known as the Program Counter (PC). The next number is the contents of the location (initially 0). If this corresponds to an encoded instruction, then the decoded (assembly) version follows in parentheses. Finally, the % is the prompt for the Jam2000 simulator. The "current" memory location is the one named by the PC, so in general the prompt should be interpreted as
     [PC]:  memPC  (disassemble memPC) %
where "memPC" means "take the number stored in register PC, interpret it as a memory address, and fetch the contents of the indicated memory location". For example, mem17 denotes the contents of the 17th memory location (which might happen to be 4321). "Disassemble" is a function which takes a number, and returns the assembly-code representation of that value (regardless of whether you consider that number to be an instruction or data).

To do: Enter the following short Jam2000 program (which adds 8 and 14) into the simulator:

     00000849      ; (ldi R4 8)     ; R4 <- 8
     00001439      ; (ldi R3 14)    ; R3 <- 14
     00034510      ; (add R5 R4 R3) ; R5 <- R4+R3 (i.e., 22)
     00000000      ; (halt)         ; end of program
You can either Pasting the above text is sufficient, as it enters the encodings and ignores the comments. Notice how the display updates: every time you enter an instruction (or its encoding), the encoding is placed into memPC, and the PC is incremented by 1. (This is just the process of the Jam2000 simulator interface, not the Jam2000 machine itself.) Thus you are placing these instructions into mem0...mem3.

To do: Look at the contents of memory locations 0 through 3 using the m command. You should see a listing of your program. Double-check that it is correct.

To do / Q: Execute (x) the program we just typed in above. Nothing seemed to happen (even after looking at the status). Well, one small change--the program counter stepped from 4 (instruction "halt") to 5. What happened?

To execute a Jam program we (or the simulator) do the following repeatedly:

  1. Fetch the number memPC.
  2. Increment the PC by 1.
  3. Decode the previously fetched number, since it will be interpreted as an encoded instruction.
  4. Perform the indicated instruction.

Q / To do: So what happened when we typed x above? (Hint: What was the PC? What was memPC? What instruction did memPC correspond to?) Aha! Try setting the PC to 0 (How? See the help screen), and then execute the program. To check if it was successful, re-examine the status of the registers.

In conclusion, here's a summary of the important simulator commands:

Any invalid command prints a list of the available commands.


JAM

Now let's try using the simulator with some bigger sample Jam programs.

Here is a reference card for the Jam2000 instructions. They are in four groups:

There are no input instructions, so put your program data into memory locations or registers.

Example 1

We would like to calculate (+ (vector-ref v 0) ... (vector-ref v 9)). Here's one possible accumulator-based Scheme function for this:

     (define (vector-add v index elementsLeft sumSoFar)
        (cond
            [(zero? elementsLeft)
             sumSoFar]
            [else
             (vector-add v
                         (add1 index)
                         (sub1 elementsLeft)
                         (+ sumSoFar (vector-ref v index)))]))
     (vector-add v 0 (vector-length v) 0)
Below is an adaption of this code into JAM assembly. One difference is that JAM does not have vectors, so instead of incrementing a vector index, we increment the address of the memory location of the data elements.
     ; purpose: prints the sum of mem[1000]..mem[1000+10-1]

     ; register description
     ; R0 = elementsLeft = number of elements left to add
     ; R1 = sumSoFar     = sum of elements seen so far
     ; R2 = address      = address of next vector element to add
     ; R3 = element      = vector element to add
     ; R4                = constant 1

                    ; initialize registers
     (ldi R0 10)    ; elementsLeft <- 10
     (ldi R1 0)     ; sumSoFar     <- 0
     (ldi R2 1000)  ; address      <- 1000
     (ldi R4 1)     ; R4           <- 1
         
     loop:          ; loop beginning
     (bez R0 exit)  ; if elementsLeft=0, exit loop
     (ld  R3 R2)    ; element      <- mem[address]
     (add R1 R1 R3) ; sumSoFar     <- sumSoFar + element
     (sub R0 R0 R4) ; elementsLeft <- elementsLeft - 1
     (add R2 R2 R4) ; address      <- address + 1
     (jmpi loop)    ; go back to beginning of loop and repeat

     exit:
     (print R1)     ; print sumSoFar
     (newline)

     (halt)         ; end of program
Blank lines, labels, and comments are solely to improve readability. Labels are just names for specific specific memory locations. E.g., loop stands for the fourth memory address after the initial instruction. Branch and jump instructions don't actually use labels as destinations, but only immediates. This simple simulator isn't able to convert labels to numbers, so you have to do this before entering the program.

Note how the program uses a register to hold the constant 1. This is a common technique since most instructions require their arguments to be in registers.

To do: Calculate the appropriate values (i.e., memory addresses) of the labels loop and exit to finish the program above. Enter the program and some data into the simulator and run the program. Debug as necessary.

To do: Try modifying the above to find the product of the squares of twenty values. You'll need to enter your new program from scratch.

For more details on assembly languages and their encodings, see Comp 320.

For the curious... Example 2

The following code fragment is essentially equivalent to (begin (set! z (- x y)) z):

     ; purpose: prints mem[100]-mem[200]

     ; memory description
     ; assume x stored at location 100
     ; assume y stored at location 200

     ; register description
     ; R0 = a copy of x
     ; R1 = a copy of y
     ; R2 = the address of x, 100
     ; R3 = the address of y, 200
     ; R4 = z

                    ; initialize registers
     (ldi R2 100)   ; R2 <- 100
     (ld  R0 R2)    ; R0 <- Mem[R2] = Mem[100] = x
     (ldi R3 200)   ; R1 <- 200
     (ld  R1 R3)    ; R1 <- Mem[R1] = Mem[200] = y

                    ; calculation
     (sub R4 R0 R1) ; z <- R0 - R1 = x - y

     (print R4)     ; print z
     (newline)

     (halt)

To do: If our machine didn't have five registers, the above code wouldn't work. Rewrite this program to meet the same purpose, but with fewer registers. You'll need to reuse registers for multiple purposes.

Q: What's the minimum number of registers you need for this? (Hint: Look at the syntax of the sub instruction.)

Any machine has a small fixed number of registers (typically 32 or 64 in current machines), so you can't always keep all your program values in distinct registers. In general, figuring out what should be stored in each register (register allocation) is difficult, and should only be done by complicated computer algorithms. (See Comp 412.)