← 블로그로 돌아가기CPU 구조

명령어 레지스터와 디코드 단계

교육용 CPU가 명령어를 캡처하고 opcode·레지스터·즉시값 필드를 추출한 뒤 검증된 제어 신호로 바꾸는 과정을 RISC-V와 x86 사례와 함께 알아봅니다.

DennyDenny
약 7분 소요
Chalkboard feature illustration for “명령어 레지스터와 디코드 단계”

요약: 단순한 CPU에서 명령어 레지스터(IR)는 인출된 명령어를 캡처하여 디코드 회로가 안정된 비트를 보도록 합니다. 디코드 단계는 명령어 형식을 식별하고, 레지스터와 즉시값 필드를 추출하고, 인코딩이 유효한지 검사하며, 이후 단계를 위한 제어 정보를 생성합니다. 현대 프로세서는 하나의 문자 그대로인 아키텍처 “IR” 대신 인출 버퍼와 파이프라인 레지스터 여러 곳에 이 역할을 나눌 수 있습니다.

프로그램 카운터는 어디에서 가져올지 답합니다. 디코드 단계는 가져온 비트가 무엇을 뜻하는지 답합니다. 교육용 CPU는 보통 둘 사이에 현재 명령어를 보존하는 플립플롭 뱅크인 명령어 레지스터를 두고, 제어 논리가 그 값을 검사하도록 합니다.

이 모델은 유용하지만 범위를 분명히 해야 합니다. 대부분의 ISA에서 명령어 레지스터는 프로그래머가 볼 수 있는 레지스터가 아닙니다. 고성능 구현은 인출 블록, 큐, 파이프라인 레지스터 또는 디코드된 연산 캐시에 여러 명령어를 동시에 보관할 수 있습니다. “IR”은 교육용 데이터 경로의 역할을 가리키는 이름이지 모든 CPU에 존재하는 보편적인 물리 부품이 아닙니다.

교육용 데이터 경로

작은 다중 사이클 CPU는 다음처럼 나타낼 수 있습니다.

PC → instruction memory → instruction register → field extraction → control

명령어 인출 이벤트에서는 다음 순서가 진행됩니다.

  1. PC가 명령어 주소를 제공합니다.
  2. 명령어 메모리가 명령어 워드 하나를 반환합니다.
  3. 활성 클록 에지에서 IR이 그 워드를 캡처합니다.
  4. 조합 디코드 논리가 저장된 비트를 해석합니다.

IR의 write enable은 새 명령어를 받아들여야 할 때만 활성화됩니다. stall 중에는 IR이 이전 값을 유지합니다. 리셋이나 파이프라인 flush가 발생하면 제어기는 저장된 명령어를 invalid로 표시하거나 해가 없는 연산으로 바꿉니다.

어떤 다중 사이클 데이터 경로는 메모리와 IR 사이에 메모리 데이터 레지스터를 둡니다. 다른 구조는 메모리 출력을 바로 캡처합니다. 어느 순서도 보편적이지 않으며, 선택한 메모리 인터페이스의 타이밍과 핸드셰이크가 필요한 레지스터를 결정합니다.

작은 인코딩 예제

8비트 교육용 ISA가 다음 형식을 정의한다고 가정합니다.

7             5 4             2 1             0
┌──────────────┬───────────────┬───────────────┐
│ opcode [2:0] │ register [2:0]│ mode / small  │
│              │               │ immediate     │
└──────────────┴───────────────┴───────────────┘

정확한 필드는 ISA 설계에서 선택합니다. 8비트만으로 큰 opcode 공간, 서로 독립적인 레지스터 피연산자 두 개, 유용한 즉시값을 동시에 부호화할 수는 없습니다. 그래서 실제 교육용 ISA는 흔히 여러 형식을 사용합니다. 예를 들면 다음과 같습니다.

  • 레지스터 명령어는 소스와 목적지 주소에 비트를 할당할 수 있습니다.
  • load-immediate 명령어는 하위 비트 대부분을 상수로 사용할 수 있습니다.
  • 분기 명령어는 같은 하위 비트를 오프셋으로 해석할 수 있습니다.

디코드 회로는 먼저 형식을 정한 다음 남은 비트를 그 형식에 맞게 해석해야 합니다. 모든 비트에 영구적인 이름 하나씩을 붙이면 잘못된 회로가 됩니다.

고정 위치 필드: RISC-V 예제

기본 RISC-V ISA는 32비트 명령어 형식을 사용합니다. R-type 명령어는 다음과 같습니다.

31         25 24    20 19    15 14   12 11      7 6        0
┌────────────┬────────┬────────┬───────┬─────────┬──────────┐
│   funct7   │   rs2  │   rs1  │ funct3│    rd   │  opcode  │
└────────────┴────────┴────────┴───────┴─────────┴──────────┘

기본 형식 전체에서 레지스터 필드는 일관된 위치를 차지합니다.

opcode = IR[6:0]
rd     = IR[11:7]
funct3 = IR[14:12]
rs1    = IR[19:15]
rs2    = IR[24:20]
funct7 = IR[31:25]

이 슬라이스들은 산술이 아니라 배선입니다. rs1과 rs2는 레지스터 파일의 읽기 주소로 들어가고, rd는 가능한 목적지를 식별하며, opcode와 function 비트의 조합은 연산 계열과 변형을 식별합니다.

즉시값은 더 주의해서 다뤄야 합니다. 여러 RISC-V 형식에서는 즉시값 비트가 의도적으로 명령어 여러 위치에 나뉘어 있습니다. 하드웨어는 명세에 정해진 순서로 비트를 이어 붙이고, 아키텍처가 지정한 부호 비트에서 sign extension을 해야 합니다. 여전히 간단한 조합 배선이지만 언제나 하나의 연속된 슬라이스는 아닙니다.

디코드는 opcode 디코더보다 더 많은 일을 한다

완전한 연산이 여덟 개인 장난감 ISA라면 3-to-8 디코더로 원-핫 신호를 만들 수 있습니다.

Opcode교육용 연산원-핫 결과
000NOPis_nop
001LOADis_load
010STOREis_store
011ADDis_add
100SUBis_sub
101JUMPis_jump
110BRANCH-Zis_branch_z
111HALTis_halt

그 신호에서 다음과 같은 제어식을 유도할 수 있습니다.

RegWrite=is_load∨is_add∨is_sub\text{RegWrite} = \text{is\_load} \lor \text{is\_add} \lor \text{is\_sub} MemWrite=is_store\text{MemWrite} = \text{is\_store}

이는 디코더와 곱항의 합 논리를 소개하는 데 유용하지만, 실제 디코더의 일반적인 구조는 아닙니다.

더 큰 ISA에서는 다음과 같습니다.

  • 기본 opcode가 한 연산이 아니라 형식이나 명령어 계열을 선택하는 경우가 많습니다.
  • 보조 function 필드가 같은 계열의 구성원을 구분합니다.
  • 모드 또는 prefix 상태가 피연산자 크기와 해석을 바꿀 수 있습니다.
  • 일부 비트 패턴은 예약되거나 불법입니다.
  • 권한 수준과 확장 기능의 사용 가능 여부가 명령어의 유효성을 바꿀 수 있습니다.
  • 제어 출력은 보통 명령어마다 전용 선 하나를 두지 않고 인코딩됩니다.

올바른 디코더는 지원하지 않는 인코딩을 우연히 가까운 유효 명령어처럼 처리하지 않고 거부해야 합니다.

디코드가 생성하는 것

출력은 서로 무관한 수십 개 전선보다 하나의 제어 번들로 보는 편이 좋습니다. 교육용 번들에는 다음 필드가 들어갈 수 있습니다.

필드의미
alu_op덧셈, 뺄셈, 논리 연산, 비교 또는 통과 동작 선택
src_a / src_b레지스터, PC, 즉시값 또는 상수 피연산자 선택
reg_write뒤의 적절한 단계에서 레지스터 파일 쓰기 허용
mem_read / mem_write데이터 메모리 연산 요청
result_sourceALU, 메모리, PC 상대 값 또는 다른 write-back 소스 선택
branch_kind분기 없음, 조건 분기, 직접 점프 또는 간접 점프 선택
immediate다시 조립하고 확장한 상수
legal현재 머신 상태에서 인코딩이 지원되는지 표시

이 신호들은 파이프라인 validity와 단계 타이밍으로 한정해야 합니다. 디코드에서 조합 신호 reg_write = 1이 나왔다고 레지스터 파일을 즉시 갱신해도 된다는 뜻은 아닙니다. 명령어가 flush나 fault 없이 지정된 단계에 도달했을 때만 쓰기가 일어납니다.

고정 길이와 가변 길이 명령어

명령어 길이는 디코드 문제의 앞부분을 바꿉니다.

고정 길이 예제: Arm A64

Arm A64 명령어는 32비트 폭입니다. 인출 단계가 정렬된 워드를 제공하면 필드 위치를 바로 검사할 수 있습니다. 고정 길이는 명령어 경계를 찾기 쉽게 하지만 모든 연산이나 즉시값 인코딩까지 단순하게 만들지는 않습니다.

압축 명령어를 포함한 RISC-V

기본 RISC-V 명령어는 32비트입니다. 선택적인 압축 확장은 16비트 명령어를 추가하고, 아키텍처는 더 긴 인코딩을 위한 길이 인코딩 방식도 예약합니다. 인출 회로는 PC를 진행하고 다음 명령어를 정렬하기 전에 다음 명령어가 2바이트, 4바이트 또는 지원되는 다른 길이인지 알아내야 합니다.

가변 길이 예제: x86

x86 명령어는 1바이트에서 15바이트까지이며, 레거시 또는 현대적인 prefix 인코딩, opcode 바이트, ModR/M 및 SIB 주소 지정 바이트, displacement, 즉시 데이터를 포함할 수 있습니다. 어느 부분이 존재하는지는 앞선 바이트에 따라 달라집니다.

따라서 현대 x86 프런트엔드는 의미 디코드 전이나 그와 동시에 명령어 경계와 길이를 디코드해야 합니다. 구현 방식은 서로 다릅니다. 모든 프로세서가 하나의 고정된 블록 크기로 인출하거나, 같은 큐 구조를 사용하거나, 모든 명령어를 같은 경로에서 변환한다고 주장하면 안 됩니다. Intel 아키텍처 매뉴얼은 소프트웨어에 보이는 인코딩을 정의하지만 정확한 파이프라인은 마이크로아키텍처마다 다릅니다.

고정 길이와 가변 길이는 RISC와 CISC의 동의어가 아닙니다. 하나의 인코딩 속성을 설명하는 말입니다.

하드와이어드 제어와 순차 제어

작은 CPU는 부울 논리에서 제어 신호를 직접 만들 수 있습니다. 다중 사이클 제어기는 상태 머신이나 여러 내부 단계를 기술하는 항목을 가진 control store를 사용할 수도 있습니다. 실제 프로세서는 빠른 디코드 경로와 더 복잡한 연산을 위한 순차 처리 경로를 결합할 수 있습니다.

유용한 구분은 다음과 같습니다.

  • 조합 디코드는 현재 필드를 초기 제어 표현으로 매핑합니다.
  • 순차 제어는 시간에 걸쳐 여러 내부 동작을 내보냅니다.

어느 방법도 자동으로 “RISC”나 “CISC”가 되는 것은 아니며, 마이크로코드 구현이 본질적으로 느린 것도 아닙니다. 성능은 전체 구현과 워크로드에 달려 있습니다.

파이프라인 제어: stall, flush, validity

여러 명령어가 겹쳐 실행되기 시작하면 명령어 비트는 상태의 일부에 불과합니다. 각 파이프라인 레지스터에는 제어 메타데이터도 필요합니다.

Stall

다음 단계가 명령어를 받을 수 없다면 현재 fetch/decode 레지스터는 데이터를 유지하거나 ready/valid 핸드셰이크에 참여해야 합니다. validity 상태가 멈췄는데 비트만 앞으로 보내면 명령어와 제어의 정렬이 깨집니다.

Flush

잘못 예측한 분기, 예외 또는 리셋은 뒤따르는 더 어린 명령어를 무효화할 수 있습니다. 명령어 비트만 지우는 것보다 valid 비트를 지우는 편이 더 견고합니다. 어떤 ISA에서는 모든 비트가 0인 인코딩도 유효할 수 있기 때문입니다.

의존성 처리

디코드 단계는 hazard 검사를 위해 소스와 목적지 레지스터를 식별할 수 있습니다. 대응 방법은 stall, forwarding 선택, register renaming 또는 구현별 다른 동작일 수 있습니다. 필드 추출은 식별자를 제공하고, 이후 제어가 그 식별자로 무엇을 할지 결정합니다.

흔한 설계 오류

  • 메모리에 언제나 MDR이 필요하다고 가정하기. 보편적인 그림이 아니라 실제 메모리 타이밍과 인터페이스를 근거로 레지스터를 추가하세요.
  • IR의 명령어가 아직 활성 상태인데 IR을 바꾸기. write enable 또는 핸드셰이크를 사용하여 디코드 회로가 안정된 비트를 보도록 하세요.
  • 기본 opcode만 디코드하기. function 필드, 모드, 확장 기능, 유효성 검사가 필요할 수 있습니다.
  • 즉시값 비트를 회로도에 보이는 숫자 순서로 이어 붙이기. ISA의 비트 매핑을 정확히 따른 뒤 지정된 부호 비트에서 확장하세요.
  • 한정되지 않은 디코드 신호로 부수 효과를 직접 구동하기. 쓰기를 파이프라인 validity, 단계 타이밍, 예외/flush 상태로 게이트하세요.
  • 임의의 명령어 패턴으로 리셋하기. valid 비트나 제어기 리셋 상태를 우선 사용하고, 모든 비트 0이 NOP라고 가정하지 마세요.
  • 교육용 파형을 물리 타이밍의 증명으로 취급하기. 모델은 논리적 순서를 보여 줄 수 있지만 실리콘의 setup/hold closure를 증명하지는 않습니다.

교육용 디코더 만들고 검증하기

순차 명령어 실행기를 열고 다음 요소를 찾으세요.

  1. 명령어 저장 소자
  2. 각 필드를 구성하는 전선
  3. 각 필드의 영향을 받는 제어 출력
  4. 그 출력이 아키텍처 상태를 변경하도록 허용되는 지점

그런 다음 간단한 테스트 표를 만드세요.

테스트 분류변경할 항목기대 결과
각 유효 opcode기본 opcode 비트올바른 연산 계열과 부수 효과 enable
Function 변형보조 필드올바른 ALU 또는 주소 지정 변형
레지스터 주소rs / rd 필드올바른 레지스터 포트, 연산은 불변
즉시값 극단0, 최대 양수, 가장 작은 음수올바른 이어 붙이기와 확장
예약 인코딩지원하지 않는 비트 패턴불법 명령어 경로, 의도하지 않은 쓰기 없음
Stall여러 클록 동안 디코드 유지명령어와 제어가 정렬된 채 유지됨
Flush실행 중 명령어 무효화레지스터나 메모리에 부수 효과 없음

예제 하나를 수동으로 전환하는 것은 설명일 뿐 충분한 검증이 아닙니다. 모든 유효 연산 계열과 대표적인 불법 인코딩을 다뤄야 합니다.

CPU 안에서의 위치

프로그램 카운터는 인출 주소를 제공합니다. 디코드는 피연산자와 제어 의도를 식별합니다. 플래그 기반 ISA에서는 CPU 플래그 레지스터가 분기 조건의 한 소스가 될 수 있고, 다른 ISA는 레지스터를 직접 비교합니다. 제어 장치는 교육용 데이터 경로의 순서를 정하고 부수 효과를 한정합니다.

이 요소들을 함께 보면 작은 프로세서의 제어 흐름을 이해할 수 있지만, 어느 하나도 단독으로 “전체 제어 평면”은 아닙니다. 더 큰 기계에서는 캐시, 예측, 예외, 권한, hazard, 파이프라인 상태가 추가 제어를 만듭니다.

주요 참고 자료

다음으로 인출–디코드–실행 심층 분석을 읽고, 교육용 CPU에서 인코딩 하나를 수정한 뒤 디코더·테스트·문서를 함께 갱신해 보세요.

아이디어를 실제로 작동하는 회로로 만들어 보세요.