Chapter 4. 프로세서 구조 - 2
4.3 순차적(Sequential) Y86-64 구현
SEQ(sequential 프로세스를 의미)라고 하는 프로세서를 설명한다. 매 클럭 사이클에 SEQ는 한 개의 완전한 인스트럭션을 처리하는 데 필요한 모든 단계를 수행한다. 이것은 매우 긴 사이클 시간이 걸리고, 클럭 속도는 늦어진다. SEQ를 개발하는 목적은 효율적인 파이프라인 프로세서를 만들려는 목표의 첫 단계를 보여주기 위해서이다.
4.3.1 작업을 단계로 구성하기
- 선입(Fetch)
- 프로그램 카운터를 메모리주소로 사용해서 메모리로부터 인스트럭션 바이트들을 읽어들인다. 인스트럭션에서 인스트럭션 지시자 바이트의 두 개의 4비트 부분인 icode(인스트럭션 코드)와 ifun(instruction function)을 추출한다. 상수 워드
valC
를 얻어 현재 실행 중인 인스트럭션의 순차적인 다음 인스트럭션의 주소가 되는valP
를 계산한다.valP
는 PC값과 선입된 인스트럭션의 길이를 더한 값이다.
- 프로그램 카운터를 메모리주소로 사용해서 메모리로부터 인스트럭션 바이트들을 읽어들인다. 인스트럭션에서 인스트럭션 지시자 바이트의 두 개의 4비트 부분인 icode(인스트럭션 코드)와 ifun(instruction function)을 추출한다. 상수 워드
- 해독(Decode)
- 레지스터 파일에서 최대 두 개의 오퍼랜드를 읽어서
valA
,valB
를 얻어온다.
- 레지스터 파일에서 최대 두 개의 오퍼랜드를 읽어서
- 실행(Execution)
- 산술/논리 연산 유닛(ALU)이 인스트럭션이 지시하는 연산을 수행하거나, 메모리 참조 시 유효주소를 계산하거나, 스택 포인터 값을 변경한다. 이 결과 값을
valE
라고 부른다.
- 산술/논리 연산 유닛(ALU)이 인스트럭션이 지시하는 연산을 수행하거나, 메모리 참조 시 유효주소를 계산하거나, 스택 포인터 값을 변경한다. 이 결과 값을
- 메모리(Memory)
- 데이터를 메모리에 쓰거나 메모리에서 데이터를 읽어올 수 있다. 읽어온 값을
valM
이라고 부른다.
- 데이터를 메모리에 쓰거나 메모리에서 데이터를 읽어올 수 있다. 읽어온 값을
- 재기록(Write back)
- 두 결과를 레지스터 파일에 기록한다.
- PC Update
- PC는 다음 인스트럭션의 주소로 설정된다.
4.3.2 SEQ 하드웨어 구조
프로그램 카운터는 좌측 하단에 나타난 것처럼 PC에 저장된다. 그리고 나서 정보는 선을 따라 흘러가는데 먼저 위쪽으로 그러고 나서 오른쪽 주변으로 흘러간다. 오른쪽 아래로 돌아오는 피드백 경로들은 레지스터 파일에 기록할 갱신된 값들과 갱신된 프로그램 카운터를 갖는다. SEQ에서 처리되는 모든 일들은 하나의 클럭 사이클 내에서 일어난다.
4.3.3 SEQ 타이밍
순차실행을 위해 명시적인 제어를 필요로 하는 네 개의 하드웨어 유닛인 프로그램 카운터, 조건코드 레지스터, 데이터 메모리, 레지스터 파일이 남았다. 이들은 새로운 값을 레지스터에 로딩하는 것과 값들을 랜덤 액세스 메모리에 기록하는 작업을 만드는 한 개의 클럭 신호를 통해서 제어된다.
- 프로그램 카운터는 매 클럭 사이클마다 새로운 인스트럭션 주소를 적재한다.
- 조건코드 레지스터는 정수연산 인스트럭션이 실행될 때에만 값이 적재된다.
- 데이터 메모리는
rmmovq
,pushq
,call
인스트럭션이 실행될 때에만 값이 기록된다. - 레지스터 파일 두 개의 쓰기 포트를 통해 두 개의 프로그램 레지스터가 매 사이클마다 갱신될 수 있지만, 특별한 레지스터 ID인
0xF
를 포트주소로 사용해서 이 포트에 아무것도 쓰여서는 안 된다는 것을 나타낸다.
이처럼 클럭을 공급하는 것은 여러 동작들을 순서대로 제어하기 위해 필요한 모든 것이다. 실제로는 모든 상태 갱신은 동시에 발생하고 클럭이 다음 사이클을 시작하기 위해 올라가는 때에만 발생한다.
4.3.4 SEQ 단계의 구현
Fetch
이 유닛은 PC를 0번 바이트의 주소로 사용해서 메모리로부터 한 번에 10바이트를 읽어들인다. 이 바이트는 인스트럭션 바이트로 해석되고, Split
유닛에 의해 두 개의 4비트 값으로 나누어진다.icode
와 ifunc
이라고 이름 붙인 제어로직 블록들은 인스트럭션과 함수코드를 메모리에서 읽은 값으로 할당하거나 인스트럭션 주소가 잘못된 경우(imem_error
로 신호가 가는 경우)에는 nop
인스트럭션에 해당하는 값들로 할당한다.
위의 그림에 사용되는 신호의 설명은 다음과 같다.
- instr_valid
- 이 바이트는 합법적인 Y86-64 인스트럭션인가? 부정 인스트럭션 검출 위해 사용된다.
- need_regids
- 이 인스트럭션은 레지스터 지정 바이트를 포함하는가?
- need_valC
- 이 인스트럭션은 상수 워드를 포함하는가?
Decode and Write-Back
위의 그림은 SEQ에서 decode와 write-back 단계 모두를 구현하는 로직의 상세한 모습을 보여준다. 이들이 모두 레지스터 파일에 접근하기 때문에 이 두 단계는 함께 설명한다.
레지스터 파일은 두 개의 동시 읽기(A, B)와 두 개의 동시 쓰기(E, M)으로 총 네 개의 포트를 가지고 있으며 각 포트는 한 개의 주소 연결과 데이터 연결을 모두 갖는다.
네 개의 블록은 icode
, rA
, rB
, Cnd
에 기초하여 네 개의 레지스터 ID를 만들어 낸다. 레지스터 ID srcA
는 valA
를 만들기 위해 어떤 레지스터를 읽어들여야 할지를 나타낸다. 레지스터 ID dstE
는 쓰기 포트 E
를 위한 목적지 레지스터를 나타내며, 계산된 값 valE
가 저장된다.
Excute
Execute 단계는 산술/논리 유닛(ALU)을 포함한다. 이 유닛은 ADD
, SUBTRACT
, AND
, EXCLUSIVE-OR
를 입력 aluA
, aluB
에 alufun
신호의 설정에 따라 실행한다. 이 데이터와 제어신호는 그림과 같이 세 개의 제어 블록에 의해 만들어진다. ALU의 출력은 valE
신호가 된다.
excute 단계는 조건 코드 레지스터를 포함한다. ALU는 매번 동작할 때마다 조건코드가 관련된 zero, flow, overflow의 세 개의 신호를 생성한다. 그렇지만 OPq
인스트럭션이 실행될 때에만 조건코드가 설정되기를 희망한다. 따라서 조건코드 레지스터가 갱신되어야 할지 여부를 제어하는 set_cc
신호를 생성한다.cond
하드웨어 유닛은 조건부 분기나 데이터 이동이 일어나야 할지를 결정하기 위한 조건코드들과 함수코드의 조합을 사용한다. 이것은 조건부 이동에서 dstE
를 설정할 때와 조건부 분기를 위한 next PC
로직에서 사용되는 Cnd
신호를 생성한다.
Memory
메모리 단계는 프로그램 데이터를 읽거나 쓰는 일을 수행한다. 상단의 그림처럼 두 개의 제어 블록(Memory addr, Memory data)이 메모리 주소와 쓰기 연산을 위한 메모리 입력 데이터를 위한 값을 만들어 낸다. 두 개의 다른 블록(Memory read, Memory write)들은 읽거나 쓰기 연산을 수행할지 여부를 나타내는 제어 신호를 만들어 낸다. 읽기 연산이 수행될 때는 데이터 메모리는 valM
을 생성한다. 메모리 읽기와 쓰기를 위한 주소가 항상 valE
또는 valA
인 점에 유의해야 한다.
메모리 단계의 마지막 기능은 icode
, imem_error
, fetch 단계에서 만들어진 instr_valid
, 데이터 메모리에서 만든 dmem_error
신호에 따라 인스트럭션 실행에서 결정되는 상태코드 Stat
을 계산하는 것이다.
PC Update
새로운 PC 값은 인스트럭션 타입과 분기를 택할지 여부에 따라 valC
, valM
, valP
가 될 수 있다.
SEQ 현황
SEQ의 유일한 문제는 매우 느리다는 것이다. 클럭은 한 개의 사이클 내에 모든 단계를 통해 전파될 수 있도록 충분히 느리게 동작해야 한다. 이러한 구현은 각 유닛들이 전체 클럭 사이클의 일부 동안만 사용되기 때문에 하드웨어 유닛을 잘 활용하지 못하도록 한다.
이제 파이프라인을 통해 보다 좋은 성능을 만들어보자.