Comp 210 Lab 13: JAM

JAM simulator, JAM


JAM simulator

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

To do: To start the Jam2000 simulator, enter the Unix command jam (or /home/comp210/bin/jam). (Note: The simulator is written in Scheme, of course.) Alternately, you can run the simulator from within drscheme; load the teachpack /home/comp210/Projects/Jam/jam.ss, and start the simulator by evaluating (walk).

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. For example:
mem[0022]: +0000849 (ldi R4 8) % 
means (reading left-to-right) that memory location 22 contains 849, which (if interpreted as an assembly instruction) translates to (ldi R4 8). In general the prompt is
     [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). Thus, (disassemble mem_pc) denotes the assembly-code representation of mem_pc, regardless of whether you consider that number to be an instruction or data).

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

(ldi R4 8)
(ldi R3 14)
(add R5 R4 R3)
(halt)
Look at what is being displayed; you can see that these instructions are being entered into memory for you. Furthermore, be sure to pay attention to the memory location (0 through 3, above).

By the way, you can enter not just an instruction, but also a number. And, you can use scheme syntax -- including ; for comments. So the above 3-line program is the same as entering:

     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
Again, 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 cpu itself.) Thus you are placing these instructions into mem0...mem3.

Jam2000 reference sheet

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? (Note that the execute command even told you, it was executing the program starting at memory location #4!)

Use the instruction "p" to set the PC to the start of the program.

By the way, type "?" to see all the valid interface commands. (Remember, the interface is different than the Jam2000 CPU, which only understands the assembly instructions listed on the sheet.)


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.

Note that while the PC increments in both cases, typing in code/data (via the "machine inspector" interface) and executing code (the "simulator" (CPU)) are two very different processes! Code is only executed by the simulator (CPU) when the e(x)ecute command is issued.

To do: Modify the above program so that it automatically prints out (just) the result.

To do: Modify the previous program so that it prints out "0" if the result is less than zero.

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

Any invalid command prints a list of the available commands.

Tip: Most of the commands will accept shortcuts where the requested data can be put all on one line. For example:

p 0 sets the PC to 0

m 0 15 prints out the memory locations from 0 to 15

l myFile.jam opens the file "myFile.jam" and acts as if you literally typed in everything in that file.

x y will run the program starting at the current PC without prompting.

Additional information can be found in the Lecture and Homework.

JAM 2000 can also be run from Dr. Scheme. However a teachpack must be downloaded (ZIP file) and unzipped into it's own directory (the name doesn't matter). Load the teachpack "jam.ss" and then execute the function "(walk)". Note: at the moment, the teachpack does not appear to run on the Windows version of DrScheme.


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:          ; start of code to execute after loop is done
     (print R1)     ; print sumSoFar
     (newline)

     (halt)         ; end of program

Blank lines, labels (e.g. loop & exit), 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 -- that is, before you run the program, replace the references to "loop" and "exit" with the actual PC values needed.

Copy the above program into a text file using your favorite editor and then use the (l)oad command in JAM to read the file in. That way you'll be able to make changes to the program without re-typing the whole thing.

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: Re-write the above code so that it more closely matches the Scheme code, in that the address of the vector itself does not change, but rather only the offset into the vector (the index)

Q(s): Do you see duplicated work in the program (both in the Scheme and assembly)? How could this program be simplified? Is the check for zero really the best thing? Why do you think that languages like Scheme, C, C++ and Java use zero-based arrays?

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.)