티스토리 뷰
ATmega128, 마이크로프로세서 (4-2)
유로 청년 2026. 5. 6. 09:34CALL 명령어를 만났을 때, 내부에서 무슨 일이 일어날까?
CPU가 함수를 호출하는 순간, PC 레지스터와 스택 메모리에서 벌어지는 일을 단계별로 살펴봅니다.
1. CALL 명령어를 만나기 전 — Fetch, Decode, Execute 반복
ATmega128의 CPU는 아래 세 단계를 끊임없이 반복하며 프로그램을 실행합니다. 이를 명령어 파이프라인 사이클이라고 합니다.
아래와 같은 간단한 어셈블리 코드가 있다고 가정합니다. 각 명령어는 2바이트(Word) 크기입니다.
0x0202 ADD R16, R17 ; R16 ← R16 + R17
0x0204 MOV R18, R16 ; R18 ← R16
이처럼 CALL 명령어가 없는 경우 PC는 순차적으로 증가하며 코드를 실행합니다.
2. CALL 명령어를 만난 경우 — 스택과 PC의 대변화
Instruction Decoder가 해석한 명령어가 CALL임을 인식하면, 단순히 PC를 증가시키는 것이 아니라 두 가지 중요한 작업이 순서대로 발생합니다.
0x0202 LDI R17, 3 ; 준비 작업
0x0204 CALL add_func ; ← CPU가 이 명령어를 만남! (4Byte) 0x0208 MOV R18, R16 ; CALL 이후 실행될 다음 명령어 (복귀 주소)
...
0x0350 ; add_func 함수의 시작 주소
0x0350 ADD R16, R17 ; 함수 내부
0x0352 RET ; 함수 종료 후 복귀
CALL 명령어는 4바이트이므로, 0x0204에서 읽으면 PC는 0x0208로 증가합니다. 이 0x0208이 바로 복귀 주소입니다.
2-1. 복귀 주소(PC 값)를 스택 메모리에 저장
함수 실행이 끝난 뒤 원래 흐름으로 돌아오려면, 이동하기 직전에 복귀 주소를 어딘가에 저장해야 합니다. ATmega128은 이를 스택(Stack)에 Push합니다.
0x0208을 SRAM 스택 영역에 저장합니다. ATmega128은 16비트 PC를 2바이트로 나눠 순서대로 Push하며, Stack Pointer(SP)는 2 감소합니다.add_func의 주소 0x0350이 들어가고, CPU는 그 주소부터 실행을 시작합니다.CALL 명령어 전후의 스택 변화:
CALL 실행 전
CALL 실행 후
ATmega128의 스택은 높은 주소 → 낮은 주소 방향으로 자랍니다. Push 할 때마다 SP 값이 감소하고, Pop 할 때 SP 값이 증가합니다.
2-2. 이전 Frame Pointer를 스택에 저장 (함수 프롤로그)
CALL 명령어가 복귀 주소를 스택에 Push하는 것은 하드웨어가 자동으로 수행합니다. 이후에는 컴파일러가 생성한 함수 프롤로그(Prologue) 코드가 실행되어, 현재 Frame Pointer(FP) 값을 스택에 추가로 저장합니다.
0x0350 PUSH R29 ; ← 이전 FP(Y 레지스터 상위) 저장 0x0352 PUSH R28 ; ← 이전 FP(Y 레지스터 하위) 저장 0x0354 IN R28, SPL ; 새 FP ← 현재 SP로 설정
0x0356 IN R29, SPH ; (Y 레지스터 = Frame Pointer)
0x0358 ; 이제 함수 본문 실행...
0x0358 ADD R16, R17 ; 함수 본문
AVR에서 Frame Pointer는 Y 레지스터(R28:R29)를 사용합니다.
프롤로그 완료 후 스택의 최종 상태:
함수 내에서 지역 변수나 매개변수에 접근할 때 Frame Pointer를 기준으로 상대 주소를 계산합니다. 이전 Frame Pointer를 스택에 기억해두지 않으면, 함수 종료 후 이전 함수의 스택 프레임으로 올바르게 복원할 방법이 없어집니다.
3. 함수가 끝나면 — RET 명령어로 복귀
함수의 마지막에는 반드시 RET 명령어가 있습니다. CALL과 정확히 반대 과정을 수행합니다.
POP R28, POP R29)0x0208을 꺼내어(Pop) PC에 넣습니다. CPU는 0x0208 주소부터 실행을 재개합니다.0x0208의 MOV R18, R16 명령어를 이어서 실행합니다.전체 Flash 메모리 흐름 한눈에 보기:
🎁 추가적으로 알면 좋은 부분 (심화 학습용)
CALL vs RCALL — 무엇이 다를까?
CALL
- Flash 메모리 어느 주소든 호출 가능
- 명령어 크기 4 Byte
- 절대 주소(Absolute Address) 사용
- 큰 프로젝트, 원거리 함수 호출에 적합
RCALL
- 현재 PC 기준 ±2K 범위 내 함수만 호출
- 명령어 크기 2 Byte
- 상대 주소(Relative Address) 사용
- 작은 프로젝트, 실행 속도 중요 시 적합
하버드 아키텍처 — Flash와 SRAM의 분리
ATmega128은 하버드 아키텍처를 채택하여, 프로그램 메모리와 데이터 메모리를 물리적으로 분리합니다.
Flash 메모리
프로그램(명령어)이 저장되는 공간
PC가 항상 이 영역을 가리킴
읽기 전용, 비휘발성
CPU (ATmega128)
별도의 버스로
Flash와 SRAM에
동시 접근 가능
SRAM
데이터(변수, 스택)가 저장되는 공간
SP가 항상 이 영역을 가리킴
읽기/쓰기, 휘발성
핵심 정리
복귀 주소 Push
CALL 실행 시 다음 명령어 주소(PC+4)가 SRAM 스택에 자동 저장됩니다.
Frame Pointer 저장
컴파일러의 프롤로그 코드가 이전 FP를 스택에 Push하여 이전 스택 프레임 기준점을 보존합니다.
스택은 아래로 자람
Push 시 SP 감소, Pop 시 SP 증가. 높은 주소 → 낮은 주소 방향입니다.
RET로 복귀
함수 끝의 RET 명령어가 스택에서 복귀 주소를 Pop하여 PC에 적재, 원래 흐름으로 돌아옵니다.
🎬 CALL 명령어 전체 흐름 — 인터랙티브 시뮬레이션
아래 버튼을 눌러 CALL 명령어가 실행되는 과정을 단계별로 직접 확인해보세요.
📊 CALL ~ RET 전체 흐름 인포그래픽
CALL 명령어가 실행되고 RET로 복귀하기까지의 전체 과정을 한 장으로 정리했습니다.
🔗 중첩 함수 호출 — 스택이 계속 쌓이면?
함수 안에서 또 다른 함수를 호출하면, 스택에 스택 프레임이 차곡차곡 쌓입니다. 이것이 바로 재귀 호출이 가능한 이유이고, 너무 깊이 중첩되면 스택 오버플로우가 발생하는 이유이기도 합니다.
main:
CALL func_A ; 스택에 복귀주소①(main+) Push
...
; func_A에서 func_B 호출
func_A:
; 프롤로그: 이전 FP(main의 FP) Push
CALL func_B ; 스택에 복귀주소②(func_A+) Push
...
RET ; 복귀주소① 꺼내어 main으로 복귀
func_B:
; 프롤로그: 이전 FP(func_A의 FP) Push
...
RET ; 복귀주소② 꺼내어 func_A로 복귀
func_B 실행 중 스택 상태 (위 = 낮은 주소, 스택 top):
ATmega128의 SRAM은 4KB로 제한되어 있습니다. 함수 호출이 너무 깊이 중첩되거나 재귀 호출이 끝없이 반복되면, SP가 계속 감소하여 다른 데이터 영역을 침범하게 됩니다. 이것이 스택 오버플로우이며 임베디드 시스템에서 매우 치명적인 버그입니다.
📋 전체 요약 — 한눈에 보기
| 단계 | 수행 주체 | PC 변화 | SP 변화 | 설명 |
|---|---|---|---|---|
| ① Fetch | 하드웨어 (CU) | 0x0204 → 0x0208 | 변화 없음 | CALL 명령어 읽기, PC 자동 증가 (+4) |
| ② Decode | 하드웨어 (CU) | 유지 (0x0208) | 변화 없음 | IR의 명령어 해석 → CALL 확인 |
| ③ Push 복귀주소 | 하드웨어 | 유지 (0x0208) | −2 감소 | 0x0208을 SRAM 스택에 Push |
| ④ PC Jump | 하드웨어 | 0x0208 → 0x0350 | 변화 없음 | PC ← add_func 시작 주소 |
| ⑤ 프롤로그 | 소프트웨어 (컴파일러) | 0x0350 → 0x0354 | −2 감소 | 이전 FP(Y reg) Stack에 Push, 새 FP 설정 |
| ⑥ 함수 본문 | 소프트웨어 | 순차 증가 | 지역변수 따라 변화 | 실제 기능 수행 |
| ⑦ 에필로그+RET | 소프트웨어+하드웨어 | → 0x0208 (복귀) | +4 증가 | FP 복원, Stack에서 복귀주소 Pop → PC 적재 |
| ⑧ 복귀 완료 | 하드웨어 | 0x0208 →순차증가 | CALL 전과 동일 | 원래 흐름 재개 (MOV R18, R16) |
'Embedded Programming (AVR) > Atmega 128A (이론, 경희대 강의 정리)' 카테고리의 다른 글
| ATmega128, 마이크로프로세서 (4-1) (0) | 2026.05.05 |
|---|---|
| ATmega128A의 아키텍처 (0) | 2026.05.03 |
| ATmega128, 마이크로프로세서 (3-5) (0) | 2026.05.02 |
| ATmega128, 마이크로프로세서 (3-4) (0) | 2026.05.02 |
| ATmega128, 마이크로프로세서 (3-3) (0) | 2026.05.01 |
- Total
- Today
- Yesterday
- soc 설계
- FND
- test bench
- Algorithm
- Edge Detector
- atmega 128a
- Pspice
- gpio
- LED
- dataflow modeling
- stop watch
- D Flip Flop
- KEYPAD
- java
- behavioral modeling
- i2c 통신
- half adder
- pwm
- DHT11
- ring counter
- BASYS3
- vivado
- hc-sr04
- uart 통신
- structural modeling
- verilog
- Recursion
- prescaling
- Linked List
- ATMEGA128A
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
