명령어 사이클
명령어 사이클은 CPU가 하나의 명령어를 처리하기 위해 반복하는 일련의 단계입니다. 인출(Fetch) → 해독(Decode) → 실행(Execute)의 흐름이 끊임없이 반복되며, 컴퓨터가 프로그램을 실행하는 근본 동작 원리입니다.
1단계
인출 (Fetch)
PC가 가리키는 메모리 주소에서 명령어를 가져옵니다. MAR → 메모리 → MDR → IR 순서로 전달되고, PC는 자동으로 증가합니다.
2단계
해독 (Decode)
제어 유닛(CU)이 IR에 저장된 명령어를 분석합니다. 연산 코드(opcode)와 피연산자(operand)를 분리해 수행할 동작을 결정합니다.
3단계
실행 (Execute)
ALU가 산술·논리 연산을 수행하거나, 메모리 접근(LOAD/STORE)이 일어납니다. 결과는 레지스터 또는 메모리에 저장됩니다.
핵심 레지스터
PC / MAR / MDR / IR
PC는 다음 명령어 주소, MAR은 접근할 주소, MDR은 전송 데이터, IR은 현재 실행 중인 명령어를 보관합니다.
명령어 하나에 걸리는 시간
프로그램의 실행 시간은 실행한 명령어 수 × 명령어당 평균 클럭 수(CPI) × 클럭 한 주기의 길이로 나뉘어 설명됩니다. 명령어 사이클은 이 중 CPI를 이해하는 출발점입니다. 레지스터끼리 더하는 명령어와 메모리에서 값을 읽어 오는 명령어는 같은 세 단계를 거쳐도 걸리는 클럭 수가 다르고, 이 차이를 줄이려는 노력이 파이프라이닝, 캐시, 분기 예측, 비순차 실행으로 이어집니다.
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
명령어 사이클의 단계를 설명해 보세요.
Fetch에서 PC가 가리키는 주소를 MAR에 넣어 메모리에서 명령어를 읽어 MDR을 거쳐 IR에 담고 PC를 다음 명령어로 옮깁니다. Decode에서 CU가 opcode와 피연산자를 해석하고, Execute에서 ALU 연산이나 메모리 접근을 수행합니다.
면접 질문
분기·점프 명령어는 사이클에 어떤 영향을 주나요?
실행 단계에서 PC를 새 주소로 덮어씁니다. 그러면 다음 Fetch가 그 주소에서 시작합니다. 파이프라인 CPU에서는 이 때문에 이미 가져온 명령어를 버려야 할 수 있어 분기 예측이 필요합니다.
면접 질문
파이프라이닝은 명령어 사이클을 어떻게 바꾸나요?
명령어 하나가 끝나는 데 걸리는 시간이 줄지는 않지만, 단계를 겹쳐 여러 명령어를 동시에 진행하므로 단위 시간당 끝나는 명령어 수(처리량)가 늘어납니다.
면접 질문
CPI와 IPC는 무엇인가요?
CPI는 명령어 하나에 평균 몇 클럭이 걸리는지, IPC는 클럭 한 번에 평균 몇 개의 명령어가 끝나는지입니다. 서로 역수 관계이며, 같은 프로그램을 같은 클럭 속도로 돌릴 때는 IPC가 높은 CPU가 더 빠릅니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
C — 명령어 사이클 시뮬레이션
// 명령어 사이클: Fetch → Decode → Execute 반복 typedef struct { int memory[256]; // 명령어 + 데이터 동일 메모리 (폰 노이만) int PC; // Program Counter — 다음 명령어 주소 int MAR; // Memory Address Register int MDR; // Memory Data Register int IR; // Instruction Register int ACC; // Accumulator } CPU; void run(CPU *cpu) { while (1) { /* ① FETCH — PC → MAR, MEM[MAR] → MDR → IR, PC++ */ cpu->MAR = cpu->PC; cpu->MDR = cpu->memory[cpu->MAR]; cpu->IR = cpu->MDR; cpu->PC++; /* ② DECODE — opcode(상위 8비트) / operand(하위 8비트) 분리 */ int opcode = (cpu->IR >> 8) & 0xFF; int operand = cpu->IR & 0xFF; /* ③ EXECUTE */ if (opcode == 0x01) cpu->ACC = cpu->memory[operand]; // LOAD else if (opcode == 0x02) cpu->memory[operand] = cpu->ACC; // STORE else if (opcode == 0x03) cpu->ACC += cpu->memory[operand]; // ADD else if (opcode == 0x04) cpu->ACC -= cpu->memory[operand]; // SUB else if (opcode == 0xFF) break; // HALT } }
MAR/MDR은 CPU-메모리 간 속도 차이를 흡수하는 버퍼 레지스터입니다. PC는 Fetch마다 자동 증가하며, 점프 명령어만 이 값을 강제로 바꿉니다.
Python — 인터럽트 포함 확장 사이클
# Fetch → Decode → Execute → Interrupt Check 전체 사이클 def run(cpu): while True: # ① FETCH cpu['MAR'] = cpu['PC'] cpu['MDR'] = cpu['mem'][cpu['MAR']] cpu['IR'] = cpu['MDR'] cpu['PC'] += 1 # ② DECODE opcode = (cpu['IR'] >> 8) & 0xFF operand = cpu['IR'] & 0xFF # ③ EXECUTE if opcode == 0x01: cpu['ACC'] = cpu['mem'][operand] # LOAD elif opcode == 0x02: cpu['mem'][operand] = cpu['ACC'] # STORE elif opcode == 0x03: cpu['ACC'] += cpu['mem'][operand] # ADD elif opcode == 0xFF: break # HALT # ④ INTERRUPT CHECK — 사이클과 사이클 사이에서만 처리 if interrupt_pending(): stack.push(cpu['PC']) # 현재 PC 저장 cpu['PC'] = isr_address # ISR로 점프
인터럽트는 반드시 Execute 완료 후에 체크합니다. 명령어 실행 도중에는 처리하지 않아야 레지스터 상태의 일관성이 보장됩니다. 실제로는 PC(EIP)뿐 아니라 범용 레지스터 전체, EFLAGS 등을 스택에 저장해야 ISR 종료 후 원래 상태로 완전히 복귀할 수 있습니다. 위 코드는 흐름 설명을 위해 PC 저장만 표현한 단순화 예시입니다.
Q1
명령어 사이클의 기본 순서는?
Q2
Fetch 단계에서 사용되는 레지스터는?
Q3
Decode 단계에서 하는 일은?