CS61C 2020计算机组成原理Lab03
Exercise 1: Familiarizing yourself with Venus
.data
.word 2, 4, 6, 8
n: .word 9.text
main: # add t0, x0, x0# addi 是 "add immediate"(立即数加法)的缩写,表示这是一个加法指令,其中一个加数是一个立即数(即直接在指令中给出的数)# 将 x0 寄存器的值(即 0)和立即数 1 相加,然后将结果(0 + 1 = 1)存储在 t1 寄存器中。# 简而言之,这条指令将 t1 寄存器的值初始化为 1addi t1, x0, 1# la 是 "load address" 的缩写,表示这是一个加载地址的指令# 将标签 n 的地址加载到寄存器 t3 中la t3, n# lw 代表 "load word"(加载字),用于从内存中加载一个 32 位的字(word)到寄存器中。# 0(t3) 指定了要从哪里加载字的内存地址。这里使用了基址加偏移量的寻址方式,其中 0 是偏移量,t3 是基址寄存器# 执行这条指令之后,t3 不再持有地址,而是持有该地址处的数据lw t3, 0(t3)
fib: # beq 代表 "branch if equal"(如果相等则跳转)。这是一个条件分支指令,用于比较两个寄存器的值,如果它们相等,则执行跳转到指定的标签。# 如果 t3 寄存器中的值等于 0(因为 x0 的值永远是 0),那么程序将跳转到标签 finish 指示的位置。# 如果 t3 的值不是 0,程序将不会跳转,而是继续执行下一条指令。beq t3, x0, finishadd t2, t1, t0# 是将 t1 寄存器中的数据复制到 t0 寄存器中。mv t0, t1mv t1, t2addi t3, t3, -1 # j 是 "jump"(跳转)的缩写,表示这是一个无条件跳转指令,用于将程序的控制流跳转到指定的位置。# 使程序跳转到标签 fib 所在的代码位置,继续从那里执行j fib
finish: # addi a0, x0, 1 这条指令将 a0 寄存器的值设置为 1。# 在 RISC-V 架构中,进行系统调用(ecall)时,a0 寄存器通常用于指定系统调用的类型或功能代码。# 这里,将 a0 设置为 1 通常表示执行的是打印整数的系统调用addi a0, x0, 1# addi a1, t0, 0 这条指令将 t0 寄存器的值复制到 a1 寄存器中。# 在进行打印整数的系统调用时,a1 寄存器用于存储要打印的整数值。addi a1, t0, 0# 接着执行 ecall 指令时,系统将根据 a0 和 a1 寄存器的值来执行相应的操作。# 由于 a0 被设置为 1(表示打印整数的系统调用),并且 a1 包含了要打印的整数(来自 t0),# 因此 ecall 指令会导致打印出 a1 中的整数值ecall # print integer ecall# 将 a0 设置为 10 通常表示执行的是一个终止程序的系统调用addi a0, x0, 10# 随后执行的 ecall 指令,系统将根据 a0 寄存器的值来执行相应的操作ecall # terminate ecall
Paste the contents of ex1.s
in Venus and record your answers to the following questions. Some of the questions will require you to run the RISC-V code using Venus’ simulator tab.
- What do the
.data
,.word
,.text
directives mean (i.e. what do you use them for)? Hint: think about the 4 sections of memory.
A: In the context of assembly language, particularly for systems like RISC-V or MIPS, the directives .data
, .word
, and .text
are used to organize and manage different sections of a program’s memory. These align with the typical memory sections in a compiled program:
- .data Directive:
- Purpose: The
.data
directive is used to declare the data segment of the program. This segment is for initializing variables that retain their values throughout the program’s execution. It’s primarily used for static and global variables. - Memory Section: It corresponds to the data segment in memory, which is different from the stack and heap segments. This segment is used for static data allocation.
- Purpose: The
- .word Directive:
- Purpose: The
.word
directive is used within the data segment to define 32-bit words. Each.word
allocates 4 bytes of memory (since a word is 32 bits) and can be used to initialize these words with specific values. For example,.word 2, 4, 6, 8
will allocate 16 bytes of memory and initialize them with the given values. - Memory Section: These words are part of the data segment. This directive helps in reserving memory space and initializing data in this segment.
- Purpose: The
- .text Directive:
- Purpose: The
.text
directive indicates the start of the code segment of the program. This is where the actual instructions of the program are written. It’s the executable part of the program. - Memory Section: It corresponds to the text (or code) segment in memory. This segment is used for the executable instructions of the program and is distinct from data, stack, and heap segments.
- Purpose: The
These directives help in organizing a program into distinct sections, each with its own role in the program’s structure and execution. The .data
segment for initializing and storing data, the .word
directive for defining 32-bit integers in the data segment, and the .text
segment for the actual executable instructions. This organization reflects the typical division of memory in compiled programs into segments like text (code), data, stack (for function calls and local variables), and heap (for dynamically allocated memory).
- Run the program to completion. What number did the program output? What does this number represent?
A: The program output the number 34. This number represents the 9th number in the Fibonacci sequence.
In the Fibonacci sequence, each number is the sum of the two preceding ones, starting from 0 and 1. So, the sequence goes 0, 1, 1, 2, 3, 5, 8, 13, 21, 34,… and so on. The number 34 is the 9th term in this sequence (if we start counting from 0 as the first term).
- At what address is
n
stored in memory? Hint: Look at the contents of the registers.
A: after “la t3, n” , t3这个地方变成了 268435464
Exercise 2: Translating from C to RISC-V
C程序:
int source[] = {3, 1, 4, 1, 5, 9, 0};
int dest[10];int fun(int x) {return -x * (x + 1);
}int main() {int k;int sum = 0;for (k = 0; source[k] != 0; k++) {dest[k] = fun(source[k]);sum += dest[k];}return sum;
}
对应的汇编语言:
.data
source:.word 3.word 1.word 4.word 1.word 5.word 9.word 0
dest:.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word 0.text
main:addi t0, x0, 0addi s0, x0, 0# load address , 将source的内存地址加载到 寄存器s1中la s1, sourcela s2, dest
loop:# slli:这是一个指令操作码,代表“Shift Left Logical Immediate”。它表示将一个寄存器中的值向左逻辑移位(即在右边补零)一个立即数指定的位数# 将 t0 寄存器中的值向左移动 2 位,然后将结果存入 s3 寄存器slli s3, t0, 2# add 是寄存器中的值相加add t1, s1, s3lw t2, 0(t1)beq t2, x0, exitadd a0, x0, t2# 用于对栈指针(Stack Pointer)进行操作# addi sp, sp, -8 的操作是将栈指针 sp 的当前值减去 8,它实际上在栈上为新的数据或变量预留了 8 个字节的空间addi sp, sp, -8# 代表“Store Word”。它用于将一个字(word)的数据从寄存器存储到内存中。# 将寄存器 t0 中的数据存储到栈指针 sp 当前指向的内存地址处。由于偏移量是 0,这意味着数据被存储在栈顶sw t0, 0(sp)sw t2, 4(sp)# Jump and Link”。这个指令用于跳转到程序中的另一个位置(在这个例子中是标签 square 所指示的位置),同时将返回地址(即跳转之后下一条指令的地址)保存在一个特定的寄存器中jal squarelw t0, 0(sp)lw t2, 4(sp)addi sp, sp, 8add t2, x0, a0add t3, s2, s3sw t2, 0(t3)add s0, s0, t2addi t0, t0, 1# jal x0, loop 的操作是跳转到标签 loop 指示的代码位置,但不保存返回地址(因为它写入了零寄存器 x0)jal x0, loop
square:add t0, a0, x0add t1, a0, x0addi t0, t0, 1addi t2, x0, -1mul t1, t1, t2mul a0, t0, t1# jr ra 的操作是从 ra 寄存器中读取存储的地址,并跳转到那个地址继续执行程序。# 这种指令通常用于函数的末尾,用来返回到调用该函数的地方。# 当一个函数执行完毕,通过 jr ra 指令,程序控制权会返回到调用该函数的下一条指令处继续执行# Jump Register(操作指令) 和 Return Address寄存器jr ra
exit:add a0, x0, s0add a1, x0, x0ecall # Terminate ecall
我的回答:
- The register representing the variable
k
Answer: t0
- The register representing the variable
sum
.
Answer: s0
- The registers acting as pointers to the
source
anddest
arrays.
Answer: s1 和 s2
- The assembly code for the loop found in the C code.
Answer: loop
- How the pointers are manipulated in the assembly code.
Answer: 使用偏移
gpt4的回答(更详细,举了例子,和我的答案是一样)
The register representing the variable k
- The variable
k
seems to be represented by the registert0
. This is inferred from the lineaddi t0, x0, 0
, which initializest0
to 0 (similar tok = 0
in C), and the lineaddi t0, t0, 1
, which incrementst0
by 1 (similar tok++
in a loop).
The register representing the variable sum
- The variable
sum
appears to be represented by the registers0
. This is based on the lineaddi s0, x0, 0
(initializings0
to 0, likesum = 0
), and the lineadd s0, s0, t2
, which adds the value int2
tos0
(akin to accumulating values insum
).
The registers acting as pointers to the source
and dest
arrays
s1
is used as a pointer to thesource
array (la s1, source
).s2
is used as a pointer to thedest
array (la s2, dest
).
The assembly code for the loop found in the C code
- The loop in the assembly code is represented by the label
loop:
and includes instructions from this label down tojal x0, loop
. The loop terminates whenbeq t2, x0, exit
is executed, which happens whent2
(loaded from thesource
array) is zero, effectively translating to a check likeif (source[k] == 0) break;
in C.
How the pointers are manipulated in the assembly code
- The pointers (
s1
forsource
ands2
fordest
) are manipulated using offset calculations. For instance,slli s3, t0, 2
andadd t1, s1, s3
calculate the address to read from or write to in thesource
ordest
arrays. Theslli s3, t0, 2
instruction shiftst0
left by 2 bits, effectively multiplyingk
by 4 (since eachword
is 4 bytes). This value is then added tos1
ors2
to get the correct address in the array for the current loop iteration.
Exercise 3: Factorial
完成factorial.s, 实现求阶乘的功能
我的解法:
.globl factorial.data
n: .word 8.text
main:la t0, nlw a0, 0(t0)lw t0, 0(t0)# 跳转到标签 factorial 指示的地址去执行代码,并将下一条指令的地址保存在 ra 寄存器中jal ra, factorialfinish:addi a1, a0, 0addi a0, x0, 1 #a0设置为1,表示打印整数ecall # Print Resultaddi a1, x0, '\n'addi a0, x0, 11 # a0设置为11,表示打印字符ecall # Print newline# a0设置为10,表示终止程序addi a0, x0, 10ecall # Exitfactorial:# YOUR CODE HEREaddi t1, t0, -1beq t1, x0, finishmul a0, a0, t1add t0, t1, x0j factorial
安装java环境后,测试代码是否正确:
Exercise 4: RISC-V function calling with map
.globl map.text
main:# 跳转到标签 create_default_list 指示的地址去执行代码,并将下一条指令的地址保存在 ra 寄存器中jal ra, create_default_listadd s0, a0, x0 # a0 = s0 is head of node list#print the listadd a0, s0, x0jal ra, print_list# print a newlinejal ra, print_newline# load your argsadd a0, s0, x0 # load the address of the first node into a0# load the address of the function in question into a1 (check out la on the green sheet)### YOUR CODE HERE ###la a1, square # 加载 square 函数的地址到 a1# issue the call to mapjal ra, map# print the listadd a0, s0, x0jal ra, print_list# print another newlinejal ra, print_newlineaddi a0, x0, 10ecall #Terminate the programmap:# Prologue: Make space on the stack and back-up registers### YOUR CODE HERE ###addi sp, sp, -12 # 为三个寄存器分配栈空间sw ra, 0(sp) # 保存 rasw s0, 4(sp) # 保存 s0sw s1, 8(sp) # 保存 s1beq a0, x0, done # If we were given a null pointer (address 0), we're done.add s0, a0, x0 # Save address of this node in s0add s1, a1, x0 # Save address of function in s1# Remember that each node is 8 bytes long: 4 for the value followed by 4 for the pointer to next.# What does this tell you about how you access the value and how you access the pointer to next?# load the value of the current node into a0# THINK: why a0?### YOUR CODE HERE ###lw a0,0(s0) # 加载当前节点的值到 a0# Call the function in question on that value. DO NOT use a label (be prepared to answer why).# What function? Recall the parameters of "map"### YOUR CODE HERE #### 这条指令的功能是从一个寄存器中读取地址,并跳转到该地址执行,同时将下一条指令的地址(即返回地址)保存在默认的链接寄存器 ra(返回地址寄存器)jalr a1 # 调用 a1 寄存器中的函数# store the returned value back into the node# Where can you assume the returned value is?### YOUR CODE HERE ###sw a0, 0(s0) # 将返回值存回当前节点# Load the address of the next node into a0# The Address of the next node is an attribute of the current node.# Think about how structs are organized in memory.### YOUR CODE HERE ###lw a0, 4(s0) # 加载下一个节点的地址到 a0# Put the address of the function back into a1 to prepare for the recursion# THINK: why a1? What about a0?### YOUR CODE HERE ###add a1, s1, x0 # 恢复函数地址到 a1# recurse### YOUR CODE HERE ###jal ra, map # 递归调用 mapdone:# Epilogue: Restore register values and free space from the stack### YOUR CODE HERE ###lw ra, 0(sp) # 恢复 ralw s0, 4(sp) # 恢复 s0lw s1, 8(sp) # 恢复 s1addi sp, sp, 12 # 释放栈空间jr ra # Return to callersquare:add t0, a0, x0mul a0 ,t0, t0jr racreate_default_list:addi sp, sp, -12sw ra, 0(sp)sw s0, 4(sp)sw s1, 8(sp)# li 指令的全称是 "Load Immediate"。它用于将一个立即数(即直接指定的数值)加载到一个寄存器中。这个指令通常用于初始化寄存器的值li s0, 0 # pointer to the last node we handledli s1, 0 # number of nodes handled
loop: #do...li a0, 8jal ra, malloc # get memory for the next nodesw s1, 0(a0) # node->value = isw s0, 4(a0) # node->next = lastadd s0, a0, x0 # last = nodeaddi s1, s1, 1 # i++addi t0, x0, 10bne s1, t0, loop # ... while i!= 10lw ra, 0(sp)lw s0, 4(sp)lw s1, 8(sp)addi sp, sp, 12jr raprint_list:# bne: 这是一个条件分支指令,代表“branch if not equal”。它的作用是比较两个寄存器的值,如果它们不相等,则跳转到指定的标签或地址。bne a0, x0, printMeAndRecursejr ra # nothing to print
printMeAndRecurse:add t0, a0, x0 # t0 gets current node addresslw a1, 0(t0) # a1 gets value in current nodeaddi a0, x0, 1 # prepare for print integer ecallecalladdi a1, x0, ' ' # a0 gets address of string containing spaceaddi a0, x0, 11 # prepare for print string syscallecalllw a0, 4(t0) # a0 gets address of next nodejal x0, print_list # recurse. We don't have to use jal because we already have where we want to return to in raprint_newline:addi a1, x0, '\n' # Load in ascii code for newlineaddi a0, x0, 11ecalljr ramalloc:addi a1, a0, 0addi a0, x0 9ecalljr ra