1. Title
Analysis of Assembly Language through RISC-V CPU
2. Category
"Assembly", "Machine Language"
3. Key Concepts
C언어로 작성한 Bubble Sort Program이 어떻게 기계어로 번역되고 실행되는지, Assembly 언어 분석을 통해 확인한다.
4. Setup
CPU Course Project - RISC-V (Single Cycle)
1. TitleRISC-V CPU (RV32I)2. Category"SystemVerilog", "CPU", "RISC-V" 3. Key ConceptsRISC-V는 오픈소스 명령어 집합 구조(ISA)이기 때문에, 누구나 자유롭게 사용하고 확장할 수 있다. 기존의 상용 ISA(예: x86, ARM)는 라
salmon1113.tistory.com
해당 프로젝트를 통해 작성된 Single Cycle RISC-V CPU를 기반으로 Machine Code 분석을 진행한다,
main:
addi sp,sp,-64
sw ra,60(sp)
sw s0,56(sp)
addi s0,sp,64
sw zero,-52(s0)
sw zero,-48(s0)
sw zero,-44(s0)
sw zero,-40(s0)
sw zero,-36(s0)
sw zero,-32(s0)
li a5,1
sw a5,-52(s0)
li a5,2
sw a5,-48(s0)
li a5,5
sw a5,-44(s0)
li a5,8
sw a5,-40(s0)
li a5,6
sw a5,-36(s0)
addi a5,s0,-52
li a1,5
mv a0,a5
call sort(int*, int)
li a5,-13361152
addi a5,a5,272
sw a5,-20(s0)
li a5,-470212608
addi a5,a5,153
sw a5,-24(s0)
lw a4,-24(s0)
lw a5,-20(s0)
add a5,a4,a5
sw a5,-28(s0)
li a5,0
mv a0,a5
lw ra,60(sp)
lw s0,56(sp)
addi sp,sp,64
jr ra
sort(int*, int):
addi sp,sp,-48
sw ra,44(sp)
sw s0,40(sp)
addi s0,sp,48
sw a0,-36(s0)
sw a1,-40(s0)
sw zero,-20(s0)
j .L4
.L8:
sw zero,-24(s0)
j .L5
.L7:
lw a5,-24(s0)
slli a5,a5,2
lw a4,-36(s0)
add a5,a4,a5
lw a4,0(a5)
lw a5,-24(s0)
addi a5,a5,1
slli a5,a5,2
lw a3,-36(s0)
add a5,a3,a5
lw a5,0(a5)
ble a4,a5,.L6
lw a5,-24(s0)
slli a5,a5,2
lw a4,-36(s0)
add a3,a4,a5
lw a5,-24(s0)
addi a5,a5,1
slli a5,a5,2
lw a4,-36(s0)
add a5,a4,a5
mv a1,a5
mv a0,a3
call swap(int*, int*)
.L6:
lw a5,-24(s0)
addi a5,a5,1
sw a5,-24(s0)
.L5:
lw a5,-40(s0)
addi a4,a5,-1
lw a5,-20(s0)
sub a5,a4,a5
lw a4,-24(s0)
blt a4,a5,.L7
lw a5,-20(s0)
addi a5,a5,1
sw a5,-20(s0)
.L4:
lw a5,-40(s0)
addi a5,a5,-1
lw a4,-20(s0)
blt a4,a5,.L8
nop
nop
lw ra,44(sp)
lw s0,40(sp)
addi sp,sp,48
jr ra
swap(int*, int*):
addi sp,sp,-48
sw ra,44(sp)
sw s0,40(sp)
addi s0,sp,48
sw a0,-36(s0)
sw a1,-40(s0)
lw a5,-36(s0)
lw a5,0(a5)
sw a5,-20(s0)
lw a5,-40(s0)
lw a4,0(a5)
lw a5,-36(s0)
sw a4,0(a5)
lw a5,-40(s0)
lw a4,-20(s0)
sw a4,0(a5)
nop
lw ra,44(sp)
lw s0,40(sp)
addi sp,sp,48
jr ra
1. Bubble Sort Programming (C)
void swap(int *a, int *b);
void sort(int *pNum, int size);
int main() {
int aNum[6] = {1, 2, 5, 8, 6};
int a, b, c;
sort(aNum, 5);
a = 0xff342110;
b = 0xe3f92099;
c = b + a;
return 0;
}
void sort(int *pNum, int size){
int temp;
for (int i = 0; i<size-1; i++)
{
for(int j=0; j<size-1-i; j++){
if(pNum[j] > pNum[j+1]){
swap(&pNum[j], &pNum[j+1]);
}
}
}
}
void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
Compiler Explorer
godbolt.org
위의 작성된 C 코드를 RISC-V Complier로 변환하면 다음과 같이 Assembly Code를 얻을 수 있다.
2. Assembly Code -> Machine Code
RISC-V Online Assembler
RISC-V Online Assembler. This is a very crude online assembler for RISC-V assembly (all variants that gas supports)
riscvasm.lucasteske.dev
Assembly Code를 해당 사이트를 통해 기계어로 번역할 수 있다. 번역 전, 아래 코드를 main: 함수 위에 추가하여 스택 포인터 초기화를 진행한다.
li sp, 400
//스택 포인터(sp) 레지스터에 400이라는 값을 넣는 명령어.
//프로그램을 실행하기 전에 스택 포인터를 적절한 값으로 초기화하는 것은 일반적인 관례이다.
- Code Hex Dump: 어셈블리 코드를 기계어로 변환한 결과인 16진수(Hexadecimal) 덤프를 보여준다.
- Objdump Code Disassembly: 컴파일된 기계어 코드를 사람이 읽기 쉬운 어셈블리 코드로 변환하여 보여준다.
3.Assembly Code 분석
file.elf: file format elf64-littleriscv
Disassembly of section .text:
0000000000000000 :
0: 19000113 li sp,400
0000000000000004
:
4: fc010113 addi sp,sp,-64
8: 02112e23 sw ra,60(sp)
c: 02812c23 sw s0,56(sp)
10: 04010413 addi s0,sp,64
14: fc042623 sw zero,-52(s0)
18: fc042823 sw zero,-48(s0)
1c: fc042a23 sw zero,-44(s0)
20: fc042c23 sw zero,-40(s0)
24: fc042e23 sw zero,-36(s0)
28: fe042023 sw zero,-32(s0)
2c: 00100793 li a5,1
30: fcf42623 sw a5,-52(s0)
34: 00200793 li a5,2
38: fcf42823 sw a5,-48(s0)
3c: 00500793 li a5,5
40: fcf42a23 sw a5,-44(s0)
44: 00800793 li a5,8
48: fcf42c23 sw a5,-40(s0)
4c: 00600793 li a5,6
50: fcf42e23 sw a5,-36(s0)
54: fcc40793 addi a5,s0,-52
58: 00500593 li a1,5
5c: 00078513 mv a0,a5
60: 044000ef jal ra,a4
64: ff3427b7 lui a5,0xff342
68: 11078793 addi a5,a5,272 # ffffffffff342110 <_sstack+0xffffffffff331710>
6c: fef42623 sw a5,-20(s0)
70: e3f927b7 lui a5,0xe3f92
74: 09978793 addi a5,a5,153 # ffffffffe3f92099 <_sstack+0xffffffffe3f81699>
78: fef42423 sw a5,-24(s0)
7c: fe842703 lw a4,-24(s0)
80: fec42783 lw a5,-20(s0)
84: 00f707b3 add a5,a4,a5
88: fef42223 sw a5,-28(s0)
8c: 00000793 li a5,0
90: 00078513 mv a0,a5
94: 03c12083 lw ra,60(sp)
98: 03812403 lw s0,56(sp)
9c: 04010113 addi sp,sp,64
a0: 00008067 ret
00000000000000a4 :
a4: fd010113 addi sp,sp,-48
a8: 02112623 sw ra,44(sp)
ac: 02812423 sw s0,40(sp)
b0: 03010413 addi s0,sp,48
b4: fca42e23 sw a0,-36(s0)
b8: fcb42c23 sw a1,-40(s0)
bc: fe042623 sw zero,-20(s0)
c0: 09c0006f j 15c
c4: fe042423 sw zero,-24(s0)
c8: 0700006f j 138
cc: fe842783 lw a5,-24(s0)
d0: 00279793 slli a5,a5,0x2
d4: fdc42703 lw a4,-36(s0)
d8: 00f707b3 add a5,a4,a5
dc: 0007a703 lw a4,0(a5)
e0: fe842783 lw a5,-24(s0)
e4: 00178793 addi a5,a5,1
e8: 00279793 slli a5,a5,0x2
ec: fdc42683 lw a3,-36(s0)
f0: 00f687b3 add a5,a3,a5
f4: 0007a783 lw a5,0(a5)
f8: 02e7da63 ble a4,a5,12c
fc: fe842783 lw a5,-24(s0)
100: 00279793 slli a5,a5,0x2
104: fdc42703 lw a4,-36(s0)
108: 00f706b3 add a3,a4,a5
10c: fe842783 lw a5,-24(s0)
110: 00178793 addi a5,a5,1
114: 00279793 slli a5,a5,0x2
118: fdc42703 lw a4,-36(s0)
11c: 00f707b3 add a5,a4,a5
120: 00078593 mv a1,a5
124: 00068513 mv a0,a3
128: 05c000ef jal ra,184
12c: fe842783 lw a5,-24(s0)
130: 00178793 addi a5,a5,1
134: fef42423 sw a5,-24(s0)
138: fd842783 lw a5,-40(s0)
13c: fff78713 addi a4,a5,-1
140: fec42783 lw a5,-20(s0)
144: 40f707b3 sub a5,a4,a5
148: fe842703 lw a4,-24(s0)
14c: f8f740e3 blt a4,a5,cc
150: fec42783 lw a5,-20(s0)
154: 00178793 addi a5,a5,1
158: fef42623 sw a5,-20(s0)
15c: fd842783 lw a5,-40(s0)
160: fff78793 addi a5,a5,-1
164: fec42703 lw a4,-20(s0)
168: f4f74ee3 blt a4,a5,c4
16c: 00000013 nop
170: 00000013 nop
174: 02c12083 lw ra,44(sp)
178: 02812403 lw s0,40(sp)
17c: 03010113 addi sp,sp,48
180: 00008067 ret
0000000000000184 :
184: fd010113 addi sp,sp,-48
188: 02112623 sw ra,44(sp)
18c: 02812423 sw s0,40(sp)
190: 03010413 addi s0,sp,48
194: fca42e23 sw a0,-36(s0)
198: fcb42c23 sw a1,-40(s0)
19c: fdc42783 lw a5,-36(s0)
1a0: 0007a783 lw a5,0(a5)
1a4: fef42623 sw a5,-20(s0)
1a8: fd842783 lw a5,-40(s0)
1ac: 0007a703 lw a4,0(a5)
1b0: fdc42783 lw a5,-36(s0)
1b4: 00e7a023 sw a4,0(a5)
1b8: fd842783 lw a5,-40(s0)
1bc: fec42703 lw a4,-20(s0)
1c0: 00e7a023 sw a4,0(a5)
1c4: 00000013 nop
1c8: 02c12083 lw ra,44(sp)
1cc: 02812403 lw s0,40(sp)
1d0: 03010113 addi sp,sp,48
1d4: 00008067 ret
Disassembly of section .heap:
0000000000010000 <_sheap>:
...
Disassembly of section .stack:
0000000000010200 <_estack>:
...
Assembly Code를 살펴보면, 작성된 C Code가 main(), sort(), swap() 함수로 분리되어 저장된 것을 확인할 수 있다. 첨부된 엑셀 파일은 명령어 Flow에 따른 Register File, RAM에 저장되는 값을 추적한 파일이다.
- 0000000000000004 ~ 00000000000000a0 : main()
- 00000000000000a4 ~ 0000000000000180 : sort()
- 0000000000000184 ~ 00000000000001d4 : swap()
해당 파일 분석해보면, 초기 배열인 {1, 2, 5, 8, 6} 값이 RAM에 저장된 후, Register File에 읽고 쓰는 과정을 반복하며 최종적으로 {1, 2, 5, 6, 8} 배열으로 정렬된 것을 확인할 수 있다.
실행 흐름을 더 자세히 분석해보자.
주요 과정:
- RAM(메모리)에 값 저장: 배열을 0으로 초기화한 후 특정 값을 할당.
- 연산 수행: 값을 읽어와 비교 및 정렬 수행.
- 함수 호출 흐름: main → sort → swap 순으로 진행.
1. 메인(main) 함수 분석 (0x00000000)
이 함수는 배열을 메모리에 저장하고 sort 함수를 호출한 후 종료한다.
(1) 스택 공간 확보 & 초기화
4: fc010113 addi sp,sp,-64 # 스택 포인터를 64만큼 감소 (스택 공간 확보)
8: 02112e23 sw ra,60(sp) # return address(ra) 저장
c: 02812c23 sw s0,56(sp) # 프레임 포인터 s0 저장
10: 04010413 addi s0,sp,64 # s0를 현재 스택 프레임의 시작 위치로 설정
- 스택 공간을 64 바이트 확보하고, ra(반환 주소)와 s0(프레임 포인터)를 백업.
(2) 배열을 0으로 초기화
14: fc042623 sw zero,-52(s0) # 배열[0] = 0
18: fc042823 sw zero,-48(s0) # 배열[1] = 0
1c: fc042a23 sw zero,-44(s0) # 배열[2] = 0
20: fc042c23 sw zero,-40(s0) # 배열[3] = 0
24: fc042e23 sw zero,-36(s0) # 배열[4] = 0
- zero(=0)를 사용해 배열의 모든 값을 0으로 초기화.
(3) 배열 값 설정
2c: 00100793 li a5,1 # a5 = 1
30: fcf42623 sw a5,-52(s0) # 배열[0] = 1
34: 00200793 li a5,2 # a5 = 2
38: fcf42823 sw a5,-48(s0) # 배열[1] = 2
3c: 00500793 li a5,5 # a5 = 5
40: fcf42a23 sw a5,-44(s0) # 배열[2] = 5
44: 00800793 li a5,8 # a5 = 8
48: fcf42c23 sw a5,-40(s0) # 배열[3] = 8
4c: 00600793 li a5,6 # a5 = 6
50: fcf42e23 sw a5,-36(s0) # 배열[4] = 6
- 배열이 [1, 2, 5, 8, 6]으로 설정됨.
(4) 정렬 함수(sort) 호출
54: fcc40793 addi a5,s0,-52 # 배열 주소를 a5에 저장
58: 00500593 li a1,5 # a1 = 배열 크기(5)
5c: 00078513 mv a0,a5 # 배열 포인터를 a0에 저장
60: 044000ef jal ra,a4 # sort 함수 호출 (0xa4로 점프)
- jal ra, a4로 sort 함수 호출.
- ra(return address)에 0x64(다음 실행할 명령어 주소)가 저장됨.
2. 정렬 함수(sort) 분석 (0x000000a4)
정렬 함수는 버블 정렬을 수행한다.
(1) 스택 공간 확보 & 매개변수 저장
a4: fd010113 addi sp,sp,-48 # 스택 공간 48바이트 확보
a8: 02112623 sw ra,44(sp) # return address 저장
ac: 02812423 sw s0,40(sp) # s0 저장
b0: 03010413 addi s0,sp,48 # s0 초기화
b4: fca42e23 sw a0,-36(s0) # 배열 포인터 저장
b8: fcb42c23 sw a1,-40(s0) # 배열 크기 저장
- 스택을 설정하고 매개변수를 백업.
(2) 버블 정렬 루프 (외부 루프)
bc: fe042623 sw zero,-20(s0) # i = 0 초기화
c0: 09c0006f j 15c # i 루프 시작
- i 값을 0으로 초기화 후 루프 시작.
(3) 내부 루프: 인접한 두 수 비교
cc: fe842783 lw a5,-24(s0) # j 불러오기
d0: 00279793 slli a5,a5,0x2 # j * 4 (배열 인덱스 변환)
d4: fdc42703 lw a4,-36(s0) # 배열 포인터 로드
d8: 00f707b3 add a5,a4,a5 # 배열[j] 주소 계산
dc: 0007a703 lw a4,0(a5) # 배열[j] 값 불러오기
e0: fe842783 lw a5,-24(s0) # j 불러오기
e4: 00178793 addi a5,a5,1 # j + 1
e8: 00279793 slli a5,a5,0x2 # (j + 1) * 4
ec: fdc42683 lw a3,-36(s0) # 배열 포인터 로드
f0: 00f687b3 add a5,a3,a5 # 배열[j+1] 주소 계산
f4: 0007a783 lw a5,0(a5) # 배열[j+1] 값 불러오기
f8: 02e7da63 ble a4,a5,12c # if (배열[j] <= 배열[j+1]) continue
- 배열[j] > 배열[j+1]이면 swap 함수 호출.
(4) swap 함수 호출
100: fe842783 lw a5,-24(s0) # j 불러오기
110: 00178793 addi a5,a5,1 # j+1
120: 00078593 mv a1,a5 # 두 번째 매개변수 (j+1)
124: 00068513 mv a0,a3 # 첫 번째 매개변수 (j)
128: 05c000ef jal ra,184 # swap 함수 호출 (0x184)
3. swap 함수 분석 (0x00000184)
swap 함수는 배열[j]와 배열[j+1]을 바꿈.
1a0: fdc42783 lw a5,-36(s0) # 배열[j] 주소
1a4: 0007a783 lw a5,0(a5) # 배열[j] 값
1a8: fef42623 sw a5,-20(s0) # temp = 배열[j]
1ac: fd842783 lw a5,-40(s0) # 배열[j+1] 주소
1b0: 0007a703 lw a4,0(a5) # 배열[j+1] 값
1b4: fdc42783 lw a5,-36(s0) # 배열[j] 주소
1b8: 00e7a023 sw a4,0(a5) # 배열[j] = 배열[j+1]
1bc: fd842783 lw a5,-40(s0) # 배열[j+1] 주소
1c0: fec42703 lw a4,-20(s0) # temp 값
1c4: 00e7a023 sw a4,0(a5) # 배열[j+1] = temp
- 배열[j]와 배열[j+1]을 교환한 후 ret으로 돌아감.
4.Testbench를 통한 Machine Code 분석
rvcodec.js · RISC-V Instruction Encoder/Decoder
luplab.gitlab.io
Machine Code (Instruction Code)는 해당 사이트를 통해 분석할 수 있는데, Vivado의 Testbench와 RISC-V 모듈을 조합하여서도 확인할 수 있다.
19000113
fc010113
02112e23
02812c23
04010413
fc042623
fc042823
fc042a23
fc042c23
fc042e23
fe042023
00100793
fcf42623
00200793
fcf42823
00500793
fcf42a23
00800793
fcf42c23
00600793
fcf42e23
fcc40793
00500593
00078513
044000ef
ff3427b7
11078793
fef42623
e3f927b7
09978793
fef42423
fe842703
fec42783
00f707b3
fef42223
00000793
00078513
03c12083
03812403
04010113
00008067
fd010113
02112623
02812423
03010413
fca42e23
fcb42c23
fe042623
09c0006f
fe042423
0700006f
fe842783
00279793
fdc42703
00f707b3
0007a703
fe842783
00178793
00279793
fdc42683
00f687b3
0007a783
02e7da63
fe842783
00279793
fdc42703
00f706b3
fe842783
00178793
00279793
fdc42703
00f707b3
00078593
00068513
05c000ef
fe842783
00178793
fef42423
fd842783
fff78713
fec42783
40f707b3
fe842703
f8f740e3
fec42783
00178793
fef42623
fd842783
fff78793
fec42703
f4f74ee3
00000013
00000013
02c12083
02812403
03010113
00008067
fd010113
02112623
02812423
03010413
fca42e23
fcb42c23
fdc42783
0007a783
fef42623
fd842783
0007a703
fdc42783
00e7a023
fd842783
fec42703
00e7a023
00000013
02c12083
02812403
03010113
00008067
해당 코드는 Assembly Code를 Machine Code로 변환한 결과이다. 이를 ".mem" 형식으로 저장한 후, Vivado Project에 Import 한다.
이후, $readmemh() 태스크를 이용해 해당 파일에서 데이터를 읽어 ROM 메모리 배열에 로드하게 되면, Testbench를 통해서도 RISC-V의 동작을 디버깅 할 수 있다.
`timescale 1ns / 1ps
module ROM(
input logic [31:0] addr,
output logic [31:0] data
);
logic [31:0] rom[0:2**10 - 1];
initial begin
$readmemh("code.mem", rom);
end
assign data = rom[addr[31:2]]; // address는 PC에서 4byte씩 증가하기 때문에, 이를 반영하기 위한 2bit shift(4로 나눠줌)
endmodule
'AI SOC COURSE > SYSTEM VERILOG (CPU 설계)' 카테고리의 다른 글
CPU Course Review - 12. AMBA BUS (APB Protocol - GPO) (0) | 2025.02.25 |
---|---|
CPU Course Review - 11. AMBA BUS (APB Protocol - RAM) (0) | 2025.02.24 |
CPU Course Review - 09. RISC-V CPU 개요 (0) | 2025.02.12 |
CPU Course Review - 08. RAM (0) | 2025.02.12 |
CPU Course Review - 07. Register (0) | 2025.02.11 |