CS 201 - Spring 2018.


[Home]

Welcome to CS 201!

Logical problem of the day:

What does the following code do?
#lang racket
((λ(x)(printf "#lang racket\n(~a\n ~s)" x x))
 "(λ(x)(printf \"#lang racket\\n(~a\\n ~s)\" x x))")

((lambda (s) (display (list s (list (quote quote) s))))
  (quote (lambda (s) (display (list s (list (quote quote) s))))))

Word of the Day

From the Jargon File.

Today's word(s): Quine see also: examples

Lecture 28: Machine Architecture. 4/4

Administrivia

  • Lunch: On Wednesday, we eat lunch at Franklin following class. (but not today)

  • The contacts page contains a google calendar showing office hours and related events.

  • Midterm: Tuesday April 10th, 7pm, ML 211 Here is a practice exam. Ignore problem 4. There will be a UNIX question, as in the first midterm.
  • [Assignments]. hw6 is now available.

  • Piazza course site If you have not received a notice, please sign up or let me know.

    Computer Architecture IV.

    Summary:

    In the preceding lecture, we considered the problem of reading in a zero-terminated sequence of numbers, printing out the reverse of the sequence, and halting. Here we temporarily simplify the task to reading in a zero-terminated sequence of numbers, storing them in consecutive memory locations, and then halting. (The task of printing out the reverse of the sequence will be deferred to the homework.) In the last lecture, we considered the following, NOT a solution.

        read-num:  input
                   skipzero
                   jump store-num
                   halt
        store-num: store num
                   jump read-num
        num:       data 0
    
    This is not a solution because each number read in overwrites the previous one, so that at the end only the most recently read number will be available. What we need is a way of storing into num the first time through the loop, storing into the next memory location the next time through the loop, and so on.

    We considered two possible solutions to this problem. The first, highly deprecated, solution is to write self-modifying code. That is, we may treat the store instruction as data, load it into the accumulator, add 1 to it, and store it back into its memory register. This would have the effect of changing the instruction from "store 6" (in the original program, because num corresponds to address 6) to "store 7", so that the next number read in from the user would be stored in memory location 7. The code for this kind of solution would be as follows.

        read-num:  input
                   skipzero
                   jump store-num
                   halt
        store-num: store num
                   load store-num
                   add constant-one
                   store store-num   
                   jump read-num
        constant-one: data 1
        num:       data 0 
    
    Once your TC-201 simulator is running, you might want to try this running this program to see what it does. Self-modifying programs are very difficult for human beings to understand, debug, and maintain, because of the large amount of "state" the human has to keep track of -- not only the current data values, but also the current version of the program that is running. (See "juggling eggs")

    (When computer memory was scarce, self-modifying code effectively recycled memory addresses - like a studio apartment where you cook, eat, work, and sleep in the same room. You had to make do with what you had.)

    So instead we introduce the last two instructions for the TC-201 computer, which allow us to implement "pointers" in our programs. [Note: we also have a shift instruction, described in hw6.rkt.] A pointer indicates where in memory an operation is to be performed. The two instructions are as follows.

        name    opcode     operation
        ----    ------     ---------
        loadi    1011      acc := Mem[extract-address(Mem[addr])]
        storei   1100      Mem[extract-address(Mem[addr])] := acc
    
    The names stand for "load indirect" and "store indirect". The extract-address() operation indicated above means that we extract the rightmost 12 bits of a 16-bit quantity and treat it as the address of a register in RAM.

    Thus, the instruction "loadi addr" is executed as follows: read from memory the contents of the register with address addr, and take the rightmost 12 bits of that as another address, addr'. Copy the contents of the memory register with address addr' to the accumulator. Finally, increment the program counter, as usual. Similarly, the instruction "storei addr" is executed as follows: read from memory the contents of the register with address addr, and take the rightmost 12 bits of that as another address, addr'. Copy the contents of the accumulator to the memory register with address addr'.

    The following example may help illuminate the function of loadi and storei. We will simulate the TC-201 for three instructions, starting with the following configuration.

        acc:   0000 0000 0000 0000
        pc:         0000 0000 0000
        rf:                      1
        aeb:                     0
    
    
    address    contents of memory register addressed
    -------    -------------------------------------
        0      1011 0000 0000 0101
        1      1100 0000 0000 0110
        2      0000 0000 0000 0000
        .
        .
        5      0000 0000 0011 0100
        6      0000 0000 0011 0111
        .
        .
        52     0000 1111 0000 1111
        53     1010 1010 1010 1010
        54     1111 0000 1111 0000
        55     0101 0101 0101 0101
    
    Because the pc contains address 0, we read the instruction at address 0: 1011 0000 0000 0101, decode the opcode 1011 to find that it is a loadi instruction. We take the address field of the instruction, 0000 0000 0101, which is address 5, and read the contents of memory register 5, which is 0000 0000 0011 0100. We take the rightmost 12 bits of that, 0000 0011 0100, and interpret it as address 52. Then the contents of memory register 52 are copied to the accumulator, and the program counter is incremented by 1, to produce the following configuration after the loadi instruction completes.
        acc:   0000 1111 0000 1111
        pc:         0000 0000 0001
        rf:                      1
        aeb:                     0
    
    
    address    contents of memory register addressed
    -------    -------------------------------------
        0      1011 0000 0000 0101
        1      1100 0000 0000 0110
        2      0000 0000 0000 0000
        .
        .
        5      0000 0000 0011 0100
        6      0000 0000 0011 0111
        .
        .
        52     0000 1111 0000 1111
        53     1010 1010 1010 1010
        54     1111 0000 1111 0000
        55     0101 0101 0101 0101
    
    Now the program counter holds address 1, so we read the instruction at address 1, which is 1100 0000 0000 0110. We decode the opcode, 1100, and find that it is a storei instruction. We take the address field, 0000 0000 0110, which is address 6, and read from address 6, getting the contents 0000 0000 0011 0111. We take the rightmost 12 bits of that as an address, address 55, and copy the contents of the accumulator to memory register 55. The program counter is then incremented, resulting in the following configuration after the storei instruction has been executed.
        acc:   0000 1111 0000 1111
        pc:         0000 0000 0010
        rf:                      1
        aeb:                     0
    
    
    address    contents of memory register addressed
    -------    -------------------------------------
        0      1011 0000 0000 0101
        1      1100 0000 0000 0110
        2      0000 0000 0000 0000
        .
        .
        5      0000 0000 0011 0100
        6      0000 0000 0011 0111
        .
        .
        52     0000 1111 0000 1111
        53     1010 1010 1010 1010
        54     1111 0000 1111 0000
        55     0000 1111 0000 1111
    
    Note that the contents of registers 5 and 6 are unchanged, but we have copied the contents of register 52 (pointed to by register 5) to register 55 (pointed to by register 6). Next, the instruction at address 2 is executed. Because it is a halt, all that happens is that the run flag (rf) is set to 0, and execution halts.

    We can make use of the idea of a pointer (which we repeatedly increment) and the storei instruction to solve the problem of reading in a zero-terminated sequence of numbers and storing them in consecutive locations in memory, and then halting.

        read-num:  input
                   skipzero
                   jump store-num
                   halt
        store-num: storei pointer
                   load pointer
                   add constant-one
                   store pointer
                   jump read-num
        pointer:   data table
        constant-one: data 1
        table:     data 0
    
    The memory location pointer initially contains the address corresponding to table. Thus, the first number read in from the user will be stored in the memory register corresponding to table. Then the sequence of instructions adds one to the value of the pointer, so that it now holds the address of the memory register after table. Thus, the next number read in will be stored in the next memory location, and so on, until a zero is input by the user.

    To see why this solution works in a little more detail, we first consider how the assembler that you will write for the homework will translate the above program into a sequence of 16-bit patterns to store in the RAM starting at address 0. Conceptually, the assembler first needs to determine how to translate all the symbolic addresses in the program into numbers. To do this, it merely numbers the instructions and data statements starting with 0, to determine the addresses that the corresponding instructions or data elements will have. Numbering the instructions and data statements of the above program, we have the following.

    0    read-num:  input
    1               skipzero
    2               jump store-num
    3               halt
    4    store-num: storei pointer
    5               load pointer
    6               add constant-one
    7               store pointer
    8               jump read-num
    9    pointer:   data table
    10   constant-one: data 1
    11   table:     data 0
    
    From this we can extract a "symbol table" allowing us to translate the symbolic labels to numbers:
       label          corresponding address
       -----          ---------------------
       read-num       0
       store-num      4
       pointer        9
       constant-one   10
       table          11
    
    With this table in hand, we can now translate the sequence of instructions and data statements one by one into the corresponding 16-bit patterns. For an instruction, we look up the 4 bit opcode corresponding to the name of the instruction, and, if the instruction has an address field, we convert the numeric address into a 12-bit pattern to combine with the opcode. If there is no address field, we fill the remaining 12 bits with 0's. For a data statement, we take the number after the "data" field and convert it, using 16-bit sign/magnitude representation, into a 16-bit pattern.

    For example, the first instruction is "input" (which takes no address field), so the resulting bit pattern is 0101 0000 0000 0000. The second instruction, "skipzero" is translated into 1000 0000 0000 0000, because the skipzero opcode is 1000. The third instruction is "jump store-num". The opcode is 0111, for jump, and looking up store-num in the symbol table, we find it is address 4, which we put in the address field in binary, to get the pattern 0111 0000 0000 0100. For the data statement "data 1" we convert 1 to a 16-bit quantity in sign/magnitude representation to get the pattern 0000 0000 0000 0001. For the data statement "data table", we look up table in the symbol table and find that it is address 11, which we convert to 16 bit sign/magnitude representation, giving the pattern 0000 0000 0000 1011. Putting all this together, the complete translation of the above program would be as follows.

    addr     assembly language         machine language
    ---- -------------------------    --------------------
    0    read-num:  input             0101 0000 0000 0000
    1               skipzero          1000 0000 0000 0000
    2               jump store-num    0111 0000 0000 0100
    3               halt              0000 0000 0000 0000
    4    store-num: storei pointer    1100 0000 0000 1001
    5               load pointer      0001 0000 0000 1001
    6               add constant-one  0011 0000 0000 1010
    7               store pointer     0010 0000 0000 1001
    8               jump read-num     0111 0000 0000 0000
    9    pointer:   data table        0000 0000 0000 1011
    10   constant-one: data 1         0000 0000 0000 0001
    11   table:     data 0            0000 0000 0000 0000
    
    Note that we have arranged for the table to be the last piece of data in the program, because numbers read from the user will be stored in consecutive memory locations starting with table. We simulated this program for one loop to understand a bit better how it works.

    We spent a little while discussing how to extend this program to print out in reverse the sequence of numbers read in before halting. The basic idea is to use "loadi pointer" to get the number into the accumulator and "output" to print it. Then the pointer is decreased by 1 and the loop is repeated. To know when to stop, there were two proposals -- one using a sentinel of 0 (which the user cannot cause to be stored in the table) to mark the beginning of the table, and the other to save the original start of the table in a separate variable, so that the pointer could be compared to it. (Note that in the above program, pointer points to the next available location in memory, not to the last stored number.)

    Getting to know UNIX

    UNIX Introduction Next: Principle 5 - unset


    [Home]