## 목차 ##

  1. CPU의 구조
  2. 명령어 실행
  3. 명령어 파이프라이닝
  4. 명령어 세트


## 0.Intro ##

CPU는 프로그램 실행을 하는 가장 핵심적인 부품이다. CPU는 아래의 동작들을 차례로 수행함으로써 프로그램을 실행한다.

1. 명령어 인출(fetch) : 메모리에서 명령어 가져오기

2. 명령어 해독(decode) : 명령어를 해석하여 연산 수행 및 결과를 저장

3. 데이터 인출

4. 데이터 처리

5. 데이터 저장

명령어를 실행하기 위해 필요한 데이터가 있고, 그 결과를 저장할 필요가 있다면 3,4,5가 실행된다.

이러한 CPU의 동작을 세부적으로 알기위해 생겨먹은 구조를 간단히 파악할 필요가 있다.




## 1. CPU의 구조 ##

cpu_structure

ALU

  • 각종 산술연산(4칙연산) 및 논리연산(AND, OR, NOT 등) 수행

레지스터(Register)

  • CPU내부에 위치한 기억장치로 명령어 실행도중 발생하는 데이터들을 임시 저장
  • 메인 메모리보다 CPU가 더 빨리 접근할 수 있음
  • 부피가 커 많은 수를 포함시키지는 못함

제어(Control) 유닛

  • 명령어 해석
  • 명령어 실행을 위한 신호 발생




## 2. 명령어 실행 ##

  • 명령어 사이클(instruction cycle)

    CPU가 한 개의 명령어를 실행하는 전체 과정으로 [인출+실행] 단계로 구성

instruction_cycle

프로그램 시작부터 종료까지 명령어 사이클이 반복적으로 실행된다.



cpu_inner_architecture

위 그림은 명령어 사이클에 사용되는 CPU 내부 레지스터들이다. 각 레지스터의 역할은 아래와 같다.

프로그램 카운터(Program Counter: PC)

다음에 인출(fetch)될 명령어가 존재하는 메모리 주소가 저장된다. 마지막 명령어 인출 후 '명령어 주소 크기 단위'만큼 자동으로 증가하거나 혹은 분기(if나 switch와 같은)문의 경우 목적지 주소로 업데이트된다. 이 주소는 주소버스를 통해 내보내진다.



누산기(Accumulator: AC)

연산 과정에 필요한 데이터를 일시적으로 저장한다. 따라서 비트 수는 워드 크기와 같다.



명령어 레지스터(Instruction Register: IR)

가장 최근에 인출된 명령어를 저장한다. 이후에 여기에 저장된 명령어가 실행된다.



기억장치 주소 레지스터(Memory Address Register: MAR)

PC의 주소값을 주소버스에 내보기전 일시적으로 저장하는 곳이다. MAR이 주소버스와 직접 연결된다. 즉 인출할 명령어의 주소는 PC → MAR → 주소버스 → 메모리를 거친다.



기억장치 버퍼 레지스터(Memory Buffer Register: MBR)

메모리에 저장할 데이터 혹은 메모리로부터 읽은 데이터(인출된 명령어 포함)를 임시적으로 저장하기 위한 버퍼 레지스터이다. 따라서 데이터 버스와 접속되어 있다. 예를들어 다음에 실행할 명령어를 인출한다면 메모리 → 데이터버스 → MBR → IR → 제어유닛 순으로 데이터가 이동한다.

위에서 설명한 CPU의 내부구조를 기반으로 명령어 인출 및 실행 사이클에서 일어나는 동작에 대해 살펴보자.


### 2-1. 인출 사이클 ###

프로그램 카운터가 가리키는 주소를 통해 메모리에서 명령어를 인출한다. 이후 PC의 값이 다음 메모리주소값으로 증가한다(혹은 분기의 경우 목적지 주소로 갱신한다). 인출 사이클은 더 작게는 마이크로 연산(micro-operation)들로 나타낼 수 있다.

micro_operation

인출 사이클은 위에서 보는바와 같이 3개의 마이크로 연산으로 이루어져있다. 각 마이크로 연산은 CPU 클럭 주기에 하나씩 실행된다. 즉, 인출사이클이 실행되기 위해서는 총 3 CPU 클럭이 필요하다.

첫 번째 주기에서는 다음 인출 명령어가 존재하는 주소가 PC에서 MAR로 이동한다. 곧바로 해당 주소가 주소버스를 타고 메모리로 전달된다.

두 번째 주기에서는 메모리의 해당 번지수에 있던 명령어가 데이터버스를 타고 MBR로 들어온다. 이 과정이 진행되는 동시에 PC값이 명령어 주소 크기 단위만큼 증가한다. 가령 메모리주소에 8bit가 사용되고 명령어가 16bit로 이루어져있다면 하나의 명령어는 2개의 번지수를 차지하게 된다. 따라서 PC값은 2가 증가한다.

마지막 주기에서는 MBR에서 IR로 명령어가 이동한다.

여기까지가 인출 사이클이다. 이후 IR에 저장된 명령어가 실행되는 단계가 실행 단계이다. 아래 그림은 인출 사이클이 실행되는 흐름도이다.

fetch_cycle


### 2-2. 실행 사이클 ###

IR에 저장된 명령어를 해독하고 실행한다. 실행의 종류는 크게 4가지로 분류된다.

데이터 읽기

CPU와 메모리, 혹은 I/O장치 사이의 데이터 교환

데이터 쓰기

연산결과 혹은 입력장치에서 읽은 데이터를 메모리나 보조기억장치에 저장

데이터 처리

산술연산과 논리연산 

프로그램 제어

프로그램의 실행순서 결정



위에서 말한 실행 종류들별로 마이크로 연산 과정이 다르다. 명령어 종류별로 예시를 보겠다.

그 전에 명령어는 연산 종류를 나타내는 연산코드와 연산에 필요한 연산과 관련된 메모리 주소를 나타내는 오퍼랜드로 구성되어있다는 것을 상기하자.

instruction_format



a)데이터 읽기 예제

메모리의 특정 주소에서 데이터를 읽어서 AC에 저장(LOAD addr)

data_read_execution

첫 번째 주기에서 IR에 저장된 명령어의 오퍼랜드(데이터를 읽을 메모리 주소)가 MAR로 이동한다.

두 번째 주기에서 해당 주소의 데이터(M[MAR])가 데이터 버스를타고 MBR로 이동한다.

세 번째 주기에서는 MBR에 임시로 저장되었던 데이터가 AC로 이동한다.



b)데이터 쓰기 예제

AC의 값을 메모리의 특정 주소에 저장(STA addr)

data_write_execution

첫 번째 주기에서 IR에 저장된 명령어의 오퍼랜드(데이터를 저장할 메모리 주소)가 MAR로 이동한다.

두 번째 주기에서 AC의 데이터가 MBR로 이동한다.

세 번째 주기에서 해당 메모리 주소(M[MAR])와 MBR의 데이터가 각각 주소버스와 데이터버스를 타고 메모리로 전송된 후 데이터가 저장된다.



c)데이터 처리 예제

메모리의 특정 주소에서 데이터를 읽어 AC값과 더해서 AC에 저장(ADD addr)

data_process_execution

첫 번째 주기에서 IR에 저장된 명령어의 오퍼랜드(데이터를 읽을 메모리 주소)가 MAR로 이동한다.(곧바로 주소버스를 타고 메모리로 이동한다.)

두 번째 주기에서 메모리로부터 값이 들어와 MBR에 저장된다.

세 번째 주기에서 AC값과 MBR값이 더해져 AC에 저장된다.



d)프로그램 제어 예제

PC가 아닌 다른 주소로의 분기 명령어(JUMP addr)

program_control_execution

이 경우에는 명령어 실행 마이크로 오퍼레이션이 한 주기만에 실행된다. 예를들어 if문이 만족하여 if문의 블럭을 실행하는 경우이다.

명령어의 오퍼랜드(분기할 목적지 주소)를 PC로 보내버리면 된다. 그러면 다음 명령어를 메모리의 해당 주소에서 인출할 것이다.


### 2-3. 인터럽트 사이클 ###

1) 인터럽트란?

인터럽트(interrupt)란 현재 진행중인 명령어 실행 사이클을 즉시 중단하고 다른 동작을 처리하도록 요구하는 매커니즘이다. 순차적인 명령어에 포함되지는 않지만 CPU 외부로부터 요구가 들어오면 실행된다.

인터럽트 발생시 실행해야 하는 동작을 인터럽트 서비스 루틴(ISR - Interrupt Service Routine)이라고 부른다. 인터럽트 서비스 루틴에대한 처리가 끝나면 CPU는 원래 루틴으로 복귀하여 계속 프로그램을 실행한다.

interrupt

사실 명령어 인출 및 실행 단계에서 설명하지 않은 추가 동작이 더 있다. 실행 사이클이 끝난 뒤 새로운 인출 사이클이 시작되기 전에 인터럽트 요구신호가 대기중인지 검사하는 것이다. 만약 인터럽트 비트가 SET 되어있다면 아래의 동작을 수행한다.



2) 인터럽트 사이클 실행 순서

a) ISR수행이 끝난 후 복귀를 위해 PC의 값을 스택(stack)에 저장
b) ISR의 시작 주소를 PC에 로드한다. 이 주소는 인터럽트를 요구한 장치로부터 받거나 미리 계획된 인터럽트일경우 메모리에서 가져온다.

요약하자면 인터럽트 사이클은 아래의 순서대로 실행된다.

[인터럽트 요구신호 검사 → (인터럽트 비트가 SET이면)현재 내용을 스택에 저장 → 인터럽트 시작주소를 PC에 로드]

인터럽트 시작 주소를 PC에 로드하는 것 까지가 인터럽트 사이클이다. ISR의 시작주소가 PC에 적재되었기 때문에 다음 사이클에서는 ISR에 대해 명령어 인출-실행 단계를 거친다. 이를 마이크로연산으로 분리해보면 아래 그림과 같다.

interrupt_micro_operation

첫 번째 주기에서는 현재 PC의 내용이 시스템버스를 타고 MBR을 통해 메모리로 이동한다.

두 번째 주기에서는 스택포인터(SP)가 가르키는 스택의 마지막 주소를 MAR를 통해 메모리로 내보낸다. 동시에 ISR의 시작 주소를 PC에 올린다.

마지막 주기에서는 MBR에 저장되어있던 원래의 PC내용이 데이터버스를 타고 메모리로 이동하면 두 번째 주기에서 메모리가 받았던 스택의 마지막 주소에 이 내용이 저장된다.(이로써 원래의 순차적 프로그램 순서에서 다음에 인출할 명령어 주소가 스택에 저장된 것이다.) 그리고 동시에 스택포인터 SP의 값이 1 감소한다.

결론적으로 PC에 있던 내용이 스택에 저장되고, PC에 ISR의 시작주소가 적재되었기 때문에 다음 사이클에는 ISR이 수행되는 것이다. 그리고 ISR이 끝나면 스택에 저장되어있던 원래 PC의 내용을 가져와 계속 수행하게된다.

스택포인터인 SP는 CPU 내부에 있는 특수목적 레지스터들 중의 하나이다. SP는 항상 스택의 최상위 주소(Top Of Stack - TOS)를 가리킨다. 또한 일반적으로 스택 영역은 메모리의 끝 부분이 사용되므로 스택에 내용이 쌓일수록 SP는 1씩 감소하게 된다.

아래 그림은 인터럽트가 포함된 전체 명령어 사이클을 표현한다.

instruction_cycle_with_interrupt

외부 장치의 인터럽트 요구가 있다고해서 항상 실행중인 사이클을 중단하고 ISR이 실행되는것은 아니다. CPU 설정값에서 INTERRUPT_ENABLE 비트와 같이 인터럽트 가능 여부를 설정할 수 있는 값이 SET 되어있어야한다.



3) 인터럽트 도중 레지스터 내용의 변경을 방지하는 방법

인터럽트 사이클 도중에 원래 사이클이 사용하던 값들이 저장된 레지스터의 값이 변경된다면? 예를들어 연산 중간 결과가 AC에 저장되어있었는데 ISR 수행 도중 AC값을 변경한다면 인터럽트 후 원래의 사이클로 복귀했을때 문제가 발생한다. 이를 해결하기 위해 ISR 시작 단계에서는 CPU 레지스터들의 내용을 스택에 저장하였다가 ISR 수행 후 다시 가져오는 절차가 수행된다.

4) 다중 인터럽트

ISR 수행도중 다른 외부장치가 또 다른 인터럽트를 요구하는 경우를 다중 인터럽트(Multiple Interrupt)라고 부른다.

다중 인터럽트를 처리하는 방법에는 2가지가 있다.

a) interrupt disable 상태로 변경

ISR 수행 도중에는 interrupt를 disable하여 다른 인터럽트가 끼어들지 못하게 한다. 현재 처리중인 ISR이 끝나고 다시 interrupt enable 상태가 되면 대기중인 인터럽트가 인식된다.

b) 인터럽트 우선순위 설정

ISR 수행도중 우선순위가 더 높은 인터럽트 요구가 들어올 경우에는 해당 인터럽트로 새로 분기하고 우선순위가 낮은 인터럽트에 대해서는 대기를 시켜놓는 것이다. 이 내용을 그림으로 표현하면 아래와 같다.

multiple_interrupt

이 경우 PC와 다른 인터럽트 내용을 저장하고 ISR이 끝난 후 갱신하는 내용은 앞에서 설명한 절차와 동일하다.


### 2-4. 간접 사이클 ###

연산이 필요한 경우(더하기, 빼기같은) 명령어의 오퍼랜드는 데이터가 있는 메모리의 주소를 나타낸다고 했었다. 그런데 데이터의 주소가 아닌 데이터의 주소를 내용으로 가지고있는 메모리의 주소 경우가 있다. 다시말하면 메모리에서 데이터가 저장된 곳의 주소를 받아와 다시 그 주소로 찾아가 데이터를 가져와야한다. 이 경우 데이터의 실제 주소를 가지고 있는 주소를 가져오는 과정을 간접 사이클이라고 하는데, 간접 사이클은 인출 사이클과 실행 사이클 사이에 위치한다. 이 사이클은 간접주소방식(이후 내용에서 설명한다)에서 사용된다.

간접 사이클에서는 IR의 오퍼랜드를 MAR로 내보내고 메모리의 해당 주소에서 데이터가 저장된 곳의 주소를 가져와 MBR에 저장한다. 여기까지는 일반적인 실행 사이클과 같다. 그런데 여기사 MBR의 값을 IR의 오퍼랜드와 바꿔치기하는 과정이 한번 더 일어난다. 이제야 IR에 있는 명령어의 오퍼랜드가 데이터의 실제 주소를 가지게 되어 데이터를 가져올 수 있게된 것이다.

이를 마이크로 연산으로 나타내면 아래 그림과 같다.

indirect_cycle




## 3. 명령어 파이프라이닝 ##

명령어 파이프라닝(instruction pipelining)은 명령어 실행에 사용되는 하드웨어를 여러 단계(stage)로 분할하고 각각이 동시에 서로 다른 명령어들을 처리하도록 하여 속도를 향상시키는 기술이다. 일반적으로 분할 단계가 많아질수록 처리 속도가 높아진다.


### 3-1. 2-단계 명령어 파이프라인 ###

인출 사이클과 실행 사이클을 처리하는 하드웨어를 독립적인 모듈로 분리하여 두 사이클을 동시에 처리하는 기술이다. 인출된 명령어가 실행되는 동시에 다음 명령어를 인출하는 것이다. 그림으로 나타내면 아래와 같다.

2-stage-pipeline

이처럼 명령이 실행되는 동안 다음 명령어를 미리 인출하는 것을 명령어 선인출(instruction prefetch)라고 부른다.

각 클럭 주기마다 인출된 명령어의 실행과 다음 명령어의 인출이 동시에 일어나기때문에 2번째 명령어부터는 한 클럭 주기만에 완료되는 효과를 얻을 수 있다. 이러한 단계가 반복되면 전체 프로그램 실행 시간은 절반으로 수렴할 것이다.

하지만 위 그림에서는 인출 단계과 실행 단계의 실행이 각각 한 클럭 주기에 일어난다는 것을 가정한 것이다. 실제로는 각 사이클은 여러 마이크로 연산으로 분리될 수 있고 실제로는 각각의 마이크로 연산의 실행에 한 클럭 주기가 소요된다. 또한 일반적으로 인출단계보다는 실행단계가 더 많은 마이크로 연산으로 이루어져있고 따라서 더 많은 클럭주기가 소요된다. 결과적으로 명령어 인출이 끝나더라도 다음 명령어 선인출을 위해서 현재 진행중인 실행 사이클이 종료될 때 까지 기다려야 하므로 시간 낭비가 발생한다. 이러한 이유때문에 2단계 명령어 파이프라이닝은 실제 프로그램 처리 속도를 2배까지 올리지 못한다.


### 3-2. 4단계 명령어 파이프라인 ###

1) 4단계 파이프라이닝의 동작 방식

2단계 파이프라인의 한계를 극복하기 위해 아래 그림과 같이 처리 단계를 더 세부적인 4단계로 나누는 방법이 4단계 명령어 파이프라이닝이다.

a)명령어 인출(IF)
b)명령어 해독(ID)
c)오퍼랜드 인출(OF)
d)실행(EX)

편의상 각 단계가 같은 시간을 소요한다는 가정하면(여기서는 1 클럭 주기) 아래 그림과 같은 명령어 사이클의 시간 흐름도를 생각할 수 있다.

4-stage-pipeline

위 그림에 따르면 1개의 명령어 사이클이 수행되는데 4 클럭주기가 소요된다.

10개 명령어의 경우 : 파이프라이닝이 없다면 40클럭주기, 4단계 파이프라이닝에서 13클럭주기가 소요된다. → 약 3.08배 속도향상

100개 명령어의 경우 : 파이프라이닝이 없다면 400 클럭주기, 4단계 파이프라이닝에서 103 클럭주기가 소요된다. → 약 3.89배 속도향상

이런 식으로 계산했을 때 명령어가 100만개인 경우는 약 3.99999배가 향상된다. 명령어가 많아질수록 4배의 속도향상에 수렴한다.

이것을 일반화 시켜서 파이프라이닝 단계 수를 k(즉, 하나의 명령어 사이클이 수행되는데 걸리는 클럭주기)로 하고 처리해야할 명령어의 수가 N개라고 했을때 총 소요되는 클럭주기는 *\k + (N-1)*이 된다.

즉, 첫 번째 명령어만 k주기가 소요되고 이후부터는 1주기마다 1개씩 실행되므로 나머지 N-1개에 대해서는 1주기만 소요되는것과 같다.

2) 4단계 파이프라이닝의 문제점

앞서 설명한 4단계 파이프라이닝은 상황을 매우 단순화시킨 이상적인 경우이다. 그러나 실제 4배의 속도향상이 되지못하게 하는 여러가지 문제점들이 존재한다.

a) 불필요한 시간 소모

앞에서는 모든 명령어가 4개의 파이프라인을 거친다고 가정하였으나 꼭 그렇지는 않다. 오퍼런드 인출단계는 필요한 경우에는 수행되지만 항상 수행되는것은 아니다. 그러나 파이프라인 하드웨어 단순화를 위해 모든 명령어가 항상 4단계를 거치도록 해야하므로 불필요한 시간 소모가 발생한다.

(b) 각 단계가 항상 같은 시간을 소모하지 않는다

파이프라인 클록은 처리시간이 가장 긴 단계를 기준으로 정해진다. 일반적으로는 EX가 가장 긴 시간을 소모한다. 그러나 IF, ID, OF 단계의 결과는 EX가 완료될때까지 기다렸다가 보내져야하므로 불필요한 시간소모가 발생한다.

(c) 기억장치 충돌

실제 메모리에는 한 번에 하나의 모듈만 접근할 수 있다. 그러나 IF와 OF 단계에서는 모두 인출을 위한 메모리 접근이 필요하다. 따라서 둘 중 하나는 나머지 하나의 처리를 기다리느라 지연될 수 밖에 없다.

(d) 조건 분기 명령어

EX단계의 처리 결과에 따라 선인출되어 처리되고있던 명령어들이 무효화되어 제거될 수가 있다. 예를들어 EX 처리 중 조건문이 만족되어 특정 명령어로의 분기해야하는 경우 앞 단계들이 이미 처리된 명령어들이 필요가 없어지게 된다.

4_stage_pipeline_with_branch

(b)를 해결하기 위해 명령어 사이클을 더욱 세부적으로 분기하는 슈퍼파이프라이닝(superpipelining) 기술을 사용한다. 최근에는 대부분의 프로세서들이 10단계 이상의 파이프라인 구조를 사용한다.

(c)기억장치 충돌을 해결하기 위해 명령어 캐시데이터 캐시로 분리하여 IF와 OF가 동시에 처리될 수 있는 방법을 사용한다.

(d)의 해결을 위해서도 여러 방법이 존재한다. 최근 분기 결과를 저장해두는 분기역사표(branch history table)을 사용하여 어떤 명령어를 선인출할지를 확률적으로 계산하는 모델인 분기 예측이 가장 널리 사용된다. 또한 추가적인 버퍼를 두어 다음 명령어와 분기 목적지의 명령어를 모두 인출하는 방법인 분기 목적지 선인출도 있다. 이외에도 루프버퍼, 지연분기라는 방법도 존재한다.


### 3-3. 슈퍼스칼라 ###

슈퍼스칼라(superscalar)는 CPU 내부에 여러개의 파이프라인 하드웨어를 포함시킨 구조이다.

superscalar

그림에서 아래의 2-way 슈퍼스칼라 파이프라인의 명령어 실행 시간 흐름도를 보면 알 수 있듯이 단순히 파이프라인의 갯수가 높아진만큼 한 번에 2개의 명령어를 처리할 수 있도록하여 프로그램 처리 속도를 높이는 것이다.

기본적으로 파이프라인 구조는 4단계 파이프라이닝이다. 여기서 파이프라인 갯수를 m(m-way 슈퍼스칼라)라고 했을 때 전체 명렁어 갯수 N에 대한 처리 시간을 계산해보자.

처음 명령어는 항상 k(위 그림에서는 4) 클럭주기가 소요되고 남은 (N-m)개에 대해서는 한번에 m개씩 처리되므로 (N-m)/m 클럭주기가 소요된다. 따라서 소요되는 전체 시간은 T = k + (N-m)/m이다.

명령어 수가 매우 많아질 경우 m-way 스칼라 파이프라이닝은 1-way 스칼라 파이프라이닝에 비해 처리속도가 m배 빨라질 수 있다.

그러나 여기서도 이론상의 m배를 달성하지 못하는 문제점들이 존재한다. 예를들어 두 명령어 사이에 의존성(한 명령어가 다른 명령어의 결과를 데이터로 사용하는 경우같은)이 있을경우이다. 이 경우에 파이프라인의 일부 단계들이 유휴(idle) 상태가 되어 놀게된다. 따라서 이론상의 m배를 달성하지 못한다.

이러한 한계에도 불구하고 여러 이점들로 최근 개발되는 대부분의 프로세서들은 슈퍼스칼라 파이프라인 구조를 사용하고 있다.


### 3-4. 듀얼코어 및 멀티코어 ###

CPU 코어(core)란 프로세서 내에서 명령어 사이클에 반드시 필요한 핵심 모듈들을 의미한다. 즉, 명령어 파이프라인들로 이루어진 슈퍼스칼라 모듈, ALU, 레지스터 세트 등이 코어다.

멀티코어 프로세서란 코어를 한 프로세스안에 여러개 집어넣은 프로세서이다. 멀티코어를 통해 슈퍼스칼라를 구현하면 동시에 여러개의 명령어를 처리할 수 있다.

멀티코어에서는 각 CPU코어가 내부캐시와 버스 인터페이스만을 공유하며 동시에 여러 프로그램을 처리할 수 있다. 즉, 동시에 여러 명령어 사이클을 수행하는 슈퍼스칼라와 달리 동시에 여러 태스크 프로그램을 처리할 수 있으므로 독립성이 더 높다. 이를 두고 멀티 태스킹(multi-tasking)이라고 부른다.

추가적으로 하나의 코어 내에 상태 레지스터 세트를 여러개 두어 하나의 코어에서 여러 스레드를 실행시키는 것을 멀티 스레딩(multi-threadiing) 기법이라 부르며, 이를통해 CPU의 처리 성능을 향상시킬 수도 있다.




## 4. 명령어 세트 ##

CPU 종류마다 다른 명령어 종류와 수를 가지고 있다. 하나의 CPU를 위해 정의되어있는 명령어들의 집합을 명령어 세트(instruction set)라 한다.

명령어 세트의 설계에는 다음 사항들이 고려되어야한다.

1) 연산 종류

수행할 연산의 수, 종류

2) 데이터 유형

연산이 수행될 데이터 유형. 즉, 데이터 길이나 표현방식(정수, 부동소수점) 등

3) 명령어 형식

명령어의 길이, 오퍼랜드의 필드 개수와 길이 등

4) 주소지정 방식

오퍼랜드의 주소를 지정하는 방식


### 4-1. 연산의 종류 ###

CPU가 수행할 수 있는 '일'을 의미한다. 아래가 가장 기본적인 것들이다.

a) 데이터 전송

레지스터와 메모리 사이에서 데이터를 전달하는 일

b) 산술 연산

정수와 실수같은 데이터의 사칙연산

b) 논리 연산

AND, OR, NOR 등의 논리연산

d) 입출력

CPU와 외부장치들 사이의 데이터 교환

e) 프로그램 제어

분기나 서브루틴 호출과 같은 명령의 실행 순서를 결정하고 변경하는 연산이다. 원래 실행하던 프로그램을 메인 프로그램이라하고 그 흐름을 메인 루틴이라 한다면 서브루틴은 메인 프로그램에서 사이사이에 존재하는 보조적인 실행 흐름이다. 프로그래밍 언어상에서 main()함수의 본체(메인 루틴)가 실행되다가 함수를 만나면 함수의 몸통에 해당하는 명령어가 실행되고 이것이 기계에서의 서브루틴 호출이다.

서브루틴의 실행을 위해서는 두 가지 연산이 필요한데, CALL과 RET이다.

CALL 명령어가 실행되면 잠시 메인 프로그램이 중지되고 서브루틴으로 이동한다. 서브루틴의 마지막에 RET 명령어를 만나면 자신을 호출한 메인 프로그램의 위치로 복귀한다.

subroutine

위 그림에서 메인 루틴이 실행되다가 SUB1이라는 루틴이 호출되어 해당 루틴으로 이동한다. SUB1이 실행되는 도중에 SUB2가 두 번 호출된다. 이후 RET 명령어를 만나면 자신을 호출한 상위 루틴으로 복귀한다.

서브루틴이 시작되기 전 현재 위치를 메모리의 스택에 저장해두는데, 이유는 서브루틴의 실행이 종료되고 원래의 위치로 복귀하기 위함이다.

서브루틴의 실행 순서를 마이크로 연산으로 나타내면 아래와 같다.

subroutine_micro_operation

PC의 내용과 SP의 내용이 각각 MBR과 MAR을 타고 메모리로 나가서 SP가 가르키는 스택 주소에 PC의 내용이 저장된다. 이와 더불어 서브루틴 X의 주소가 PC에 업로드되고 스택포인터의 값이 1 감소한다.


서브루틴이 종료될 때의 실행순서를 마이크로 연산으로 나타내면 아래와 같다.

서브루틴이 종료될 때는 기존 프로그램의 실행을 위해 스택에 저장해두었던 PC의 값을 가져와야한다. SP의 값을 1 증가시켜 PC내용이 저장된곳을 가르키게 한 후 이 주소를 MAR로 내보내면 메모리의 해당 주소에서 내요을 꺼내어 PC로 전달한다. 이제 PC에서 다음 명령어의 주소를 인출하면 원래의 프로그램 명령어가 인출된다.


### 4-2. 명령어 형식 ###

명령어는 CPU가 무엇을 해야할지 충분히 알 수 있도록 필요한 모든 정보를 제공해야 한다. 명령어는 아래와 같은 필드들로 구성된다.

a) 연산코드

LOAD, ADD, CALL과 같은 연산의 종류를 지정한다.

b) 오퍼랜드

연산에 필요한 데이터 혹은 데이터의 주소이다. 각 연산은 한개 혹은 두개의 입력 오퍼랜드와 한개의 출력 오퍼랜드를 가질 수 있다.

c) 다음 명령어 주소

분기나 서브루틴 호출과 같이 실행 순서를 변경하는 경우에 필요한 명령어의 주소이다. 순차적인 프로그램 실행시에는 필요하지 않다.


명령어 형식은 위에서 설명한 필드의 수와 배치방식, 각 필드에 할당할 비트수에 의해 결정된다.

아래 그림은 워드가 2바이트(16비트)인 경우의 명령어 형식 예시이다.

instruction_format_field

오퍼랜드는 데이터, 메모리 주소 및 레지스터 번호를 저장하는 용도로 사용할 수 있다.

위 그림에서는 연산코드가 4bit인만큼 사용할 수 있는 연산의 종류는 2^4 = 16가지이다.

예를들어 오퍼랜드1은 레지스터 번호를, 오퍼랜드2는 메모리 주소를 지정하는 용도일 수 있다. 결과적으로 레지스터 번호는 4비트를 사용하므로 16가지를, 메모리 주소는 256개(0번지~255번지)를 지정할 수 있다. 또다른 예로

오퍼랜드가 1개만 필요하다면 12비트를 모두 사용할 수 있다. 이 때 오퍼랜드가 부호가 없는 정수 데이터의 저장 용도로 쓰인다면 0~4095의 범위를 표현할 수 있고 메모리 주소 저장 용도로 쓰인다면 4096개(0번지~4095번지)의 주소를 지정할 수 있다.

※ 명령어 형식의 설계

오퍼랜드 수를 기준으로 명령어의 종류를 아래와같이 나눌 수 있다.

a) 1-주소 명령어

오퍼랜드가 1개인 명령어 형식

LOAD X : AC ← M[X]

위 어셈블리 코드는 메모리의 X번지에서 데이터를 가져와 AC에 적재하기위한 것이다. 이 경우 메모리의 주소만 필요하므로 1-주소 명령어 형식으로도 가능하다.

b) 2-주소 명령어

오퍼랜드가 2개인 명령어 형식

ADD R1,R2 : R1 ← R1 + R2

위 어셈블리 코드는 2-주소 명령어형식으로 표현한 것이다. 레지스터 R1의 값과 R2의 값을 더해서 R1에 저장하라는 명령이다. 1-주소 명령어형식을 사용해서 같은 일을 하려면 여러번의 연산이 필요하고 따라서 연산코드가 한줄보다 길어지게 된다.

c) 3-주소 명령어

오퍼랜드가 3개인 명령어 형식

MUL X,R1,R2 : M[X] ← R1 × R2

3-주소 명령어 형식을 사용하면 R1과 R2를 더해서 메모리의 X번지에 저장하는 코드를 위와같이 한 줄로 작성할 수 있다. 만약 2주소 형식을 사용한다면 아래의 과정들이 필요하다.

MUL R1,R2 : R1 ← R1 × R2

STOR X,R1 : M[X] ← R1

코드가 두 줄로 늘어났다.


위 예시들에서 보았듯이 명령어 형식은 프로그램 작성 코드 길이에 영향을 미친다. 그러나 명령어 해독 과정이 복잡하다는 단점도 존재한다.


### 4-3. 주소지정 방식 ###

명렁어의 비트 수는 일반적으로 CPU가 한 번에 처리할 수 있는 데이터의 길이, 즉 CPU의 워드(word)와 같다. 제한된 명령어 길이에서 우리는 제한된 오퍼랜드 길이를 사용할 수 밖에없다. 이에따라 지정할 수 있는 메모리 주소의 수에는 한계가 있고 더 큰 용량의 메모리사용을 위해 여러가지 주소지정 방식이 생겨났다.

주소지정 방식은 매우 다양하고 CPU에따라 다르지만 기본적인 방식들을 살펴본다.

주소지정 방식에대해 보기전에 기본적인 용어를 정하면 아래와 같다.

  • EA : 유효주소(Effective Address), 데이터가 저장된 실제 주소
  • A : 명령어 내의 주소필드 내용(오퍼랜드가 메모리 주소인 경우)
  • R : 명령어 내의 레지스터 번호(오퍼랜드가 레지스터 번호인 경우)
  • (A) : 메모리 A번지의 내용
  • (R) : 레지스터 R의 내용

주소지정 방식의 목적은 명령어 실행과정에서 유효주소인 EA를 결정하는 것이다. 이 유효주소 EA가 최종적으로 데이터에 접근하기 위한 주소로 사용된다.



1) 직접 주소지정 방식

오퍼랜드의 내용을 그대로 유효주소 EA로 사용한다. 즉, EA=A 이다.

direct_addressing_mode

직접 주소지정 방식에서는 유효 주소 EA를 결정하기 위한 다른 절차나 계산이 없다는 장점이 있다. 그러나 연산코드를 제외한 부분의 비트수에 따라 지정할 수 있는 주소의 수가 제한된다는 단점이 있다.



2) 간접 주소지정 방식

명렁어의 오퍼랜드 필드가 가리키는 주소에 실제 데이터가 저장된 주소를 지정하는 방식이다. 즉, EA = (A) 이다. 다시말해, 오퍼랜드의 내용이 A이고, 메모리의 A번지에 저장된 주소를 통해 실제 데이터가 있는 주소로 한번 더 찾아가는것이다.

indirect_addressing_mode

오퍼랜드의 길이로 10비트만 사용했던 경우에 2^10 = 1024개의 주소만 사용할 수 있었다면 간접 주소지정방식에서는 메모리의 단어길이만큼의 주소를 사용할 수 있다. 만약 메모리의 단어 길이가 16비트라면 2^16 = 65,536개의 주소를 지정할 수 있다.

직접주소지정방식과 간접주소지정방식을 구분하기 위해 명령어에는 아래 그림과 같이 '간접비트' I가 포함되어야한다.

indirect_bit

예를들면 I가 0이면 직접주소지정방식이고 1이면 간접주소지정 방식이라고 CPU에게 알려주는 것이다.

간접주소지정방식의 단점은 두 번의 메모리 액세스가 필요하다는 것이다.

또한 간접 주소지정이 여러번 중복되어 사용되는 경우도 있다. 이 경우 메모리 액세스가 여러번 필요하다는 단점이 존재한다.



3) 묵시적 주소지정 방식

묵시적
관형사·명사
직접적으로 말이나 행동으로 드러내지 않고 은연중에 뜻을 나타내 보이는. 또는 그런 것.
(네이버 국어사전 - https://ko.dict.naver.com/)

묵시적 주소지정은 오퍼랜드가 아니더라도 이미 사용할 데이터가 있는곳의 위치를 안다는 것이다. 예를들어 'PUSH R1'과 같이 레지스터 R1의 내용을 스택에 저장하라는 명령어의 실행을 위해서는 스택 포인터 SP가 사용되어야한다. 이와같이 사용할 주소를 묵시적으로 알 수 있는 경우 해당 주소가 유효주소로 사용된다.



4)즉시 주소지정 방식

즉시 주소지정방식을 사용하는 명령어는 연산에 필요한 데이터가 오퍼랜드에 포함되어 있다. 이 경우 아무런 데이터 인출을 위한 주소가 필요하지 않다.

immediate_addressing_mode

주의해야할 점은 데이터의 표현 범위가 오퍼랜드의 길이에 의해 제한된다는 점이다.



5) 레지스터 주소지정 방식

레지스터 주소지정 방식에서는 오퍼랜드 필드의 내용이 레지스터 번호이다. 즉, EA = R이 된다.

register_addressing_mode

오퍼랜드로 사용할 수 있는 비트 길이가 k라면 2^k개의 레지스터 번호를 지정할 수 있다.

이 방식을 사용하면 CPU가 메모리보다 레지스터로 접근하는 시간이 훨씬 짧기 때문에 더 빨리 명령을 실행할 수 있다. 그러나 데이터 저장이 레지스터로 제한된다는 단점 또한 존재한다.



6) 레지스터 간접 주소지정 방식

오퍼랜드 필드가 가리키는 레지스터가 유효주소를 가지고 있다. 즉, EA = (R)이다. 레지스터에서 유효주소를 인출하고 메모리의 해당 주소로 이동하여 데이터를 인출한다.

register_indirect_addressing_mode



7) 변위 주소지정 방식

'직접 주소지정 방식'과 '레지스터 간접 주소지정 방식'을 결합하여 만든 주소지정 방식이다. 여기서 오퍼랜드 필드는 2개를 사용하는데, 하나는 변위를 나타내는 주소 A이고 다른 하나는 레지스터 번호 R이다. 레지스터의 내용 (R)을 A에 더함으로써 유효주소 EA가 결정된다. 즉, EA = A + (R) 이다.

변위 주소지정 방식에서 사용하는 레지스터 R의 종류에 따라 여러가지 변위 주소지정 방식들이 정의되는데, 대표적으로는 3가지가 있다.



a) 상대 주소지정 방식

레지스터로 프로그램 카운터 PC가 사용된다. 유효주소 EA = A + (PC)이다.

이 방식은 주로 분기에 사용된다. PC의 내용은 다음에 인출될 명령어의 주소이다. 즉, 유효주소 EA는 다음에 실행할 명령어 주소(PC의 값)를 기준으로 A만큼 움직인 값이다. A가 음수면 이전으로, 양수면 이후로 분기한다.

아래 그림은 상대 주소지정 방식이 분기에 사용된 경우이다.

relative_addressing_mode

PC의 내용과 A가 더해진 결과가 PC에 다시 적재된다. 따라서 이후 명령어에서 해당 주소로 분기하게 된다.

일반적인 경우 분기를 위해 전체 분기목적지 주소가 필요하지만, 상대 주소지정 방식을 사용하면 PC의 내용을 기준으로 하는 변위만 알면 되기에 더 적은수의 비트만 필요하다는 장점이 있다. 또한 분기의 경우에는 PC의 내용을 사용하므로 레지스터 번호 R을 지정해줄 필요가 없어 A필드와 R필드가 모두 주소필드로 사용될 수 있다.



b) 인덱스 주소지정 방식

레지스터로 인덱스 레지스터 IX가 사용된다. 유효주소 EA = A + (IX)이다.

이 방식은 주로 배열에 접근하는 용도로 사용된다. 따라서 A는 보통 배열의 시작주소이다. 이렇게 되면 유효주소는 A에서 시작되는 배열의 (IX)번째 내용이 된다. 가령 (IX)가 0이라면 A에 있는 배열의 0번째 원소의 주소를 나타낸다.

index_addressing_mode

명령어 사이클동안 인덱스 레지스터의 내용이 자동적으로 증가,감소되도록 하는 자동 인덱싱(auto-indexing) 방식을 사용하면 배열의 데이터들에 순차적으로 연속 접근할 수도 있다.


4-4. 상용 프로세서들의 명령어 형식

+ Recent posts