[컴퓨터 구조] The Processor(프로세서) - MIPS 단일 사이클 구조

Yeontachi·2025년 5월 8일

단일 사이클(single-cycle) MIPS 프로세서의 하드웨어 구성

MUX(Multiplexor)의 역할

MIPS의 기본 구현은 명령어를 실행하기 위해 필요한 회로 요소들-예를 들어 MUX(멀티플렉서)와 제어선(Control lines)-로 구성되어 있다. 이 구현에서는 총 세 개의 주요 MUX가 존재하며, 각각은 명령어의 종류에 따라 서로 다른 경로를 선택하게 도와주는 역할을 한다.

먼저, 가장 위쪽에 있는 MUXPC(Program Counter)를 다음에는 어떤 값으로 바꿀지를 결정한다. 일반적으로 PC + 4가 되어 다음 명령어로 넘어가지만, 만약 지금 실행 중인 명령어가 branch(분기) 명령어이고, 그 분기 조건이 충족된다면, 이 MUX는 분기 목적지 주소(branch destination address)로 PC 값을 바꾸게 된다. 이 MUX는 단순히 제어선 하나로 작동하지 않고, ALU에서 나오는 Zero 신호와 Branch 신호라는 두 신호를 AND 게이트로 결합하여 제어한다. 즉, ALU 연산 결과가 0이라는 뜻의 Zero 신호와 "이건 분기 명령이다"라는 Branch 신호가 둘 다 참일 때만 분기 주소로 점프한다.

다음으로, 중간에 위치한 MUX는 레지스터 파일로 다시 써넣을 데이터를 결정한다. 이건 R-type이나 lw(load word) 명령어에서 사용된다. R-type 명령어라면 ALU 결과를 다시 레지스터에 써야 하고, lw 명령어라면 메모리에서 읽어온 값을 써야한다. 따라서 이 MUX는 ALU 결과데이터 메모리의 출력 값 중에서 어떤 걸 레지스터에 쓸지 선택한다. 어떤 걸 쓸지는 제어 유닛의 MemToReg 신호에 따라 결정된다.

  • 만약 명령어가 R-type 연산이나 branch 명령어라면, 두 번째 ALU 입력도 레지스터에서 가져와야 한다.
  • 반면, lw나 sw(store word) 명령어처럼 즉시 값(immediate)을 사용하는 명령어라면, ALU의 두 번째 입력으로는 명령어 내부의 오프셋(offset) 필드가 들어가야 한다.

이 MUX는 ALUSrc라는 제어 신호를 통해 어떤 입력이 들어갈지 결정된다. 이 외에도 여러 개의 제어선(control lines)이 함께 동작한다.

  • ALU가 어떤 연산을 해야 할지를 알려주는 ALUOp
  • 메모리에서 읽을지(MemRead), 쓸지(MemWrite)
  • 레지스터에 값을 써야 하는지(RegWrite)

이러한 제어 신호들은 모두 명령어 종류에 따라 다르게 설정되며, 명령어에 맞는 회로 동작이 이루어지도록 도와준다.

명령어를 실행하기 위한 첫걸음: 명령어 가져오기(Fetch)

명령어를 실행하려면, 가장 먼저 해야 할 일은 명령어 메모리에서 해당 명령어를 읽어오는 일이다. 이 작업을 위해서는 총 세 가지 요소가 필요하다.

  1. 명령어 메모리(Instruction Memory) : 명령어들을 저장하는 공간이다. 이 메모리는 단지 명령어를 읽기만 하면 되므로, 제어 신호 없이 주소를 주면 곧바로 그 위치의 명령어를 출력한다. 따라서 이 메모리는 읽기 전용(Read-only)처럼 작동하며, 특별한 read 신호는 필요 없다.(실제 프로그램을 메모리에 로드할 때는 쓰기가 필요하기도하다. 여기선 생략)

  2. 프로그램 카운터(Program Counter, PC) : 현재 실행할 명령어의 주소를 저장하는 32비트 레지스터이다. 모든 클럭 사이클의 끝마다, 이 PC는 업데이트되며 쓰기 제어 신호 없이 자동으로 새 값을 받는다.

  3. 더하기 장치(Adder) : PC 값을 4 더해서 다음 명령어 주소를 계산한다. ALU와 같은 구조지만 항상 두 입력을 더하도록 고정되어 있다.

위 세 가지 요소가 함께 작동하면, PC가 가리키는 주소의 명령어를 메모리에서 읽어오고, 동시에 PC 값에 4를 더해 다음 명령어 주소를 준비할 수 있다.

Fetch Stage : PC -> Instruction Memory -> 명령어 읽기, 동시에 PC + 4 계산

R-format 명령어 처리: 연산과 레지스터 접근

# Example: R-format
add $t1, $t2, $t3
  • 읽기(Register Read) : $t2와 $t3의 값을 읽는다.
  • ALU 연산 : 위 둘을 더한다.
  • 쓰기(Register Write) : 결과를 $t1에 저장

이를 구현하기 위해 필요한 구성 요소는 다음과 같다.

  1. 레지스터 파일(Register File) : MIPS에는 32개의 범용 레지스터가 존재하며, 이들은 모두 하나의 구조(register file) 안에 저장되어 있다. 이 구조 안에서는 다음과 같은 일이 일어난다.

    • 읽기 : 두 개의 읽을 레지스터 번호(Read Register 1, 2)를 입력으로 넣으면, 해당 번호에 해당하는 값들이 곧바로 출력으로 나온다. 읽기는 항상 자동으로 이루어지며, 제어 신호 없이도 가능하다.
    • 쓰기 : 하나의 쓸 레지스터 번호(Write Register)쓸 데이터(Write Data)를 입력으로 주고, RegWrite 제어 신호가 활성화되어 있어야 클럭 엣지에서 실제 쓰기가 일어난다.
  2. ALU(산술/논리 연산 유닛) : 레지스터 파일에서 읽어온 두 값은 ALU로 전달되어 연산이 수행된다. 예를 들어 add, sub, and, or, slt 같은 연산이 가능하며, 연산 결과는 다시 레지스터 파일에 쓰이게 된다.

R-format 실행 :
1. 두 개의 레지스터 값 읽기
2. ALU 연산 수행
3. 결과를 다시 레지스터에 저장(RegWrite 제어 신호 필요)

MIPS에서의 load/store 명령어: 메모리와의 상호작용

lw $t1, offset($t2)   // 메모리에서 값을 읽어 $t1에 저장
sw $t1, offset($t2)   // $t1의 값을 메모리에 저장

이 명령어들은 먼저 메모리 주소를 계산해야 한다. 메모리 주소는 두 부분을 더해서 만들어진다.

  • Base Register : $t2
  • Offset : 명령어 안에 포함된 16비트 부호 있는 값

이 두 값을 더해 실제 메모리 주소를 만들고, 이 주소에 따라 데이터를 읽거나 쓴다.

sw(store word) 명령어

$t1에 들어 있는 값을 메모리에 저장해야 하므로, 먼저 $t1레지스터 파일에서 읽어야 한다. 이후 계산된 메모리 주소에 해당 값을 저장(Write)한다. 이때 데이터 메모리(data memory)는 다음과 같은 기능이 필요하다.

  • 주소 입력 : 어디에 쓸지 결정
  • 쓰기 데이터 입력 : 무엇을 쓸지를 결정
  • 쓰기 제어 신호(write control signal) : 쓰기 여부를 제어

lw(load word) 명령어

계산된 메모리 주소로부터 데이터를 읽어야(read)하며, 이 값은 $t1저장되어야 하므로, 결국 레지스터 파일에 쓰기(Write)를 해야 한다. 따라서 데이터 메모리는 다음이 필요하다.

  • 주소 입력 : 어디에서 읽을지
  • 읽기 제어 신호(read control signal)
  • 출력 : 메모리로부터 읽어온 데이터

추가적으로 필요한 요소들

  • 레지스터 파일(Register file) : $t2와 $t1의 값들을 읽거나, $t1에 값을 저장해야 하므로 꼭 필요하다.
  • ALU : $t2 + offset 주소 계산을 수행하기 위해 필요하다. 즉, ALU가 offset을 더하는 역할을 한다.
  • Sign Extension Unit : 명령어 안에 있는 offset은 16비트 부호 있는 값이다. 하지만 ALU는 32비트 연산을 하므로, offset을 32비트로 확장(sign-extend)을 해야 한다.

메모리 단위의 설계 특성

데이터 메모리는 단순한 조합 논리가 아니라 상태 요소(state element)이다. 쓰기 연산은 클럭 엣지(시점)에 맞춰 이루어지며, 이를 위해 메모리는 쓰기 신호(write enable)가 필요하다. 읽기 연산은 그냥 주소만 넣는다고 되는 것이 아니라, 명시적인 read 신호가 있어야 한다. 잘못된 주소를 읽으려고 하면 시스템에 문제가 생길 수 있기 때문이다.

  • Sign Extension Unit
    • 16비트 offset을 32비트로 확장
    • ALU 연산에 사용됨
  • Data Memory Unit
    • 주소 입력
    • 쓰기 데이터 입력
    • 읽은 결과 출력
    • 읽기/쓰기 제어 신호 따로 존재(한 시점에는 둘 중 하나만 활성화 가능)

즉, 정리하면

단계설명
1. 레지스터 읽기$t2, $t1에서 값 읽기 (base와 store할 값)
2. offset 처리16비트 → 32비트로 Sign-Extend
3. 주소 계산ALU로 base + offset 수행
4. 메모리 접근sw: 메모리에 값 저장
lw: 메모리에서 값 읽기
5. 레지스터 쓰기lw인 경우 읽어온 값을 $t1에 저장

MIPS의 beq 명령어: 조건 분기 처리하기

beq $t1, $t2, offset

위 명령어는 다음과 같은 작업을 한다.
1. $t1과 $t2를 비교하여 값이 같다면,
2. 현재 명령어 위치에서 offset 만큼 떨어진 곳으로 분기(점프)한다.

분기 주소를 계산할 때의 두 가지 핵심 사항

이 명령어를 처리할 때는 분기 주소(branch target address)를 올바르게 계산해야 하는데, 그 과정에 두 가지 중요한 규칙이 있다.

  • 분기 기준 주소는 PC + 4 : 분기 명령어는 현재 주소가 아니라 그 다음 명령어의 주소(PC + 4)를 기준으로 offset을 더해 계산한다. 다행히도, MIPS의 명령어 가져오기 단계에서 이미 PC + 4가 계산되어 있기 때문에, 바로 사용하면 된다.
  • offset은 워드 단위이므로 왼쪽으로 2비트 Shift : MIPS의 offset 필드는 워드 주소 단위로 계산된다.(명령어 하나 = 4바이트) 따라서 실제 주소를 계산하려면 offset을 왼쪽으로 2비트 쉬프트해야 한다.

조건에 따라 분기를 할지 말지 결정

단순히 분기 주소만 계산한다고 끝나는 것이 아니라, 실제로 분기를 할지 말지를 결정해야 한다.

  • 만약 $t1== $t2이면 -> 분기 조건 만족, 분기 주소로 이동
  • 만약 두 값이 다르면 -> 조건 불만족, 그냥 PC + 4 주소의 다음 명령어 실행

분기 동작 구현을 위한 요소들

  1. ALU(산술 논리 장치) : $t1 - $t2를 계산해서 두 값이 같은지 확인한다. 결과가 0이면, ALU의 Zero 출력 신호가 활성화되어 조건이 참이라는 것을 알 수 있다.
  2. Sign Extension Unit + Shift Left2 : 명령어 안에 있는 16비트 offset을 먼저 32비트로 부호 확장한 다음, 왼쪽으로 2비트 이동(shift)한다.
  3. Adder(덧셈기) : PC + 4와 위에서 변환한 offset 값을 더해서 분기 대상 주소를 계산한다.
  4. MUX(멀티플렉서) :
    • 조건이 참이면 -> 분기 주소 선택
    • 조건이 거짓이면 -> 그냥 PC + 4 선택
    • 이 선택은 ALU의 Zero 신호에 의해 제어된다.

결과적으로 datapath는 두 가지를 수행해야 한다.
1. 분기 주소 계산(PC + offset << 2)
2. 두 레지스터 값 비교($t1 - $t2 == 0 ?)

이러한 분기 처리 구조는 아래와 같은 특징이 있다.

  • Shift left 2 유닛은 실제로 복잡한 하드웨어가 아니라, 단순히 입력 값의 하위에 00을 덧붙이는 배선 구조이다.
  • offset은 이미 sign-extension 되었기 때문에 이 shift는 "의미 있는 정보"를 손실하지 않는다.
  • 분기 여부 판단 로직은 ALU의 Zero신호와 Control Unit에 의해 결정되며, 이 결과에 따라 PC가 다음 명령어 주소로 갈지, 분기할지를 정한다.

정리하면,

단계설명
1. 레지스터 비교ALU로 $t1 - $t2 계산 → Zero 여부 확인
2. offset 처리offset 부호 확장 → 2비트 shift
3. 분기 주소 계산PC + 4 + (offset << 2)
4. 분기 여부 결정Zero == 1이면 분기, 아니면 PC + 4 유지
5. MUX로 PC 선택조건 만족 시 분기 주소 / 아니면 다음 주소

Single datapath

지금까지 살펴본 MIPS 명령어의 세 가지 주요 유형들을 각각을 실행하기 위한 datapath 구조로 따로 살펴보았다. 이제 이들을 하나의 통합된 단일 datapath로 결합하면 아래와 같다.

결합할 때 주의할 점은, 동일한 하드웨어 자원은 한 명령어 내에서 두 번 사용이 불가능하다. 이를 해결하기 위해서는 MUX나 별도 장치로 해결해야한다. 또한 명령어 메모리와 데이터 메모리의 분리가 필요하다. 명령어를 실행하는 동안 동시에 데이터를 읽거나 써야 하는 경우가 많기 때문에, 명령어용 메모리와 데이터용 메모리는 서로 다른 메모리로 구성해야 한다. 공유 가능한 자원의 경우-예를 들어 ALU, Register file-하나만 두고, 서로 다른 명령어들이 쓸 수 있도록 입력 경로를 MUX로 전환한다.

R-type과 Memory 명령어는 아래와 같은 차이점이 있다.

구분R-typelw/sw
ALU 입력두 개 모두 레지스터 값하나는 레지스터, 하나는 sign-extended offset
결과 저장 위치ALU 결과를 레지스터에 저장lw는 메모리에서 읽은 값을 저장

이 차이를 해결하기 위해 ALU 입력 MUX(ALUSrc), Register Write Data MUX(MemToReg) 두 개의 MUX를 사용한다. 이렇게 두 개의 MUX를 추가하면, 하나의 ALU, 하나의 레지스터 파일만으로도 두 명령어 유형을 모두 처리 가능하다.

R-Type/Load/Store Datapath

Full Datapath

구성 요소역할
Instruction Memory명령어 읽기 (읽기 전용, PC 사용)
Register File명령어에 사용될 레지스터 값 읽기/쓰기
ALU산술 연산 또는 주소 계산
Sign Extension Unit16비트 offset을 32비트로 확장
Shift Left 2offset을 워드 주소로 변환
Data Memorylw/sw를 위한 데이터 읽기/쓰기
MUX (2개)ALU 입력 선택, 레지스터 쓰기 값 선택
PC + 4 Adder기본적인 명령어 순차 실행 주소 계산
Branch Target Adder분기 주소 계산 (PC + offset << 2)
Control Unit모든 경로 제어 신호 생성 (ALUSrc, MemToReg, Branch, etc.)
profile
기초를 다지는 중입니다.📚🧑‍💻

0개의 댓글