명령어 집합 | |
CISC | AMD64 ● x86 ● · M68K · 68xx · Z80 · 8080 · MOS 65xx · VAX |
RISC |
AArch64
ARM ·
RISC-V
● ·
MIPS
● ·
DEC Alpha ·
POWER
PowerPC ·
CELL-BE LoongArch · OpenRISC · PA-RISC · SPARC · Blackfin · SuperH · AVR32 AVR |
VLIW EPIC |
E2K · IA-64 · Crusoe |
1. 개요
명령어 집합(Instruction Set Architecture(ISA), Instruction Set)은 소프트웨어와 하드웨어, 특히 CPU와의 사이의 약속이다. ISA는 여러 명령어들을 정의하며 또한 현재 시스템의 상태가 어떻게 구성되어 있고 명령어를 실행할 때 그 상태가 어떻게 바뀌는지에 대해서 정의한다. 그래서 소프트웨어를 구현하는 프로그래머 입장에서는 ISA를 통해 작성한 프로그램이 컴퓨터에서 어떻게 실행될 것인지 알 수 있고, 하드웨어를 구현하는 설계자 입장에서 ISA는 어떤 명령이 수행되었을 때 어떻게 수행되도록 설계해야 하는지의 명세서가 된다. 이렇게 ISA를 정의함으로써 프로그래머 입장에서의 인터페이스와 하드웨어 설계자 입장에서의 구현을 분리할 수 있다.2. 상세
컴퓨터의 상태를 저장하는 요소가 메모리이니, 컴퓨터의 상태는 현재 메모리에 무엇이 저장되어 있는지라고 해도 될 것이다. 이 가운데는 CPU 내부에서 빠르게 이용할 수 있는 메모리인 레지스터가 있다. 이 레지스터는 프로그램을 어디까지 실행했는지에 대한 정보를 담고 있는 프로그램 카운터(PC), 계산중에 만들어지는 값과 같은 데이터를 저장할 수 있는 데이터 레지스터 등 여러 종류가 있다. 명령어(instruction)는 이진수 코드의 조합으로, 프로세서가 명령어를 실행하면 레지스터에 저장된 정보와 같은 시스템의 상태가 바뀐다. ISA는 효과적인 프로그램 실행을 위해 다양한 종류의 명령어를 보유한다.- 산술 논리 명령어 : 산술논리장치(ALU)를 이용하여 사칙연산이나 논리 연산 등을 수행한다. add, sub 등이 있다.
- 데이터 전송 명령어 : 메모리 간에 데이터를 전송한다. load/store라고 하며, 특히 레지스터 간에 데이터를 전송하는 것은 보통 move 명령어라 한다.
- 실행 흐름 제어 명령어 : 조건에 따라 서로 다른 실행 흐름으로 분기하는 branch 계열의 명령어, 서브루틴을 실행하는 call 명령어, 운영체제에게 흐름을 전달하는 trap 명령어 등이 있다.
- 부동소수점 연산 명령어 : 부동소수점 실수의 연산을 하는 명령어들이다.
일반 사용자 입장에서는 응용 소프트웨어가 정상적으로 돌아가느냐를 가르는 두번째 장벽[1]이다. Windows on ARM이 탑재된 기기가 영 신통치 않은 판매량을 보이는 것도 이 이유이다.
2.1. 명령어의 구성
명령어는 보통 다음 두 부분으로 구성된다. 각 부분들이 이진수로 표현되고 조합되어 하나의 명령어를 구성한다.- opcode : ADD, SUB와 같이 명령의 종류를 나타낸다.
- operand : opcode, 어떤 대상에 대해 그 명령을 수행할 것인지에 대한 operand로 구성된다. 명령의 종류에 따라 함께 오는 operand의 구성은 다양하다. operand가 아예 없는 명령어도 있고, 하나, 둘, 심지어 셋 이상일 수도 있다. 흔히 오는 operand의 종류로는 레지스터, 메모리 주소, 상수 값 등이 있다.
3 2 1 1 1 1 1 7 6 0
1 0 9 5 4 2 1
+-----------------------+---------+-----+---------+-------------+
|0 0 0 0 0 0 0 0 0 1 0 1|0 0 0 0 1|0 0 0|0 0 0 1 0|0 0 1 0 0 1 1|
+-----------------------+---------+-----+---------+-------------+
이렇게 조립된 00000000010100001000000100010011이 명령의 이진수 표현이 되고, 이것을 실행하는 프로세서는 1번 레지스터의 값과 상수 5를 더해 2번 레지스터에 저장하는 동작을 한다. 이런 식으로 명령어를 표현한 것이 기계어인데, 사람이 읽기 편하게 하기 위해 다음과 같이 기계어에 대응하는 어셈블리어를 사용하여 표현할 수도 있다.
addi x2, x1, 0
2.2. 명령어의 형식
명령어는 피연산자를 지정하는 방식에 따라 다음과 같은 형식으로 나눌 수 있다:명령어 형식 | 명령어의 예시 | 예시의 의미 | 설명 | 사용될 수 있는 예 |
0-operand | add | Stack[0] ← Stack[0] + Stack[1] | 스택의 맨 위에서부터 피연산자를 읽어온다. | 스택 구조 (예: x87) |
1-operand | add r1 | ACC ← ACC + Regs[r1] | 누산기를 암시적으로 사용하고, 추가 피연산자를 지정한다. | 누산기 구조 (예: 인텔 4004) |
2-operand | add r1, r2 | Regs[r1] ← Regs[r1] + Regs[r2] | 하나의 레지스터는 값을 읽고 쓰는 데 사용하고 하나의 레지스터는 값을 읽는 데 사용한다. | 레지스터 구조, 주로 CISC (예: x86) |
3-operand | add r1, r2, r3 | Regs[r1] ← Regs[r2] + Regs[r3] | 값을 읽을 레지스터들 및 쓸 레지스터를 각각 지정한다. | 레지스터 구조, 주로 RISC (예: ARM, MIPS, RISC-V) |
2.3. 주소 지정 방식 (addressing mode)
명령어가 어디에 있는 값을 읽고 써야 하는지 지정하는 방식을 주소 지정 방식이라 한다. 가능한 주소 지정 방식은 ISA마다, 명령어마다 다양하다. 아래에 다양한 주소 지정 방식을 설명하였다. Regs[r]는 레지스터 r에, Mem[x]는 메모리 주소 x에 있는 값을 의미한다.주소 지정 방식 | 명령어의 예시 | 예시의 의미 | 설명 | 사용될 수 있는 예 |
레지스터(register) | add r1, r2 | Regs[r1] ← Regs[r1] + Regs[r2] | 레지스터에 저장된 값을 접근할 때 지정한다. | 연산 중간 결과의 저장 |
상수 / 즉치(immediate) | add r1, 3 | Regs[r1]←Regs[r1] + 3 | 명령어 자체에서 상수 값을 읽어올 때 지정한다. | 상수 값과의 연산 |
직접 지정(direct) | add r1, (1000) | Regs[r1]←Regs[r1] + Mem[1000] | 고정된 주소의 메모리를 접근할 때 지정한다. | 전역 변수 접근 |
레지스터로 간접 지정(register indirect) | add r1, (r2) | Regs[r1]←Regs[r1] + Mem[Regs[r2]] | 레지스터에 있는 주소의 메모리를 접근할 때 사용한다. | 포인터 접근 |
거리로 지정(displacement) | add r1, 4(r2) | Regs[r1]←Regs[r1] + Mem[4 + Regs[r2]] | 레지스터에 있는 주소에 일정 거리를 더한 주소의 메모리를 접근할 때 사용한다. | 지역 변수 접근 |
레지스터로 거리 지정(index) | add r1, (r2+r3) | Regs[r1]←Regs[r1] + Mem[Regs[r2] + Regs[r3]] | 레지스터에 있는 주소에 다른 레지스터 값에 해당하는 거리를 더한 주소의 메모리를 접근할 때 사용한다. | 배열 접근 |