Skip to Content
독학사독학사 3단계임베디드시스템06. 파이프라인과 명령 수행, 인터럽트 지연

이번 문서의 목표: 이 파일을 다 읽으면 ARM 파이프라인이 왜 필요하고 어떻게 동작하는지 단계별로 설명할 수 있고, 파이프라인 때문에 생기는 “PC가 실제보다 앞서 보이는” 함정을 계산으로 풀 수 있으며, 인터럽트 지연(interrupt latency)을 구성 요소별로 나눠 실제 사이클 수로 계산할 수 있다.

왜 명령어를 한 번에 하나씩 끝내지 않고 파이프라인으로 처리할까

05편까지는 예외가 발생했을 때 CPU 내부에서 레지스터와 모드가 어떻게 바뀌는지를 다뤘다. 그런데 “CPU가 명령어를 처리한다”는 것 자체가 실제로는 인출(fetch)→해독(decode)→실행(execute)이라는 여러 단계를 거치는 일이다. 만약 한 명령어가 이 세 단계를 모두 마친 뒤에야 다음 명령어의 인출을 시작한다면, 매 명령어마다 세 단계의 시간을 온전히 다 써야 하므로 처리량이 낮다.

파이프라인(pipeline)은 공장의 조립 라인처럼, 한 명령어가 해독 단계에 있는 동안 다음 명령어를 이미 인출해 두는 방식으로 여러 명령어의 처리 단계를 겹쳐 실행하는 기법이다. 이렇게 하면 각 단계를 담당하는 하드웨어가 매 사이클 쉬지 않고 일하게 되어, 전체 처리량이 크게 늘어난다.

ARM의 3단계 파이프라인

쉽게 말하면: ARM7TDMI와 Cortex-M 계열은 기본적으로 인출-해독-실행 세 단계를 겹쳐서 처리하는 3단계 파이프라인을 쓴다.

가장 단순한 형태인 ARM7TDMI(및 Cortex-M0 계열)의 파이프라인은 다음 3단계로 구성된다.

단계이름하는 일
1인출(Fetch)메모리에서 명령어를 읽어온다
2해독(Decode)읽어온 명령어가 무슨 연산인지 해석하고 레지스터를 읽는다
3실행(Execute)ALU 연산·메모리 접근·레지스터 기록 등 실제 동작을 수행한다
클록 사이클12345
명령어 1인출해독실행
명령어 2인출해독실행
명령어 3인출해독실행

이 표에서 보듯, 명령어 1이 실행 단계에 있는 시점(사이클 3)에 명령어 2는 이미 해독 단계, 명령어 3은 인출 단계에 들어가 있다. 세 개의 명령어가 동시에 파이프라인 안에서 서로 다른 단계를 밟고 있는 것이다. 이렇게 겹쳐 처리한 덕분에, 파이프라인이 가득 찬 이후에는 매 사이클마다 명령어 하나씩 완료되는 것처럼 보인다(이상적인 경우 기준이며, 실제로는 뒤에서 다룰 분기 등으로 지연이 생긴다).

Cortex-M4처럼 더 발전한 코어는 3단계보다 세분화된 파이프라인을 쓰기도 하지만, 독학사 시험에서 다루는 핵심은 “왜 파이프라인이 필요한가”와 “파이프라인 때문에 생기는 함정 두 가지”이므로, 이 문서는 그 두 함정 — PC 오프셋과 분기 패널티 — 을 집중적으로 다룬다.

함정 1: 파이프라인 때문에 PC가 실제보다 앞서 보인다

쉽게 말하면: 명령어 하나가 실행 단계에 있을 때, 그 명령어를 기준으로 한 PC 값은 이미 두 명령어 뒤(8바이트 앞선 주소)를 가리키고 있다.

이것이 클래식 ARM(ARM7TDMI, ARMv4T 기반)에서 가장 유명한 시험 함정이다. 3단계 파이프라인이 항상 세 개의 명령어를 동시에 처리하고 있다는 것은, 어떤 명령어가 실행 단계에 있는 바로 그 순간에 PC는 이미 그 명령어의 주소보다 명령어 2개만큼 앞선 주소(인출 단계에 있는 명령어의 주소)를 가리키고 있다는 뜻이다.

ARM 명령어는 32비트(4바이트) 고정 길이이므로, 이 어긋남은 정확히 다음과 같다.

PC가 가리키는 주소=실행 중인 명령어의 주소+8\text{PC가 가리키는 주소} = \text{실행 중인 명령어의 주소} + 8
  • PC\text{PC}: 프로그램 카운터(Program Counter)의 현재 값
  • 88: 명령어 2개 앞선 만큼의 바이트 오프셋(명령어 1개 = 4바이트 × 2단계 앞섬)

예를 들어 주소 0x1000에 있는 명령어가 지금 막 실행 단계에 들어갔다면, 그 순간 PC 레지스터의 실제 값은 0x1000 + 8 = 0x1008이다. 이 어긋남을 몰라서 생기는 실제 문제가 있다. MOV PC, r0처럼 현재 PC 값을 읽어서 다른 레지스터에 저장한 뒤, 그 값을 기준으로 분기 주소를 계산하는 코드를 짤 때, “지금 실행 중인 이 명령어의 주소”라고 착각하고 오프셋을 계산하면 실제로는 8만큼 어긋난 주소로 튀게 된다.

시험 함정: 이 +8 오프셋은 ARM 상태(32비트 명령어)일 때의 값이다. Thumb 상태(16비트 명령어)에서는 명령어 2개 앞섬이 2×2=42 \times 2 = 4바이트이므로 오프셋이 +4가 된다. “PC는 항상 +8”이라고 암기하면 Thumb 모드 문제에서 틀린다.

이 때문에 05편에서 다룬 예외 진입 시 LR에 저장하는 “복귀 주소”도 예외 종류에 따라 서로 다른 보정값을 뺀다. 예를 들어 IRQ 예외가 발생하면 하드웨어는 LR에 현재 PC(인출 중이던 명령어 주소) - 4를 저장하는데, 이는 인터럽트가 발생한 시점에 실행이 끝나지 못한 명령어로 정확히 되돌아가기 위해 파이프라인 어긋남만큼을 미리 보정해 둔 값이다.

함정 2: 분기 명령은 파이프라인을 비운다(파이프라인 플러시)

쉽게 말하면: 분기(branch) 명령이 실행되면 이미 인출·해독해 둔 다음 명령어들이 전부 쓸모없어져 버려지고, 파이프라인을 처음부터 다시 채워야 한다.

파이프라인은 “다음 명령어는 현재 명령어 바로 다음 주소에 있다”는 가정 아래 미리 인출·해독을 진행한다. 그런데 분기(branch) 명령어가 실행되면 이 가정이 깨진다. 분기 목적지 주소는 실행 단계에 가서야 확정되는데, 그 시점에는 이미 잘못된(분기 이전 순서를 따라간) 명령어 두 개가 인출·해독 단계에 들어가 있는 상태다. 이 잘못 들어간 명령어들은 실행되면 안 되므로 파이프라인에서 버려지고(파이프라인 플러시, pipeline flush), CPU는 새로 계산된 분기 목적지 주소부터 인출을 다시 시작해야 한다.

3단계 파이프라인 기준으로, 분기가 성공하면 보통 2사이클의 패널티(penalty, 지연)가 발생한다. 잘못 인출·해독된 두 단계만큼을 다시 채워야 하기 때문이다. 이 패널티 때문에 조건 분기가 많은 코드는 파이프라인이 없는 이상적인 경우보다 실제 실행 시간이 늘어난다.

시험 함정: 분기 패널티는 파이프라인 단계 수와 직결된다. 3단계 파이프라인이면 2사이클, 더 깊은 파이프라인(5단계, 8단계 등)일수록 잘못 채워진 단계가 많아 패널티도 커진다. “파이프라인이 깊을수록 항상 빠르다”는 생각은 분기가 잦은 코드에서는 틀릴 수 있다.

인터럽트 지연이란 무엇인가

쉽게 말하면: 인터럽트 지연은 “인터럽트가 발생한 순간”부터 “그 인터럽트를 처리하는 첫 명령어가 실제로 실행되는 순간”까지 걸리는 시간이다.

인터럽트 지연(interrupt latency)은 외부 장치가 인터럽트 요청 신호를 보낸 시각부터, CPU가 그 요청에 대응하는 인터럽트 서비스 루틴(ISR, Interrupt Service Routine)의 첫 번째 명령어를 실제로 실행하기 시작하는 시각까지의 시간을 말한다. 실시간 시스템(13편에서 자세히 다룬다)에서는 이 지연이 얼마나 짧고 예측 가능한지가 시스템의 신뢰도를 좌우하므로, 독학사 시험에서도 지연을 구성하는 요소를 나눠 계산하는 문제가 자주 나온다.

인터럽트 지연은 크게 다음 세 요소의 합으로 계산할 수 있다.

Tlatency=T완료+T저장+T인출T_{latency} = T_{완료} + T_{저장} + T_{인출}
  • TlatencyT_{latency}: 전체 인터럽트 지연 시간
  • T완료T_{완료}: 인터럽트 요청이 들어온 시점에 이미 실행 중이던 명령어(또는 명령어 그룹)를 마무리하는 데 걸리는 시간
  • T저장T_{저장}: 예외 진입 시 레지스터(또는 뱅킹된 레지스터, Cortex-M의 자동 스택 프레임)를 저장하는 데 걸리는 시간
  • T인출T_{인출}: 벡터 테이블을 읽어 ISR의 시작 주소를 확정하고 그 위치에서 명령어를 인출하는 데 걸리는 시간

클래식 ARM 기준 예시 계산

클록 주파수가 50메가헤르츠(MHz)인 ARM7TDMI 코어에서, 다음 조건으로 인터럽트 지연을 계산해 보자.

  • 인터럽트 요청 시점에 실행 중이던 명령어가 완료될 때까지 최대 3사이클이 걸린다고 가정한다(가장 긴 메모리 접근 명령어 기준).
  • 예외 진입 시 LR·SPSR 저장과 모드 전환에 2사이클이 걸린다.
  • 벡터 테이블에서 목적지 주소를 읽고 ISR의 첫 명령어를 인출하는 데 2사이클이 걸린다.
Tlatency=3+2+2=7 사이클T_{latency} = 3 + 2 + 2 = 7 \text{ 사이클}

클록 주파수가 50MHz이므로 1사이클의 시간은 다음과 같다.

Tcycle=150×106=20 나노초T_{cycle} = \frac{1}{50 \times 10^6} = 20 \text{ 나노초}
  • TcycleT_{cycle}: 클록 한 번이 걸리는 시간(주기)
  • 50×10650 \times 10^6: 50MHz를 헤르츠 단위로 풀어 쓴 값(초당 클록 횟수)
Tlatency(시간)=7×20ns=140 나노초T_{latency}(\text{시간}) = 7 \times 20\text{ns} = 140 \text{ 나노초}

즉 이 조건에서는 인터럽트가 발생한 뒤 ISR의 첫 명령어가 실행되기까지 140나노초가 걸린다. 이 계산에서 핵심은 “완료 대기 시간”이 고정값이 아니라 인터럽트가 언제 들어오느냐에 따라 달라지는 값이라는 점이다. 만약 인터럽트가 하필 가장 긴 명령어(예: 여러 레지스터를 한 번에 적재하는 LDM)가 막 시작된 직후에 들어온다면, 그 명령어가 끝날 때까지 기다려야 하므로 T완료T_{완료}가 늘어난다.

시험 함정: 인터럽트 지연의 최댓값은 “가장 긴 명령어가 실행되는 도중에 인터럽트가 들어온 경우”를 기준으로 계산한다. 평균적인 명령어 실행 시간으로 계산하면 최악의 경우(worst case)를 놓치게 되는데, 실시간 시스템 설계에서는 평균이 아니라 최악의 경우 지연이 기준이 된다.

Cortex-M의 인터럽트 지연: 고정 지연과 테일 체이닝

Cortex-M 계열은 인터럽트 응답 속도를 예측 가능하게 만들기 위해 하드웨어 차원에서 지연을 최소화하고 고정했다. 대표적으로 Cortex-M3/M4는 다음과 같은 특징을 갖는다(참고자료의 Cortex-M4 TRM 참고).

  • 고정된 최단 지연: 대기 상태(wait state) 없는 메모리 조건에서, 인터럽트 발생부터 ISR 첫 명령어 실행까지 12사이클로 고정되어 있다. 05편에서 다룬 자동 스택 저장(R0~R3, R12, LR, PC, xPSR을 하드웨어가 자동으로 push)이 이 12사이클 안에 포함된다.
  • 테일 체이닝(tail-chaining): 하나의 ISR 처리가 끝나자마자 대기 중인 다음 인터럽트가 있으면, 스택을 다시 팝(pop)했다가 곧바로 또 푸시(push)하는 낭비 없이 바로 다음 ISR로 넘어간다. 이 경우 지연이 12사이클보다 훨씬 짧아진다(보통 6사이클 수준).
  • 레이트 어라이벌(late-arrival): 현재 예외 처리 진입 절차가 진행되는 도중에 더 우선순위 높은 인터럽트가 새로 들어오면, 이미 시작한 스택 저장 절차를 그대로 활용하면서 목적지만 새 인터럽트의 ISR로 바꿔치기해 처리한다.
Tlatency, Cortex-M=12×TcycleT_{latency,\ Cortex\text{-}M} = 12 \times T_{cycle}
  • 1212: 대기 상태 없는 조건에서 고정된 지연 사이클 수
  • TcycleT_{cycle}: 코어 클록 한 사이클의 시간

만약 이 Cortex-M 코어가 100MHz로 동작한다면 Tcycle=1100×106=10nsT_{cycle} = \dfrac{1}{100 \times 10^6} = 10\text{ns}이므로, 인터럽트 지연은 12×10ns=12012 \times 10\text{ns} = 120나노초로 고정된다. 클래식 ARM과 달리 “실행 중이던 명령어가 언제 끝나는지”에 따라 크게 흔들리지 않는다는 점이 실시간 응답성 측면에서 중요한 차이다.

자주 틀리는 점

  • 파이프라인의 PC 오프셋(+8 또는 +4)을 “명령어가 실행되는 주소”와 혼동해, 분기 주소 계산이나 예외 복귀 주소 계산에서 부호나 크기를 반대로 적용하는 실수가 흔하다.
  • 분기 패널티를 “항상 고정된 절대 사이클 수”로 암기하고 파이프라인 단계 수가 다른 코어에도 그대로 적용하는 오류가 있다. 패널티는 파이프라인 깊이에 따라 달라진다.
  • 인터럽트 지연 계산에서 벡터 테이블 인출 시간이나 레지스터 저장 시간 중 하나를 빠뜨리고 “명령어 완료 대기 시간”만으로 답을 내는 경우가 많다. 세 요소를 모두 더해야 한다.

핵심 정리

  • ARM은 인출-해독-실행 3단계 파이프라인으로 여러 명령어의 처리 단계를 겹쳐 실행해 처리량을 높인다.
  • 파이프라인 때문에 실행 중인 명령어 기준 PC 값은 ARM 상태에서 +8, Thumb 상태에서 +4만큼 앞서 보인다.
  • 분기 명령어는 잘못 인출된 명령어를 버리는 파이프라인 플러시를 일으키며, 3단계 파이프라인 기준 2사이클의 분기 패널티가 발생한다.
  • 인터럽트 지연은 (실행 중이던 명령어 완료 시간) + (레지스터 저장 시간) + (벡터 인출 시간)의 합이며, 실시간 시스템에서는 최악의 경우 지연을 기준으로 설계한다.
  • Cortex-M은 대기 상태 없는 조건에서 12사이클의 고정 지연을 보장하며, 테일 체이닝과 레이트 어라이벌로 연속·긴급 인터럽트 처리를 최적화한다.

마무리 복습

문제 14지선다
ARM7TDMI의 3단계 파이프라인을 구성하는 순서로 옳은 것은?
문제 24지선다
ARM 상태(32비트 명령어)로 동작 중일 때, 어떤 명령어가 실행 단계에 있는 순간의 PC 값에 대한 설명으로 옳은 것은?
문제 44지선다
분기(branch) 명령어가 실행될 때 3단계 파이프라인에서 일어나는 일로 옳은 것은?
문제 54지선다
인터럽트 지연(T_latency)을 구성하는 세 요소로 옳게 짝지어진 것은?
문제 64지선다
클록 주파수 100메가헤르츠(MHz)인 Cortex-M 코어가 대기 상태 없이 12사이클의 고정 인터럽트 지연을 갖는다고 할 때, 실제 지연 시간은?
문제 74지선다
Cortex-M의 테일 체이닝(tail-chaining)에 대한 설명으로 옳은 것은?

참고 자료

Last updated on