안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

컴퓨터 구조 (Architecture)/CSAPP

[CSAPP] Sequential Implementation

피그브라더 2020. 3. 9. 18:40

동일한 ISA를 탑재하는 CPU라고 할지라도 내부 구현 방식은 다양할 수 있다. 특히 '명령어를 해석하고 실행하는 방식'의 관점에서 구현 방식을 나눠보면, Sequential Implementation(이하 SEQ)Pipelined Implementation(이하 파이프라인)이 대표적이다. SEQ가 가장 단순하지만 정확한 구현 방식이라면, 파이프라인은 성능의 개선을 위해 새롭게 고안된 구현 방식이라고 할 수 있다. 우리는 앞서 x86-64의 단순한 버전으로서 공부한 Y86-64를 기준으로, SEQ와 파이프라인이 구체적으로 어떻게 구현되는 것인지 알아볼 것이다. 이번 포스팅에서는 먼저 SEQ 방식에 대해 알아보도록 하자.

 

1. SEQ Overview

1-1. 프로세스 상태 (Process State)

① PC (Program Counter)

② CC (Condition Code)

③ 레지스터 파일 (Register File)

④ 메모리 (Memory) = 데이터 메모리 (Data Memory) + 명령어 메모리 (Instruction Memory)


1-2. 명령어 실행 단계 (SEQ Stages)

SEQ 방식에서는 모든 명령어들의 실행이 아래와 같은 6단계(Fetch, Decode, Execute, Memory, Write back, PC update)를 거치는 하나의 패턴으로 처리된다. 단지 명령어의 종류마다 각 단계에서 사용되는 값과 하드웨어가 다를 뿐이다. 이때 중요한 사실은, 각 단계들이 한 사이클 내에 동시에 처리된다는 것이다. 각 단계는 단순히 CPU 내부 회로 상에서 특정 계산을 담당하는 한 부분일 뿐이다. 예를 들어, 조건 만족 여부를 판단하기 위해 Execute 단계에서 Cnd의 값을 계산한다는 것은 Execute 단계까지 가야 Cnd 값을 계산할 수 있다는 의미가 아니다. 단지 Execute 단계를 담당하는 부분의 논리 회로에서 Cnd 값이 계산된다는 의미이다.

 

단계 동작 관련 계산값 (→ 그림에서 흰색 타원)
Fetch 명령어 메모리에서 명령어를 읽어온다. icode, ifun, rA, rB, valC
다음 명령어의 주소를 계산한다. valP
Decode

피연산자로 사용되는 레지스터의 값을 읽어온다. srcA, srcB, valA, valB
목적지 레지스터의 번호를 지정한다. dstE, dstM
Execute ALU를 통해 단순 값 혹은 메모리 주소를 계산한다. valE
조건 만족 여부를 판단한다. Cnd
Memory 데이터 메모리에서 값을 읽어온다. valM
데이터 메모리에 값을 쓴다. X
Write Back 레지스터에 값을 쓴다. dstE, dstM, valE, valM
PC update PC 값을 갱신한다. newPC

 

2. Y86-64 각 명령어의 단계별 처리 예시

2-1. OPq


2-2. rmmovq


2-3. popq


2-4. cmovXX


2-5. jXX


2-6. call


2-7. ret

 

3. SEQ 하드웨어 구조

3-1. SEQ 하드웨어 구조

맨 처음에 간단히 살펴본 SEQ 하드웨어 구조를 자세히 뜯어보면 다음과 같다. 파란색 박스는 프로세서 상태에 해당하는 장치들이다. 그리고 노란색 동그라미는 컨트롤 신호들로서, MUX 셀렉터 신호, 메모리/레지스터 활성화 신호, ALU 함수 결정 신호 등에 해당한다.

 


3-2. 컨트롤 신호의 발생 (Generating Control Signals)

그렇다면 컨트롤 신호는 어떠한 원리로 발생하는 것일까? 앞서 말했듯 컨트롤 신호의 종류로는 MUX 셀렉터 신호, 메모리/레지스터 활성화 신호, ALU 함수 결정 신호 등이 있다. 잘 생각해 보면, 이것들은 결국 해당 명령어의 실행을 처리할 때 각 단계에서 어떤 값과 하드웨어를 사용할지 결정하는 신호들이다. 따라서 99%의 컨트롤 신호는 명령어의 종류(icode)만 보고도 결정 가능하다. 명령어의 종류에 따라 각 단계에서 사용되는 값과 하드웨어가 달라지기 때문이다. 그렇다면 나머지 1%는 무엇일까?

 

먼저 명령어 특성상 조건 만족 여부를 판단해야 하는 경우이다. cmovXX와 jXX 명령어가 이에 해당한다. 이러한 경우, icode의 값 이외에도 ifun의 값과 CC의 값을 알아야 해당 동작을 수행할지 말지 결정할 수 있다. 다음으로, 명령어 실행 상태(에러 발생 여부)를 판단해야 하는 경우이다. 명령어 메모리에 잘못된 접근을 하거나(imem_error 신호 활성화), 데이터 메모리에 잘못된 접근을 하거나(dmem_error 신호 활성화), 잘못된 명령어를 실행하는 경우(instr_valid 신호 비활성화) 프로그램 실행을 즉시 중단시켜야 한다. 이를 판단하기 위해선 imem_error, dmem_error, instr_valid 등의 값도 알아야 한다.

 

결국, CPU 내부 컨트롤 신호 발생 장치는 새로운 사이클에 진입하여 명령어 하나를 읽는 순간 몇 가지 입력을 바탕으로 아래와 같이 모든 컨트롤 신호들을 결정하고, 이를 바탕으로 해당 사이클 내에 그 명령어의 실행을 각 단계에서 적절히 처리하게 된다.

 


3-3. Y86-64 각 명령어의 실행 흐름 예시

 

더보기

 

 

 

 

 

 

4. SEQ 단계별 컨트롤 로직

4-1. Fetch

Predefined Block 역할
PC 
(Program Counter)
다음에 실행할 명령어의 주소를 저장하는 레지스터
명령어 메모리 
(Instruction Memory)
프로그램 명령어를 저장하는 메모리 (일단 10바이트만큼 읽음)
Split 명령어의 첫 번째 바이트를 icode와 ifun으로 분리
Align 명령어의 나머지 비트 배열에서 rA, rB, valC를 추출
PC increment PC 값에 명령어의 길이를 더한 값을 계산
Control Logic 역할
icode 명령어의 종류를 나타내는 명령어 코드(Instruction Code)를 결정한다.
ifun 명령어의 구체적 기능을 구별하는 명령어 함수(Instruction Function)를 결정한다.
need_valC 명령어 비트 배열에 valC가 존재하는지 판단한다.
need_regids 명령어 비트 배열에 레지스터 바이트가 존재하는지 판단한다.
instr_valid 유효한 명령어를 실행하는지 판단한다.

 

참고로 imem_error와 instr_valid는 명령어의 실행 상태를 판단할 때 필요한 컨트롤 신호이다. imem_error는 명령어 메모리에 잘못된 주소로 접근했을 때 활성화되며, instr_valid는 유효한 명령어를 실행했을 때만 활성화된다.


4-2. Decode (+ Write back)

① Predefined Block 역할
레지스터 파일 (Register File) 읽기 포트 : A, B
쓰기 포트 : E, M
※ 주소로 0xF(=15)를 입력하면 어떠한 레지스터에도 접근하지 않음을 의미
② Control Logic 역할
srcA, srcB 읽을 레지스터의 번호(주소)를 결정한다.
dstE, dstM 쓸 레지스터의 번호(주소)를 결정한다.

 

Cnd는 조건 만족 여부에 따라 데이터를 이동시킬지 말지를 결정하는 컨트롤 신호로, Execute 단계에서 계산이 된다. 조건이 만족되면 Cnd 의 값이 1로 계산되므로 rB가 dstE에 입력되도록 하고, 반대로 조건이 만족되지 않으면 Cnd의 값이 0으로 계산되어 RNONE(= 0xF)이 dstE에 입력되도록 한다. dstE에 0xF가 입력되면 데이터 이동이 일어나지 않게 되는 원리를 응용한 것이다.


4-3. Execute

① Predefined Block 역할
ALU (Arithmetci Logic Unit) 네 종류의 산술/논리 연산 수행
컨디션 코드 레지스터의 값 셋팅
CC (Condition Code) 산술/논리 연산 결과값에 따라 셋팅되는 컨디션 코드 레지스터
cond 조건 만족 여부를 판단 (조건 분기/이동 시 필요)
② Control Logic 역할
aluA ALU에 첫 번째 입력(A)으로 들어갈 값을 결정한다.
aluB ALU에 두 번째 입력(B)으로 들어갈 값을 결정한다.
alufun ALU가 어떠한 연산을 수행할지 결정한다. (ADD, SUB, AND, XOR)
set_cc 컨디션 코드 레지스터의 값이 셋팅되어야 하는 상황인지 판단한다.


4-4. Memory

① Predefined Block 역할
데이터 메모리 (Data Memory) 프로그램 데이터를 저장하는 메모리
② Control Logic 역할
mem_read 데이터 메모리로부터 값을 읽을지 판단한다.
mem_write 데이터 메모리에 값을 쓸지 판단한다.
mem_addr 접근할 데이터 메모리의 주소를 결정한다.
mem_data 데이터 메모리에 쓸 값을 결정한다.
Stat 명령어 실행 상태를 판단한다. (AOK, ADR, INS, HLT)


4-5. PC Update

① Predefined Block 역할
PC (Program Counter) 다음에 실행할 명령어의 주소를 저장하는 레지스터
② Control Logic 역할
new_pc 다음 PC 값을 계산

 

5. SEQ Summary

5-1. 구현 방식 및 한계점

SEQ 구현 방식의 핵심은 일련의 단계들을 거치는 하나의 방식으로 모든 명령어의 실행을 똑같이 처리한다는 것이다. 하지만 구현 방식이 단순한 만큼, 성능은 다른 구현 방식에 비해 현저히 떨어진다. 왜 그런 것일까?

 

모든 명령어들의 실행을 하나의 일관된 방식으로 처리하려면, 한 사이클 내에 각 단계들에 해당하는 하드웨어들이 전부 사용된다는 가정 하에 클락의 주기가 설계되어야 한다. 예를 들어 하드웨어로 A, B, C, D가 있다고 할 때, 명령어 I1은 A, B를 사용하고, 명령어 I2는 B, C, D를 사용하고, 명령어 I3은 A, B, C, D를 사용한다고 해보자. 그러면 I3 때문에라도 한 사이클 내에 A, B, C, D를 모두 안정적으로 처리할 수 있을 만큼 클락의 주기가 충분히 길어야 한다. I1과 I2 입장에서는 그렇게까지 클락의 주기가 길 필요가 없음에도 불구하고 말이다. 그래서 SEQ 방식은 한 사이클 내에 실제로 하드웨어들이 의미 있게 사용되는 시간적 비율이 작고, 전체적인 처리 속도도 느릴 수밖에 없다.


5-2. 동작 예시

마지막으로 SEQ 구현 방식에서 명령어의 실행에 따라 상태에 해당하는 PC, 레지스터, 메모리, 컨디션 코드 등이 어떻게 변화해 가는지 가시적으로 한 번 살펴보자. 실시간으로 출력이 입력에 따라 변화하는 CLC와 달리 상태에 해당하는 장치들은 클락의 Rising-edge 때만 값이 변경된다는 것과, 한 명령어의 실행은 한 사이클에 처리가 된다는 사실을 염두에 두고 다음 그림들을 차근차근 따라가 보자.

 

'컴퓨터 구조 (Architecture) > CSAPP' 카테고리의 다른 글

[CSAPP] Pipelining - Part 1  (0) 2020.03.11
[CSAPP] Pipelining - Introduction  (0) 2020.03.10
[CSAPP] Y86-64 - Logic Design  (0) 2020.03.07
[CSAPP] Y86-64 - Basics  (2) 2020.03.05
[CSAPP] x86-64 - Miscellaneous Topics  (5) 2020.03.03