컴퓨터 구조 기초 - 2
레지스터 크기와 명령어
16비트 컴퓨터가 있다고 가정하자. 그러면 레지스터 사이즈도 16비트인 게 좋을 것이다.
그리고 16비트의 레지스터 r0~r7이 있다고 하자.
명령어 역시 레지스터 크기와 같아야 좋으니까 16비트라고 하자.
자 그럼 16비트로 어떻게 명령어를 구성할 것인가?
명령어의 종류가 8개인 컴퓨터라고 가정하자. 명령어가 8개이니까 이 모든 명령어를 식별하기 위해서는 3비트가 필요하다.
사칙연산 만들어보기
사칙 연산을 예로 들어 보자.
A = B+C
를 의미하는 명령어를 구현하고 싶다. 어떻게 해야 할까?
우선 '덧셈'이라는 명령에 해당하는 id가 있을 것이다(3비트)
그리고 B+C의 값을 저장할 레지스터가 필요하다. 대충 레지스터도 r0~r7까지 8개가 있다고 하자. 레지스터의 주소를 구분하는 데에 또 3비트가 필요하다.
그리고 B와 C에 해당하는 정보도 명령어에 들어있어야 한다. B,C 레지스터가 될 수도 있고 그냥 숫자가 될 수도 있다. B,C가 각각 레지스터인지 아닌지를 판별하기 위해 1비트를 추가로 할당하자. 그러면 B,C의 크기는 각각 4비트가 된다.
정리하면 다음과 같다.
[ 남음(2) | '더해'(3) | A (3) | B(4) | C(4) ]
만약 다음과 같은 어셈블리 명령어가 있다면,
ADD r2, r1, 7
[ (2) | 'ADD'(3) | r2 (3) | r1(4) | 7(4) ]
위와 같이 명령어가 구성될 것이다.
위 명령어를 처리하는 과정은 어떻게 될까?
우선 메모리에 명령어가 올라가고,
Fetch단계에서 메모리에 있는 명령어를 I/O 버스를 통해 명령어 레지스터인 IR에 저장한다.
Decode단계에서 컨트롤이 이 명령어를 해석하고,
Execution단계에서 ALU가 명령을 실행한다.
LOAD & STORE 명령어 만들기
위에서 든 예제는 레지스터와 숫자만 사용하였다. 실제로는 메모리의 값을 이용해서 연산할탠데 이건 어떻게 해결할까?
우선 명령어 두 개가 필요하다 LOAD와 STORE.
LOAD는 메모리에 있는 값을 레지스터로 불러온다.
STORE는 레지스터에 있는 값을 메모리에 저장한다.
자 그럼 LOAD와 STORE 명령어의 피연산자는 무얼까? 하나는 레지스터이고 다른 하나는 메모리 주소일 것이다.
어셈블리어로 표현하면 다음과 같을 것이다.
LOAD r1, 0x10 //0x10번지의 값을 불러와 r1에 저장
STORE r2, 0x20 //r2의 값을 0x20번지에 저장
위에서 명령어는3비트, 레지스터는3비트라고 했다. 편의상 메모리 주소는 8비트를 사용한다고 가정해보자.
그럼 명령어 구성은 다음과 같다.
[잉여(2) | 'LOAD'(3) | r1(3) | 0x10(8)]
Direct 모드와 Indirect모드
위 명령어 구성을 보면 문제가 딱 보인다. 바로 레지스터는 16비트인데 메모리를 표현하는 데에는 8비트밖에 못 쓴다는 것이다.
이를 해결하기 위해 Direct모드와 Indirect모드가 있다.
Direct모드는 메모리 주소에 바로 접근하는 방식이고,
Indirect모드는 중간에 하나의 메모리 주소를 더 경유하는 방식이다.
가령 아래와 같은 어셈블리어가 있다면,
LOAD r1, 0x10 //0x10번지에 있는 값을 r1에 로드
Indirect모드에서는
LOAD r1, [0x10] //0x10번지에 있는 주소를 참조하여 해당 주소의 값을 r1에 저장
위와 같이 [0x10]과 같이 대괄호를 이용하며 메모리 주소를 표현한다. 뜻은 0x10번지에 저장된 메모리 주소를 의미한다.
가령,
int a = b + 7 //a : 0x1000, b : 0x0010
위와 같은 코드에서 0x1000번지에 접근하려면 8비트 메모리 공간만으로 표현할 수 없다.
그렇기 때문의 아래의 과정을 수행해야 한다.
1. 0x1000라는 숫자를 만들어서 레지스터에 저장한 다음,
2. 해당 레지스터의 값을 임의의 메모리공간 0x0001에 저장하고,
3. [0x0001]을 이용하여 연산 결과를 저장
어셈블리 언어로는 다음과 같은 느낌일 것이다.
MUL r1, 8, 8
MUL r2, 8, 8
MUL r3, r1, r2 //0x1000이 r3에 저장되어있다.
STORE r3, 0x0001 //0x0001에 r3의 값을 저장한다.
LOAD r1, 0x0010 //r1에 B(0x0010)의 값을 로드
ADD r2, r1, 7 //r2에 연산 결과값 저장
STORE r2, [0x0001] //0x0001번지에 저장된 주소(=0x1000)에 r2값을 저장
이제 Direct와 Indirect모드의 구분에 1비트를 사용해야 하니 명령어 구조는 다음과 같이 될 것이다.
[잉여(1) | Indirect(1) | 'LOAD'(3) | r1(3) | [0x0001](8) ]
RISC / CISC
명령어의 숫자는 몇 개가 적합할까?
CISC(Complex instruction set computer)는 명령어가 수 백개 존재하는 CPU이다. 필요에 따라 명령어 길이도 유동적으로 변할 수 있다. 옛날 CPU들이 이런 구조였다.
이렇게 명령어가 수백 개 있으면 아무래도 사용하는 입장에서 편할 것이다.
그러나, 단점도 있다. 명령어 수가 많고 크기가 일정하지 않기 때문에 CPU가 복잡해진다. 그리고 복잡한 구조에서는 성능 향상에 제한이 따른다.
이런 배경에서 RISC가 나왔다. CISC에서 잘 안쓰는 명령어를 없애버린 다음 명령어 길이를 일정하게 디자인했다. 이제 성능 향상이 좀 더 쉬워졌다. 근래에는 다 이 구조를 쓴다.
RISC가 높은 성능을 내는 이유는 클럭당 명령어 처리 수가 두개 이상이기 때문이다. (파이프라이닝 기법)