포스트

임베디드 스터디 - 명령어 집합 구조

임베디드 스터디 - 명령어 집합 구조

이번 글 참고자료:
정익사, 컴퓨터 구조의 이해

명령어 형식

  • 명령어의 종류와 수는 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 수준)
 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년에 시작한 프로젝트
 ARMRISC-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 라이센스를 따릅니다.