임베디드 스터디 - 명령어 집합 구조
임베디드 스터디 - 명령어 집합 구조
이번 글 참고자료:
정익사, 컴퓨터 구조의 이해
명령어 형식
- 명령어의 종류와 수는 CPU마다 다르며, 각 CPU의 명령어들의 집합을 명령어 세트(Instruction set)라고 한다.
- 명령어 세트는 설계 시 연산 종류, 데이터 형태, 명령어 형식, 주소 지정 방식 등에 의해 결정된다.
- 연산 데이터 길이, 수의 표현 방식에 의해 명령어가 결정된다.
- 일반적으로 [명령 코드(Operation code), 피연산자(Operand)] 형태로 구성된다.
가변 길이 명령어 형식
- 명령어가 다양한 크기로 사용된다.
- CISC(Complex Instruction Set Computer)에서 사용되는 명령어 형식
고정 길이 명령어 형식
- 명령어의 길이가 CPU의 워드 길이와 동일함
- RISC(Reduced Instruction Set Computer)에서 사용되는 명령어 형식
- 각 명령어 형식에 따라 사용 목적이 다르다.
- 3-주소 명령어 : 여러 개의 범용 레지스터를 가진 컴퓨터에서 특수 목적에만 제한적으로 사용
- 2-주소 명령어 : 대부분의 CPU에서 가장 많이 채택하고 있는 형식
- 1-주소 명령어 : 누산기를 갖는 CPU에서 누산기 기반의 명령을 내릴 때 사용
- 0-주소 명령어 : 스택 구조 CPU에서 사용.
1 2 3 4
3-주소 명령어: ADD R1, R2, R3 → R1 = R2 + R3 2-주소 명령어: ADD R1, R2 → R1 = R1 + R2 1-주소 명령어: ADD R2 → ACC = ACC + R2 0-주소 명령어: ADD → 스택 top 두 개를 더함
CISC vs RISC
- 데스크탑 PC에서 자주 보이는 x86 아키텍처는 CISC, 임베디드 장치에서 자주 보이는 ARM 아키텍처는 RISC이다.
- CISC는 디코더 회로의 복잡성으로 인해 트랜지스터가 RISC보다 많이 있어 전력소모량이 많다.
- 임베디드로 ARM이 자주 사용되는 이유 중 하나이다.
(RISC 소모 전력 : mW 수준, CISC 소모 전력 : 수십~수백 W 수준)
- 임베디드로 ARM이 자주 사용되는 이유 중 하나이다.
| CISC (x86) | RISC (ARM) | |
|---|---|---|
| 명령어 하나의 역할 | 복잡한 동작 한 번에 | 단순한 동작 하나씩 |
| 명령어 수 | 수백~수천 종류 | 수십~백여 종류 |
| 하드웨어 | Decode 회로 복잡 | Decode 회로 단순 |
| 파이프라인 | 불리 (명령어 길이 가변) | 유리 (고정 길이) |
- 예를 들어, CISC에서
MOVSB라는 가변 명령어 하나는 RISC(ARM)에서 다음과 같은 명령어를 실행해야 동일한 기능을 수행할 수 있다.
1
2
3
4
5
LDR R0, [src] // 메모리에서 데이터 읽기
STR R0, [dst] // 메모리에 데이터 쓰기
ADD src, src, #4 // 소스 주소 증가
ADD dst, dst, #4 // 목적지 주소 증가
// ... 복사할 크기만큼 반복
RISC-V vs RISC
- RISC를 오픈소스로 사용하기 위해 탄생한 것이 RISC-V
- UC Berkeley에서 2010년에 시작한 프로젝트
| ARM | RISC-V | |
|---|---|---|
| 라이선스 | 유료 (로열티) | 무료 (오픈소스) |
| ISA 설계 | 고정 | 모듈식 (확장 가능) |
| 생태계 | 성숙 | 빠르게 성장 중 |
| 사용처 | 스마트폰, 임베디드 | IoT, 서버, AI 가속기 |
- 특히, RISC-V는 모듈식 설계가 핵심이다. 기본 정수 연산 ISA에 필요한 확장만 골라서 추가가 가능하다.
1 2 3 4
RV32I : 기본 정수 연산 RV32M : 곱셈/나눗셈 추가 RV32F : 단정밀도 부동소수점 추가 RV32C : 압축 명령어 추가 (코드 크기 감소)
어드레싱 모드
- 데이터의 전송 방식에 대한 구분
- 명령어를 실행하기 위해 명령 수행에 필요한 데이터를 찾을 수 있는 주소를 계산하는 방법
즉치 주소 지정 방식 (Immediate Addressing Mode)
- 오퍼랜드에 연산에 필요한 숫자 데이터를 직접 넣는 방식
- 연산을 위해 메모리를 액세스할 필요가 없음
- 사용할 수 있는 데이터 크기가 오퍼랜드 필드의 크기로 제한됨
- ASM언어로 다음과 같이 예시를 들 수 있다.
1
2
// ASM으로 MOV R1, 5
int a = 5;
직접 주소 지정 방식 (Direct Addressing Mode)
- 오퍼랜드에 연산에 필요한 데이터가 있는 유효 주소(EA, Effective Address)를 넣는 방식
- 데이터 액세스를 위해 별도로 유효 주소에 대한 해석 및 처리가 필요 없음
- 주소의 크기가 오퍼랜드 필드의 크기로 제한됨
- C언어로 다음과 같이 예시를 들 수 있다.
1
2
3
4
// ASM으로 MOV R1, [5] → R1 = Memory[5] (주소 5의 내용)
// 전역변수는 고정 주소를 가짐 → 직접 어드레싱에 더 가까움
int global_a = 5;
int b = global_a;
간접 주소 지정 방식 (Indirect Addressing Mode)
- 오퍼랜드에 연산에 필요한 데이터가 있는 유효 주소(EA, Effective Address)가 있는 주소를 넣는 방식
- 기억 장치 용량에 제한이 없음
- 명령어 실행 과정에서 두 번씩 메모리를 참조해야 함
- C언어로 다음과 같이 예시를 들 수 있다.
1
2
3
4
// ASM으로 MOV R1, [[5]] → R1 = Memory[Memory[5]]
int a = 5; // 즉치
int *p = &a; // p에 a의 주소 삽입
int b = *p; // b에 p(a의 주소) 삽입
레지스터 간접 주소 지정 방식 (Register Indirect Addressing Mode)
- 오퍼랜드에 레지스터 번호를 넣는 방식
- 지정된 레지스터의 내용은 메모리 주소다.
- 접근 가능한 기억장치의 주소는 레지스터의 크기에 달렸다.
1
2
3
4
5
// ASM으로 MOV R1, [R2] → R1 = Memory[R2]
// R2 레지스터가 주소를 담고, 그 주소에서 데이터를 읽는다.
int a = 42;
int *p = &a; // p(레지스터)에 a의 주소 저장
int b = *p; // b = Memory[p]
변위 주소 지정 방식 (Displacement Addressing Mode)
- 레지스터 간접 주소 지정의 파생형 : 오퍼랜드를 2개 받는다.
- 첫 번째 오퍼랜드는 레지스터 번호,
두 번째 오퍼랜드는 임의의 수 - 유효 주소는 $레지스터에 저장된 주소 + 두 번째 오퍼랜드 숫자$
- 첫 번째 오퍼랜드는 레지스터 번호,
1
2
3
4
// ASM으로 MOV R1, [R2 + 8] → R1 = Memory[R2 + 8]
int arr[5] = {10, 20, 30, 40, 50};
int *base = arr; // 기준 주소 (레지스터)
int val = *(base + 2); // base + 변위(2) → arr[2] = 30
상대 주소 지정 방식 (Relative Addressing Mode)
- 레지스터 간접 주소 지정의 파생형 : 레지스터를 PC를 사용한다.
- PC의 분기 명령에서 주로 사용됨
- 일반적인 분기 명령어보다 적은 수의 비트만으로 명령어 세트를 구성할 수 있다.
1
2
3
4
5
6
// ASM으로 BNE +offset → PC = PC + offset (조건 분기)
// C의 if/loop는 컴파일러가 내부적으로 PC 기준 상대 분기로 변환한다.
int x = 10;
if (x > 5) { // 조건이 거짓이면 PC + offset 위치(else 이후)로 점프
x = 0;
}
베이스 레지스터 주소 지정 방식 (Base Register Addressing Mode)
- 베이스 레지스터와 오퍼랜드를 더하여 유효주소를 계산
- 베이스 레지스터는 메모리 주소의 기준 주소를 정해주는 레지스터
- 프로그램 전체를 옮길 때 사용
1
2
3
4
5
6
7
8
9
10
11
// ASM으로 MOV R1, [BASE + 4] → R1 = Memory[BASE + 4]
// 구조체 포인터가 베이스 레지스터 역할을 한다.
typedef struct {
int x; // offset 0
int y; // offset 4
int z; // offset 8
} Point;
Point p = {1, 2, 3};
Point *base = &p; // 베이스 레지스터 = 구조체 기준 주소
int y_val = base->y; // Memory[base + 4]
인덱스 주소 지정 방식
- 인덱스 레지스터의 내용과 오퍼랜드를 더하여 유효주소를 계산
- 인덱스 레지스터는 인덱스 값을 저장하는 특수 레지스터
- 베이스 레지스터와의 차이는 그저 연산에 사용하는 레지스터의 차이다.
- 인덱스 레지스터 : 명령어가 포함된 주소를 기준으로 한 인덱스 값
- 베이스 레지스터 : 기준이 되는 주소가 저장된 값
1
2
3
4
// ASM으로 MOV R1, [arr + INDEX] → R1 = Memory[arr + INDEX]
int arr[5] = {10, 20, 30, 40, 50};
int index = 3; // 인덱스 레지스터
int val = arr[index]; // Memory[arr + index] = arr[3] = 40
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.