이론/기초

컴퓨터 구조 기초 - 2

tsyang 2023. 7. 1. 19:02

레지스터 크기와 명령어


 

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 높은 성능을 내는 이유는 클럭당 명령어 처리 수가 두개 이상이기 때문이다. (파이프라이닝 기법)