내장 프로그램
- 여러 종류의 데이터와 명령어를 메모리에 숫자로 저장할 수 있는 모든 프로그램
RISC-V: UC 버클리가 개발한 2010년대 이후 컴퓨터 구조
- 컴파일러는 C 프로그램을 RISC-V 어셈블리 언어 명령어로 바꾸는 작업을 한다.
1. 명령어 기초
피연산자
- 64 비트, 더블 워드 크기를 가지는 레지스터에 피연산자가 저장된다.
- 레지스터 개수는 32개가 최대이다.
--- 예시
f = (g + h) - (i +j);
- // f, g, h, i, j가 각각 레지스터 x19, x20, x21, 22에 할당되었다면,
- add x5, x20, x21 // x5에 g + h 결과를 저장한다.
- add x6, x21, x22 // x6에 i + j 결과를 저장한다.
- sub x19, x5, x6 // x19에 x5 - x6 결과를 저장한다.
모든 연산은 레지스터가 대상이 되므로 메모리와 레지스터 가에 데이터를 주고받는 명령어가 있어야한다.
이런 명령어를 "데이터 전송 명령어(data transfrer instruction)"라고 한다.
적재 (Load)
메모리에서 레지스터로 데이터를 복사해 오는 데이터 전송 명령
명령어의 이름은 ld (load doubleword)이다.
--- 예시
- // A는 100개 짜리 1바이트 단위 배열이다.
- g = h + A[8];
- // g,h는 x20, x21에 그리고 배열 A의 시작주소가 x22에 저장되어 있다면,
- ld x9, 8(x22) // 기준 주소에서 8 바이트 오프셋을 가진 곳에서 값을 가져온다.
- add x20, x21, x9 // g = h + A[8]; h값과 더하여 그 결과를 저장한다.
저장 (Store)
적재와 반대로 레지스터 내용을 메모리로 복사하는 명령
명령어 이름은 sd (store doubleword)이다.
--- 예시
- A[12] = h + A[8];
- ld x9, 8(x22)
- add x9, x21, x9
- sd x9, 12(x22)
상수 또는 수치 피연산자
단순 상수를 더하거나 빼는 연산에 사용된다.
상수를 수치(immediate) 피연산자라고 한다.
- // 레지스터 x22에 4를 더한다.
- addi x22, x22, 4
2. 부호있는 수와 부호없는 수
일반적인 수를 기수로 표현하면 다음과 같다.
$ d \times Base^i $
이진수로 표현되는 컴퓨터 자료는 더블 워드, 최대 64비트로 표현해야 한다.
가장 왼쪽 비트를 Most Siginificant Bit (MSB), 가장 오른쪽 비트를 Least Significnt Bit (LSB)라고 한다.
이진 비트로 표현된 수를 연산한 결과가 하드웨어로 구현된 비트로 표현이 불가능하면 오버플로가 발생했다고 한다.
"부호있는" 이진수를 표현하는 방식은 2의 보수 표현법이라고 한다.
MSB가 1이면 음수를 0이면 양수를 의미한다.
부호있는 이진 비트가 오버플로가 발생할 경우 음수와 음수의 연산에서 MSB가 0이 되거나 양수와 양수의 연산에서 MSB가 1이되는 경우이다.
수 x 와 2의 보수 x_bar의 합은 모든 비트가 1이다. 1(x64 bits)는 -1을 의미하므로 다음과 같은 식이 성립된다.
$x + \bar{x} = -1$
어떤수 x의 음수 -x에 대한 2진수 표현은 x의 2의 보수에 1을 더하면 된다.
$-x = \bar{x} + 1$
3. 명령어의 내부 표현
다음 어셈블리 명령어를 십진수로 표현할 수 있다.
add x9, x20, x21
0 | 21 | 20 | 0 | 9 | 51 |
6개의 부분으로 명령어를 표현하는데, 각 부분을 필드라고 한다. 위 레이아웃을 명령어 형식이라고 한다. RISC-V 명령어의 길이는 워드 길이인 32비트이다.
명령어를 숫자로 표현한 것을 기계어라고 하고 이런 명령어들의 시퀀스를 기계 코드라고 한다.
RISC-V 명령어 필드
기본 R(register) 타입의 명령어 형식을 가리킨다.
R-타입 명령어
funct7 | rs2 | rs1 | funct3 | rd | opcode |
7 bits | 5 bits | 5 bits | 5 bits | 5 bits | 7 bits |
- opcode : 명령어가 실행할 연산의 종류로서 연산자라고 부른다.
- rd: 목적지 레지스터, 연산결과가 기억된다.
- funct3: 추가 opcode 필드
- rs1: 첫 번째 src 피연산자 레지스터
- rs2: 두 번째 src 피연산자 레지스터
- funct7: 추가 opcode 필드
위 타입에서는 2^5 = 32 보다 큰 값을 레지스터에 저장할 수 없다. ld 명령어가 만약 32보다 큰 오프셋에서 데이터를 가져온다면 위 명령어로 표현이 불가능하다. 두번째 명령어 형식은 I-타입으로 상수 피연산자 하나를 갖는 산술 피연산자에 의해 사용되며 적재 명령어에서도 사용된다.
I-타입 명령어
immediate | rs1 | funct3 | rd | opcode |
12 bits | 5 bits | 3 bits | 5 bits | 7 bits |
12비트 수치값은 부호 있는 정수로 해석되므로 $-2^11 ~ 2^11 -1$까지의 정수를 표현할 수 있다. I 타입 명령어가 적재 명령어에 사용될 경우 수치 값은 바이트 변위를 표현하고 베이스 레지스터 rd에 있는 기준 주소로 부터 $\pm 2^11 = \pm 2048 = \pm 256 더블 워드$를 지정할 수 있다.
ld x9, 64(x22): 명령어는 rs1 필드에 22, 수치 필드에 64가 rd 필드에 9가 들어간다.
sd 명령어는 베이스 주소와 저장할 데이터 레지스터 2개와 주소 변위를 위한 수치 필드가 필요하다. 다음 S 타입의 명령어는 sd 명령어를 표현할 수 있다.
S-타입 명령어
immediate[11:5] | rs2 | rs1 | funct3 | immediate[4:0] | opcode |
7 bits | 5 bits | 5 bits | 3 bits | 5 bits | 7 bits |
S-타입 형식에서 12비트 수치값은 하위 5비트와 상위 7 비트로 나뉜다. 이는 rs1과 rs2가 5비트 필드로 유지하기 위해 이렇게 설계되었다.
R 타입 명령어, add는 opcode 50과 funct7 0를 가지고 sub는 opcode 50과 funct7 32를 가진다.
I 타입 명령어 형식에서 기준 주소 오프셋이나 상수 수치를 표현하기 위해 12비트 수치 필드를 사용한다.
S 타입 명령어는 수치 필드와 레지스터 필드 2개가 특징이다.
오늘날 컴퓨터는 두가지 중요한 원리를 기반으로 한다.
1. 명령어는 숫자로 표현된다.
2. 프로그램은 메모리에 기억되어 있어서 데이터처럼 읽고 쓸 수 있다.
이것이 내장 프로그램의 개념이다. 명령어를 숫자로 취급하고 프로그램이 이진수 파일 형태로 되어 있다면 어떤 컴퓨터든 동일한 컴퓨터 시스템에서 실행 가능하다.
4. 논리 연산 명령어
shift: 비트의 자리를 이동시킨다.
- slli x11, x9, 4 // x9 레지스터 값을 4비트 왼쪽으로 쉬프트하여 x11에 저장한다.
S 타입 명령어로 표현 가능하다.
immediate[11:5](funct6) | rs2 | rs1 | funct3 | immediate[4:0] | opcode |
0 | 4 | 19 | 1 | 11 | 19 |
slli 명령어는 opcode가 19, rd는 11, funct3 1이고 rs1은 19, 수치 필드는 4이다.
and, or, xor : 각각 다음 C 프로그램의 연산자와 동일하다.
- and x9, x10, x11 // x9 = x10 & x11
- or x9, x10, x11 // x9 = x10 | x11
- xor x9, x10, x11 // x9 = x10 ^ x11
5. 판단을 위한 명령어
조건에 따라 분기를 실행하는 명령어가 있다. if(조건) go to와 비슷한 실행을 한다.
- beq rs1, rs2, L1 // if(rs1 == rs2) go to L1
- bne rs1, rs2, L1 // if(rs1 != rs2) go to L1
예제
if (i == j) f = g + h; else f = g - h;
- f,g,h,i,j -> x19 ~ x23
- bne x22, x23, Else // go to Else if i != j
- add x19, x20, x21 // f = g + h
- beq x0, x0, Exit // if 문이 끝으로 빠져나온다.
- Else: sub x19, x20, x21 // f = g - h
- Exit:
순환문
예제
while( save[i] == k )
i += 1;
i, k -> x22, x24
순환의 끝에서 루프의 처음으로 돌아갈 수 있도록 한다.
- Loop: slli x10, x22, 3 // x10에 8 * i를 곱한다.
- add x10, x10, x25 // save[i]의 기준 주소를 더한다.
- ld x9, 0(x10) // 임시 레지스터 x9에 저장한다.
- bne x9, x24, Exit // if save[i] != k 일 경우 빠져나온다.
- addi x22, x22, 1 // i += 1
- beq x0, x0, Loop // go to Loop
- Exit:
Case/Switch 문장
여러 조건에 따라 분기하는 분기 주소 테이블을 구성한다. 분기 테이블은 프로그램상의 테이블의 인덱스만 계산해서 해당 루틴으로 분기할 수 있다.
프로그램은 레지스터에 분기할 주소를 적재한 후 적재된 주소를 이용하여 분기한다. indirect jump 명령어가 그 역할을 하며 레지스터에 명시된 주소로 무조건 분기한다.
RISC-V의 jump-and-link register 명령어가 그 역할을 한다.
참고 : Computer Organization and Design RISC-V edition
'Computer Science 기본 지식 > 컴퓨터 구조' 카테고리의 다른 글
[컴퓨터 구조] 5. CPU 성능 측정 (0) | 2021.04.18 |
---|---|
[컴퓨터 구조] 4. 명령어 실행 (0) | 2021.04.18 |
[컴퓨터 구조] C++ 링커 동작 방식 확인 (0) | 2021.04.17 |
[컴퓨터 구조] 3. 컴퓨터 연산 (0) | 2021.04.16 |
[컴퓨터 구조] 2. 하드웨어 프로시저 지원 / 프로그램 번역과 실행 (0) | 2021.04.15 |