SoSe25 Exam Overview

Aufgabe 3 · 8 points · trace

Verwendet nur: a0, a1, t0-t6

Original German, Korean translation, method, source-grounded solution, recall-answer audit, wrong-answer explanations, active recall, and source citations are separated below.

complete

Related Concepts and Current Sources

Weak-topic hook

No active weak-topic rows currently map to this Aufgabe.

Subproblem learning view

소문제별 1타 강사식 풀이 교실

문제를 읽은 직후 필요한 개념을 직관적으로 잡고, 같은 순서로 손풀이를 재현하도록 구성했습니다.

Teilaufgabe

3

8 points

Original German

Recalled German source, locator current:Gedächtnisprotokoll Rechnerorganisation SoSe25.md#aufgabe-3:

Aufgabe 3 - C in RISC-V übersetzen, ungefähr 8 Punkte. Es war eine C-Methode gegeben, welche ein Array aufsummiert und die Summe zurückgibt. Die Methode sollte in RISC-V umgesetzt werden. Dafür durften nur die Register a0, a1, sowie t0-t6 verwendet werden. In a0 lag die Adresse des Ararys, in a1 lag die Länge. Die Summe sollte in s0 zurückgegeben werden.

int length = a.length;
int sum = 0;
for (int i = 0; i < length; i++) {
    int x = a[i];
    if (x < 0) {
        x = -x;
    }
    sum = sum + x;
}

Recalled proposed answer:

li s0, 0
li t0, 0
loop:
    beq t0, a1, end
    slli t1, t0, 2
    add t1, t1, a0
    lw t2, 0(t1)
    bge t2, zero, else
    sub s0, s0, t2
    addi t0, t0, 1
    j loop
else:
    add s0, s0, t2
    addi t0, t0, 1
    j loop
end:

Normalization note: Ararys는 오타로 보이며 Arrays로 읽는다. 가장 큰 불확실성은 "sum should be returned in s0"이다. 같은 문장 안에서 허용 register가 a0, a1, t0-t6뿐이라고 했기 때문에 s0 사용은 조건 위반이다.

한국어 문제

정수 배열의 원소를 모두 읽어서 각 원소의 절댓값을 더하는 C 코드를 RISC-V로 옮겨라. 입력으로 a0에는 배열 a의 시작 주소가 있고, a1에는 배열 길이 length가 있다. 사용할 수 있는 register는 a0, a1, t0부터 t6까지라고 기억되어 있다. 기억 기록에는 합계를 s0에 돌려주라고 되어 있지만, 이 조건은 허용 register 목록과 충돌한다.

한 줄 핵심

C loop를 pointer, loop counter, loaded value, accumulator 상태로 나누고 매 iteration의 register 변화를 trace한다.

0. 초보자 개념 다리

C loop를 counter, pointer, 현재 값, 누적합을 담는 네 상자로 나눈다.

1. 이 문제의 풀이 루틴

  1. 각 C 변수에 register를 고정 배정한다.
  2. 초기화·조건·body·증가·jump를 label로 나눈다.
  3. 주소를 base+index×element_size로 계산한다.
  4. 한 iteration을 PC/register 표로 trace한다.

2. 왜 이 방법이 맞을까?

C 상태가 register와 branch에 정확히 대응하면 loop 의미가 보존된다.

3. 시험장 실수 방지

word array의 index와 byte offset을 섞지 않는다.

최대 상세 해설 · Aufgabe 3 완전 초보 강의: C 배열 절댓값 합계를 RISC-V loop로 번역하기

이 강의의 도착점

C, 배열, pointer, register, memory, branch를 전혀 모르는 학습자도 문제의 의미를 일상어로 풀고, register 계획을 세운 뒤 주소 계산·load·절댓값·누적·반복·종료를 한 줄씩 RISC-V로 작성하고 전체 상태를 손으로 추적한다.

0. 정말 아무것도 모른다면 여기서 시작

  • 이 문제의 C 코드는 숫자가 여러 개 들어 있는 배열을 처음부터 끝까지 읽는다. 숫자가 음수면 양수로 뒤집고, 그 값을 계속 더한다. 예를 들어 배열이 [3,-5,0,-2]라면 3+5+0+2=10을 만드는 문제다.
  • C는 forif 같은 큰 문장을 제공한다. CPU는 ‘반복해라’나 ‘음수면’이라는 문장을 한 번에 이해하지 못한다. CPU가 이해하는 RISC-V에서는 비교하고, 조건에 따라 다른 주소로 이동하고, 다시 위로 돌아가는 작은 instruction을 직접 배열해야 한다.
  • Register는 CPU 안의 아주 작은 메모지다. 한 register에는 현재 index, 다른 register에는 sum, 또 다른 register에는 배열 주소를 적어 둔다. Assembly 문제를 풀 때 가장 먼저 변수마다 어느 메모지를 쓸지 정해야 중간에 값을 덮어쓰지 않는다.
  • Memory는 주소가 붙은 큰 서랍장이다. a0에는 첫 번째 원소의 값이 아니라 첫 번째 원소가 들어 있는 서랍의 주소가 들어 있다. 이를 base address 또는 pointer라고 한다.
  • RISC-V의 memory 주소는 byte 단위다. int 하나는 이 문제에서 4 byte이므로 a[0], a[1], a[2]의 시작 주소는 base, base+4, base+8이다. index i를 주소에 바로 더하지 않고 반드시 i×4를 더해야 한다.
  • lw는 load word다. 계산한 memory 주소에서 4 byte를 읽어 register 하나에 넣는다. 배열 자체를 바꾸는 것이 아니라 읽기만 하므로 이 풀이 전체에서 memory 내용은 변하지 않는다.
  • Branch는 ‘조건이 맞으면 label로 이동하라’는 instruction이다. bge t0,a1,end는 t0가 a1보다 크거나 같으면 end로 간다. 이것으로 C의 반복 조건 i < length가 거짓이 되는 순간을 표현한다.
  • 문제 기억 기록에는 허용 register가 a0, a1, t0-t6뿐이라고 하면서 결과를 s0에 두라고도 되어 있어 서로 충돌한다. 가장 엄격한 해석은 허용 목록을 지키고 일반 RISC-V ABI return register인 a0에 결과를 두는 것이다. 그래서 시작 주소는 먼저 t6에 안전하게 복사한다.
  • 아래 풀이의 핵심 약속은 세 가지다. loop 맨 위에서 t0는 다음에 읽을 index, t6는 변하지 않는 base address, a0는 지금까지의 절댓값 합이다. 매 반복 뒤에도 이 약속이 유지되면 코드는 맞다.

1. 문제에 나오는 말부터 하나씩

C variable
이름이 붙은 값. 이 코드에는 length, sum, i, x가 있다.
array / 배열
같은 종류의 값을 memory에 연속해서 놓은 구조. a[i]는 i번째 원소다.
index
배열에서 몇 번째 원소인지 나타내는 번호. 첫 원소는 0이다.
pointer / base address
배열의 값 자체가 아니라 첫 번째 원소가 놓인 memory 주소.
byte address
memory 주소 하나가 1 byte 위치를 가리키는 방식. int가 4 byte라 다음 int 주소는 +4다.
register
CPU 안의 빠른 값 보관함. a0, a1, t0 같은 이름을 쓴다.
a0, a1
RISC-V ABI에서 함수 인수와 return 값에 자주 쓰는 register. entry에서 a0=base, a1=length다.
t0~t6
중간 계산에 쓰는 temporary register. 이 문제의 허용 목록에 포함된다.
zero / x0
읽으면 항상 0인 특별한 register. 여기에 쓰는 값은 버려진다.
register allocation
C의 각 변수와 중간값을 어느 register에 둘지 미리 정하는 것.
instruction
CPU가 한 번에 실행하는 작은 명령. 예: add, lw, bge.
label
instruction 위치에 붙인 이름. loop:, nonneg:, end:가 이동 목적지다.
addi rd,rs1,imm
rs1 값에 작은 상수 imm을 더해 rd에 저장한다. 0으로 초기화하거나 i를 1 증가시킬 수 있다.
slli rd,rs1,2
bit를 왼쪽으로 2칸 밀어 4배한다. 여기서는 i×4 byte offset을 만든다.
offset
base address에서 얼마나 떨어졌는지를 byte 수로 표현한 값. a[i]의 offset은 i×4다.
effective address
실제로 load/store가 접근할 최종 주소. 여기서는 base+i×4다.
lw rd,0(rs1)
rs1이 가리키는 주소에서 32-bit word를 읽어 rd에 넣는다.
signed integer
양수와 음수를 모두 표현하는 정수. lw로 읽은 int와 bge 비교를 signed로 해석한다.
bge rs1,rs2,label
signed 비교에서 rs1≥rs2이면 label로 이동한다.
sub rd,zero,rs
0-rs를 계산하여 음수의 부호를 뒤집는다. -x 구현에 사용한다.
accumulator / 누적합
지금까지 더한 결과를 계속 보관하는 값. 이 풀이에서는 a0가 sum 역할을 한다.
loop invariant
반복문의 매 시작점에서 항상 참이어야 하는 약속. 코드가 맞는지 증명하는 기준이다.
ABI
함수 인수·return register 등 software끼리 지키는 호출 약속. 보통 정수 return 값은 a0에 둔다.
RV32I
32-bit base integer RISC-V instruction set. M extension의 mul 없이 shift로 ×4를 만들 수 있다.

2. 선생님과 같이 한 칸씩 푸는 과정

  1. 먼저 C 코드가 계산하는 값을 한 문장으로 바꾼다: i=0부터 length-1까지 모든 a[i]의 절댓값을 sum에 더한다.
  2. 작은 예로 확인한다. [3,-5,0,-2]라면 각 원소의 절댓값은 [3,5,0,2], 최종 sum은 10이다. 이 값이 나중 trace의 정답 기준이다.
  3. 입력을 확인한다. 함수 시작 시 a0는 배열 첫 주소, a1은 원소 개수다. a0는 아직 sum이 아니라 pointer다.
  4. register 사용표를 먼저 만든다: t6=base, a1=length, a0=sum/result, t0=i, t1=offset/현재 주소, t2=x 또는 abs(x).
  5. 왜 base를 t6에 복사하는가? a0를 결과 register로 재사용하려면 a0를 0으로 바꿔야 한다. 원래 base를 보존하지 않으면 다음 lw 주소를 계산할 수 없다.
  6. 첫 instruction addi t6,a0,0은 a0에 있던 base를 t6에 복사한다. +0이라 값은 그대로다.
  7. addi a0,zero,0은 a0를 0으로 만든다. 이제 a0의 역할은 sum이며, 앞으로 최종 return 값이 된다.
  8. addi t0,zero,0은 loop index i를 0으로 초기화한다. 첫 번째 원소 a[0]부터 시작한다.
  9. loop: label에서 invariant를 확인한다. t0=i, a0는 a[0]부터 a[i-1]까지의 절댓값 합, t6는 배열 base다.
  10. bge t0,a1,end는 C의 i < length를 반대로 검사한다. i가 length 이상이면 읽을 원소가 없으므로 반드시 lw보다 먼저 end로 간다.
  11. length=0을 넣어 생각해 본다. 시작부터 0≥0이 참이어서 memory를 한 번도 읽지 않고 결과 0으로 끝난다. 이 때문에 top-check 위치가 중요하다.
  12. slli t1,t0,2는 i를 4배하여 byte offset을 만든다. 왼쪽 shift 1은 ×2, shift 2는 ×4다.
  13. 예를 들어 i=3이면 t1=12다. int 세 칸을 지나 네 번째 원소 a[3]로 가려면 base+12 byte가 맞다.
  14. add t1,t6,t1은 base와 offset을 더해 실제 주소 &a[i]를 만든다. t1은 offset 역할에서 address 역할로 바뀐다.
  15. lw t2,0(t1)은 계산한 주소의 4-byte int를 읽어 t2에 넣는다. memory에는 쓰지 않으므로 배열은 그대로다.
  16. bge t2,zero,nonneg는 x가 0 이상인지 signed 비교한다. 양수와 0이면 이미 절댓값이므로 negate instruction을 건너뛴다.
  17. x가 음수이면 branch가 not taken되어 sub t2,zero,t2를 실행한다. t2가 -5라면 0-(-5)=5가 되어 절댓값이 된다.
  18. nonneg:에 도착하면 양수였든 음수를 뒤집었든 항상 t2=abs(a[i])라는 공통 상태가 된다. 두 경로를 하나로 합치는 지점이다.
  19. add a0,a0,t2는 지금까지의 sum에 현재 절댓값을 더한다. 이전 sum이 3이고 t2가 5라면 새 sum은 8이다.
  20. addi t0,t0,1은 i를 하나 증가시킨다. 다음 반복에서 다음 배열 원소를 읽게 한다.
  21. jal zero,loop는 return address를 zero에 버리고 무조건 loop로 이동한다. assembler에서는 j loop라고도 쓸 수 있지만 실제 base instruction 의미를 보여 주기 위해 jal을 썼다.
  22. 마지막 원소 처리 뒤 i가 length가 되면 loop top의 bge가 end로 이동한다. end에서는 a0가 최종 sum이고 더 이상 memory를 읽지 않는다.
  23. 전체 코드에서 사용하는 일반 register를 점검한다. a0, a1, t0, t1, t2, t6뿐이며 recall의 허용 목록을 지킨다.
  24. 실제 함수 return instruction까지 요구한다면 보통 jalr zero,ra,0가 필요하지만 recall 허용 목록에 ra가 없고 문제 snippet 범위가 불명확하므로 graded 핵심은 end에서 a0에 결과를 만드는 것이다.
  25. 마지막으로 [3,-5,0,-2]를 손으로 돌린다. 주소는 0x1000,0x1004,0x1008,0x100C이고 sum은 0→3→8→8→10이 된다.

3. 그래서 정답은 무엇인가?

허용 register 충돌을 엄격히 처리한 정답은 아래와 같다. t6에 base를 보존하고 a0를 sum/return으로 사용한다.

    addi t6, a0, 0        # t6 = base address
    addi a0, zero, 0      # sum = 0; result register
    addi t0, zero, 0      # i = 0
loop:
    bge  t0, a1, end      # if i >= length, stop
    slli t1, t0, 2        # byte offset = i * 4
    add  t1, t6, t1       # address = base + offset
    lw   t2, 0(t1)        # x = a[i]
    bge  t2, zero, nonneg # if x >= 0, keep it
    sub  t2, zero, t2     # x = -x
nonneg:
    add  a0, a0, t2       # sum += abs(x)
    addi t0, t0, 1        # i++
    jal  zero, loop       # repeat
end:
    # a0 = result

4. 이제 정확한 개념으로 한 단계 더 깊게

  • C의 for(init; condition; update)는 Assembly에서 초기화, loop-top 조건 branch, body, update, back jump의 다섯 부분으로 펼친다.
  • 배열 원소 주소 공식은 address(a[i]) = base + i × sizeof(element)다. RV32I int는 4 byte이므로 base + i×4다.
  • RISC-V memory는 byte-addressed다. i=1을 base에 그대로 더하면 다음 int가 아니라 첫 int 내부의 두 번째 byte를 가리킨다.
  • slli t1,t0,2는 t0의 bit pattern을 왼쪽으로 2칸 밀어 4배한다. RV32I base ISA에 mul이 없어도 2의 거듭제곱 곱셈은 shift로 계산할 수 있다.
  • lw는 32-bit word를 register로 읽는다. 주소 계산과 data load는 별도 instruction이다. add는 memory를 읽지 않는다.
  • Loop condition i < length의 부정은 i >= length다. 그래서 bge i,length,end가 자연스러운 exit branch다.
  • Top-check loop는 body보다 먼저 조건을 검사하므로 length=0에서도 안전하다. 이를 pre-test loop라고 생각할 수 있다.
  • bge는 signed branch다. 배열의 x가 음수인지 검사할 때 zero와 signed 비교해야 한다. unsigned branch를 사용하면 음수가 큰 양수처럼 보인다.
  • 절댓값 경로를 합치기 전에 t2를 정규화한다. nonneg label에서 t2=abs(original x)라는 invariant가 생기므로 누적 instruction을 한 번만 작성할 수 있다.
  • Accumulator a0의 invariant는 loop 시작에서 a0 = Σ abs(a[k]) for 0≤k<i다. body가 a[i]를 더하고 i를 증가시키면 다음 반복에서도 같은 형태가 유지된다.
  • a0는 입구에서 base pointer지만 출구에서 result다. 하나의 register가 시간에 따라 역할을 바꾸므로, 아직 필요한 base를 t6에 먼저 보존해야 한다.
  • Recall의 s0 요구와 register 제한은 동시에 만족할 수 없다. source가 비공식 기억 기록이므로, 허용 목록과 표준 ABI에 맞춘 a0 return을 본 풀이의 기준으로 명시한다.
  • s0는 일반적으로 callee-saved register다. 실제 함수에서 사용한다면 원래 값을 stack에 저장하고 return 전에 복구해야 하므로 8점 loop 번역 문제를 불필요하게 복잡하게 만든다.
  • INT_MIN=-2147483648은 RV32에서 부호를 뒤집어도 같은 bit pattern이 되는 특수값이다. ISO C signed overflow 관점의 예외지만, recall 시험은 일반적인 값의 RV32 wraparound trace를 의도한 것으로 본다.
  • jal zero,loop는 x0에 link를 쓰므로 link가 버려져 무조건 jump처럼 동작한다. Pseudoinstruction j loop의 base 형태다.

5. 시험장에서 그대로 쓰는 단계별 풀이

  1. 문제에서 입력, 출력, 허용 register, element 크기를 표시한다.
  2. C 변수별 register 표를 먼저 만든다: base=t6, length=a1, sum/result=a0, i=t0, address=t1, x=t2.
  3. a0의 base를 t6에 복사한 뒤 a0와 t0를 0으로 초기화한다.
  4. loop top에서 bge t0,a1,end로 memory access 전에 종료 조건을 검사한다.
  5. slli t1,t0,2로 index를 4-byte offset으로 바꾼다.
  6. add t1,t6,t1로 현재 원소의 effective address를 만든다.
  7. lw t2,0(t1)로 current int를 읽는다.
  8. bge t2,zero,nonneg로 signed nonnegative path를 선택하고, 음수 path에서는 sub t2,zero,t2를 실행한다.
  9. 공통 nonneg 지점에서 add a0,a0,t2로 한 번만 누적한다.
  10. i를 증가시키고 jal zero,loop로 되돌아간다.
  11. end에서 a0가 결과인지, memory가 변하지 않았는지, 허용되지 않은 register가 없는지 검사한다.
  12. 최소 한 개 양수와 한 개 음수 원소를 손으로 trace하여 branch 방향과 sum 변화를 검산한다.

6. 예시와 변형 문제 연결

  • 예제 1: base=0x1000, length=4, array [3,-5,0,-2]. 접근 주소는 0x1000,0x1004,0x1008,0x100C, abs 값은 3,5,0,2, sum 변화는 0→3→8→8→10이다.
  • 예제 2: length=0. 초기화 후 첫 bge 0,0,end가 taken되어 lw가 한 번도 실행되지 않고 a0=0으로 끝난다.
  • 예제 3: array [7]. x=7이면 bge t2,zero,nonneg가 taken되어 negate를 건너뛰고 sum=7이 된다.
  • 예제 4: array [-1]. x=-1이면 branch가 not taken되고 sub t2,zero,t2가 t2=1을 만든 뒤 sum=1이 된다.
  • 예제 5: i=10일 때 int array byte offset은 10이 아니라 40이다. slli ...,2 결과가 40인지 확인하면 주소 계산 실수를 잡을 수 있다.
  • 변형 1: 원소의 절댓값이 아니라 원래 값을 합하려면 sign branch와 sub를 삭제하고 load 뒤 바로 add한다.
  • 변형 2: 음수 원소만 절댓값으로 더하려면 nonnegative path가 accumulation을 건너뛰어 increment로 가게 label을 나눈다.
  • 변형 3: element가 byte라면 lb를 쓰고 index를 4배하지 않는다. 주소 공식은 언제나 base+i×element size다.

7. 독일어 만점 답안 템플릿

Zuerst wird die Basisadresse aus a0 in t6 gesichert, weil a0 anschließend als Summen- und Rückgaberegister verwendet wird. t0 enthält den Index. Für jedes Element wird mit slli t1,t0,2 der Byte-Offset berechnet, mit lw der Wert geladen, ein negativer Wert durch sub t2,zero,t2 negiert und anschließend zu a0 addiert. Die Schleife endet vor dem Speicherzugriff, sobald t0 >= a1 gilt. Der korrigierte Code benutzt nur a0, a1 und t0 bis t6.

8. 자주 나오는 오답과 교정

  • C 코드를 instruction 한 줄씩 기계적으로 번역하려고 하기: 먼저 variable/register 계획과 control-flow 구조를 그려야 한다.
  • a0가 배열 첫 값이라고 생각하기: a0에는 첫 값이 아니라 첫 값의 주소가 있다.
  • base+i로 주소 계산하기: int 하나가 4 byte라 정확한 주소는 base+i×4다.
  • slli ...,4로 4배하려 하기: shift amount 4는 16배다. 4배는 shift 2다.
  • add instruction이 memory에서 원소를 읽는다고 생각하기: address 계산 뒤 반드시 lw가 필요하다.
  • 종료 검사를 lw 뒤에 두기: length=0에서도 a[0]을 잘못 읽을 수 있다.
  • beq i,length,end만 쓰기: 정상 nonnegative length에서는 가능하지만 i<length의 정확한 부정은 i>=length라 bge가 의도를 더 안전하게 표현한다.
  • 음수 비교에 unsigned branch를 쓰기: -5가 매우 큰 양수처럼 해석될 수 있다.
  • sub t2,t2,zero로 negate하기: 이는 t2-0이라 값이 그대로다. 정확한 부호 반전은 sub t2,zero,t2다.
  • 양수 path와 음수 path 양쪽에 increment와 back jump를 복제하기: 한쪽 수정 누락이 생기기 쉬우므로 abs 값을 만든 뒤 공통 경로로 합친다.
  • a0를 0으로 만든 뒤 base로 계속 사용하기: base가 사라져 address 0 근처를 읽게 된다. 먼저 t6에 저장한다.
  • recall 답처럼 s0를 바로 사용하기: 허용 register 목록 밖이고 실제 함수라면 callee-saved 보존 문제도 생긴다.
  • i 증가를 빼먹기: 같은 원소를 영원히 읽는 infinite loop가 된다.
  • back jump를 빼먹기: 첫 원소 하나만 처리하고 끝난다.
  • lwsw를 혼동하기: sw를 쓰면 배열을 변경해 문제의 의미가 달라진다.
  • 최종 sum만 확인하고 address sequence를 확인하지 않기: 우연히 같은 합이 나와도 잘못된 memory를 읽었을 수 있다.

9. 답을 보지 않고 확인하기

  1. 이 C 코드가 [3,-5,0,-2]에 대해 계산하는 최종 값은 무엇인가?
  2. a0에는 함수 entry에서 배열 값과 배열 주소 중 무엇이 들어 있는가?
  3. 왜 base address를 t6에 먼저 복사해야 하는가?
  4. int array에서 a[3]의 byte offset은 얼마이며 왜 그런가?
  5. slli t1,t0,2는 t0를 몇 배 하는가?
  6. 왜 loop 종료 branch를 lw보다 앞에 두는가?
  7. bge t2,zero,nonneg가 taken되는 x의 범위는 무엇인가?
  8. t2=-5일 때 sub t2,zero,t2 후 값은 무엇인가?
  9. loop 시작점에서 a0에 들어 있는 값의 정확한 의미는 무엇인가?
  10. memory [3,-5]를 처리한 뒤 a0와 t0는 각각 얼마인가?
  11. recall의 s0 결과 요구를 그대로 채택하지 않은 이유는 무엇인가?
  12. 이 코드가 사용하는 일반 register를 모두 나열할 수 있는가?
확인문제 정답 보기
  1. 절댓값 합 3+5+0+2=10이다.
  2. 배열 첫 원소가 놓인 base address가 들어 있다.
  3. a0를 sum/result로 0부터 재사용하면 원래 base가 사라지므로, 이후 address 계산을 위해 t6에 보존한다.
  4. 12 byte다. int 하나가 4 byte이고 3×4=12이기 때문이다.
  5. 4배 한다. 왼쪽 shift 2는 2²=4를 곱하는 것과 같다.
  6. length=0이거나 모든 원소를 처리한 뒤 범위를 벗어난 memory를 읽지 않기 위해서다.
  7. signed 값 x≥0, 즉 0과 모든 양수에서 taken된다.
  8. 0-(-5)=5이므로 t2=5다.
  9. index i보다 앞선 원소들, 즉 a[0]부터 a[i-1]까지의 절댓값 합이다.
  10. 두 원소 처리 뒤 sum a0=8, 다음 index t0=2다.
  11. 비공식 recall 안에서 허용 register가 a0,a1,t0-t6뿐이라는 조건과 s0 요구가 충돌하며, 표준 정수 return register도 a0이기 때문이다.
  12. a0, a1, t0, t1, t2, t6를 사용한다. zero는 상수 0 register다.

Interactive practice

C/RISC-V loop trace

Step으로 배열을 순회하며 address, loaded value, absolute value와 sum을 확인하세요.

Intro

Metadata

FeldInhalt
Nummer3
TitelC in RISC-V übersetzen
Punkte8
Empfohlene Zeit8 Minuten
KonzepteRV32I, C-Schleife, Arrays, lw, slli, signed branch, absolute value, register constraints
Tutor modetrace
Recall-source confidencemittel: Aufgabe und Beispielcode sind vorhanden, aber die Rückgaberegister-Angabe widerspricht der Registerbeschränkung
Verification sourcesGedächtnisprotokoll Rechnerorganisation SoSe25.md#aufgabe-3; Uebung\Übung 2.pdf, pages-1-3 und pages-4-4; Uebung\Übung 2 Musterlösung\Rechnerorganisation_Übung2_Lösung.pdf, pages-1-3, pages-4-6, pages-7-8; Uebung\Übung 3.pdf, pages-1-3; Uebung\Übung 3 Musterlösung.pdf, pages-1-3

Hinweis: Das Gedächtnisprotokoll ist not official und keine offizielle Klausur oder Musterlösung. Die dortige Lösung wird deshalb geprüft, nicht übernommen.

Original German

Recalled German source, locator current:Gedächtnisprotokoll Rechnerorganisation SoSe25.md#aufgabe-3:

Aufgabe 3 - C in RISC-V übersetzen, ungefähr 8 Punkte. Es war eine C-Methode gegeben, welche ein Array aufsummiert und die Summe zurückgibt. Die Methode sollte in RISC-V umgesetzt werden. Dafür durften nur die Register a0, a1, sowie t0-t6 verwendet werden. In a0 lag die Adresse des Ararys, in a1 lag die Länge. Die Summe sollte in s0 zurückgegeben werden.

int length = a.length;
int sum = 0;
for (int i = 0; i < length; i++) {
    int x = a[i];
    if (x < 0) {
        x = -x;
    }
    sum = sum + x;
}

Recalled proposed answer:

li s0, 0
li t0, 0
loop:
    beq t0, a1, end
    slli t1, t0, 2
    add t1, t1, a0
    lw t2, 0(t1)
    bge t2, zero, else
    sub s0, s0, t2
    addi t0, t0, 1
    j loop
else:
    add s0, s0, t2
    addi t0, t0, 1
    j loop
end:

Normalization note: Ararys는 오타로 보이며 Arrays로 읽는다. 가장 큰 불확실성은 "sum should be returned in s0"이다. 같은 문장 안에서 허용 register가 a0, a1, t0-t6뿐이라고 했기 때문에 s0 사용은 조건 위반이다.

Korean Translation

정수 배열의 원소를 모두 읽어서 각 원소의 절댓값을 더하는 C 코드를 RISC-V로 옮겨라. 입력으로 a0에는 배열 a의 시작 주소가 있고, a1에는 배열 길이 length가 있다. 사용할 수 있는 register는 a0, a1, t0부터 t6까지라고 기억되어 있다. 기억 기록에는 합계를 s0에 돌려주라고 되어 있지만, 이 조건은 허용 register 목록과 충돌한다.

Concept Lesson

이 문제는 "C loop를 assembly loop로 정확히 펼칠 수 있는가"를 본다. 필요한 전제는 네 가지다.

개념시험장에서 해야 할 일
int array 주소 계산RV32I에서 int는 4 byte이므로 a[i] 주소는 base + i * 4이다. mul이 없어도 slli offset, i, 2i*4를 만든다. Übung 2의 Array 문제도 int가 4 byte이고 slli를 쓰라고 명시한다.
loop terminationload 전에 i >= length를 검사해야 한다. 그래야 length = 0일 때 a[0]를 읽지 않는다.
signed absolute valuex < 0는 signed 비교다. bge x, zero, nonneg로 음수가 아닌 경우를 건너뛰고, 음수면 sub x, zero, x-x를 만든다.
register disciplinea0는 입력 base address이면서 보통 ABI return register다. 둘 다 만족하려면 base address를 먼저 t6에 복사하고 a0를 sum으로 재사용한다.

학생들이 자주 틀리는 지점은 i*4를 빼먹고 a+i를 읽는 것, beq i,length만 써서 비정상 길이에 취약한 것, 음수 처리에서 sum -= xx = -x; sum += x를 섞다가 register를 깨는 것, 그리고 s0를 무심코 쓰는 것이다.

Problem Interpretation

Given:

항목
a0 entrybase address of int a[]
a1 entrylength
allowed registersa0, a1, t0-t6
memory element sizesizeof(int) = 4 bytes

Find:

sum = Σ abs(a[i]) for i = 0 .. length-1.

Constraints and traps:

TrapCorrect rule
s0 returnNot allowed by the recalled register list. Use a0 as corrected return register, or mark s0 as only possible under a different exam condition.
overwrite a0 too earlyCopy base to t6 first.
a[i] addressaddr = base + (i << 2), not base + i.
loop endTest before lw: if i >= length, exit.
negative valuesigned branch against zero; then x = 0 - x.
INT_MINIn C, -INT_MIN overflows signed int; the recalled exam likely expects RV32 wraparound behavior, but this edge case should be mentioned if challenged.

Required hand-written intermediate state: register allocation, loop labels, address formula, branch condition, and a trace table with PC/register/memory for at least one positive and one negative element.

Solving Procedure

  1. Register plan first: t6=base, a0=sum/result, t0=i, t1=offset/address, t2=x.
  2. Initialize: save base, clear sum, clear index.
  3. At loop top, compare i with length; leave the loop before memory access if done.
  4. Compute byte address: offset=i<<2, addr=base+offset.
  5. Load signed word with lw.
  6. If x is negative, replace it by -x.
  7. Add x to sum.
  8. Increment i and jump back.
  9. End with result in a0. If a real function return is required by a surrounding harness, add jalr zero, ra, 0 only if ra is permitted or the ABI wrapper is outside the graded snippet.
Detailed Solution

Corrected RV32I solution under the stated allowed-register condition:


    addi t6, a0, 0        # t6 = base, because a0 will become sum
    addi a0, zero, 0      # sum = 0
    addi t0, zero, 0      # i = 0

loop:
    bge  t0, a1, end      # if i >= length: stop before loading
    slli t1, t0, 2        # t1 = i * 4 bytes
    add  t1, t6, t1       # t1 = &a[i]
    lw   t2, 0(t1)        # t2 = a[i], signed 32-bit word
    bge  t2, zero, nonneg # if x >= 0: keep x
    sub  t2, zero, t2     # x = -x

nonneg:
    add  a0, a0, t2       # sum += abs(x)
    addi t0, t0, 1        # i++
    jal  zero, loop       # repeat

end:
    # result is in a0

Why each instruction is there:

CodeMeaning
addi t6, a0, 0a0 initially contains the base pointer. Since the result should be in a0, preserve the base in a temporary.
bge t0, a1, endImplements i < length as exit condition i >= length. It also handles length = 0 safely.
slli t1, t0, 2Converts element index to byte offset. Übung 2 explicitly uses slli for array index times 4.
lw t2, 0(t1)Loads the current int.
bge t2, zero, nonnegSigned check for nonnegative values.
sub t2, zero, t2Computes absolute value for negative x.
add a0, a0, t2Accumulates into the corrected return register.

PC/Register/Memory Trace

Assume this concrete test:

Initial stateValue
PC0
a00x1000
a14
MemoryM[0x1000]=3, M[0x1004]=-5, M[0x1008]=0, M[0x100c]=-2

Instruction addresses:

PCInstruction
0addi t6, a0, 0
4addi a0, zero, 0
8addi t0, zero, 0
12bge t0, a1, end
16slli t1, t0, 2
20add t1, t6, t1
24lw t2, 0(t1)
28bge t2, zero, nonneg
32sub t2, zero, t2
36add a0, a0, t2
40addi t0, t0, 1
44jal zero, loop
48end

Condensed trace by iteration:

StepPC patht0=i beforeAddress calculationLoaded t2Abs t2a0=sum afterMemory
init0,4,8-t6=0x1000--0unchanged
012,16,20,24,28,36,40,4400x1000 + (0<<2)=0x1000333read M[0x1000], unchanged
112,16,20,24,28,32,36,40,4410x1000 + (1<<2)=0x1004-558read M[0x1004], unchanged
212,16,20,24,28,36,40,4420x1000 + (2<<2)=0x1008008read M[0x1008], unchanged
312,16,20,24,28,32,36,40,4430x1000 + (3<<2)=0x100c-2210read M[0x100c], unchanged
end12 -> 484branch because 4 >= 4--10no load

Instruction-level trace for the first negative element:

PCInstructionRegister updateNext PC
12bge t0, a1, end with t0=1,a1=4branch not taken16
16slli t1, t0, 2t1=420
20add t1, t6, t1t1=0x100424
24lw t2, 0(t1)t2=-528
28bge t2, zero, nonnegbranch not taken because -5 < 032
32sub t2, zero, t2t2=536
36add a0, a0, t2a0=840
40addi t0, t0, 1t0=244
44jal zero, loopno register write12

Register Condition Audit

ConditionVerdict
Uses only a0, a1, t0-t6yes
Uses s0no, because it is not in the allowed set
Return in a0verified by Übung 3 ABI examples where a0 carries arguments and return values
Return in s0incompatible with the recalled allowed-register condition; treat as recall uncertainty

If the real exam sheet truly allowed s0 and demanded return in s0, the minimal variant is to replace a0 sum with s0 and keep t6 as base. Under the supplied cron constraints, that variant is not accepted because s0 is outside the allowed register set.

Recall-Answer Audit

ItemAudit
File claimTranslate absolute-value array sum loop from C to RISC-V. Inputs: a0=base, a1=length; allowed: a0, a1, t0-t6; recalled return: s0.
Correct partsThe recalled answer initializes sum/index, computes i*4, loads with lw, branches on sign, accumulates, and loops.
Incomplete partsIt does not explain PC/register/memory state, does not discuss the s0 conflict, and does not protect the base pointer if result must be in a0.
First errorli s0,0 violates the recalled allowed-register list.
Violated ruleAllowed registers are only a0, a1, t0-t6; Übung 3 also frames s0-s11 as saved registers whose use matters under calling conventions.
Corrected answerUse t6 for the saved base, a0 for the sum/result, t0 for index, t1 for offset/address, t2 for element/absolute value.
Source verification statusSource problem is unverified recall; array and loop mechanics are verified against current Übung/Lösung 2; return-register and saved-register reasoning is verified against current Übung/Lösung 3.

Wrong-Answer Explanations

Wrong answerWhy students choose itWhy wrongViolated ruleFast checkCorrect approach
add t1, a0, t0; lw t2, 0(t1)They remember array indexing but forget byte addressing.For int, index 1 should read base+4, not base+1.int is 4 byte; use byte offset.Check a[10]: offset must be 40 byte.slli t1,t0,2; add t1,base,t1.
beq t0, a1, end onlyIt matches i == length for normal loops.If length is negative or i somehow skips, equality is not robust; i >= length is the direct negation of i < length.Loop condition translation.For length=0, both work; for length=-1, beq would wrongly load.Use bge t0,a1,end before lw.
sub a0, a0, t2 for negative values while a0 is sumThey try to do sum -= x.It works only if t2 remains negative and the sum register is correct; but it hides the absolute-value step and is easy to mix with later add.Maintain clear invariant: t2=abs(x) before accumulation.Trace sum=3,x=-5: either sum -= x gives 8, but then a later unconditional add would break it.First do sub t2,zero,t2, then one common add sum,sum,t2.
Return in s0The recall text says so.The same recall text says s0 is not allowed. Also ABI examples use a0 for return values.Register constraint and ABI return register.Circle all used registers; s0 is outside the list.Copy base to t6, return sum in a0.
Overwrite a0 with sum without saving basea0 is the natural return register.Then add t1,a0,t1 uses the current sum as base address.Preserve live input values.After init a0=0, first load would read address 0.addi t6,a0,0 before clearing a0.

Exam-Room Method

Time budget: 8 minutes.

First table to write:

VariableRegister
baset6
lengtha1
sum/resulta0
it0
offset/addresst1
x/abs(x)t2

Partial-credit work:

  1. Write the correct loop skeleton with top check and back jump.
  2. Write slli index,2 beside the address line.
  3. Show the signed branch for negative handling.
  4. Add a register-use checklist and explicitly mark the s0 conflict.

Last check: run a two-element mental test [3,-5]. The PC must branch around the negate for 3, execute negate for -5, and end with sum 8 in a0.

Active Recall

Questions

  1. Concept check: Why is slli t1, t0, 2 correct for int a[] in RV32I?
  2. Trace: With a0=0x2000, a1=2, memory M[0x2000]=-1, M[0x2004]=7, what are final a0, t0, and the loaded addresses?
  3. Transfer: Modify the loop to sum only negative values as positive magnitudes and skip nonnegative values.
  4. Register check: Why can the recalled s0 return be rejected under the supplied conditions?

Answers

  1. int occupies 4 bytes, so byte offset is i*4; left shift by 2 multiplies by 4.
  2. Loaded addresses are 0x2000 and 0x2004; final a0=8, t0=2; memory unchanged.
  3. Move add a0,a0,t2 into the negative path after sub t2,zero,t2, and for nonnegative values jump directly to the increment.
  4. Because the allowed register set is a0, a1, t0-t6; s0 is absent. Übung 3 also treats s0-s11 as saved registers that cannot be casually overwritten.

Sources

SourceLocatorUsed for
Gedächtnisprotokoll Rechnerorganisation SoSe25.mdaufgabe-3, lines 134-173 in corpus chunkrecalled original task and recalled proposed answer; unverified
Uebung\Übung 2.pdfpages-1-3hand execution requirement, PC/register trace style, array task statement
Uebung\Übung 2 Musterlösung\Rechnerorganisation_Übung2_Lösung.pdfpages-1-3example PC/register tables after each instruction
Uebung\Übung 2 Musterlösung\Rechnerorganisation_Übung2_Lösung.pdfpages-4-6array address calculation, int = 4 bytes, slli instead of mul, loop over array
Uebung\Übung 2 Musterlösung\Rechnerorganisation_Übung2_Lösung.pdfpages-7-8memory access examples and simulator/memory inspection context
Uebung\Übung 3.pdfpages-1-3calling convention and register-save task statements
Uebung\Übung 3 Musterlösung.pdfpages-1-3s0-s11 preserved, t0-t6 caller-saved, a0 argument/return examples

Completion Check

CheckStatus
Original German includeddone
Korean translation includeddone
Beginner-first concept lessondone
Given/find/constraints/trapsdone
Repeatable method before answerdone
Detailed corrected solutiondone
array address = index * 4 verifieddone
signed absolute value explaineddone
loop termination before loaddone
allowed register and return register conflict auditeddone
PC/register/memory trace includeddone
Recall-answer audit includeddone
At least three wrong answers includeddone
Active recall with answers includeddone
Current Übung/Lösung 2-3 cited firstdone