CS 61C Spring 2024 | Lab 3: RISC-V, Venus

1,320 阅读7分钟

Lab 3 教你如何使用 Venus 这个 RISC-V 执行模拟器。

Setup

In my cs61c directory, pull the files for this lab with:

$ git fetch starter main
$ git merge FETCH_HEAD

Exercise 1: Venus Basics

This exercise will walk you through the process of connecting your file system to Venus, which should save you a lot of trouble copy/pasting files between your local drive and the Venus editor.

Open venus.cs61c.org in your web browser (Chrome or Firefox are recommended).

Go back to the editor tab, and edit ex1_hello.s so that the output prints 2024.

  • Hint: The value in a1 is printed when ecall executes. What ecall does is out of scope for this class though.

My answer:

.text

addi a0 x0 1 # a0 = 0 + 1
addi a1 x0 2024 # a1 = 0 + 2024

# This prints out the integer stored in a1
ecall

# This exits the program
addi a0 x0 17
addi a1 x0 0
ecall

Exercise 2: Using the Venus Debugger

This exercise will ask you to write down your answers in ex2_answers.txt. The question numbers may be different from the step numbers, please be careful!

  1. Open fib.s in the Venus debugger using one of the two ways listed above.

    • Question 1: What is the machine code of the highlighted instruction? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • image.png
      • 0b funct7 rs2 rs1 funct3 rd opcode
      • 0b 0000000 00000 00000 000 00101 0110011
      • 0b 0000 0000 0000 0000 0000 0010 1011 0011
      • 0x000002B3
    • Question 2: What is the machine code of the instruction at address 0x34? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • image.png
      • 0x00000073
  2. Click the "step" button to advance to the next instruction. The second instruction should now be highlighted.

  3. Click the "prev" button to undo the last executed instruction. Note that undo may or may not undo operations performed by ecall, such as exiting the program or printing to console.

  4. On the right side of the screen, click the "Registers" tab to view the values of all 32 registers. This tab may be already selected if it the title is highlighted in yellow. Make sure you on looking at the integer registers, not the floating point registers.

    • Question 3: What is the value of the sp register? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • image.png
      • 0x7FFFFFDC
  5. Continue stepping until the value in t1 changes.

    • Question 4: What is the new value of the t1 register? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • image.png
      • 0x00000001
    • Question 5: What is the machine code of the current instruction? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • image.png
      • 0x10000E17
  6. Step until you are at address 0x10. At this point, t3's value has been updated.

    • Question 6: What is the value of the t3 register? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • 0x10000000
  7. If we look at the current instruction, we're loading from the t3 register. Use the "Memory" tab (next to the "Registers" tab), and input the answer of question 6 (the value of t3) into the "Address" box. You may need to scroll down on the memory tab before it is visible. Press "Go" to go to that memory address

    • Question 7: What is the byte that t3 points to? The answer should be an 8 bit (1 byte) hexadecimal number, with the 0x prefix.
      • 0x0C
  8. Set a breakpoint at address 0x28 by clicking the row at that address. The row should turn light red and a breakpoint symbol should appear.

  9. Continue until the breakpoint by pressing "Run".

    • Question 8: What is the value of the t0 register? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • 0x00000001
  10. Continue 6 more times.

    • Question 9: What is the new value of the t0 register? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • 0x0000000D
      • 0 1 1 2 3 5 8 13
  11. Sometimes, reading hexadecimal values aren't very helpful. Set the display settings to "Decimal" by using the dropdown at the bottom of the register tab. This can also be done for the memory tab.

    • Question 10: What is the value of the t0 register in decimal? The answer should be a decimal number without a prefix.
      • 13
  12. Click the instruction at address 0x28 again to unset the breakpoint.

  13. Click run to finish running the program, since there are no more breakpoints.

    • Question 11: What is the output of the program? The answer should be a decimal number without a prefix.
      • 144

Exercise 3: Using Memcheck

Similar to the previous exercise, this exercise will ask you to write down your answers in ex3_answers.txt. The question numbers may be different from the step numbers, please be careful!

  1. Open ex3_memcheck.s in the Venus editor and read through the entire program to get an idea of what it does.

  2. Run the program. Oh no, the program errors! Let's take a look at the error message.

    • Question 1: What address did the program try to access, but caused the error? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • 0x10008058
    • Question 2: How many bytes was the program trying to access? The answer should be a decimal number without a prefix.
      • 4
  3. This seems like a memory error, so let's give memcheck a shot. Enable memcheck (normal mode) and reopen ex3_memcheck.s in VDB.

  4. Run the program. Look, a memcheck error with more details! Read the error carefully.

    • Question 3: What address did the program try to access, but caused the error? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • 0x10008080
    • Question 4: How many bytes were allocated in the block related to the error? The answer should be a number without units.
      • 40
    • Question 5: Which line of the source file caused this error? The answer should be a number.
      • 18
  5. Compare your answer to Question 2 and Question 4. Note that memcheck may change the memory address that malloc returns.

  6. Let's try to debug this error. Recall that t1 contains the loop counter.

    • Question 6: What is the value of t1 based on the memcheck error message? The answer should be a decimal number.
      • 10
  7. Fix this error in the source code and save the file.

  8. Run the program again. The process complete without any invalid access errors. However, it complains that there's some unfreed memory.

    • Question 7: How many bytes were not freed when the program exited? The answer should be a decimal number without units.
      • 40
  9. Rerun the program with memcheck in verbose mode. Remember to reopen the file in VDB.

    • Question 8: What is the address of the block that was not freed? The answer should be a 32 bit hexadecimal number, with the 0x prefix.
      • 0x10008058
  10. Fix this error by calling free.

  11. Disable memcheck for the next two exercises.

Exercise 4: Array Practice

Consider the discrete-valued function f defined on integers in the set {-3, -2, -1, 0, 1, 2, 3}. Here's the function definition:

f(-3) = 6
f(-2) = 61
f(-1) = 17
f(0) = -38
f(1) = 19
f(2) = 42
f(3) = 5

Implement the function in ex4_discrete_fn.s in RISC-V, with the condition that your code may NOT use any branch and/or jump instructions! Make sure that your code is saved locally. We have provided some hints in case you get stuck.

All output values are stored in the output array which is passed to f through register a1. You can index into that array to get the output corresponding to the input.

Make sure that you only write to the t and a registers. If you use other registers, strange things may happen (you'll learn about why soon).

My answer:

  • ex4_discrete_fn.s
.globl f # this allows other files to find the function f

# f takes in two arguments:
# a0 is the value we want to evaluate f at
# a1 is the address of the "output" array (read the lab spec for more information).
# The return value should be stored in a0
f:
    # Your code here
    addi t0, a0, 3 # t0 = a0 + 3, a0 is the value we want to evaluate f at, t0 is the index of the array
    slli t0, t0, 2 # t0 = t0 * 4, t0 is the offset of the element in the array
    add t0, t0, a1 # t0 = t0 + a1, t0 is the address of the element in the array
    lw a0, 0(t0) # a0 = *t0, a0 is the value of the element in the array

    # This is how you return from a function. You'll learn more about this later.
    # This should be the last line in your program.
    jr ra

Exercise 5: Factorial

In this exercise, you will be implementing the factorial function in RISC-V. This function takes in a single integer parameter n and returns n!. A stub of this function can be found in the file ex5_factorial.s.

The argument that is passed into the function is located at the label n. You can modify n to test different factorials. To implement, you will need to add instructions under the factorial label. There is an recursive solution, but we recommend that you implement the iterative solution. You can assume that the factorial function will only be called on positive values with results that won't overflow a 32-bit two's complement integer.

At the start of the factorial call, the register a0 contains the number which we want to compute the factorial of. Then, place your return value in register a0 before returning from the function.

Make sure that you only write to the t and a registers. If you use other registers, strange things may happen (you'll learn about why soon).

Also, make sure you initialize the registers you are using!  Venus might show that the registers are initially 0, but in real life they can contain garbage data. Make sure you set the register values that you will be using to some defined number before using them.

My answer:

  • ex5_factorial.s
# factorial takes one argument:
# a0 contains the number which we want to compute the factorial of
# The return value should be stored in a0
factorial:
    # YOUR CODE HERE
    add t0, a0, x0 # t0 = a0, t0 is the counter
    addi t1, x0, 1 # t1 = 1, t1 is the result
    addi t2, x0, 1 # t2 = 1, t2 is the multiplier
    loop:
        beq t0, x0, end # if t0 == 0, jump to end
        mul t1, t1, t2 # t1 = t1 * t2
        addi t2, t2, 1 # t2 = t2 + 1
        addi t0, t0, -1 # t0 = t0 - 1
        j loop # jump to loop
    end:
        add a0, t1, x0 # a0 = t1