프로세서

Bor·2021년 10월 11일
0

컴퓨터구조

목록 보기
4/15

4.1 서론

앞서 1장에서 컴퓨터의 성는은 세 가지 주요 요인, 명령어의 개수, 클럭 사이클 시간, 명령어당 클럭 사이클 수 (CPI)에 의해 결정된다는 것을 알았다. 2장에서 살펴본 것처럼 컴파일러와 명령어 집합 구조가 프로그램에 필요한 명령어 개수를 결정한다. 그러나 클럭 사이클 시간과 명령어당 클럭 사이클 수는 프로세서의 구현 방법에 따라 결정된다. 이 장에서는 MIPS 명령어 집합을 두 가지 다른 방법으로 구현하여 데이터 패스와 제어 유닛을 완성시킨다.


기본적인 MIPS 구현

핵심적인 MIPS 명령어 집합의 부분집합을 구현할 것인데, 그 부분집합은 다음과 같다.

  • 메모리 참조 명령어인 워드 적재(lw)와 워드 저장(sw)
  • 산술/논리 명령어인 add, sub, AND, OR, slt
  • 같을 시 분기 명령어인 beq와 점프 명령어 j

이 부분집합은 정수형 명령어를 모두 포함하지 않으며, 부동소수점 명령어는 하나도 포함하지 않는다.


구현에 대한 개요

⚡ 2장에서 정수형 산술/논리 명령어, 메모리 참조 명령어, 분기 명령어를 포함하는 핵심적인 MIPS 명령어를 살펴보았다. 이러한 명령어 구현에 필요한 일의 상당히 많은 부분은 명령어 종류 상관 없이 동일하다. 어떤 명령어든지 처음 두 단계는 다음과 같이 모두 동일히다.

  1. 프로그램 카운터(PC)를 프로그램이 저장되어 있는 메모리에 보내어 메모리로부터 명령어를 가져온다.

  2. 읽을 🧱레지스터🧱를 선택하는 명령어 필드를 사용하여 하나 또는 두 개의 레지스터를 읽는다. 워드 적재 명령어는 레지스터 하나만 읽으면 되지만 대부분의 다른 명령어는 레지스터 두 개를 읽는다.

이 두 단계 이후에 명령어 실행을 끝내기 위해 필요한 행동들은 명령어 종류에 따라 달라진다. 단순함과 규칙적인 특성이 많은 종류의 명령어 실행을 비슷하게 만들어 줌으로써 구현을 단순화한다.

🌈 예를 들면 점프 명령어를 제외한 모든 명령어 종류가 🧱레지스터🧱를 읽은 후에는 ALU를 사용한다. 메모리 참조 명령어는 주소 계산을 위해, 분기 명령어는 비교를 위해 사용한다.


[MIPS 부분집합 구현의 추상적 개관. 주요 기능 유닛과 그들 상이의 연결관계를 보여준다.]

모든 명령어의 실행은 PC를 사용하여 명령어 메모리에 명령어 주소를 보내는 것으로 시작된다. 명령어를 가져온 후에는 명령어의 필드를 보면 명령어가 사용하는 레지스터 피연산자를 알 수 있다.
레지스터 피연산자를 읽어오면,

  • 메모리 주소를 계산하기 위해서 (적재나 저장 명령어의 경우)
  • 산술 연산 결과를 구하기 위하여(정수 산술/논리 명령어의 경우)
  • 비교하기 위하여(분기 명령어의 경우)
    피연산자들을 연산할 수 있다.

산술/논리 명령어이면 ALU 결과를 레지스터에 쓴다. 적재나 저장 명령어이면 ALU 결과를 메모리 주소로 사용하기 위해서 레지스터 값을 저장하거나 레지스터에 적재할 값을 읽어온다. ALU나 메모리에서 나온 결관느 레지스터 파일에 넣는다. 🏊‍♂분기 명령어의 경우는 ALU를 출력을 사용해서 다음 명령어 주소를 결정하게 되는데, 이 주소는 오른쪽 덧셈기(PC와 분기 변위가 전해진 값)에서 나오거나 왼쪽 덧셈기(현재 PC에 4를 더한 값)에서 나온다. 그림에서 기능 유닛들을 연결하는 선은 버스🚌를 표시하는데, 버스는 여려 개의 신호로 이뤄진다. 화살표는 정보가 흐르는 방향을 표시.

🔥 위 그림은 MIPS 구현을 상위 수준에서 본 그림으로서, 여러 기능 유닛과 그들 사이의 연결에 초점을 맞추고 있다. 프로세서 내의 데이터 흐름을 거의 다 보여주고 있지만 중요한 두 가지 정도가 빠졌다.🔥

✔️ 그림 4.1에는 서로 다른 근원지에서 나온 데이터가 같은 유닛으로 가는 경우가 몇 군데 있다. 예글 들면 PC에 들어갈 값은 두 덧셈기 중 하나에서 나오고 레지스터 파일에 쓰일 데이터는 ALU나 데이터 메모리에서 나오며, ALU의 두 번째 입력은 레지스터나 명령어의 수치 필드에서 나온다.

[🔄멀티 플렉서(Multiplexor)🔄]
⭕ 실제로는 이들 데이터 선을 단순히 그냥 연결할 수 없다. 그러므로 다수의 근원지 중에서 하나를 선택하여 그것만을 목적지로 보내는 구성 요소를 추가해야 한다. 이 같은 선택은 멀티 플렉서(Multiplexor)라 불리는 소자를 사용하여 이뤄진다.

✔️ 어떤 유닛들은 명령어 종류에 따라 다르게 제어되어야 하는데 이 부분이 빠져있다. 예를 들어 데이터 메로리는 적재 명령어일 때는 읽기 / 저장 명령어 일 때는 쓰기를 해야 한다. 레지스터 파일은 적재 명령, 산술/논리 연산 명령어 일 때만 쓰기를 해야 한다. 멀티 플렉서처럼 명령어의 여러 필드 값에 따라 정해지는 제어선에 의해 통제된다.

아래의 그림은 앞선 그림의 데이터패스에 주요 기능 유닛을 위한 제어선과 필요한 멀티플렉서 세 개를 추가한 그림이다. 📣제어 유닛은 기능 유닛들과 두 멀티 플렉서의 제어선 값을 경정하는 데 사용하는 것으로, 명령어를 입력으로 한다.

세 번째 멀티플렉서는 PC+4와 분기 목적지 주소 중 어느 것을 PC에 써야 할지 결정하는 것인데, ALU의 zero 출력으로 제어된다. 이 출력은 beq 명령어에서 비교할 때에 사용된다. MIPS 명령어 집합의 규칙성과 단순성은 간단한 디코딩 과정만으로 제어선의 값을 결정할 수 있게 하였다.


4.2 논리 설계 관례

컴퓨터의 설계에 대해서 논의하기 위해서는 컴퓨터를 구현하고 있는 논리회로가 어떻게 동작하고 어떻게 클러킹이 되는지 결정해야 한다.

👻[조합소자]👻

MIPS 구현에 쓰이는 데이터 패스 요손ㄴ 두 가지 종류의 논리 소자들로 구성된다. 데이터 값에만 동작하는 소자와 상태를 포함하는 소자가 그것이다. 데이터 값에만 동작하는 소자는 모두 조합소자(Combinational element)인데 그 의미는 그들의 출력이 현재 입력에만 의존한다는 것이다. 조합소자는 같은 입력이 주어지면 항상 같은 출력을 낸다. 앞서 살펴봤던 ALU가 조합소자이다. ALU는 내부 기억소자가 없어서 주어진 입력에 대해 항상 같은 출력을 낸다.

🔌[상태소자]🔌

설계에 쓰이는 또 다른 종류의 소자들은 조합소자가 아니고 대신 상태(State)를 갖는다. 소자에 내부 기억 장소가 있으면 그 상태를 갖게 된다. 이러한 소자를 상태 소자라고 부른다. 그 이유는 컴퓨터의 플러그를 뱃다해도 플러그를 빼기 전에 소자가 가지고 있던 값들로 상태소자를 적재하면 다시 시작시킬 수 있기 때문이다. (명령어 메모리, 데이터 메모리, 레지스터가 이에 대한 예)

상태소자는 적어도 2개의 입력과 1개의 출력을 갖는다. 🎯꼭 있어야 되는 입력은 기록할 데이터와 클럭.🎯 클럭 입력은 데이터 값이 소자에 기록되는 시점을 결정한다. 상태소자의 출력은 이전 클럭 사이클에 기록된 값이다. 논리적으로 가장 간단한 상태소자 중 하나는 D형 플리플롭.

이 D형 플리플롭에는 두 개의 입력(데이터 값, 클럭)과 하나의 출력이 있다. MIPS 구현에는 플립플롭 말고도 두 가지 상태소자가 더 사용된다. 🗸메모리와 🗸레지스터가 그것이다. 상태소자에 언제 쓸 것인가는 클럭이 정하지만, 상태소자의 값을 읽는 것은 언제라도 가능하다.

↩️ [순차회로] ↩️

상태를 포함하는 논리소자들은 순차회로(sequential circuit)라 부르는데 이는 이들의 출력이 입력뿐만 아니라 내부 상태에도 의존하기 때문이다. 예를 들 레지스터 파일의 출력은 입력되는 레지스터 번호와 전에 레지스터에 기록된 값 모두에 영향을 받는다.


🖼️ 클러킹 방법론 🖼️

📣클러킹 방법론은 신호를 언제 읽고 쓸 수 있는지를 정의한다. 읽기와 쓰기의 타이밍을 명시한다. 이는 매우 중요한데 신호를 읽고 있는 와중에 새로운 값을 쓰면 옛 값과 새 값이 혼동될 수 있기 때문이다.

단순화를 위해 📣에지 구동 클러킹(edge-triggered clocking) 방법론을 가정한다. 이는 순차논리소자에 저장된 값은 클럭 에지에서만 바꿀 수 있다는 것을 의미한다.


위의 그림처럼 낮은 값에서 높은 값 혹은 그 반대로의 빠른 변이를 말한다. 🔌상태소자들만이 데이터 값을 저장할 수 있기 때문에 모든 조합회로는 상태소자에서 입력을 받고 상태소자로 출력을 내보낸다.🔌 입력은 이전 클럭 사이클에서 쓴 값이고 출력은 다음 클럭 사이클에서 사용할 수 있는 값이다.

그림에서 일단의 조합회로를 둘러싸고 있는 두 개의 상태소자를 보여주고 있다. 이 회로는 하나의 클럭 사이클에 동작한다. 즉 모든 신호가 상태소자 1에서 나와서 조합회로를 거쳐 상태소자 2까지 전달되는데 하나의 클럭 사이클이 걸린다. 신호들이 상태소자 2에 도착하는데 필요한 시간이 ⌚ 클럭 사이클의 길이를 정의하게 된다.

매 클럭 에지마다 상태소자에 쓰기가 행해지는 경우는 앞으로 쓰기 제어신호(control siganl)을 표시하지 않겠다. 반대로 상대 소자가 매 클럭마다 갱신되는 것이 아니라면 ⭐ 쓰기 제어신호가 분명하게 표시되어야 한다. 클럭 신호와 쓰기 제어신호는 상태 소자의 입력이며, 쓰기 제어신호가 인가되고 활성화 클럭 에지일 때만 상태소자가 변하게 된다. ⭐

제어신호: 멀티플렉서의 입력을 선택하거나 기능 유닛의 연산을 지시하기 위해 사용되는 신호. 기능 유닛에 으해 연산되는 정보를 가지고 있는 데이터 신호에 대비된다.

인가🔺 vs 비인가🔻

(너는 왜 내가 아니고 나인가,,,)
법적인 인가, 비인가가 아니다. 🔺'인가된(asserted)'🔺는 논리적으로 높은 신호를 표시하며 신호를 높은 값으로 만든다는 뜻이다. 반대로 '🔻비인가(deassert)🔻'는 신호가 논리적으로 낮은 값을 표시한다. 이렇게 용어를 구분하는 이유는 때때로 1이 논리적으로 높은 값을 나타내기도 하며, 때때로 낮은 값을 나타내기도 하기 때문이다.

위 그림처럼 에지 구동 방법론은 레지스터 내용을 읽고 그 값을 조합회로로 보내고 같은 레지스터에 쓴느 작업 모두가 한 클럭 사이클에 일어나는 것을 허용한다. 쓰기/읽기를 상향/하향 크럭에지에서 일어난다 가정해도 상관 없다. 왜냐하면 조합회로에 대한 입력은 선택된 클럭 에지에서만 변하기 때문이다.


4.3 데이터패스 만들기 🗺️

데이터패스 설계를 시작하는 적당한 방법은 MIPS명령어 종류를 각각 실행하는 데 필요한 주요 구성 요소들을 살펴보는 것이다. 각 명령어들이 어떤 데이터패스 구성요소들을 필요로 하는지 살펴보는 것으로 시작하자. 그 뒤 추상화 단계를 거쳐 깊이 들어가 보자. 🗺️

상태소자는 명령어 메모리와 프로그램 카운터이다. 이 데이터베이스는 명령어를 쓸 필요가 없기 때문에 명령어 메모리는 읽기 접근만 사용하면 된다. 명령어 메모리는 읽기 전용이므로 조합회로로 취급한다.

출력은 항상 입력 주소가 지정한 위치의 내용을 나타내며 읽기 제어신호가 필요하지 않다. 프로그램 카운터는 매 클럭 사이클 끝에 쓰기가 행해지는 32비트 레지스터이다. 따라서 쓰기 제어신호는 필요하지 않다. 덧셈기는 두 32비트 입력을 더해서 합을 출력으로 내보내는 일만하도록 되어 있는 ALU이다.

위의 그림은 우리가 필요로 하는 첫 번째 구성 요소를 보여주고 있다.

  • 🚚 Instruction memory 🚚: 프로그램의 명령어를 저장하고 주소가 주어지면 해당 명령어를 보내주는 메모리 유닛이다.
  • Program Counter : PC는 이제 외울 때도 됐는데~ 현재 명령어의 주소를 가지고 있는 레지스터이다.
  • ➕ Adder ➕: 끝으로 PC를 다음 명령어 주소로 증가시키는 덧셈이 필요. 이 덧셈기는 조합회로이며 ALU를 가지고 쉽게 만들 수 있다. 항상 덧셈을 하도록 제어선을 연결하기만 하면 된다.

그렇게 위 그림은 명령어를 인출하고 PC를 증가시켜 다음 명령어의 주소를 구하는 데이터 패스. 이제 형식 명령어들과 연관지어서 생각해보자

우선 R형식 명령어들을 생각해보자 R형식 명령어들은 두 개의 레지스터를 읽고 레지스터에 내용을 ALU 연산을 수행하며 그 결과를 레지스터에 쓴다. 이러한 명령어들을 R 형식 명령어(산술/논리 명령어)라 부른다. 왜냐하면 이 명령어들은 산술연산이나 논리연산을 행하기 때문이다. (add, sub, AND, OR, slt)

프로세서의 범용레지스터 32개는 레지스터 파일(register file)이라고 하는 구조 속에 들어 있다. 레지스터 파일은 레지스터들을 모아 놓은 것으로 파일 내의 레지스터 번호를 지정하면 어느 레지스터라도 읽고 쓸 수 있다. 레지스터 파일은 컴퓨터의 레지스터 상태를 갖고 있다. 레지스터에서 읽어 들은 값들을 연산하려면 ALU가 필요하다.

R 형식 명령어 들은 레지스터 피연산자 세 개를 가지고 있기 때문에, 매 명령어마다 레지스터 파일에서 두 데이터 워드를 읽고 데이터 워드 하나에 써야 한다. 레지스터에서 데이터 워드를 읽기 위해서는 입력이 두 개 필요.

  • 한 입력은 레지스터 번호를 지정하고
  • 다른 입력은 레지스터에 쓸 데이터 값을 제공한다.

그러나 쓰기는 쓰기 제어신호에 의해 제어되므로 클럭 에지에서 쓰기가 일어나려면 이 제어 신호가 인가되어야 한다. 따라서 전체적으로 네 개의 입력(레지스터 번호용 3개, 데이터 용 1개)과 두 개의 출력(모두 데이터용)이 필요하다.

위의 그림에서 이를 보였다. 레지스터 번호 입력은 32개의 레지스터 중 하나를 지정해야 하므로 5비트 크기인 반면(32), 데이터 입력과 데이터 출력 버스는 모두 32비트 폭을 가진다. 위에서 ALU도 볼 수 있다. ALU는 32비트 입력 두 개 받아서 32비트 결과가 0인지 아닌지 나타내는 1비트 신호를 만든다.


console.log('🏃심호흡 크게 하고 계속 가보자!🏃') 

적재 명령어 & 저장 명령어 📁

다음에는 MIPS의 워드 적재 명령어와 워드 저장 명령어를 생각해보자. 이 두가지 명령어는 일반적으로

lw $t1, offsetValue($t2) 또는 sw $t1, offsetValue($t2)

와 같은 형식을 갖는다. 이 명령어들은 베이스 레지스터(여기서는 $t2)와 명령어에 포함되어 있는 16비트 부호 있는 변위 필드를 더하여 메모리 주소를 계산한다. 🧮 저장 명령어이면 저장할 값을 레지스터 파일에 읽어와야 한다. 이 값은 t1에 계심. 적재 명령어이면 메모리로부터 읽어들인 값을 지정된 레지스터 t1에 써야한다. 레지스터 파일과 ALU가 둘 다 필요하다.

그 외에도 명령어 16비트 변위 필드 값을 32비트 부호 있는 값으로 부호확장(sign-extend)하기 위한 유닛이 필요하며 또 읽고 쓸 데이터 메모리가 필요하다. 데이터 메모리는 저장 명령어 일 때만 쓰기를 해야한다. 따라서 데이터 메모리는 읽기와 쓰기 제어신호, 주소입력, 메모리에 쓸 데이터 입력이 필요하다. 아래의 그림은 부호 확장과 유닛과 메모리를 보여주고 있다.

부호확장
데이터의 크기를 증가시키기 위해 원래 데이터 값의 최상위 부호 비트를 폭이 더 큰 목적지 제이터 값의 상위 비트에 복사하는 것

beq명령어는 비교할 레지스터 두 개와 16비트 변위의 세 피연산자를 갖는다. 변위는 분기 명령어 주소에 대한 상대적인 🗽 분기 목적지 주소(branch target address)를 계산하는데 사용된다. 명령어의 형태는 beq t1,t1,t2, offset 이다. 이 명령어를 구현하기 위해서는 PC 값에다 명령어 변위 필드의 부호 확장 값을 더해서 분기 목적지 주소를 계산해야 한다.

분기 명령어의 정의에는 우리가 주의를 기울어야 하는 두 가지 점이 있다.

  • 명령어 집합 구조는 분기 주소 계산의 베이스 주소가 분기 명령어 다음 명령어의 주소라고 서술하고 있다. 명령어 인출 데이터패스에서 PC + 4(다음 명령어의 주소)를 계산하기 때문에 이 값을 분기 목적지 주소 계산의 베이스로 사용하는 것이 편하다
  • 구조는 또한 변위 필드는 2비트만큼 왼쪽 자리이동하여 워드 변위가 있다고 서술한다. 이렇게 함으로써 변위 필드의 유효 범위를 4배만큼 증가시킨다.

분기 목적지 주소를 계산하는 것 외에 실행할 다음 명령어가 뒤에 있는 명령어가 될지 아니면 분기 목적지 주소에 있는 명령어가 될 지 판단해야 한다. 조건이 사실일 때(beq) 분기 목적지 주소가 새로운 PC값이 되며 🙆 분기가 일어났다(branch taken)라고 말한다. 피연산자 값이 같지 않으면 증가된 PC값이 새 PC 값이 된다. (다른 보통 명령어와 같이). 이 경우에는 🙅‍♂분기가 일어나지 않았다(branch not taken)고 말한다.

따라서 분기 데이터패스는 🗽 분기 목적지 주소를 계산하고 레지스터 내용을 비교하는 두 가지 일을 한다 분기를 다루는 데이터패스 부분을 아래 그림에서 보였다. 분기 목적지 주소를 계산하기 위해서 분기 데이터 패스는 ☑️ 부호확장 유닛과 ☑️ 덧셈기를 포함한다. 비교를 수행하기 위해서는 레지스터 피연산자 두 개가 필요하고 이들을 읽어내기 위해서는 레지스터 파일이 필요하다. 이 외에도 비교연산은 ALU를 사용한다.

ALU는 결과가 0인지를 나타내는 출력 신호를 제공하기 때문에, 두 레지스터 피연산자를 제어 신호와 함께 ALU에 보내 ➖ 뺄셈을 하게 한다. ALU의 Zero신호가 인가되면 두 개의 값이 같다는 것을 알 수 있다. Zero 출력은 연산 결과가 0인지를 항상 표기하지만 우리는 분기 명령어의 같은지 여부 테스트에서만 사용하자!

분기 명령어를 위한 데이터 패스는 ALU를 사용하여 분기 조건을 계산하고 별도의 덧셈을 사용하여 분기 목적지 주소를 계산한다. 🗽 분기 목적지 주소는 증가한 PC 값과 명령어의 하위 16비트를 부호 확장하고 ⏪️ 왼쪽으로 2비트 자리이동한⏪️ 값의 합이다.

🥚 Shift left 2라고 표시된 유닛은 부호확장된 변위 필드의 하위에 00(two)를 덧붙이는 단순한 신호의 통로. 자리이동 값이 2비트로 고정돼 실제적인 자리이동 하드웨어는 필요하지 않다. 변위가 16비트를 부호확장한 것이므로 자리이동은 복제된 부호 비트만 두 개 없애 버리게된다. 논리회로는 ALU의 Zero출력을 보고 증가된 PC값과 분기 목적어 주소 중 어느족이 PC에 들어가야 하는지를 결정한다.

[점프 명령어]

⏪️ 점프 명령어는 명령어의 하위 26비트를 2비트만큼 ⏪️ 왼쪽으로 자리이동한 값으로 PC의 하위 28비트를 대체한다. 이 자리 이동은 점프 변위 뒤에 00을 덧붙이면 된다.

0개의 댓글