[Crash Course: Computer Science] #8 명령어와 프로그램

이민선(Jasmine)·2022년 12월 14일
0
post-thumbnail

지난 시간에는 ALU, Control Unit, 메모리, clock을 합쳐서 기본적인 CPU를 만들어보았다.
명령어는 4개만 접해봤다. (LOAD_A, LOAD_B, STORE_A, ADD)

✨깜짝 리뷰
첫번째 명령어가 LOAD_A 14라면? 14번지에 저장된 숫자(예컨대 3)를 가져다가 Register A에 저장.
두번째 명령어가 LOAD_B 15라면? 15번지에 저장된 숫자(예컨대 14)를 가져다가 Register B에 저장.
세번째 명령어가 ADD B A라면? ALU를 사용하여 3과 14를 더해서 (2번째로 적혀있는)Register A에 17 저장.
네번째 명령어가 STORE_A 13이라면? 메모리 13번지에 값 17을 저장해라.

이제 CPU에게 다른 명령어들(instructions)도 주자.

🍩 SUB : 빼기
🍩 JUMP : 프로그램을 새 위치로 점프시킴. 명령어 처리 순서를 바꾸거나, 명령어 몇개를 건너 뛰고 싶을 때 유용함.
ex. JUMP0은 프로그램을 처음으로 되돌아가게 함.
명령어의 뒤 4비트가 가리키는 값을 명령어 주소 레지스터(instruction address register)의 현재 값에 덮어씀.
🍩 JUMP_NEG: ALU의 음수(negative) 플래그가 true로 설정될 때만 점프함.
산술 연산 결과가 음수가 될 때만 음수 플래그가 설정된다. (Crash Course #5 참고.)
산술 연산 결과가 양수일 때는 점프가 일어나지 않고 바로 다음 명령어로 처리함.
🍩 Halt: 컴퓨터가 처리를 언제 끝낼지 알려줌. 지난 시간에는 별도로 배우지 않았지만, Halt 명령어가 없으면 다음 명령을 계속 수행함.

Halt가 없으면 opcode가 0인 명령어가 없어 컴퓨터가 crash됨. (아래 사진에서 만약 Halt가 없고, 명령어가 4번부터 쫙 0인 경우.)

이제 본격적으로 JUMP 명령어를 배워보자.

위의 그림처럼, 주소 0~3까지에 있는 명령어를 수행하고 난 후 4번지 주소에서 JUMP 2를 실행할 차례가 되었다고 하자.
이 명령어 주소 레지스터의 값을 2로 덮어쓴다.
그러면 프로세서는 다음 fetch 사이클에 HALT가 아닌 메모리 2번지에 있는 ADD B A를 가져온다.
그러면 레지스터A에 있는 값은 2고, 레지스터 B에 있는 값은 1이기 때문에, Register A가 1만큼 증가한다.
이걸 13번지에 저장하고 나면 또 JUMP2를 만나서 또 2번지로 돌아간다.
이걸 반복 또 반복하면서 1씩 더하게 되고, Halt명령어로 갈 수가 없어 무한루프(infinite loop)가 되는 것이다.

반복 루프를 빠져나오려면, 조건부 점프(특정 상태가 되어야만 점프)가 필요하다.
JUMP_NEGATIVE 명령어는 조건부 점프의 한 예이다.

이번에는 SUB, JUMP_NEG 명령어를 살펴보자.


레지스터A에는 11을, 레지스터B에는 5를 저장하는 것까지는 앞에서 배운 원리와 동일하다.
그런데 이번에는 2번지 주소에 있는 명령어가 SUB이다. SUB B A.
레지스터 A에서 B를 뺀 값을 레지스터 A에 저장하라는 뜻이다.
이 때 11-5=6>0이므로 음수 플래그는 false이다. 그러므로 5로 JUMP하지 않고 다음 명령어를 실행한다.
다음 명령어는 JUMP2이므로, 주소 2번지로 돌아가면 다시 레지스터A의 값인 6에서 레지스터B의 값인 5를 빼고 1을 레지스터A에 저장한다.
그럼 다시 주소 3번지로 가서 1>0이므로 주소 4번지로 가고, 또 2로 JUMP한다.
이번에는 1-5=-4<0이므로 ALU의 음수플래그가 true가 되어 주소 3번지에서 5로 점프하게된다.(조건부 점프)
이 때 루프를 비로소 빠져나오게 되는 것이다.

이후 5번에서 ADD B A 실행하여 -4 + 5 =1을 레지스터A 에 저장하고,
6번에서 13번지에 1을 저장하고
7번에서 Halt 한다.

이처럼, 이 프로그램은 7개의 명령어로 이루어져 있지만, CPU는 총 13개의 명령어를 실행했다.
내부적으로 루프를 2번 돌았기 때문이다.
이 코드는 11을 5로 나누었을 때 나머지를 계산하는 것과 같다. 결과는 1이다.
(11에서 5를 음수가 될 때까지 반복적으로 빼주는 작업.음수가 되어버리면 다시 5를 더하는 것.)
루프를 2번 돌았기 때문에 5가 11에 2번 들어간다. 즉 몫이 2임을 알 수 있다.

이러한 코드는 메모리의 저장한 수를 바꾸면 어떤 수라도 연산이 가능하다.
소프트웨어는 하드웨어가 할 수 없는 일을 가능케한다.
ALU에는 나누기 기능이 없다. 다만 프로그램이 이 기능을 할 수 있게 만든 것!

우리가 만든 CPU에서 모든 명령어는 8비트이고, opcode는 첫 4비트만 차지했다.
그래서 4비트의 모든 조합을 사용해도 CPU는 최대 16개의 명령어만 지원할 수 있다.(15는 이진수로 1111)
그리고 명령어는 메모리 위치를 나타내는 데에도 하위 4비트만 사용한다. 그래서 최대 16개의 메모리 위치만 가리킬 수 있다.
이는 무언가 일을 하기에는 충분하지 않다. 예를 들어서 17번지로는 JUMP할 수가 없다. 17을 4비트로는 표현 불가능하니까!
그래서 현대 CPU는 2가지 전략을 쓴다.

전략(1) 명령어 길이 늘리기
명령어 길이(instruction length)를 32나 64비트로 늘린다.

전략(2) 가변적인(variable) 명령어 길이
CPU가 JUMP와 같은 명령어를 만나면 점프할 주소를 읽어와야 한다. 그래서 메모리에서 JUMP 뒤에 저장된 값을 즉시 읽어온다. 이 때 이 값을 immediate value라고 한다. (이게 왜 가변적인 명령어 길이라는 거지? 이해가 안가는 부분이다.)
이런 프로세서를 설계하면, 명령어의 길이는 몇 바이트라도 만들 수 있다고 한다. (가변적인 명령어 길이니까 그렇다고 하는데 역시나 이해가 안간다.)
그런데 이렇게 하면 CPU의 fetch cycle이 좀 더 복잡해진다.

CPU의 실제 예제
1971년에 Intel은 4004 프로세서를 출시했다. 이것은 모든 기능을 단일 칩에 넣은 최초의 CPU이자, Intel 프로세서의 기반을 닦은 칩이다.
46개의 명령어를 지원하는데, 컴퓨터가 하는 모든 일들을 만드는 데에 충분하다. 우리가 지금까지 설명한 JUMP, ADD, SUBTRACT, LOAD 같은 명령어가 많이 사용된다. 많은 메모리 주소를 가리킬 수 있도록 JUMP같은 명령어에 8비트 immediate value를 사용한다.

프로세서는 1971년 이후 긴 여정을 거쳐, Intel Core i7과 같은 현대 컴퓨터 프로세서는 수천개의 서로 다른 명령어와 명령어의 변형 형태를 가지고 있다. 길이의 범위도 1바이트~15바이트에 이른다.
예를 들어, ADD의 변형 형태로 10여개 이상의 서로 다른 opcode를 가지고 있다.

이렇게 명령어 집합 크기가 거대하게 커짐으로, 많은 부가 부속품들이 사용되어 프로세서를 설계하는 데에 시간이 오래 걸리게 되었다. 이 부분은 다음 시간에 이야기한다!

profile
기록에 진심인 개발자 🌿

0개의 댓글