레지스터 개요
CPU의 데이터 경로를 단순화시켜서 표현한 그림이다.
위 경로의 레지스터들을 하나씩 알아보자.
1. Program Counter (PC)
- 프로그램의 현재 명령어 주소를 저장하는 레지스터
- 각 명령어 실행 후, PC는 다음 명령어의 주소를 가리키게 업데이트 된다. (보통 PC+4)
2. Instruction Memory
- 프로그램에 있는 명령어들을 저장하는 메모리
- PC에 의해 지정된 주소에서 명령어를 읽는다. 보통 명령어는 32비트
3. Control Unit
- 제어 유닛은 명령어의 opcode(명령어 유형)을 읽어들여, 각종 제어 신호를 생성한다.
이 제어 신호들은 ALU, 레지스터, 메모리 등 다른 컴포넌트들이 어떻게 동작해야 하는지를 결정한다. - 예: RegDst, ALUSrc, MemtoReg, RegWrite, MemRead, MemWrite, Branch 등
4. Registers (레지스터 파일)
- 32개의 32비트 레지스터들이 포함된 블록이다. CPU는 이 레지스터들에서 데이터를 읽고 쓰기를 할 수 있다.
- 명령어에서 두 개의 소스 레지스터(Read register 1, Read register 2)를 읽고, 결과를 저장할 목적 레지스터(Write register)에 쓴다.
5. ALU (Arithmetic Logic Unit)
- 두 레지스터 값의 더하기, 빼기, 논리 연산 등을 수행하는 산술 및 논리 연산을 수행하는 장치
- ALU Control 유닛은 명령어에 따라 적절한 ALU 동작을 설정한다.
6. Sign-Extend
- 즉시 값을 16비트에서 32비트로 확장하는 블록이다.
MIPS 명령어에서 즉시 값은 보통 16비트로 주어지는데, 이를 32비트로 확장하여 연산에 사용할 수 있도록 한다.
7. Data Memory
- 메모리에서 데이터를 읽거나 메모리에 데이터를 쓰는 과정에서 사용된다.
- 예를 들어, lw(load word) 명령은 메모리에서 데이터를 읽고, sw(store word) 명령은 레지스터의 데이터를 메모리에 쓴다.
8. MUX (Multiplexers)
- 여러 입력 중 하나를 선택해 출력하는 장치
- 예를 들어, MUX는 레지스터의 값을 사용할지, 또는 sign-extended 값을 사용할지 결정한다.
9. Shift Left 2
- 주소 계산에 사용되는 블록으로, 주소 값을 2비트만큼 왼쪽으로 이동시켜 곱셈 연산을 빠르게 처리한다.
보통 분기(branch) 명령어에서 사용된다.
10. Branching and PC Update
- 분기 명령어(beq, bne 등)의 경우, ALU가 두 레지스터 값을 비교하고, 그 결과에 따라 분기할지 말지를 결정한다.
- 만약 분기 조건이 충족되면, PC는 분기 주소로 업데이트되고, 그렇지 않으면 PC는 그냥 PC + 4로 업데이트된다.
11. ALU Zero Flag
- ALU의 연산 결과가 0이면 Zero 신호가 활성화된다. 주로 조건 분기 명령어에서 중요하다.
예를 들어, beq(equal) 명령어는 두 값이 같은지를 확인하고, 같으면 분기한다.
위 그림의 화살표를 따라 명령어 실행(Instruction Execution) 과정을 간단하게 알아보자.
1. PC → Instruction Memory, fetch instruction
- PC (Program Counter): 현재 실행 중인 명령어의 주소를 저장한다.
- Instruction Memory (명령어 메모리): 프로그램에 저장된 명령어를 보관한다.
실행할 명령어의 주소를 가리키고, 그 명령어를 가져옵니다. 이 과정을 명령어 페치(fetch)라고 한다.
2. Register numbers → register file, read registers
- 명령어가 페치되면, 해당 명령어에서 사용되는 레지스터 번호를 읽어와, CPU의 레지스터 파일(Register File)에서 실제 데이터를 가져온다.
- 예를 들어, add $t1, $t2, $t3 명령어에서는 $t2와 $t3 레지스터의 데이터를 읽어와, 이 두 값을 더한 결과를 $t1 레지스터에 저장한다.
3. Depending on instruction class (명령어 유형에 따라 다음 동작 수행)
- ALU를 사용하여 산술 연산(덧셈, 뺄셈 등)을 수행하거나, 메모리 주소를 계산하거나, 분기할 때 사용할 목표 주소를 계산한다.
- Data Memory에 접근하여 데이터를 읽거나 쓰는 작업을 수행한다.
- PC는 명령어가 완료되면 다음 명령어의 주소로 업데이트한다.
보통 PC + 4이지만, 분기 명령어(beq, j 등)의 경우 목표 주소로 PC를 설정한다.
먼저 명령어 메모리에서 명령어를 가져오고, 필요한 레지스터 데이터를 읽어온다. 그 후, 명령어 유형에 따라 ALU를 사용하여 연산을 수행하거나 메모리 접근을 진행하고, 마지막으로 PC를 업데이트하여 다음 명령어를 준비하는 과정을 거친다.
전체적인 구조를 한번에 다 알아보긴 어려우니 각 부분을 나눠서 보자.
CPU 데이터 경로에서 명령어가 실행되는 과정
- PC (Program Counter):
- 현재 실행할 명령어의 주소를 가리키며, 명령어 메모리로부터 명령어를 가져오는 데 사용된다.
- Instruction Memory:
- PC에 의해 가리키는 주소로부터 명령어를 읽어온다.
이 명령어는 레지스터나 데이터 메모리로 전달되기 위해 주소(Address)와 명령어(Instruction)로 분리된다.
- PC에 의해 가리키는 주소로부터 명령어를 읽어온다.
- Add:
- PC + 4를 계산하여 다음 명령어 주소를 계산한다.
- Registers:
- 명령어에서 참조된 레지스터 번호를 읽어오고, 해당 레지스터의 데이터를 가져온다.
ALU는 두 레지스터 값을 입력으로 받아 연산을 수행한다.
- 명령어에서 참조된 레지스터 번호를 읽어오고, 해당 레지스터의 데이터를 가져온다.
- ALU:
- 산술 연산(덧셈, 뺄셈 등)이나 메모리 주소 계산 등을 수행한다.
계산된 결과는 Data Memory로 전송되거나 다시 레지스터에 저장됩니다.
- 산술 연산(덧셈, 뺄셈 등)이나 메모리 주소 계산 등을 수행한다.
- Data Memory:
- ALU의 결과에 따라 메모리에서 데이터를 읽거나 쓰는 작업을 수행한다.
멀티플렉서(MUX)를 사용하여 신호 경로를 제어하는 방법
CPU 데이터 경로에 추가로 멀티플렉서(MUX)를 적용해보자. 여러 경로가 서로 연결될 때 MUX가 사용되어 어떤 경로가 선택될지 결정한다. 빨간 원으로 표시된 부분들이 MUX가 필요한 지점이다.
- 첫 번째 빨간 원 (PC 경로)
- PC는 다음 명령어를 가리켜야 하지만, 경우에 따라 분기(branch) 명령어가 실행되면 다른 주소를 가리켜야 할 수 있다. 이 경우, 두 주소 중 어느 것을 선택할지를 결정해야 한다. 이 때 MUX가 사용된다.
- 두 번째 빨간 원 (ALU 경로)
- ALU는 두 개의 입력을 받는데, 이 입력은 레지스터 값 또는 즉시 값(immediate value)일 수 있다.
MUX는 어떤 값을 ALU로 전달할지를 결정한다.
- ALU는 두 개의 입력을 받는데, 이 입력은 레지스터 값 또는 즉시 값(immediate value)일 수 있다.
- 세 번째 빨간 원 (ALU 결과와 데이터 메모리 경로)
- ALU의 결과는 Data Memory 주소로 사용되거나, 레지스터에 저장될 값이 될 수 있다.
아래는 처음에 봤던 레지스터 개요 그림에서 CPU 데이터 경로만 추출한 그림이다.
이렇게 하나씩 추가해서 경로를 알아보자..
이와 같은 단일 사이클 데이터 경로는
특히, 제어 유닛과 멀티플렉서(MUX)를 사용해서 명령어 실행 흐름을 어떻게 제어하는지에 중점을 두고 있다.
1. Program Counter (PC)
- PC는 현재 실행할 명령어의 주소를 가리킨다. 이 주소는 Instruction Memory에서 명령어를 가져오고, 다음 명령어를 실행하기 위해 업데이트된다.
- 분기(branch) 명령어가 실행될 경우, MUX를 통해 분기 여부에 따라 새로운 분기 주소로 PC가 업데이트될 수 있다.
2. Instruction Memory
- PC가 가리키는 주소에서 명령어를 가져온다. 명령어는 CPU가 실행할 작업을 지정하는 32비트 데이터로, 레지스터와 데이터 메모리에 어떤 작업을 해야 하는지를 나타낸다.
3. Registers (레지스터 파일)
- 명령어에서 지정된 레지스터 번호를 사용하여, 두 개의 소스 레지스터로부터 데이터를 읽는다. 이 데이터는 연산에 사용되며, 연산 결과는 추후 목적 레지스터에 저장된다.
- 여기에서도 MUX를 통해 어떤 레지스터에 결과를 쓸지 결정한다. 제어 신호인 RegWrite는 레지스터에 쓰기를 활성화할지 여부를 제어한다.
4. Control Unit (제어 유닛)
- Control Unit은 명령어를 해석하고, CPU 내부의 각 컴포넌트에 필요한 제어 신호를 보낸다.
- 이 신호들은 ALU, Data Memory, Registers, MUX 등에 명령어 실행 방식을 지시한다.
5. ALU (Arithmetic Logic Unit)
- ALU의 제어 신호는 Control Unit으로부터 오며, ALU operation에 의해 어떤 연산을 수행할지 결정한다.
- ALU 연산 후, 결과가 0인지 여부를 나타내는 Zero 플래그도 설정된다. (조건 분기용)
6. Data Memory
- ALU가 계산한 주소를 사용하여 메모리에 접근하고, 메모리에서 데이터를 읽거나 쓴다.
- 제어 신호 MemRead와 MemWrite를 통해 메모리에서 데이터를 읽을지 쓸지를 결정한다.
7. Branching (분기)
- 분기 명령어(beq)의 경우, ALU는 두 값을 비교하여 결과가 0일 때(즉, 두 값이 같을 때) 분기해야 한다.
- Branch 신호는 분기 명령어 실행 여부를 결정하며, 분기가 참일 경우 PC는 분기 주소로 업데이트된다. 그렇지 않으면 그냥 PC + 4로 넘어간다.
디지털 회로
디지털 시스템에서 정보는 전압의 상태에 따라 이진수(0과 1)로 표현된다.
일반적으로 낮은 전압은 0을, 높은 전압은 1을 의미한다.
논리 게이트를 통해 입력에 대해 즉각적인 연산을 수행하는 회로를 말한다.
또한 이전 입력의 결과 정보를 저장하고, 클록 신호에 따라 상태를 변경할 수 있다.
이러한 요소들이 모여 CPU 및 기타 디지털 시스템이 작동하게 된다.
- AND-gate (AND 게이트):
- Y = A & B: 두 입력(A와 B)이 모두 1일 때만 출력 Y가 1이 된다.
- Multiplexer (MUX):
- Y = S ? I1 : I0: 선택 신호(S)가 1이면 I1을 출력하고, S가 0이면 I0을 출력한다.
여러 입력 중 하나를 선택해서 출력하는 역할이다.
- Y = S ? I1 : I0: 선택 신호(S)가 1이면 I1을 출력하고, S가 0이면 I0을 출력한다.
- Adder (더하기 회로):
- Y = A + B: 두 입력(A와 B)을 더해 출력 Y를 생성한다.
- Arithmetic/Logic Unit (ALU):
- Y = F(A, B): 입력 A와 B에 대해 다양한 연산을 처리한다.
F는 연산 종류(덧셈, 뺄셈, 논리 연산 등)를 결정하는 함수를 의미한다.
- Y = F(A, B): 입력 A와 B에 대해 다양한 연산을 처리한다.
정보를 저장하는 요소들이다. 현재 상태와 클록 신호에 따라 저장된 값이 변경된다.
클록 신호가 0에서 1로 변화하는 순간에 데이터가 업데이트 된다.
클록 신호와 함께 쓰기 제어(Write Control) 신호가 있어야만 값이 업데이트된다.
쓰기 제어 신호가 1일 때만 데이터를 저장하며, 그렇지 않으면 값을 유지한다.
이렇게 조합 논리 회로를 통해 결과값을 상태에 저장하고,
회로에서 상태가 결과가 1이 되면 클록 신호는 1로 상승, 아니면 0으로 하강하여 사이클을 반복한다.
(조합 논리 회로는 클럭과 무관하게 계속 연산하는중.. 상태 결과값에 따라 클록 변화)
명령어 형식에 따른 연산 과정
R-형 명령어
R-형 명령어는 두 레지스터에서 값을 읽고 ALU에서 연산을 수행한 후, 결과를 다시 레지스터에 저장한다.
두 개의 레지스터 값을 읽으면, ALU에서 산술 또는 논리 연산을 수행하고 연산 결과를 레지스터에 저장한다.
ALU에는 add, sub, and, or, slt 등의 명령어가 있다. 이 명령어들은 모두 6비트의 opcode에서 항상 000000이며, 실제로 수행되는 연산은 funct 필드에 의해 결정된다.
I-형 명령어
로드 및 스토어 명령어는 메모리에서 데이터를 읽어오거나 메모리에 데이터를 저장하는 I-형 명령어 형식이다.
메모리 주소를 계산하기 위해 레지스터에서 값을 읽고, 16비트의 오프셋을 사용하여 메모리 주소를 계산한다.
이때 오프셋은 부호 확장(sign-extend)을 거쳐 32비트로 확장된다.
ALU는 베이스 주소와 확장된 오프셋을 더해서 메모리 주소를 계산한다.
I-형 명령어의 분기 과정
분기 명령어는 조건에 따라 프로그램 흐름을 변경하는 명령어이다.
예를 들어, 두 값이 같으면 특정 위치로 분기하는 beq 명령어가 있다.
우선, I-Format Instructions (I-형 명령어)를 통해 두 개의 레지스터 값(예: beq의 경우 비교할 두 값)을 읽는다.
ALU를 사용해서 분기 명령어는 두 값의 차를 계산하는데, 그 결과가 0이면 분기가 발생한다. (Zero 플래그가 설정)
ALU가 두 값을 비교해 Zero 플래그가 설정되면 분기 명령어가 실행된다.
위 그림에서는 두 개의 레지스터에서 데이터를 읽어와 비교한 후에 결과가 0이면 분기한다.
Shift-left 2에서 16비트 오프셋을 왼쪽으로 2비트 시프트 후 워드 단위로 변환하고
ADD에서 PC+4에 오프셋을 더해 분기 주소를 계산한다.
참고) 명령어의 opcode와 ALUOp를 사용해 각 명령어가 어떤 연산을 수행할지를 나타낸 표
MIPS 아키텍처의 제어 유닛(Control Unit)이 명령어의 필드로부터 제어 신호를 생성하는 과정
- R-형 명령어:
- opcode가 000000인 R-형 명령어는 rs, rt, rd, shamt, funct 필드로 나뉜다.
- 이 명령어는 연산에 사용할 두 레지스터(rs, rt)를 읽고, 결과를 rd 레지스터에 기록한다.
- funct 필드는 ALU가 수행할 연산을 정의한다.
- Load/Store 명령어:
- opcode가 35(lw) 또는 43(sw)인 명령어다.
- 레지스터 rs에서 주소를 읽고, rt 레지스터에서 데이터를 읽거나 메모리로 저장한다.
- Branch 명령어:
- opcode가 4인 beq 명령어다. 두 레지스터(rs, rt)를 비교한 후, Zero 플래그에 따라 분기할지를 결정한다.
- 16비트 오프셋을 사용하여 분기 주소를 계산한다.
- 입력 (Inputs):
- Op5~Op0는 opcode의 각 비트를 나타낸다. 각 비트 조합에 따라 해당 명령어가 R-형, lw, sw, beq 중 무엇인지를 결정한다.
- 출력 (Outputs):
- RegDst: 목적 레지스터가 rt인지 rd인지 선택한다.
R-형 명령어의 경우, 결과를 rd에 기록하고, 나머지 명령어는 rt를 사용한다. - ALUSrc: ALU에 전달되는 두 번째 입력이 레지스터에서 오는지, 즉시 값(imm)에서 오는지 결정한다.
- MemtoReg: 메모리에서 읽은 데이터를 레지스터에 쓸지, ALU 결과를 쓸지를 결정한다.
- RegWrite: 레지스터에 값을 기록할지 결정한다.
- MemRead/Write: 메모리에서 데이터를 읽거나 쓰는 명령어에 사용된다.
- Branch: 분기 명령어인 경우 활성화되어 분기 주소로 PC를 변경한다.
- ALUOp: ALU가 수행할 연산을 정의하는 신호로, opcode에 따라 연산 유형이 결정된다.
- RegDst: 목적 레지스터가 rt인지 rd인지 선택한다.
각각의 명령어(R-타입, Load, Branch, Jump)에 따라 데이터 경로가 어떻게 구성되고 처리되는지
1. Datapath with Control (일반적인 데이터 경로)
2. R-Type Instruction (R-타입 명령어의 데이터 경로)
R-타입 명령어는 두 개의 레지스터 값을 읽어 산술/논리 연산을 수행한 후, 결과를 레지스터에 기록한다.
Instruction[25:21]과 Instruction[20:16]이 각각 레지스터 파일에서 두 레지스터(rs, rt)를 선택하면
ALU는 제어 유닛에서 설정한 연산을 수행하고, 결과는 rd 레지스터에 저장된다.
이 과정에서 RegDst, ALUSrc, RegWrite 등의 Control 제어 신호가 중요한 역할을 한다.
3. Load Instruction (Load 명령어의 데이터 경로)
Load 명령어(lw)는 메모리에서 데이터를 읽어 레지스터에 저장하는 명령어다.
Instruction[25:21]은 메모리 주소를 계산하기 위한 베이스 주소가 저장된 레지스터(rs)를 선택한다.
Sign-extend는 16비트 오프셋을 32비트로 확장하여 ALU에서 베이스 주소와 더하고,
메모리에서 읽은 데이터는 Registers의 rt 레지스터에 저장된다.
여기서 MemRead, MemtoReg, ALUSrc의 Control 신호가 중요한 역할을 한다.
4. Branch Instruction (Branch 명령어의 데이터 경로)
Branch on Equal(beq) 명령어는 두 레지스터의 값을 비교하여, 값이 같으면 분기하는 명령어다.
두 레지스터(rs, rt)의 값을 ALU에서 비교하여 Zero 플래그를 확인한다.
Zero가 설정되면, 분기 주소를 계산하여 PC를 해당 주소로 업데이트한다.
이 과정에서 Branch, ALUSrc 신호가 사용되며, Shift-left 2가 분기 주소를 계산하는 데 사용된다.
5. Jump Instruction (Jump 명령어의 데이터 경로)
Jump 명령어(j)는 PC를 특정 주소로 즉시 변경하는 명령어다.
Instruction[25:0] 필드에서 주어진 주소를 Shift-left 2를 통해 28비트로 확장하고, 현재의 PC 상위 4비트와 결합하여 새로운 PC를 설정한다.
이 명령어는 Jump 제어 신호를 통해 PC가 해당 주소로 바로 변경된다.
각각의 데이터 경로는 명령어의 타입에 따라 약간씩 다르며, 각 명령어가 실행되는 과정에서 제어 신호가 중요한 역할을 한다.
R-타입 명령어는 주로 연산과 결과 기록에 초점을 맞추고,
Load 명령어는 메모리 접근,
Branch 명령어는 조건부 분기,
Jump 명령어는 즉시 주소로 이동하는 역할을 한다.