Studio 6A


Studio Setup

Follow the steps from Studio 4A! The assignment link is link

Names

Open questions.md and list the names of everyone in your group.

Instruction Encoding

Binary Encoding

Using Appendix B identify the 32-bit binary representation of the instructions (show work and answers in questions.md). Write out the final answer in hexadecimal (8 digits of hex = 32 bits). Note that the last three have a lot of similarity. Show your final results in questions.md

  1. ori s0, s1, 32
  2. or s0, s1, s2
  3. and s0, s1, s2
  4. add s0, s1, s2

Encoding Conveniences

ALU operation selection: Review the description of the ALU from homework 5a. There is a way to select bits from instructions to make the ALU do the corresponding operation. Briefly describe what bits may be candidates to identify when the ALU should do add, or, and and operations.

Immediates: In addition to being organized to facilitate selecting operations, instructions are organized to facilitate identification of the “immediate” instructions, like ori vs. the register-only variations, like or. Which bit(s) may be being used for this purpose? Quickly compare some pairs of instructions, like ori and or, andi and and, addi and add, etc. to see if there’s a distinguishing feature.

Assembly Math

Complete code in math.s that will compute 3*a + b - c using only the instructions from table B.1 (compute use addition rather than multiplication). Test your work with different values.

As you’re working:

  1. Use the “Run & Debug” button on the left activity panel (Play button with a bug) to open the Venus assembly language simulator. It will try to start the simulator on whatever .s file is currently open. Actually running it will require using the “Launch with all views” button that will appear at the top of the window.
  2. A floating panel should appear with buttons to run the code, step through line-by-line, and reload to the start. Step through the program a few times. Practice using this and the registers view to observe how the program modifies the registers as it runs.
  3. Examine the memory in the “Memory” pane. Currently it should show only instructions. You can use the Up and Down buttons to move to different regions of memory (or enter an address in hexadecimal)
  4. Stop the program, deliberately make a typo (syntax error / invalid line or instructions) and try to run it again. Where is the error message displayed? (Answer in questions.md)

Stop the simulator

Be sure to use the “Stop” button to stop the simulator before moving on to a new file. Close the tab for math.s.

More Assembly Math

Open and complete moremath.s, which asks you to approximate the computation of 110% of an integer without using multiplication or division. Come up with an approach using ~2-8 instructions and re-run your program with a few different test cases.

Assembly Loops

Complete the loop problem described in loops.s. You can use the formula $ \frac{n \cdot (n+1)}{2} $ to check your work. Test your work with different values.

Registering Challenges

The entirety of large programs running on a RISC-V processor have to share the 31 registers for almost all operations. Compilers translate programs into a form that does all work by use and re-use of just these 31 variables! This is actually quite challenging and the type of thing that is error prone in humans. In fact, most modern programming languages and style guides discourage reusing variables for different meaning/data over time!

The “register conventions” (rules of use) are:

Register ABI Name Description Saver
x0 zero Hard-wired zero
x1 ra Return address Caller
x2 sp Stack pointer Callee
x3 gp Global pointer
x4 tp Thread pointer
x5–7 t0–2 Temporaries Caller
x8 s0/fp Saved register/frame pointer Callee
x9 s1 Saved register Callee
x10–11 a0–1 Function arguments/return values Caller
x12–17 a2–7 Function arguments Caller
x18–27 s2–11 Saved registers Callee
x28–31 t3–6 Temporaries Caller

From the perspective of someone writing a function (you):

  • If you use any registers where the Saver is Callee, you must ensure they are their original value before you return.
  • You can freely use anything where the Saver is Caller.

From the perspective of someone writing code that is calling a function (might apply to you too):

  • If you use any registers where the Saver is Caller, you must assume that any information in them could be destroyed by a jal to any function. If you need the contents, you should move it someplace else.
  • You can freely use the items where the Saver is Callee if you are the topmost function, which is rare. In most cases the code you’re writing is both called and will call something else, so you have to follow both sets of policies.

Turn the code you wrote for the prior part (sum) into a valid RISC-V function that follows the register conventions in the TODO location of functionfun.s. Your function will be called near the top of the file:

   # Call the function
            jal checkpoint_regs   # This function helps check that you are using registers correctly
    li a0, 100
    jal sum
            jal check_regs        # This function helps check that you are using registers correctly

The jal sum calls your code. This code includes two additional function calls, jal checkpoint_regs and jal check_regs, to help ensure you are following the register use rules. They are indented extra to highlight they are extra, artificial pieces of code and not part of the program’s intended function.

  1. Complete the sum function and confirm that it works on a few test cases. Be sure to follow the register conventions!
  2. Add an instruction that breaks the register conventions for an s-register. Re-run your program and note the error message printed by jal check_regs
  3. Change a value in an s-register and then change it back to the original value. For example, move the original value someplace you can use, change the register, then move the original value back before the function returns. What happens?
  4. Add in more instructions that change other s-registers and check the error(s).
  5. The stack pointer register, sp is also “Callee Preserved”. What happens when you change its value?
  6. The “stack” is the normal place to accomplish “move the original value someplace you can use” and is vital to the sharing process. The normal approach to using the stack is something like:
      functionstart:
       addi sp, sp, -20   # Set aside space in a multiple of 4.  This would be space for 5 words (4*5=20)
       # "Save" any of the callee-saved registers we need to use in consecutive places on the stack
       #    starting with 0, increasing by 4s, and not exceeding the value set aside-3
       sw s0, 0(sp)   # my_stack[0] = s0     (indices are word indices.  Each word is 4 bytes)
       sw s1, 4(sp)   # my_stack[1] = s1     (so "word index[1]" is 4 bytes into the stack)
       sw s2, 8(sp)   
       sw s3, 12(sp)
       sw s4, 16(sp)
       # Function body
       ...
    
       # Restore everything: Get back all the saved values first
       lw s0, 0(sp)   # s0 = my_stack[0]     (indices are word indices.  Each word is 4 bytes)
       lw s1, 4(sp)   # s1 = my_stack[1]     (so "word index[1]" is 4 bytes into the stack)
       lw s2, 8(sp)   
       lw s3, 12(sp)
       lw s4, 16(sp)
       addi sp,sp, 20  # Restore the stack's original value too (by undoing the initial subtract)
       jr ra           # Return
    
  7. Apply this approach to “storing / restoring” values and update your function to use s-registers for its computation. (You can used a condensed form since you probably don’t need 5 registers)
  8. Answer the following questions in questions.md
    1. Considering the pattern above. Why is it beneficial for most functions to only have a single return point in their assembly language? (Only one place that has a jr ra. Even if the function itself has several points that say return, they typically all result in code that goes to a single jr ra for the function)
    2. Briefly summarize how the above should ensure that any changes to sp or any of the s registers being used should not be noticed by any functions that call this function.
    3. Note that the jal instruction itself changes ra. How should the above be modified for any function that calls another function?

Submission / End-of-class: Commit And Push

1. First, be sure to commit and push files to GitHub (as shown in studio)

1.1

Source Selection

1.2

Commit Message

Caution!

Failure to type in a commit message will cause VSCode to open a window to enter the message (in the editor area) and the Source Control pane will appear to be stuck (a waiting animation) until you type in a message and close the message pane.

1.3

Commit and Push

2. Then go to GitHub.com and confirm the updates are on GitHub

End of Studio: Stop the Codespace

Caution!

Be sure to “stop” your Codespace. You have approximately 60 hours of Codespace time per month. Codespaces often run for ~!5 minutes extra if tabs are just closed.

Codespace