임베디드 레시피 Chapter 2. ARM
본문에서는 ARM의 종류, 버전, 특징에 대한 상세한 얘기는 다루지 않고 중요한 부분만 배웁니다.
ARM 프로세서의 동작방식을 이해하면, 전체 컴퓨터구조 및 다른 여러 프로세서에 대해서도 자신감이 붙습니다.
ARM 공부는 마치 대중목욕탕 뜨거운 물과 같습니다. 아이들은 발끝 몇 번 담가보고 도망가는 너무 뜨거운 물이라도 어른들은 ‘시원하다’며 아무렇지도 않은 얼굴로 곧바로 들어갑니다. 마찬가지로 ARM은 잘 모르는 사람에게는 어디서부터 시작해야 할지 막막한 세상이지만, 막상 한 번 알고 나면 별거 아닙니다.
ARM은 RISC 머신입니다. RISC는 명령어 길이가 고정돼있고, 명령어 종류가 많지 않고, Addressing 방법이 적어서 chip 복잡도가 낮고 크기도 작아지고, 전력소비도 줄어듭니다. Hard wired 방식을 사용해 명령어 하나를 전용으로 담당하는 회로를 만들어 디자인합니다.
1. ARM의 동작 Modes
Mode | CPSR | Description |
|
USER | 0x10 | Normal program execution mode | |
FIQ | 0x11 | When a high priority (fast) interrupt is raised | 우선순위가 높은 Fast interrupt 발생 |
IRQ | 0x12 | When a low priority (normal) interrupt is raised | Interrupt 발생 |
SVC (Supervisor) | 0x13 | A protected mode when a SWI instruction is executed | Power-on이나 reset이 발생한 경우에 진입 |
ABT (Abort) | 0x17 | Used to handle memory access violations | Access 하려는 주소가 접근할 수 없는 주소거나 (Data abort) 명령어를 fetch할 수 없거나 (Prefetch abort) |
UND (Undefined) | 0x1B | Used to handle undefined instructions | Instruction을 decode 했는데 무슨 명령어인지 알 수 없을 때 |
SYS (System) | 0x1F | Run privileged operating system tasks | 너무 약한 권한을 가진 USR 모드를 보완하기 위한 priviledged 모드. |
ARM은 총 7가지 mode로 동작하고 크게 User mode와 Privileged mode로 나뉩니다.
- 각 Mode의 특성을 잘 이해한다면, 조금 더 적합한 mode를 선택해서 SW를 구성할 수 있게 됩니다.
- Privileged mode는 IRQ/FIQ 등의 인터럽트 사용 가능 유무를 포함해 다양한 시스템 기능에 접근한 뒤 설정할 수 있습니다.
- Privileged mode 간에 mode 변경이 자유롭지만, User mode → privileged mode는 불가능합니다. 반드시 인터럽트나 예외같은 트랩이 발생해야만 모드가 변경됩니다.
ARM의 default mode는 ‘SVC(Supervisor)’ mode입니다. 시스템에 전원이 인가된 직후 부팅될 때 ARM의 모든 기능을 사용하기 위해서 SVC 모드로 진입해야 하기 때문입니다.
2. ARM 레지스터와 context
ARM core는 총 37개 레지스터를 가지고 있고, 동작 mode가 바뀌면 사용하는 레지스터 set도 위와 같이 바뀝니다.
- 원래라면 18개씩 7개 동작mode, 그러니까 총 126개의 레지스터를 가지고 있어야 하지만, 특별한 목적과 역할을 가진 레지스터들만 각 mode 별로 따로 있고 나머지는 모두 공용으로 사용해서 총 37개를 가집니다.
- R0~R15 (16개) + CPSR (1개) +
USR/SYS 제외 5개 mode 전용 R13, R14, SPSR (5 ×3 = 15개) + FIQ 전용 R8~R12 레지스터 (5개) - FIQ는 고유한 R8~R12레지스터를 추가로 갖고있습니다. 다른 mode보다 레지스터를 많이 갖고 있는 이유는 FIQ가 우선순위가 높고 빠른 인터럽트를 처리하기 위한 mode기 때문입니다. 많은 인터럽트 = 빠른 인터럽트 처리이므로 추가 레지스터를 배정했습니다.
- R13 (스택 포인터, Stack pointer)
- 현재 mode가 사용하고 있는 스택의 최상단 주소를 가리킵니다.
- 스택은 주소가 아래로 자라나기 때문에 데이터가 들어갈 때마다 주소는 감소합니다.
- R14 (링크레지스터, Link Register, LR)
- 함수호출, 인터럽트 등 서브루틴으로 이동할 때, 다시 현재 위치로 돌아오기 위해 복귀할 주소를 저장하는 공간입니다.
- 분기 호출 어셈블리 명령어를 수행할 때, LR 레지스터에 복귀주소를 HW적으로 자동으로 저장한 뒤 지정된 operand 주소로 분기합니다.
CPSR (Current Program Status Register)
CPSR (Current Program Status Register)는 정말로 중요한 레지스터로, 현재 시스템의 상태와 동작 mode를 알려주는 역할 뿐만 아니라, 상태를 바꿔주는 역할을 담당합니다.
- 최상단 4-bit의 NZCV bits: 각 bit의 조건에 따라 fetch한 명령어를 실행할지 말지를 결정합니다.
- N(=Negative): 연산 결과가 마이너스일 때 1로 set.
- Z(=Zero): 연산 결과가 0일 때 1로 set.
- C(=Carry): 연산 결과에서 자리 올림이 발생한 경우 1로 set.
- V(=Overflow): 연산 결과가 32-bit를 넘어 sign-bit가 상실된 경우 1로 set.
- I/F bit: IRQ (Normal Interrupt)와 FIQ (Fast Interrupt)를 on/off 합니다. Critical section에 진입하거나 일부 인터럽트를 비활성화해야 할 때 `MRS, MSR` 어셈블리 명령어를 이용하는데 바로 이 bit를 수정합니다.
- T bit: 현재 시스템이 ARM mode인지 Thumb mode인지를 나타냅니다.
- 하위 5-bits: 현재 시스템의 동작 mode를 안내합니다. 또한, 이 값을 바꾸면 해당 mode로 전환됩니다.
SPSR (Saved Program Status Register)는 CPSR의 백업, CPSR을 저장하는 레지스터입니다.
- Exception에 의한 Context swithcing 이 발생할 때, 현재 CPSR 값을 SPSR에 저장합니다.
- CPSR의 하위 5-bits를 다른 mode를 가리키는 bits로 바꿔 mode를 바꾼 뒤 작업을 수행합니다.
- SPSR 값을 CPSR에 덮어씌워 원래 mode로 돌아갑니다.
Thumb mode
처음 ARM이 32-bit로 시장에 나왔을 당시 임베디드 시스템 주력 메모리는 1 WORD, 16-bit였습니다. 물론 16-bit data bus line 메모리에서도 사용할 수 있었지만, ARM의 명령어를 사용하려면 메모리는 매회 2번 fetch 해야 했습니다. 따라서 효율적으로 동작할 수 있도록 ARM은 16-bit 명령어 set Thumb를 만들었습니다. Thumb mode를 사용한다면, R8~R12 레지스터는 사용하지 않습니다.
Context
다른 프로세스를 처리하거나 함수를 호출하거나 인터럽트를 처리할 때, Context switching이라는 용어를 사용합니다. 본래 진행하던 흐름에서 벗어나 새로운 흐름으로 새로 시작한다는 의미인데, 이때 'context'가 의미하는 것이 바로 레지스터 set입니다. 현재 실행중인 프로그램의 정보가 들어있기 때문에 특정 순간의 레지스터 set을 저장/복원하면 해당 순간으로 돌아갈 수 있습니다.
예를들어 함수 A 안에서 함수 B를 호출한다고 가정하면,
- B를 호출하기 직전까지의 context(e.g. R0~R12, LR)를 스택에 저장합니다.
- 함수 B를 수행한 뒤에 다시 복귀할 때는 스택에 저장했던 내용을 복구합니다.
이때, 함수호출 같은 단순한 흐름 변경일 땐 CPSR은 따로 백업/복구하지 않습니다. 왜냐하면, 서브루틴 내에서 PSR의 값을 따로 변경할 일은 없고 그래서도 안되기 때문입니다. 하지만, exception이 발생할 때는 바뀌니 백업합니다. Exception에 대한 내용은 추후 chapter 4에서 다룹니다.
3. AAPCS (ARM Architecture Procedure Call Standard)
ARM은 각 레지스터의 사용법에 대한 표준을 만들었고 ARM 컴파일러는 이 표준에 맞춰 기계어를 만듭니다.
위 표의 'Synonym'이란, 기존 레지스터의 별명이라고 생각하면 됩니다.
R0~R3 (a1~a4) (Argument, Result, Scratch)
- R0~R3는 함수에서 argument(parameter)를 받을 때 사용하는 레지스터입니다.
- 예를들어, `int foo(int a, int b, int c)` 함수를 `foo(10, 20, 30);`로 호출한다면,
- `R0 = 10, R1 = 20, R3 = 30` 이렇게 배정됩니다.
- 또는 함수의 return 값을 저장할 때도 사용됩니다.
- Return 값은 보통 R0에 들어가고, 여러 개인 경우 R0~R3을 사용합니다.
- 만일 argument나 result가 4개를 초과할 경우는 어쩔 수 없이 stack에 넣습니다.
- 마지막으로, R0~R3는 연습장이라는 의미의 scratch 레지스터라고 부르기도 합니다. CPU가 마치 연습장처럼 연산 중간중간에 임시저장 용도로 마구 사용한다는 의미입니다.
- 따라서 R0~R3는 callee(호출된 함수)가 마음대로 수정할 권리를 가지기 때문에 여기에는 caller(호출하는 함수)가 중요한 정보를 넣어두면 안 되고, 설령 넣어놨더라도 복귀될 수 있도록 스택에 넣고 복구하는 추가작업을 따로 해야 한다.
R4~R11 (v1~v8) (Variable)
- 지역변수를 저장하는 용도로 사용하는 레지스터들입니다.
- 이 또한 마찬가지로 한도를 넘게 되면 stack에 저장합니다.
R12~R15 (Special purpose registers)
- R12도 특수한 목적으로 사용되는 레지스터로 분류됩니다. 추후 배우게 될 ARM-Thumb interworking 기능의 long branch 또는 vaneer를 사용할 때 주소의 임시 저장소로 활용합니다.
- 앞서 R13 (스택 포인터), R14 (링크 레지스터), R15(프로그램 카운터)에 대해서는 다뤘습니다
- R12~R15도 사용하려고 하면 억지로 임시저장소로 사용할 수 있습니다만, 예기치 못한 동작을 일으킬 수 있습니다. 왜냐하면, R13은 항상 유효한 스택 포인터, R14는 항상 유효한 복귀 주소라고 가정하고 컴파일 및 실행하는 경우가 있기 때문입니다.
4. Exception (예외)
이런 exception이 발생했을 때, jump 할 주소들을 모아둔 배열을 exception vector table 이라고 부릅니다.
1절에서 배운 mode와 함께 접목시켜서 다시 한번 설명하면 다음과 같습니다.
- Power-on & Reset - SVC mode - 0x00번지로 jump
- HW Interrupt (Normal interrupt) - IRQ mode - 0x18번지로 jump
- FIQ에 등록된 HW Interrupt (Fast interrupt) - FIQ mode - 0x1C번지로 jump
- Fetch 한 명령어를 알 수 없음 (Prefetch abort) - ABT mode - 0x0C번지로 jump
- Fetch 한 피연산자에 접근할 수 없음 (Data abort) - ABT mode - 0x10번지로 jump
Exception 발생 시 일어나는 과정
Exception이 발생했을 때 일어나는 일들을 정리한 그림입니다.
그림에 나와있지 않은 내용 두 가지를 추가하자면,
- context를 백업할 때는 R13을 움직여서 R0~R12, R14(LR)를 스택에 저장합니다. 이 한 덩어리를 스택 프레임이라고 부릅니다.
- R14에 복귀할 주소를 저장하는데, 이전 포스트에서 설명했듯 pipelining 때문에 현재 exception이 발생한 곳과 PC값은 2개 명령어만큼 떨어져 있기 때문에 주소 보정 작업이 필요합니다.
Q. 왜 USR, SYS mode에는 SPSR이 없을까?
USR, SYS로 진입하는 exception이 없기 때문입니다. Exception vector table을 보시면 USR, SYS에 대한 handler가 없는데, 이는 USR, SYS로 진입하는 exception이 없다는 뜻입니다. 따라서 SPSR에 백업할 게 없습니다. 그리고 kernel이 탑재된 복잡한 임베디드 시스템의 경우에는 상시 동작하는 기본 모드가 USR이므로 이전 mode가 무엇이었는지 기록할 필요도 따로 없습니다. 굳이 알고 싶다면 stack에 넣습니다.
Q. 그럼 어떻게 USR mode로 진입함?
Exception이 발생해서 privileged mode로 바뀌었고, USR mode로 진입하는 exception이 없다면, CPSR 레지스터의 mode bits를 직접 수정해서 USR mode로 이동하면 됩니다.
Q. FIQ가 Exception vector table 최하단에 있는 이유는?
FIQ는 메모리 접근조차 최소화하기 위해 자신만의 레지스터(R8~R12)를 추가로 갖고있는 exception입니다. 따라서 vector table에서 branch 하는 것조차 아끼기위해 vector table 바로 아래에 FIQ handler를 둬서 branch 없이도 바로 실행될 수 있도록 합니다.
5. AMBA
5.1. SoC
ARM은 processor를 만들어서 파는 회사가 아니라 CPU architecture를 파는 회사입니다.
- 자신이 design한 architecture를 가져다 쓰는 회사로부터 로열티를 받아 비즈니스를 하지요.
- 퀄컴, 삼성, 인텔 등은 ARM의 라이선스를 구매해 자신들의 제품에 맞게 re-design 하기도 합니다.
ARM이 Core를 판매하니,
- core에 여러 기능을 덧붙혀 하나의 chip으로 만들어 판매하는 SoC(System On Chip) 시장이 생겼습니다.
- SoC 내부 각 기능을 가진 block (IP, Intellectual Property)을 판매하는 회사도 생겨났습니다.
- HDL 또는 verilog 같은 HW description language를 지원하는 design tool을 사용해 모듈을 설계하고 ASIC 라이브러리로 최종 netlist를 만들어 IP를 만듭니다.
이렇게 ARM core와 여러 IP를 묶어서 SoC로 엮으면 한 개의 MCU가 만들어집니다.
5.2. AMBA
시장이 이러한 흐름으로 전개되고 확장되니, 엔지니어들은 새로운 고민이 생겼습니다.
서로 다른 IP들과 Core가 내부에서 어떻게 호환성 있게 서로 연결될까?
ARM은 내부 IP들끼리 잘 통신하면서 동시에 ARM core를 가장 효율적으로 활용할 수 있는 방법을 적용한 Bus 프로토콜을 무료로 풀어버렸는데, 이것이 바로 AMBA (Advanced Microcontroller Bus Architecture) 프로토콜입니다. 이때 bus 프로토콜은 bus 위로 data를 어떻게 송수신할 것인지에 대한 약속을 말합니다.
AMBA는 AHB, ASB, APB 3가지 인터페이스로 나뉩니다.
- AHB(High performance): 이름대로 ‘burst mode’를 지원해 data의 빠른 전송이 가능한 고성능 bus 인터페이스.
- APB(Peripheral): AHB는 고속 bus이므로 상대적으로 저속인 peripheral IP가 중간에 끼면 전체속도가 느려집니다. 따라서 이런 저속의 IP들을 묶어서 사용하기 위한 bus 인터페이스를 만들어 효율성을 올렸습니다. Mux 기반 bus이므로 address, control, data line이 모두 하나의 bus로 통합돼 시분할로 운용됩니다. 예를 들어, control 신호 쏘고, address 쏘고, data를 연속해서 쏴주는 burst mode를 사용합니다.
- ASB(AMBA : System)
저희는 앞서 Chapter 1에서 bus 사용권을 중재해주는 존재 '아비터(Arbiter)'에 대해서 짧게 다뤘습니다.
아비터를 중심으로 AMBA의 AHB 동작 예시를 살펴봅시다.
- Master는 arbiter에게 '나 버스 쓰고싶어'라고 HBUSREQx 신호를 보냅니다.
- Arbiter는 다른 누군가 bus를 쓰고 있다면 읽씹하고, 쓰고 있지 않다면 '응 너 써'라는 응답신호인 HGRANTx 신호로 대답합니다.
- Master는 '허락받았으니 내가 bus 쓸게'라는 재확인 신호 HLOCKx을 arbiter에게 보냅니다.
이제 arbiter는 master보다 priority가 낮은 요청은 읽씹할겁니다. - Master는 원하는 slave의 주소인 HADDR을 decoder에게 제공합니다.
- Decoder는 해당 slave에게 HSELx 신호를 줘 '누가 너한테 접근하고 싶대'라고 알려 활성화시킵니다.
- Master는 타깃 slave에게 control 신호를 전송합니다. 여기서는 HWRITE 신호입니다.
- Slave는 master에게 '응 알았어 나 준비 끝났어'라고 HREADY 신호로 대답합니다. 만일 HREADY 신호가 안 온다면, wait 상태로 기다리고 있어야 합니다.
- Master는 쓰고 싶은 데이터를 HWDATA에 담아 전달합니다. (읽을 때는 HRDATA에)
여기까지 데이터 통신 과정을 이해했다면, 한 단계 더 들어가 봅시다.
하나의 트랜잭션은 1 address phase + 1 data phase로 이뤄집니다.
즉, 타깃 주소와 control 신호(Read/ Write)를 보내고 데이터를 보내는 과정으로 이뤄집니다.
만일, 대량의 데이터를 쓰고 싶거나, 읽고 싶다면 이런 방식은 비효율적입니다.
AMBA AHB의 'Burst mode'는 이런 bulk 트랜잭션에 딱 맞는 기능입니다. 타깃 주소를 최초 1회 전달하면, 데이터가 끝날 때까지 알아서 사용자 입맛에 맞게 타겟 주소가 알아서 증가돼 data phase가 연이어집니다. Burst mode를 위해 master와 slave 사이에는 HWRITE 이외에도 HTRANS[1:0]라는 2-bit 신호, HBURST[2:0]라는 3-bit control 신호를 추가로 주고받습니다.
HTRANS는 현재 트랜잭션의 mode를 알려주는 역할을 합니다. 특히, AHB는 데이터를 쉼 없이 전달하는 'Burst mode'를 지원하는데, 이전 데이터와 현재 데이터와의 연관성을 나타내는 역할도 겸합니다.
- IDLE: 데이터를 전송하지 않는 트랜잭션을 의미합니다. 위 과정 중 3번을 보면, master가 arbiter에게 HLOCKx 신호를 보냈었는데, 이건 정말 신호를 보낼 뿐이지 읽거나 쓰려는 데이터를 전송하는 게 아닙니다. 이런 트랜잭션은 HTRANS를 IDLE로 표시합니다.
- BUSY: Burst mode로 데이터를 주고받을 때, 중간에 master가 다음 사이클을 위한 준비가 아직 안 돼서 IDLE 상태를 만들고 싶을 때 사용합니다.
- NONSEQ: 단일 데이터 전송 or Burst mode를 시작할 때 사용합니다. (즉, 이전 데이터 전송과 현재 데이터가 연관이 없음을 의미합니다.)
- SEQ: Burst mode로 전송할 때 사용합니다. (이전 데이터 전송과 현재 데이터가 연관이 있음을 의미합니다.)
HSIZE는 한 번에 데이터를 얼마나 보낼지 설정합니다. Data width보다 작거나 같게 설정해야 합니다.
HBURST는 burst mode에서 타깃 주소를 어떻게 자동으로 증가할지를 결정합니다.
- INCR4로 설정돼 있다면 매 데이터 전송 이후 자동으로 타깃 주소가 4의 배수씩 증가합니다.
- WRAP4로 설정돼있다면 INCR4처럼 타겟 주소가 4의 배수씩 증가하되 범위 내에서만 증가합니다.
그 범위는 HBURST의 값 × HSIZE의 값입니다.- 예를 들어, WRAP4에 HSIZE는 `0x010` (1 WORD)로 설정돼 있고 시작 타깃주소는 `0x38`이라고 합시다.
- WRAP `4` × 1-WORD( = 4-byte) = 16-byte = 0x10 byte입니다.
- 즉, 0x10 단위로 경계가 만들어집니다.`0x00~0x0 F, 0x10~0x1F, 0x20~0x2F, ...` 이런 식으로요.
- `0x38`을 시작으로 하지만, `0x38 → 0x3 B` 그 다음 `0x40`이 아니라 경계에 막혀 `0x30`으로 갑니다.
- `0x38 → 0x3B → 0x30 → 0x34` 이렇게 경계 내에서 증가합니다.
위와 같은 타이밍도를 해석해 봅시다.
- T0: 가장 먼저 burst mode를 시작해야 하고, 이전 데이터는 없으므로 HTRANS = NONSEQ입니다.
- T0~T6: HBURST가 INCR 모드입니다 4바이트씩 주소가 알아서 증가하는 모습을 확인할 수 있습니다.
- T1: Master가 모종의 이유로 준비가 안 됐나 보네요, HTRANS = BUSY를 보내서 주소 자동 증가를 한 사이클 미룹니다.
- T2~: Burst mode가 시작됐으므로 이전에 보낸 데이터와 지금 현재 데이터가 연관돼 있음을 보이기 위해 HTRANS = SEQ이 출력됩니다.
- T2, T4, T5, T7: HREADY 이후 다음 사이클에 HRDATA[31:0]에 데이터가 출력되는 것을 확인할 수 있습니다.
AMBA가 발전하며 AXI라는 AMBA 3.0 spec도 나왔습니다.
- Burst mode 기반 bus이며 시작주소만으로도 burst transfer 가능합니다.
- Response channel이 추가됐습니다.
- R/W가 동시에 가능합니다.
- ARM11 이상의 backbone bus로 이용되고 있습니다.