초심 프로젝트의 첫번째 주제로 ARM Architecture 를 주제로 새롭게 알게된 것을 정리했다. 교재는 다음과 같다.
| 주교제 | ARM System Developer`s Guide |
| 부교제 | ARM System-on-Chip Architecture |
ARM 은 RISC 이다. ARM 은 프로세서내에 하드웨어 디버그 기술을 포함하고 있다. 따라서 소프트웨어 엔지니어들은 프로세서가 코드를 실행하는 동안 내부에서 어떤 일들이 일어나는지를 살펴볼 수 있다.
RISC 는 하드웨어에 의해 수행되는 명령어들의 복잡성을 줄이는 것을 목표로 하고 있는 데, 그 이유는 하드웨어보다는 소프트웨어에 유연성과 기능성을 제공하는 것이 보다 유리하기 때문이다.
RISC 특성외에 몇가지 다른 특징들도 포함하고 있다.
임베디드 시스템에 포함되어 있는 하드웨어 컴포넌트는 다음과 같다.
배럴 시프터는 한 개의 연산으로 데이터 워드 내에 있는 다수의 비트를 이동하거나 회전시킬 수 있는 하드웨어 장치이다. 즉 32 비트 배럴 시프터의 경우라면, 1 사이클 안에 좌측이나 우측으로 최대 32 비트씩 이동시킬 수 있다.
로드 스토어 아키텍처에서 로드 명령어은 메모리에서 코어내의 레지스터로 데이터를 복사하고, 스토어 명령어는 레지스터에서 메모리로 데이터를 복사하는 데 사용된다.
| 레지스터 번호 | 설명 |
| r0 ~ r12 | 일반 레지스터 |
| r13 | 스택 포인터(sp)로 사용되어 왔으며, 현재 프로세서 모드의 스택 맨 위 주소값을 저장한다 |
| r14 | 링크 레지스터(lr)로 불리며, 코어가 서브루틴을 호출할 때마다 그 복귀주소를 저장한다 |
| r15 | 프로그램 카운터(pc)로, 프로세서가 읽어들인 다음 명령어의 주소를 저장한다 |
| cpsr | 현재의 프로그램 상태 레지스터 |
| spsr | 이전에 저장된 프로그램 상태 레지스터 |
ARM 에서는 총 6개의 특권 모드(abort, FIQ, IRQ, supervisor, system, undefined)와 하나의 일반모드(user), 즉 전체적으로 7개의 모드가 있다.
| 모드 | 설명 |
| abort | 메모리 액세스가 실패했을 경우 |
| FIQ, IRQ | ARM 프로세서에서 사용할 수 있는 2가지의 인터럽트 레벨을 위한 모드 |
| supervisor | 프로세서에 리셋이 걸렸을 때에 진입하는 모드로, 일반적으로 운영체제 커널이 동작하는 모드 |
| system | user 모드의 특수한 버전으로 cpsr 을 완전히 읽고 쓸 수 있다 |
| undefined | 프로세서가 정의되지 않은 명령어나 지원되지 않은 명령어를 만났을 때에 진입하는 모드 |
| user | 프로그램과 애플리케이션을 위해 사용하는 모드 |
레지스터 파일 안에 있는 37개의 레지스터 중에서 20개의 레지스터는 매번 프로세서 모드에 따라 숨겨져 있는데, 이 것을 뱅크 레지스터라고 부른다.
프로세서 모드가 변경되었다면, 새로운 모드의 뱅크 레지스터가 기존의 레지스터를 대체한다. 이때 대체하기전의 기존의 레지스터는 그대로 있으며, 어떤 명령어에 의해서도 영향을 받지 않는다.
프로세서의 각 모드는 해당 익셉션이나 인터럽트를 발생시키는 하드웨어에 의하거나 cpsr 을 직접 제어하여 변경할 수 있다. 익셉션과 인터럽트는 현재 작업을 중단시키고 특정위치로 분기한다.
| 모드 | 약자 | 특권 기능 | 모드비트 [4:0] |
| abort | abt | yes | 10111 |
| fast interrupt request | fiq | yes | 10001 |
| interrupt request | irq | yes | 10010 |
| supervisor | svc | yes | 10011 |
| system | sys | yes | 11111 |
| undefined | und | yes | 11011 |
| user | usr | no | 10000 |
기억해야 할 것은 cpsr 을 직접 제어하여 모드 변경을 한 경우에는 cpsr 이 spsr 에 복사되지 않는다는 것이다. cpsr 의 저장은 익셉션이나 인터럽트가 발생할 때에만 발생된다. 전원이 코어에 공급되면 특권모드인 supervisor 모드에서 시작된다.
코어의 상태는 어떤 명령어가 실행될 것인지 결정한다.(ARM, Thumb, Jazelle) Thumb 상태에서는 순수한 16비트 Thumb 명령어만이 실행되고, ARM, Thumb, Jazelle 명령어를 섞어서 코딩할 수 없다.
아래 표는 ARM 과 Thumb 명령어의 특징을 나타내고 있다.
| ARM(cpsr T=0) | Thumb(cpsr T=1) | |
| 명령어 크기 | 32비트 | 16비트 |
| 코어 명령어 | 58 | 30 |
| 조건부 실행 | 대부분 가능 | 분기 명령어에서만 가능 |
| 데이터 처리 명령어 | 배럴 시프터와 ALU의 액세스 가능 | 배럴 시프터 명령어와 ALU 명령어가 분리됨 |
| 프로그램 상태 레지스터 | 특권 모드에서만 읽고 쓰기 가능 | 직접 액세스 불가 |
| 레지스터 사용 | 15개의 범용 레지스터 + pc | 8 개의 범용 레지스터 + 7개의 상위 레지스터 + pc |
특정 인터럽트 소스가 프로세서에게 인터럽트 요청을 할 수 없도록 하는 것이다. cpsr 은 인터럽트 마스크 비트 6번과 7번(F와I)의 2개를 가지고 있는데, I 비트가 1이면 IRQ 가 마스크되며, F 비트가 1이면 FIQ 비트가 마스크 된다.
cpsr 의 상태 플래그(Q,V,C,Z,N)을 확인해서 현재 프로그램의 상태를 확인할 수 있다.
조건부 실행이란 코어가 어떤 명령어를 실행할 지의 여부를 제어할 수 있음을 의미한다. 대부분의 명령어들은 코어가 그것을 실행할 지의 여부를 결정할 수 있는 조건 인자를 가지고 있는데, 이것은 상태 플래그의 설정값을 기반으로 한다. 명령어를 실행하기전에 코어는 자신이 가지고 있는 조건 인자와 cpsr 의 상태 플래그를 비교한다. 만약 이것이 일치하면 명령어는 실행되고 그렇지 않으면 명령어는 무시된다. 만약 조건 인자가 없다면 AL(항상 실행)로 설정된다.
ARM7 의 경우는 총 3단계의 파이프라인을 가지고, ARM9 은 5단계, ARM10 은 6단계의 파이프라인을 가진다.
ARM 파이프라인은 어떤 명령어가 실행 단계로 넘어오기 전까지는 그 명령어를 처리하지 않는다. ARM7 의 경우, 네 번째 명령어가 읽혀질 무렵에서야 비로소 하나의 명령어를 실행한다.
실행 단계에서 pc는 항상 명령어 주소에 8 을 더한 값을 가리킨다. 다시 말하면 pc는 실행되고 있는 명령어의 주소보다 2단계 앞선 주소를 가리키고 있는 것이다. 이것은 pc 를 이용하여 상대 오프셋값을 계산할 때에 매우 중요하며, 전체 파이프라인에 대한 구조적인 특징이기도 하다. Thumb 상태에서는 프로세서의 pc 값이 “명령어 주소 + 4” 라는 사실을 기억한다.
익셉션이나 인터럽트가 발생하였을 때, 프로세서는 pc 에 특정 메모리 주소값을 넣는다. 이 주소값은 벡터 테이블이라는 특정주소 영역 안에 있는 값이다.
메모리맵 주소 0x00000000 은 벡터 테이블을 위해 32 비트 워드값들로 예약되어 있다.
| Reset vector | 전원이 공급될 때 프로세서에 의해 처음으로 실행되는 명령어의 위치. 이 명령어에서 초기화 코드로 분기한다 |
| Undefined Instruction vector | 프로세서가 명령어를 분석할 수 없을 때에 사용된다 |
| Software Interrupt vector | SWI 명령어를 실행시켰을 때에 호출됨. SWI 명령어는 운영체제 루틴에서 벗어나고자 할 경우에 주로 사용된다 |
| Prefetch Abort vector | 프로세서가 정확한 접근권한 없이 어떤 주소에 명령어를 읽어들이려고 할때 발생. 실제 abort 는 파이프라인의 디코드 단계에서 발생한다 |
| Data Abort vector | 명령어가 정확한 접근권한 없이 데이터 메모리를 액세스하려고 시도할 때에 발생한다 |
| Interrupt Request vector | 프로세서의 현재 흐름을 중단시키기 위해 외부 하드웨어 장치에 의해 사용된다 |
| Fast Interrupt Request vector | 더욱 빠른 응답 시간을 요구하는 하드웨어를 위해 할당되어진다 |
다음은 벡터 테이블의 정보를 표로 나타낸 것이다.
| 익셉션/인터럽트 | 벡터주소 | 상위 벡터주소 |
| reset | 0x00000000 | 0xffff0000 |
| undefined instruction | 0x00000004 | 0xffff0004 |
| software instruction | 0x00000008 | 0xffff0008 |
| prefetch abort | 0x0000000c | 0xffff000c |
| data abort | 0x00000010 | 0xffff0010 |
| reserved | 0x00000014 | 0xffff0014 |
| interrupt request | 0x00000018 | 0xffff0018 |
| fast interrupt request | 0x0000001c | 0xffff001c |
ARM 코어 주변에 확장시킬 수 있는 하드웨어 장치에는 캐시 및 TCM, 메모리 관리 장치(MMU), 코프로세서 인터페이스의 3가지가 있다.
캐시는 주메모리와 코어사이의 위치한 빠른 메모리 블록으로, 메모리 소자로 부터 보다 효율적인 패치(fetch)를 가능케 해준다.
ARM 은 두 종류의 캐시를 가지고 있다.
캐시는 전반적인 성능을 향상시켜주는 반면 예측성을 떨어뜨린다. 실시간 시스템에서 명령어나 데이터를 읽거나 쓰는 데 걸리는 시간이 예측 가능해야 된다. 이를 위해 TPM(Tightly Coupled Memory) 라는 일종의 메모리를 이용하여 구현할 수 있다.
캐시와 TCM 을 접목함으로써 ARM 프로세서는 성능을 향상시키고 예측 가능한 실시간 응답성을 보장한다.
ARM 코어는 3가지 유형의 메모리 관리 하드웨어를 지원한다.
코프로세서는 ARM 프로세서에 부착하여 사용하는 것으로, 명령어 세트를 확장시키거나 값을 설정할 수 있는 레지스터를 제공하여 코어의 처리기능을 확장시켜 준다. 이 새로운 명령어들은 ARM 파이프라인의 디코드 단계에서 처리된다. 만약 디코드 단계에서 코프로세서 명령어를 발견한다면, 이 명령어는 관련 코프로세서로 넘겨진다. 하지만 코프로세서가 존재하지 않거나, 이 명령어를 인식하지 못할 경우에는 undefined instruction 익셉션을 발생한다.
ARM 은 그 버전에 따라서 지원하는 명령어들이 다르다. 하지만 새로운 버전은 보통 기존 명령어에 새로운 명령어를 추가하는 방식으로, 하위 버전과의 호환성을 보장하고 있다.
레지스터 안에서 데이터를 조작하는데 사용된다.
가장 간단한 ARM 명령어로서 초기값을 설정하거나 레지스터간에 데이터를 이동하기 위해 사용된다.
| 명령어 | 설명 |
| MOV | 32 비트값을 레지스터로 복사 |
| MVN | 32 비트값의 NOT 을 레지스터로 복사 |
r5 = 5 r7 = 7 MOV r7 , r5 r5 = 5 r7 = 5
데이터 처리 명령어는 산술 연산 장치(ALU) 에서 처리된다. ARM 명령어의 강력한 특징은 ALU 로 입력되기 전에 배럴 시프터에 의해 2진 단위로 좌우 시프트가 가능하다는 점이다.
r5 = 5 r7 = 7 MOV r7, r5, LSL #2 ; r5 << 2 5 * 4 r5 = 5 r7 = 20
| 명령어 | 설명 | 결과 | |
| LSL | 왼쪽으로 논리 시프트 | x « y | |
| LSR | 오른쪽으로 논리 시프트 | (unsigned)x » y | |
| ASR | 오른쪽으로 산술 시프트 | (signed)x » y | |
| ROR | 오른쪽으로 로테이트 | ((unsigned)x » y) | (x « (32 - y) |
| RRX | 오른쪽으로 확장 로테이트 | (c 플래그 « 31 | (unsigned)x » 1) |
다음은 r1 을 1만큼 왼쪽으로 시프트하는 것을 보여주고 있다. 명령어에 S 가 붙어 있으므로 cpsr 안의 C 플래그가 업데이트된다.
cpsr = nzcvqiFt_USER r0 = 0x00000000 r1 = 0x80000004 MOVS r0, r1, LSL #1 cpsr = nzCvqiFt_USER ; 1만큼 왼쪽으로 시프트 되면서 최상위 비트가 캐리지 리턴 됨으로서 cpsr 의 C 플래그가 1로 세팅된다 r0 = 0x00000008 r1 = 0x80000004 ; r1 의 값은 변화가 없다.
32 비트 signed/unsigned 값의 덧셈과 뺄셈을 구현하기 위해 사용된다.
| 명령어 | 설명 |
| ADC | 캐리를 고려한 32 비트값의 덧셈 |
| ADD | 32 비트값의 덧셈 |
| RSB | 32 비트값의 뺄셈(반전) |
| RSC | 캐리를 고려한 32 비트값의 뺄셈(반전) |
| SBC | 캐리를 고려한 32 비트값의 뺄셈 |
| SUB | 32 비트값의 뺄셈 |
다음은 간단한 뺄셈 연산을 보여주는 예제이다.
r0 = 0x00000000 r1 = 0x00000002 r2 = 0x00000001 SUB r0, r1, r2 r0 = 0x00000001
RSB 명령어는 상수값 #0 에서 r1 을 뺀 후, 그 결과를 r0 에 저장한다. 다음은 어떤 값을 음수로 만들기 위해 사용하는 방법이다.
r0 = 0x00000000 r1 = 0x00000077 RSB r0, r1, #0 ; Rd = 0x0 - r1 r0 = -r1 = 0xffffff89
SUBS 명령어는 루프문에서 카운터를 감소시킬 때에 사용하면 유용하다. 다음은 r1 에 저장되어 있는 값 1 에서 상수값 1을 뺀 후, 그 결과값 0 을 r1에 저장한다. cpsr 안의 ZC 는 1로 업데이트 된다.
cpsr = nzcvqiFt_USER r1 = 0x00000001 SUBS r1, r1, #1 cpsr = nZCvqiFt_USER r1 = 0x00000000
산술 연산이나 논리 연산에서 두 번째 오퍼랜드의 시프트가 가능하다는것은 ARM 명령어 세트에서 매우 강력한 특징이다.
r0 = 0x00000000 r1 = 0x00000005 ADD r0, r1, r1, LSL #1 r0 = 0x0000000f r1 = 0x00000005
논리 명령어는 2개의 소스 레지스터에 비트 단위로 논리 연산을 수행한다.
| 명령어 | 설명 |
| AND | 32 비트 AND 논리 연산 |
| ORR | 32 비트 OR 논리 연산 |
| EOR | 32 비트 XOR 논리 연산 |
| BIC | 비트 클리어(AND NOT) 논리 연산 |
다음은 비트 클리어를 수행하는 예제이다.
r0 = 0000 r1 = 1111 r2 = 0101 BIC r0, r1, r2 r0 = 1010
레지스터 r2 는 비트 패턴을 포함하고 있는데, r2 에 저장되어 있는 1의 값에 대응되는 r1의 비트들이 0으로 클리어된다. 이 명령어는 상태비트를 0으로 설정할 때에 특히 유용하며 cpsr 에서 인터럽트 마스크를 변경하기 위해 자주 사용된다.
32 비트값을 가진 레지스터를 비교하고 테스트하기 위해 사용된다. 이 명령어는 결과에 따라 cpsr 플래그 비트를 업데이트하며 다른 레지스터에는 영향을 미치지 않는다. 해당 비트들을 세트한 다음, 조건부 실행을 사용하여 프로그램의 흐름을 변경하는 데 사용된다.
| 명령어 | 설명 |
| CMN | 음수 비교 |
| CMP | 양수 비교 |
| TEQ | 두 32 비트값이 같은지를 비교 |
| TST | 32 비트값의 테스트 비트 |
다음은 CMP 명령어를 이용한 예제이다.
cpsr = nzcvqiFt_USER r0 = 4 r9 = 4 CMP r0, r9 cpsr = nZcvqiFt_USER
비교 명령어는 비교되는 레지스터에는 영향을 끼치지 않고, 오직 cpsr 의 상태 비트만을 업데이트 한다는 것을 반드시 이해하기 바란다.
64 비트 곱셈 명령어는 64 비트를 표현하는 2개의 레지스터를 이용하여 곱셈을 한다. 결과는 하나의 결과 레지스터에 저장하거나 2개의 레지스터를 이용하여 저장한다.
| 명령어 | 설명 |
| MLA | 32 비트 곱셈-덧셈 명령어 |
| MUL | 32 비트 곱셈 명령어 |
| SMLAL | 64 비트 signed 곱셈-덧셈 명령어 |
| SMULL | 64 비트 signed 곱셈 명령어 |
| UMLAL | 64 비트 unsigned 곱셈-덧셈 명령어 |
| UMULL | 64 비트 unsigned 곱셈 명령어 |
다음은 레지스터 r1 과 r2 를 곱하여 그 결과를 레지스터 r0 에 저장하는 간단한 곱셈 명령어를 보여주고 있다.
r0 = 0x00000000 r1 = 0x00000002 r2 = 0x00000002 MUL r0, r1, r2 ; r0 = r1 * r2 r0 = 0x00000004 r1 = 0x00000002 r2 = 0x00000002
다음은 64 비트 곱셈의 예제로서 레지스터 r2 와 r3 를 곱하여 그 결과를 레지스터 r0 와 r1 에 저장한다.
r0 = 0x00000000 r1 = 0x00000000 r2 = 0xf0000002 r3 = 0x00000002 UMULL r0, r1, r2, r3 r0 = 0xe0000004 ; = RdLO r1 = 0x00000001 ; = RdHi
결과는 32 비트 레지스터에 저장하기에는 너무 크기 때문에, RdLo 와 RdHi 라는 레이블이 붙은 2개의 레지스터에 저장된다. RdLo 에는 64 비트의 결과 중 하위 32 비트의 값이 저장되며, RdHi 에는 64 비트의 값 중 상위 32 비트의 값이 저장된다.
실행의 흐름을 변경하거나 어떤 루틴을 호출하는 데 사용한다. 프로그램 카운터 pc 가 새로운 주소를 가리키도록 함으로써 실행의 흐름을 바꾸어 준다.
| 명령어 | 설명 |
| B | 분기 |
| BL | 서브루틴 호출 분기 명령어 |
| BX | ARM/Thumb 모드 전환 분기 명령어 |
| BLX | ARM/Thumb 모드 전환 및 서브루틴 호출 분기 명령어 |
다음은 분기 명령어를 이용한 예제이다.
B forward ADD r1, r2, #4 ADD r0, r6, #2 forward ; label SUB r1, r2, #4 backward ; label 무한 루프 ADD r1, r2, #4 SUB r1, r2, #4 ADD r4, r6, r7 B backward
대부분의 어셈블러는 레이블을 사용하여 분기 명령어의 자세한 부분을 감춘다.
링크값을 갖는 분기인 BL 명령어는 B 명령어와 유사하지만 링크 레지스터 lr 에 복귀할 주소를 저장해둔다. 이 명령어는 서브루틴 호출을 수행하는 데 사용된다. 서브루틴에서 복귀하려면 pc 에 링크 레지스터의 값을 넣어주어야 한다.
BL subroutine ; 서브루틴으로 분기 CMP r1, #5 ; r1 과 r5 를 비교 MOVEQ r1, #0 ; (r1 == 5) 이면 r1 = 0 ... subroutine <서브루틴 코드> MOV pc, lr ; pc = lr 을 저장하여 복귀
메모리와 프로세서 레지스터 사이에서 데이터를 전송해준다.
이 명령어는 메모리에서 레지스터, 또는 레지스터에서 메모리로 하나의 데이터를 전송하는 데 사용된다.
| 명령어 | 설명 |
| LDR | 메모리에서 레지스터로 한 워드를 읽어들임 |
| STR | 레지스터에서 메모리로 바이트나 워드를 저장 |
| LDRB | 메모리에서 레지스터로 바이트를 읽어들임 |
| STRB | 레지스터에서 메모리로 바이트를 저장 |
| LDRH | 메모리에서 레지스터로 하프워드를 읽어들임 |
| STRH | 레지스터에서 메모리로 하프워드를 저장 |
| LDRSB | 메모리에서 레지스터로 signed 바이트를 읽어들임 |
| LDRSH | 메모리에서 레지스터로 signed 하프워드를 읽어들임 |
; 레지스터 r1 이 가리키는 메모리 주소의 내용을 레지스터 r0 에 로드한다. LDR r0, [r1] ; LDR r0, [r1, #0] ; 레지스터 r0 의 내용을 레지스터 r1 이 가리키는 메모리 주소에 저장한다. STR r0, [r1] ; STR r0, [r1, #0]
ARM 명령어 세트는 메모리의 주소를 계산하기 위해 다양한 모드를 제공한다.
| 자동인덱스 | 베이스 레지스터로부터의 주소에서 주소 오프셋만큼을 계산한 다음에 새로운 주소값을 가지고 주소 베이스 레지스터를 업데이트한다 |
| 프리인텍스 | 자동인덱스와 동일하지만, 주소 베이스 레지스터를 업데이트하지는 않는다 |
| 포스트인덱스 | 주소가 사용된 후 주소 베이스 레지스터를 단지 업데이트만 한다 |
다음은 각각 다른 주소지정방식을 사용한 LDR 명령어의 예이다.
명령어 r0 = r1 += - 자동인덱스 : LDR r0,[r1, #0x4]! Mem32[r1+0x4] 0x4 - 프리인덱스 : LDR r0,[r1, #0x4] Mem32[r1+0x4] 업데이트 안 됨 - 포스트인덱스 : LDR r0,[[r1]],#0x4 Mem32[r1] 0x4
하나의 명령어로 메모리와 프로세서 사이에서 여러 개의 레지스터를 전송할 수 있다. 다중-레지스터 전송 명령어는 메모리에서 여러 블록의 데이터를 전송하거나 문맥과 스택을 저장하고 복구하는 데 단일-전송 레지스터 전송보다 효과적이다.
또한 일반적으로 ARM 은 이 명령어가 실행되고 있는 동안에 다른 명령어가 실행되지 못하게 하기 때문에 인터럽트 지연을 증대시킬 수 있다. 만약 인터럽트가 발생한다면, 다중-레지스터 전송 명령어가 완료될 때까지는 아무런 영향을 미치지 못한다.
| LDM | 여러 개의 레지스터를 읽어들임 |
| STM | 여러 개의 레지스터를 저장 |
다음은 다중 로드-스토어 명령어를 위한 주소지정방식이다.
| 주소지정방식 | 설명 | 시작 주소 | 끝 주소 | Rn! |
| IA | increment after | Rn | Rn+4*N-4 | Rn+4*N |
| IB | increment before | RN+4 | Rn+4*N | Rn+4*N |
| DA | decrement after | Rn-4*N+4 | Rn | Rn-4*N |
| DB | decrement before | Rn-4*N | Rn-4 | Rn-4*N |
예제를 통해 감을 잡아보자!!
mem32[0x80018] = 0x03
mem32[0x80014] = 0x02
mem32[0x80010] = 0x01
r0 = 0x00080010
r1 = 0x00000000
r2 = 0x00000000
r3 = 0x00000000
LDMIA r0, {r1-r3}
r0 = 0x0008001c
r1 = 0x00000001
r2 = 0x00000002
r3 = 0x0000003
다음은 베이스 주소가 업데이트될 때 사용되는 LDM-STM 쌍이다. 일반적으로 LDM 을 호출하면 반드시 STM 이 호출된다.
| STM | LDM |
| STMIA | LDMDB |
| STMIB | LDMDA |
| STMDA | LDMIB |
| STMDB | LDMIA |
예제를 통해 알아보자!!
r0 = 0x00009000 ; 초기값
r1 = 0x00000009
r2 = 0x00000008
r3 = 0x00000007
STMIB r0!, {r1-r3} ; STMIB 호출
MOV r1, #1
MOV r2, #2
MOV r3, #3
r0 = 0x0000900c ; 값이 수정됨
r1 = 0x00000001
r2 = 0x00000002
r3 = 0x00000003
LDMDA r0!, {r1-r3} ; LDMDA 호출
r0 = 0x00009000 ; 초기값으로 복원
r1 = 0x00000009
r2 = 0x00000008
r3 = 0x00000007
만약 베이스 주소를 업데이트하는 스토어 명령어를 사용하고자 한다면, 같은 수의 레지스터를 가진 이와 한 쌍인 로드 명령어는 데이터를 다시 읽어들이고 베이스 주소 포인터를 복구하는 데 사용될 것이다. 이것은 레지스터의 값들을 임시로 저장해두었다가 나중에 복구할 때에 유용하다.
다음은 블록 메모리 복사를 하는 예제이다.
; r9 는 소스 데이터의 시작 위치를 가리킴
; r10 은 결과 데이터의 시작 위치를 가리킴
; r11 은 소스의 끝을 가리킴
Loop
; 소스로 부터 32 바이트를 로드하여 r9 포인터에 업데이트함
LDMIA r9!, {r0-r7}
; 목적지로 32 바이트를 저장하고 r10 포인터를 업데이트함
STMIA r10!, {r10-r7} ; 그것들을 저장함
; 소스의 끝에 도착했는지를 체크함
CMP r9, r11
BNE loop
ARM 아키텍처는 스택 오퍼레이션을 수행하기 위해 다중 레지스터 전송 명령어를 사용하고 있다. 팝(POP) 동작(스택에서 데이터를 제거하는 일)을 위해 LDM 명령어를 사용하며, 푸시(push) 동작(스택에 데이터를 집어 넣는 일)을 위해서는 STM 명령어를 사용한다.
ascending 스택은 메모리의 상위 주소 방향으로 스택이 자라는 것을 말하며, descending 스택은 메모리의 하위 주소 방향으로 스택이 자라는 것을 의미한다. full 스택(F)을 사용할 경우, 스택 포인터 sp 는 마지막으로 사용된 위치의 주소(예를 들어, sp 는 스택 상에 마지막 아이템을 가리킴)를 가리키고 있다. 반면 empty 스택(E)은 sp 가 처음 사용되지 않을 영역의 주소(예를 들면, 스택의 마지막 아이템 다음의 빈 공간)를 가리키고 있다.
ARM 은 루틴이 어떻게 호출되고 레지스터들이 어떻게 할당될지를 정의하는 ATPCS(ARM-Thumb Procedure Call)을 규정하고 있다. ATPCS 에서 스택은 full descending 스택으로 정의되어 있으므로, LDMFD 와 STMFD 명령어는 팝과 푸시 기능을 각각 제공하고 있다.
r1 = 0x00000002
r4 = 0x00000003
sp = 0x00080014
STMFD sp!, {r1, r4}
r1 = 0x00000002
r4 = 0x00000003
sp = 0x0008000c
스택 동작을 위한 어드레싱 방법은 다음 표와 같다.
| 주소지정방식 | 설명 | 팝 | =LDM | 푸시 | =STM |
| FA | full ascending | LDMFA | LDMDA | STMFA | STMIB |
| FD | full descending | LDMFD | LDMIA | STMFD | STMDB |
| EA | empty ascending | LDMEA | LDMDB | STMEA | STMIA |
| ED | empty descending | LDMED | LDMIB | STMED | STMDA |
명령어 수행전
| 0x80018 | 0x00000001 | |
| sp | 0x80014 | 0x00000002 |
| 0x80010 | Empty | |
| 0x8000c | Empty |
명령어 수행후
| 0x80018 | 0x00000001 | |
| 0x80014 | 0x00000002 | |
| 0x80010 | 0x00000003 | |
| sp | 0x8000c | 0x00000002 |
스택을 처리하기 위해 보존해야 되는 스택 베이스, 스택 포인터, 스택 리미트의 3 개의 인자가 있다. 스택 베이스는 메모리에서 스택의 시작위치를 나타낸다. 스택 포인터는 초기에는 스택 베이스를 가리키지만, 데이터가 스택으로 들어가면 스택 포인터는 메모리의 하단으로 이동하여 스택의 가장 맨 위를 계속 가리키고 있다. 스택 포인터가 스택 리미트를 넘겨가면 스택 오버플로우 에러가 발생된다. 다음은 스택 오버 플로우 에러가 발생하였는지를 체크하기 위한 간단한 코드이다.
SUB sp, sp, #size CMP sp, r10 BLLO_stack_overflow ; 조건
ATPCS 는 레지스터 r10 을 스택 리미트 sl로 정의 하고 있다. 이것은 스택 체크를 할 때에만 사용될 수 있도록 선택할 수 있다. BLLO 명령어는 BL 명령어에 조건 인자 LO를 추가한 형태의 명령어이다. 새로운 데이터가 스택에 들어온 다음, sp 가 r10 보다 작거나, 스택 포인터가 스택 베이스보다 작아질 때에도 스택 오버플로우 에러가 발생한다.
메모리의 내용과 레지스터의 내용을 바꾸어준다. swap 명령어를 처리하고 있는 동안은 다른 어떤 명령어나 버스 액세스에 의해 중단될 수 없다. 이를 가리켜 작업이 완료될 때까지 시스템이 “버스를 잡고 있다”고 표현한다.
| 명령어 | 설명 |
| SWP | 메모리와 레지스터간에 워드를 교환 |
| SWPB | 메모리와 레지스터간에 바이트를 교환 |
mem32[0x9000] = 0x12345678 r0 = 0x00000000 r1 = 0x11112222 r2 = 0x00009000 SWP r0, r1, [r2] mem32[0x9000] = 0x11112222 r0 = 0x12345678 r1 = 0x11112222 r2 = 0x00009000
이 명령어는 운영체제 안에서 세마포어나 뮤텍스를 구현할 때에 특히 유용하다.
spin MOV r1, =semaphore MOV r2, #1 SWP r3, r2, [r1] ; 완료될 때까지 버스를 잡고 있음 CMP r3, #1 BEQ spin
소프트웨어 인터럽트 명령어는 소프트웨어 인터럽트 익셉션을 발생시키는 것으로, 어플리케이션이 운영체제 루틴을 호출하기 위한 매커니즘을 제공한다.
| 명령어 | 설명 | SWI_number |
| SWI | 소프트웨어 인터럽트 | lr_svc = SWI 다음의 명령어의 주소, spsr_svc = cpsr, pc = 벡터 + 0x8, cpsr 모드 = SVC, cpsr I = 1(IRQ 인터럽트 마스크) |
SWI 명령어를 실행할 때, 프로세서는 프로그램 카운터 pc 에 벡터 테이블의 오프셋 0x8 로 설정한다. 이 명령어는 프로세서 모드를 SVC 로 변경시켜 운영체제 루틴이 특권 모드에서 호출될 수 있도록 해준다. 각 SWI 명령어는 관련 SWI 숫자를 포함하고 있는데, 이것은 특별한 함수 호출이나 특징을 나타내는 데 사용된다.
cpsr = nzcVqift_USER pc = 0x00008000 lr = 0x003fffff ; lr = r14 r0 = 0x12 0x00008000 SWI 0x123456 cpsr = nzcVqlft_SVC ; 현재 상태 spsr = nzcVqift_USER ; 이전 상태 pc = 0x00000008 lr = 0x00008004 ; 링크 레지스터 r0 = 0x12
psr(Program Status Register)을 직접 제어하기 위한 명령어를 2가지 제공하고 있다. MRS 명령어는 cpsr 이나 spsr 의 내용을 레지스터로 전송해준다.
MSR 명령어는 반대방향, 즉 레지스터의 내용을 cpsr 이나 spsr 로 전송해준다. 이 명령어들은 cpsr이나 spsr 을 읽고 쓰기 위해 사용된다.
| 명령어 | 설명 | 형식 |
| MRS | PSR 레지스터를 범용 레지스터에 복사 | Rd=psr |
| MSR | 범용 레지스터를 PSR 레지스터로 이동 | psr[field]=Rm |
| MSR | 상수값을 PSR 레지스터로 이동 | psr[field]=immediate |
예제를 통해 알아보자!
cpsr = nzcvqIFt_SVC MRS r1, cpsr BIC r1, r1, #0x80 MRS cpsr_c, r1 cpsr = nzcvqiFt_SVC
위 코드는 먼저 MSR 이 cpsr 을 레지스터 r1 으로 복사한다. BIC 명령어는 r1 의 7번째 비트를 0으로 클리어해준다. 그 다음, 레지스터 r1 을 cpsr 로 복사하는데, 이것은 IRQ 인터럽트를 활성화시켜준다. 이 코드는 cpsr 안에 있는 다른 모든 설정값을 유지하며 control 영역안에 있는 I 비트만을 수정한다. 위 예제는 SVC 모드 안에 있다. user 모드에서는 모든 cpsr 비트를 읽을 수 있지만, 상태 플래그 영역 f 만을 업데이트 할 수 있다.
32 비트 상수값을 레지스터에 저장하는 ARM 명령어를 제공한다.
| 명령어 | 설명 | 형식 | |
| LDR | 상수값을 레지스터에 저장하는 의사 명령어 | Rd = 32 비트 상수값 | |
| ADR | 주소값을 레지스터에 저장하는 의사 명령어 | Rd = 32 비트 상대 주소값 | |
다음 예제를 통해 알아보자!
LDR r0, [pc, #const_number-8-{pc}]
...
const_number
DCD 0xff00ffff
위 예제는 32 비트 상수값 0xff00ffff 를 레지스터 r0 로 읽어들이는 LDR 명령어를 보여주고 있다. 상수값을 로드하기 위해 메모리를 엑세스한다.
이 방법은 시간에 민감한 루틴에서는 매우 큰 손실을 가져온다. 그래서 컴파일러와 어셈블러는 메모리에서 상수값을 로드하는 일을 가능한 피하기 위한 교묘한 테크닉을 사용한다. LDR 같은 명령어는 상수값을 만들어 내기 위해 MOV 나 MVN 명령어를 삽입하거나, 코드내에 데이터 영역인 리터럴 풀(literal pool)로부터 상수값을 읽기 위해 pc 상대 주소를 가지고 있는 LDR 명령어를 만들어 낸다.
ARMv5E 에서 제공하는 새로운 명령어들은 다음과 같다.
최상위 비트에서 처음으로 1이 나온 비트 사이에 0 이 몇 개나 있는가를 세는 데 사용된다.
r1 = 0b00000000000000000000010000 CLZ r0, r1 r0 = 15 ; 총 15 개
32 비트 연산 중에 오버플로우가 발생할 경우, 표현할 수 있는 가장 큰 값인 0x7fffffff 값이 유지된다. 이로써 오버플로우가 발생하였는가를 확인하기 위한 코드를 추가할 필요가 없게 된다.
| 명령어 | 포화 연산 |
| QADD | Rd = Rn+Rm |
| QDADD | Rd = Rn+(Rm*2) |
| QSUB | Rd = Rn-Rm |
| QDSUB | Rd = Rn-(Rm*2) |
예제를 보자!
cpsr = nzcvqiFt_SVC r0 = 0x00000000 r1 = 0x70000000 r2 = 0x7fffffff QADD r0, r1, r2 cpsr = nzcvQiFt_SVC r0 = 0x7fffffff ; 오버플로우가 발생하지 않음
포화 상태라는 것을 가리키는 Q 비트(cpsr 의 27번째 비트)가 1로 세트된다. Q 비트는 외부에서 0 으로 설정해줄 때까지 1의 상태를 유지한다.
ARM 명령어는 조건부로 실행될 수 있다. 주어진 조건이나 테스트 상황을 만족할 때에만 명령어가 실행되도록 설정할 수 있다. 조건부 실행 명령어를 이용하면 분기되는 상황을 감소시켜 파이프라인이 깨지는 수를 줄여준다.
디폴트 니모닉은 AL 로 표기하며, “항상 실행하라”는 의미이다.
; z 플래그가 1 인 경우에만, r0 = r1 + r2 ADDEQ r0, r1, r2
위의 예제는 EQ 조건이 추가되어 있는 ADD 명령어에 대해 보여주고 있다. 이 명령어는 cpsr 의 Z(zero) 플래그가 1로 세트되었을 때에만 실행된다.
Thumb 은 32 비트 ARM 명령어의 일부를 16 비트 명령어 세트로 인코딩한다. 16 비트 데이터 버스로 이루어진 프로세서에서는 ARM 명령어보다 Thumb 명령어가 더 나은 성능을 보여준다. 따라서 메모리가 제한되어 있는 시스템에서는 Thumb 명령어를 권한다.
평균적으로 Thumb 코드는 동일한 ARM 코드보다 약 30% 정도 적은 공간을 차지한다.
Thumb 상태에서는 모든 레지스터를 직접 액세스할 수 있는 것은 아니다.
| 레지스터 | 액세스 |
| r0-r7 | 완전 액세스 가능 |
| r8-r12 | MOV, ADD, CMP 일 경우에만 액세스 가능 |
| r13 sp | 제한적인 액세스 |
| r14 lr | 제한적인 액세스 |
| r15 pc | 제한적인 액세스 |
| cpsr | 간접 액세스 |
| spsr | 액세스 불가능 |
위의 표를 보면 cpsr 이나 spsr 을 직접 액세스할 수 있는 명령어가 없다. 즉, Thumb 명령어에는 MRS 나 MSR 에 상응하는 명령어가 없다.
cpsr 이나 spsr 의 값을 변경하기 위해서는 ARM 상태로 변경한 다음, MSR 과 MRS 를 사용하여 값을 바꾸어야 한다. Thumb 에는 코프로세서 명령어도 없다. 역시 같은 방법으로 해결해야 한다.
ARM 코드와 Thumb 코드를 함께 링크하는 방법을 말한다. ARM 루틴에서 Thumb 를 호출하기 위해서 코어는 상태변환을 해야 한다. cpsr 의 T 비트로 확인할 수 있다. BX 와 BLX 분기 명령어는 어떤 루틴으로 분기하는 동안 ARM 과 Thumb 상태를 바꾸어준다. ARM 버전과는 달리, Thumb BX 명령어는 조건부로 실행될 수 없다.
| 명령어 | 설명 |
| BX | ARM/Thumb 상태 변환 분기 명령어 |
| BXL | ARM/Thumb 상태 변환 및 서브루틴 호출 분기 명령어 |
Thumb 상태로 변환하려면 최하위 비트를 1로 설정하면 된다. 복귀주소는 BX 명령어에 의해 자동으로 보존되지는 않으므로, 분기하기에 앞서 MOV 명령어를 사용하여 복귀할 주소를 설정해두어야 한다.
; ARM 코드 CODE32 ; 워드 정렬 LDR r0, =thumbCode + 1 ; Thumb 상태로 진입하기 위해 1을 더함 MOV lr,pc ; 복귀할 주소 설정 BX r0 ; Thumb 코드 & 상태로 분기 ; 계속 ; Thumb 코드 CODE16 ; 하프워드 정렬 thumbCode ADD r1, #1 BX lr ; ARM 코드 & 상태로 복귀
BX 명령어는 0번 비트가 상태 변환을 일으키지 않는다면 일반 분기 명령어로 사용할 수도 있다.
BX 명령어 대신 BLX 명령어를 사용하면 Thumb 루틴을 호출하는 것이 단순해진다. 이것은 링크 레지스터 lr 에 복귀할 주소를 저장하기 때문이다.
CODE32 LDR r0, =thumbRoutine + 1 BLX r0 ; Thumb 코드 & 상태로 분기 ; 계속 CODE16 thumbRoutine ADD r1, #1 BX r14 ; ARM 코드 & 상태로 복귀
조건 분기 명령어는 Thumb 상태에서 유일하게 조건적으로 실행되는 명령어이다.
| 명령어 | 설명 |
| B | 무조건 분기 명령어 |
| BL | 서브루틴 호출 명령어 |
BL 명령어는 조건부 실행이 불가능하며, 분기할 수 있는 범위는 약 +/- 4MB 정도이다.
이들 명령어는 ARM 명령어와 유사한 체계를 따른다. 대부분의 Thumb 데이터 처리 명령어는 하위 레지스터에서만 동작하며 cpsr 을 업데이트한다.
상위 레지스터 r8-r14, pc 에서 동작할 수 있는 예외 명령어에는 다음과 같은 것들이 있다.
MOV Rd, Rn ADD Rd, Rm CMP Rn, Rm
상위 레지스터들을 사용할 경우에는 CMP 를 제외한 다른 명령어들은 상태 플래그를 업데이트하지 않는다. 하지만 CMP 명령어는 cpsr 을 항상 업데이트 한다. 예제를 통해 확인해보자!
cpsr = nzcvIFT_SVC r1 = 0x80000000 r2 = 0x10000000 ADD r0, r1, r2 r0 = 0x90000000 cpsr = NzcvIFT_SVC ; cpsr 을 업데이트 됨
메모리에서 레지스터를 읽거나 메모리에 레지스터를 저장하는 LDR 과 STR 명령어를 지원한다. 이들 명령어는 2개의 프리인덱스 주소지정방식(레지스터 오프셋과 상수 오프셋)을 사용한다.
mem32[0x90000] = 0x00000001 mem32[0x90004] = 0x00000002 mem32[0x90008] = 0x00000003 r0 = 0x00000000 r1 = 0x00090000 r4 = 0x00000004 LDR r0, [r1,r4] ; ① 레지스터 오프셋을 이용한 방법 r0 = 0x00000002 r1 = 0x00090000 r4 = 0x00000004 LDR r0, [r1, #0x04] ; ② 상수값 오프셋을 이용한 방법 r0 = 0x00000002
두가지 방법의 유일한 차이점은 첫번째는 레지스터 r4 안의 값에 따른 오프셋을 사용하는 반면, 두 번째 LDR 은 고정된 오프셋을 사용한다는 것이다.
이들은 IA(Increment After) 주소지정방식만을 지원한다. 이 명령어는 실행 후 베이스 레지스터인 Rn 을 항상 업데이트한다. 베이스 레지스터와 레지스터 리스트는 항상 하위 레지스터인 r0 에서 r7 까지로 제한된다.
ARM 명령어 세트와는 달리, 업데이트를 하라는 의미의 기호 ! 는 옵션이 아니다.
기억할 만한 흥미로운 점은 명령어에 스택 포인터가 없다는 것이다. Thumb 에서 스택 포인터는 레지스터 r13 으로 정해져 있고 sp 는 자동으로 업데이트되기 때문이다. 사용가능한 레지스터는 하위 레지스터인 r0 에서 r7 까지고 제한되어 있다. 스택 명령어는 full descending 스택 동작만을 지원한다.
; 서브루틴 호출
BL ThumbRoutine
; 계속
ThumbRoutine
PUSH {r1, lr}
MOV r0, #2
POP {r1, pc}
위의 예제에서 링크 레지스터 lr은 레지스터 r1 이 쌓여 있는 스택 위에 쌓인다. 복귀시에는 복귀 주소를 pc 에 로드한 후, 레지스터 r1 을 빼낸다. 이로써 서브루틴에서 복귀하게 된다.
Thumb 상태에서 익셉션이나 인터럽트가 발생하면, 프로세서는 그 익셉션을 처리하기 위해 자동으로 ARM 상태로 변경된다. Thumb SWI 명령어는 ARM 과 동일한 기능을 하며 표기법도 같다. 다른 점은 SWI 번호가 0 에서 255 까지로 제한되어 있으며 조건부 실행이 불가능하다는 것이다.
여기서는 같은 루틴이면 C 프로그래밍 상에서 어떻게 최적화 시키는가에 대한 방법에 대해 설명하고 있다.
예를 들어 루프를 한번 수행하는 데, 총 4 번의 사이클을 소요한다면, 10 번의 루프를 돌면 총 40 번의 사이클을 소요하게 된다. 이러한 루프 오버헤드를 루프 언롤링을 이용하여 줄일 수 있다. 언롤링이란 루프문의 몸체를 여러 번 반복하여 적음으로써 같은 비율만큼 반복수를 줄여주는 방법을 말한다.
int checksum(int *data, unsigned int N)
{
int sum = 0;
do
{
sum += *(data++);
sum += *(data++);
sum += *(data++);
sum += *(data++); // 한번에 4번의 루프를 일일이 적어주고
N -= 4; // 총 4 를 뺀다
}while(N != 0);
return sum;
}
이것을 컴파일한 결과는 다음과 같다.
checksum MOV r2, #0 ; sum = 0 checksum_loop LDR r3, [r0], #4 ; r3 = *(data++) SUBS r1, r1, #4 ; N -= 4 & 플래그 업데이트 ADD r2, r3, r2 ; sum += r3 LDR r3, [r0], #4 ; r3 = *(data++) ADD r2, r3, r2 LDR r3, [r0], #4 ADD r2, r3, r2 LDR r3, [r0], #4 ADD r2, r3, r2 BNE checksum_loop ; (N != 0) 이면 loop 로 분기 MOV r0, r2 ; r0 = sum MOV pc, r14 ; r0 리턴
루프 오버헤드는 4N 사이클에서 (4N)/4 = N 사이클로 줄어들었다.
2개의 포인터가 같은 주소를 가리키고 있을 때 이들을 앨리어스(alias)라 부른다. 만약 한 포인터에 값을 적었다면, 그것은 다른 포인터에서 읽은 값에 영향을 주게 될 것이다.
다음은 포인터 앨리어싱을 피하는 방법이다.
struct{
char a;
char c;
short d;
int b;
}
C 라이브러리에서 제공되는 표준 정수 나눗셈 루틴은 20 에서 100 사이클 정도가 소요되는 데, 이것은 ARM 프로세서 제품군이나 조기 종료 여부 그리고 입력 오퍼랜드의 범위에 따라 달라진다. 나눗셈(/)과 나머지(%)는 처리속도가 너무 느리기 때문에 가능하면 사용하지 않는 것이 좋다. 하지만 상수로 나누는 것이나 똑같은 분모로 반복해서 나누는 작업은 효율적으로 처리할 수 있다.
여기서는 곱셈으로 나눗셈을 대체하는 방법과 나눗셈을 가능하면 적게 호출하는 방법에 대해 설명할 것이다.
인라인 함수를 사용하면 함수 호출로 인한 오버헤드를 완전히 없앨 수 있다. 게다가 많은 컴파일러들은 C 소스 코드 상에 인라인 어셈블리를 포함할 수 있도록 지원하고 있다. 어셈블리를 포함한 인라인 함수를 사용하며, 보통은 사용할 수 없는 ARM 명령어와 최적화를 지원하는 컴파일러를 얻을 수 있다.
ARM 에서 C 코드로 포팅할 때 직면할 수 있는 문제점에 대한 설명이다.
ARM 에서 char 형은 unsigned 이다. 하지만 많은 다른 프로세서에서 char 형은 signed 형으로 취급된다. 만약 char 형의 루프 카운터를 사용한다면, 무한 루프에 빠질 수도 있다. 이 것을 해결하기 위해서는 char 형을 signed 형으로 바꿔 주거나, 루프 카운터를 int 형으로 변경한다.
오랜된 몇몇 아키텍처에서는 16 비트 int 를 사용한다. 거의 찾아보기 힘들지만, 32 비트 int 형으로 변환하는 데 문제가 될 수 있다. int 를 사용하기 전에 먼저 테스트를 해보도록 한다.
어떤 프로세서에서는 비정렬 주소에서 short 형이나 int 형 값을 로드하는 것을 지원한다. C 프로그램은 char* 를 int* 로 캐스트함으로써 이것들이 비정렬이 되도록 그 포인터들을 조작할 수 있다.
엔디안에 의존적인 코드를 없애거나 엔디안에 상관없는 코드로 대체해야 한다.
매개변수를 wide 하게 보내는 컴파일러들은 함수 프로토타입이 정확하지 않더라도 올바른 결과를 가져올 수 있다. 항상 ANSI 프로토타입을 사용하도록 한다.
비트필드내에 비트들의 레이아웃을 설정하는 것은 구현 가능하며, 엔디안에 의존적이다. 만약 C 코드가 비트들이 어떤 순서로 배열되어 있다고 가정하고 있다면 이러한 코드는 이식이 어려울 것이다.
enum 이 이식 가능할지라도 컴파일러마다 enum 으로 다른 바이트 수만큼을 할당한다. 그러므로 만약 API 구조체에 enums 를 사용한다면 다른 컴파일러간에 크로스링크나 라이브러리를 사용할 수 없다.
C 코드 상에 인라인 어셈블리를 사용하면 아키텍처간의 포팅이 어려워진다. 따라서 인라인 함수를 쉽게 대체할 수 있는 작은 인라인 함수로 분리해야 한다.
ARM 메모리 매핑된 주변장치의 형(type) 정의를 할 때 volatile 키워드를 사용하도록 한다. 이 키워드는 메모리 액세스를 할 때 컴파일러가 최적화시키는 것을 방지해준다. 또한 컴파일러는 정확한 형으로 데이터 액세스를 보장한다.
예를 들어, 만약 volatile short 형으로 메모리 위치를 정의한다면 컴파일러는 16 비트 로드-스토어 명령어인 LDRSH 와 STRH 를 사용하여 액세스할 것이다.
성능을 극대화 하기 위해서는 크리티컬한 루틴들을 직접 어셈블리 코딩을 통해 최적화 해야한다. 직접 어셈블리 코딩을 하면, C 소스에서는 사용할 수 없었던 다음의 3 가지 최적화 기법을 직접 제어할 수 있다.
ARM 명령어들은 덧셈, 뺄셈, 곱셈과 같은 간단한 원형함수만을 구현하고 있다. 따라서 나눗셈, 제곱근, 삼각함수와 같은 복잡한 연산을 수행하기 위해서는 소프트웨어 루틴을 사용해야 한다. 이러한 복잡한 연산의 성능을 향상시켜주는 많은 유용한 방법과 알고리즘이 있다.
표준이 되는 방법으로는 다음과 같은 것들이 있다.
신호값을 표현하는 방법을 선택하기 위해서는 다음의 분류를 사용한다.
다음은 ARM 에서의 DSP 코드 작성을 위한 가이드이다.
특정 어플리케이션을 위해 언롤링을 하거나 코딩을 하면 이 루틴들은 좀더 개선할 수 있다. 하지만 이 수치는 ARM 시스템에서 실제로 얻을 수 있는 지속적인 DSP 성능에 대한 좋은 아이디어를 제공해야 한다. 벤치마크는 캐시를 가진 코어의 경우 제로-대기-상태(zero-wait-state) 메모리나 캐시 적중을 가정하였을 때 로드, 스토어 루프 오버헤드를 모두 포함하고 있다.
다음은 ARM 필터링 벤치마크를 표로 나타낸것이다.
| 프로세스 코어 | 16비트 도트곱 | 16비트 블록 FIR 필터 | 32비트 블록 FIR 필터 | 16비트 블록 IIR 필터 |
| ARM7TDMI | 7.6 | 5.2 | 9.0 | 22.0 |
| ARM9TDMI | 7.0 | 4.8 | 8.3 | 22.0 |
| StrongARM | 4.8 | 3.8 | 5.2 | 16.5 |
| ARM9E | 2.5 | 1.3 | 4.3 | 8.0 |
| XScale | 1.8 | 1.3 | 3.7 | 7.7 |
다음은 ARM FFT 벤치마크 결과이다.
| 16비트 complex FFT(radix-4) | ARM9TDMI(사이클/FFT) | ARM9E(사이클/FFT) |
| 64포인트 | 3,524 | 2,480 |
| 256포인트 | 19,514 | 13,194 |
| 1,024포인트 | 99,946 | 66,196 |
| 4,096포인트 | 487,632 | 318,878 |
익셉션은 명령어의 순차적인 실행 과정을 바꾸어주는 역할을 한다.
익셉션으로는 Data Abort, FIQ, IRQ, Prefetch Abort, SWI, Reset, Undefined Instruction 의 7 가지가 있다.
인터럽트란 외부 주변 장치에 의해 발생되는 특별한 유형의 익셉션이다. 인터럽트 지연(interrupt latency)이란 외부 인터럽트 요청 신호가 발생할 때부터 특정 인터럽트 서비스 루틴(ISR)의 첫 번째 명령어를 읽어들일 때까지의 시간 간격을 말한다.
대부분의 익셉션들은 익셉션이 발생하였을 때 실행되는 소프트웨어 루틴인 익셉션 핸들러라는 관련 소프트웨어를 가지고 있다.
익셉션이 모드를 변경시킬 때, 코어는 자동으로,
| 익셉션 | 모드 | 주요 목적 |
| Fast Interrupt Request | FIQ | 고속 인터럽트 처리 |
| Interrupt Request | IRQ | 일반 인터럽트 처리 |
| SWI 와 Reset | SVC | 운영체제를 위한 보호 모드 |
| Prefetch Abort 와 Data Abort | abort | 가상 메모리와 메모리 보호 처리 |
| Undefined Instruction | undefined | 하드웨어 코프로세서의 소프트웨어 에뮬레이션 |
벡터 테이블이란, 익셉션이 발생하였을 때 ARM 코어가 분기하는 주소 테이블을 의미한다. 이들 주소는 일반적으로 여러가지 형식 중 하나의 분기 명령어를 포함하고 있다.
다음은 벡터 테이블과 프로세서 모드를 표로 나타낸 것이다.
| 익셉션 | 모드 | 벡터 테이블 오프셋 |
| Reset | SVC | +0x00 |
| Undefined Instruction | UND | +0x04 |
| Software Interrupt | SVC | +0x08 |
| Prefetch Abort | ABT | +0x0c |
| Data Abort | ABT | +0x10 |
| Not assigned | - | +0x14 |
| IRQ | IRQ | +0x18 |
| FIQ | FIQ | +0x1c |
익셉션이 동시에 발생할 때, 각각의 우선순위에 따라서 수행이 된다.
| 익셉션 | 우선순위 | I 비트 | F 비트 |
| Reset | 1 | 1 | 1 |
| Data abort | 2 | 1 | - |
| Fast Interrupt Request | 3 | 1 | 1 |
| Interrupt Request | 4 | 1 | - |
| Prefetch Abort | 5 | 1 | - |
| Software Interrupt | 6 | 1 | - |
| Undefined Instruction | 6 | 1 | - |
예를 들어, IRQ 가 발생하여 인터럽트 처리를 하던 도중에 FIQ 가 발생하면, 인터럽트 루틴을 중지시키고 FIQ 인터럽트 루틴을 실행한 후에 IRQ 인터럽트 루틴으로 제어권을 다시 넘겨준다.
Prefetch Abort 익셉션은 메모리로 부터 명령어를 읽어들이려고 시도하다가 실패한 경우에 발생한다. 이 익셉션은 명령어가 파이프라인의 실행(execute) 단계에 있고, 우선 순위가 높은 다른 익셉션이 발생하지 않을 때에 발생한다.
익셉션이 발생하면 링크 레지스터는 현재의 pc 값을 기준으로 특정 주소값으로 설정된다. 예를 들어, IRQ 익셉션이 발생하면, 링크 레지스터 lr 은 마지막으로 실행된 명령어에 8 을 더한 값을 가리킨다.
| 익셉션 | 주소 | 설명 | |
| Reset | - | lr 값이 정의되지 않음 | |
| Data Abort | lr - 8 | Data Abort 익셉션을 발생시킨 명령어를 가리킴 | |
| FIQ | lllr - 4 | FIQ 핸들러로부터의 복귀주소 | |
| IRQ | lr - 4 | IRQ 핸들러로부터의 복귀주소 | |
| Prefetch Abort | lr - 4 | Prefetch Abort 익셉션을 발생시킨 명령어를 가리킴 | |
| SWI | lr | SWI 명령어 다음 명령어를 가리킴 | |
| Undefined Instruction | lr | undefined instruction 다음 명령어를 가리킴 |
인터럽트(IRQ, FIQ)를 활성화 시키는 방법은 다음과 같다.
| cpsr 값 | IRQ | FIQ |
| 전(pre) | nzcvqjIFt_SVC | nzcvqjiFt_SVC |
| 코드(code) | enable_irq | enable_fiq |
| MRS r1, cpsr | MRS r1, cpsr | |
| BIC r1, r1, #0x80 | BIC r1, r1, #0x40 | |
| MSR cpsr_c, r1 | MSR cpsr_c, r1 | |
| 후(post) | nzcvqjiFt_SVC | nzcvqjIft_SVC |
다음은 인터럽트(IRQ, FIQ)를 비활성화 시키는 방법이다.
| cpsr 값 | IRQ | FIQ |
| 전(pre) | nzcvqjift_SVC | nzcvqjift_SVC |
| 코드(code) | disable_irq | disable_fiq |
| MRS r1, cpsr | MRS r1, cpsr | |
| ORR r1, r1, #0x80 | ORR r1, r1, #0x40 | |
| MSR cpsr_c, r1 | MSR cpsr_c, r1 | |
| 후(post) | nzcvqjIft_SVC | nzcvqjiFt_SVC |
인터럽트 처리 방법으로는 다음과 같은 것들이 있다.
| 중첩을 허용하지 않는 인터럽트 처리 방법 | 가장 간단한 인터럽트 처리 방법으로, 순차적으로 각각의 인터럽트들을 처리하는 방법 |
| 중첩을 허용한 인터럽트 처리 방법 | 우선순위 없이 다중 인터럽트를 처리하는 방법 |
| 재진입 인터럽트 처리 방법 | 우선순위를 적용하여 다중 인터럽트들을 처리하는 방법 |
| 우선순위를 적용한 간단한 인터럽트 처리 방법 | 우선순위를 적용한 인터럽트들을 처리하는 방법 |
| 우선순위를 적용한 표준 인터럽트 처리 방법 | 우선순위가 높은 인터럽트를 우선순위가 낮은 인터럽트보다 더 빠른 시간 안에 처리하는 방법 |
| 우선순위를 적용한 다이렉트 인터럽트 처리 방법 | 우선순위가 높은 인터럽트를 더 짧은 시간에 처리하기 위한 방법으로, 특정 서비스 루틴으로 직접 분기하는 방법 |
| 우선순위를 적용한 그룹 인터럽트 처리 방법 | 인터럽트들을 여러 개의 인터럽트 우선순위로 묶어서 처리하는 방법 |
IRQ 스택에 데이터가 있는 동안에는 핸들러가 문맥전환을 수행할 수 없기 때문에 문맥 전환을 수행하려면 IRQ 스택을 비워야 한다. IRQ 스택에 저장되어 있는 모든 레지스터들은 태스크의 스택, 보통 SVC 스택으로 전송된다. 이것들은 스택 프레임이라는 스택 상에 할당된 메모리 블록으로 전송된다.
펌웨어란 하드웨어를 어플리케이션이나 운영체제와 인터페이스해주는 하위 레벨의 코드를 말한다. 부트로더란 운영체제나 어플리케이션을 메모리로 다운로드한 후, 그 소프트웨어로 pc 제어권을 넘겨주는 작업을 수행하는 소프트웨어를 말한다.
다음과 같은 과정에 따라 이미지를 로드하고 부팅시킨다.
ARM 프로세서에서 실행되는 임베디드 운영체제는 다음과 같은 기본 컴포넌트들로 구성되어 있다.
여기서는 SLOS(Simple Little Operating System)라는 운영체제에서 다음의 컴포넌트들에 대한 예제를 보여준다.
캐시는 프로세서와 주메모리 사이에 위치해 있는 작고 빠른 메모리를 말한다. 이것은 최근에 참조한 시스템 메모리의 일부를 저장하고 있는 일종의 저장 버퍼이다. 프로세서는 시스템의 성능을 향상시키기 위해 가능하면 시스템 메모리보다는 캐시 메모리를 사용하기를 선호한다.
쓰기 버퍼란 프로세서 코어와 주 메모리 사이에 놓여있는 작은 FIFO(First-In-First-Out) 메모리를 말한다. 쓰기버퍼의 목적은 주 메모리에 쓰는 것과 관련된 느린 쓰기 시간으로부터 프로세서 코어와 캐시 메모리를 자유롭게 하는 데 있다.
참조의 지역성 원리란 컴퓨터 소프트웨어 프로그램이 데이터 메모리의 지역적인 부분에서 반복적으로 동작하는 코드의 작은 루프를 자주 실행한다는 것을 의미한다. 이는 캐시를 가진 프로세서 코어를 사용할 때 시스템의 평균 성능이 다소 높아지는 이유이기도 하다.
캐시 아키텍처의 특징을 설명하기 위해 ARM 커뮤니티에서 사용되는 많은 용어들이 있다. 편의를 돕기 위해 아래의 표를 만들었는데, 이는 현재 캐시를 가지고 있는 모든 ARM 코어의 특징들에 대해 설명하고 있다.
| 코어 | 캐시타입 | 캐시크기(KB) | 캐시라인크기(워드) | 연상 | 위치 | 캐시 락다운 지원 여부 | 쓰기 버퍼 크기(워드) |
| ARM720T | 통합 | 8 | 4 | 4-way | 논리캐시 | no | 8 |
| ARM740T | 통합 | 4 또는 8 | 4 | 4-way | yes 1/4 | 8 | |
| ARM920T | 분할 | 16/16 D+I | 8 | 64-way | 논리캐시 | yes 1/64 | 16 |
| ARM922T | 분할 | 8/8 D+I | 8 | 64-way | 논리캐시 | yes 1/64 | 16 |
| ARM940T | 분할 | 4/4 D+I | 4 | 64-way | yes 1/64 | 8 | |
| ARM926EJS | 분할 | 4-128/4-128 D+I | 8 | 4-way | 논리캐시 | yes 1/4 | 16 |
| ARM946EJS | 분할 | 4-128/4-128 D+I | 4 | 4-way | 논리캐시 | yes 1/4 | 4 |
| ARM1022E | 분할 | 4-128/4-128 D+I | 8 | 64-way | 논리캐시 | yes 1/64 | 16 |
| ARM1026EJS | 분할 | 16/16 D+I | 8 | 4-way | 논리캐시 | yes 1/4 | 8 |
| 인텔 StrongARM | 분할 | 4-128/4-128 D+I | 4 | 32-way | 논리캐시 | no | 32 |
| 인텔 XScale | 분할 | 32/32 D+I | 8 | 32-way | 논리캐시 | yes 1/32 | 32 |
캐시 라인(cache line)이란 캐시의 기본 컴포넌트로서 디렉토리 스토어, 데이터 섹션, 상태 정보의 세 부분으로 구성된다. 캐시 태그(cache-tag)란 캐시 라인이 주메모리로부터 어디로 로드되는지를 가리키는 디렉토리 엔트리를 말한다. 캐시 안에는 유효 비트와 더티 비트의 2 가지 상태 비트가 있다. 유효 비트는 관련 캐시 라인이 활성화 메모리를 포함하고 있을 때에 1 로 설정된다. 더티 비트는 캐시가 후기입(writeback) 정책을 사용하고 있으며 새로운 데이터가 캐시 메모리에 쓰여질 때에 활성화 된다.
캐시가 MMU 앞에 놓이느냐 뒤에 놓이느냐에 따라 물리(physical) 캐시 또는 논리(logical) 캐시로 구분된다. 논리 캐시는 프로세서 코어와 MMU 사이에 놓여 가상 주소 공간 안에 있는 코드와 데이터를 참조한다. 물리 캐시는 MMU 와 주메모리 사이에 놓여 물리주소를 사용하여 코드와 데이터를 참조한다.
직접 매핑된 캐시는 주어진 주 메모리 공간에 대해 캐시 안에 하나의 공간만 할당되어 있는 매우 간단한 캐시 아키텍처이다. 직접 매핑 캐시는 스래싱(thrashing)의 지배를 받는다.
여기서 스래싱이란, 캐시 메모리 안에 동일한 위치에 대해 소프트웨어 병목현상이 발생하는 것을 말한다. 스래싱 때문에 캐시 라인을 반복해서 제거하고 로딩하는 작업이 일어난다.
로딩과 제거는 프로그램의 일부를 캐시 라인 안에 있는 동일한 캐시 라인에 매핑되어 있는 주소에서 주 메모리로 이동하는 결과를 야기한다. 스래싱을 줄이기 위해 캐시는 way 라는 더 작은 단위로 쪼개져야 한다. 이 방법을 사용하면 하나의 주 메모리 주소에 대해 캐시 안에 여러개의 저장위치를 제공할 수 있다. 예를 들어 주 메모리안에 있는 어떤 하나의 위치는 캐시 안에 4개의 다른 위치에 매핑될 수 있다. 이 캐시를 세트 연상 캐시라고 부른다.
코어 버스 아키텍처는 캐시 시스템의 설계를 결정한다. 폰노이만 아키텍처는 코드와 데이터를 저장하기 위한 통합 캐시를 사용하고, 하버드 아키텍처는 분할 캐시를 사용한다. 분할 캐시는 명령어를 위한 캐시와 데이터를 위한 캐시가 분리되어 있는 형태를 말한다.
캐시 교체 정책은 캐시 미스가 발생하였을 때 교체를 위해 어떤 캐시를 선택할 지를 결정해준다. 설정된 정책은 캐시 컨트롤러가 캐시 메모리 안에 가능한 설정으로부터 캐시 라인을 선택하기 위해 사용하는 알고리즘을 정의한다. 교체하려고 선택된 캐시 라인을 victim 이라고 한다. ARM 캐시 코어에서 사용 가능한 2 가지 교체 정책에는 의사 랜덤 방식과 라운드 로빈 방식이 있다.
1. 라운드 로빈 방식(또는 주기적 교체) : 단순히 교체될 세트안에 있는 다음 캐시 라인을 선택하는 것을 말한다. 선택 알고리즘은 캐시 컨트롤러가 캐시 라인을 할당할 때마다 증가되는, 순차적으로 증가하는 victim 카운터를 사용한다. victim 카운터가 최대값에 이르면 정의된 베이스값으로 리셋된다.
* 의사 랜덤 교체 방식 : 교체될 세트 안에 있는 다음 캐시 라인을 랜덤하게 선택하는 것을 말한다. 선택 알고리즘은 무작위로 증가하는 victim 카운터를 사용한다. 의사 랜덤 교체 알고리즘에서 컨트롤러는 무작위로 증가값을 선택하고 이를 victim 카운터에 더해서 victim 카운터를 증가시킨다. victim 카운터가 최대값에 이르면 정의된 베이스값으로 리셋된다.
캐시 메모리에 데이터를 쓸 때 사용가능한 방법도 2 가지가 있다. 컨트롤러가 캐시 메모리만 업데이트할 경우에는 후기입 방식이라고 하고, 캐시 컨트롤러가 캐시와 주메모리에 모두 쓸 때에는 연속기입 방식이라고 한다.
1. 연속기입 방식 : 캐시가 적중(hit)을 하면 캐시와 주메모리에 모두 값을 저장한다. 이 방법의 경우 캐시와 주메모리가 항상 일관성을 유지하게 된다. 이 정책하에서는 캐시 컨트롤러는 캐시 메모리에 값을 쓰기위해 주메모리에 쓰는 작업을 수행해야 한다. 주메모리에 값을 저장해야 하기 때문에 연속기입 방식은 후기입 방식보다 느리다.
* 후기입 방식 : 유효한 캐시 데이터 메모리에만 저장하고 주메모리에는 저장하지 않는다. 결과적으로 유효 캐시 라인과 주메모리는 다른 데이터를 포함할 수도 있다. 이 방식의 장점은 서브루틴에 의해 임시 지역변수를 반복적으로 사용할 때에 생긴다. 이 변수들은 원래 임시로만 사용되기 때문에 실제로는 저장할 필요가 없다.
캐시 미스가 발생하였을 때 캐시 컨트롤러가 캐시 라인을 할당하기 위해 사용하는 방법에도 2 가지가 있다. read-allocate 방식이란 데이터를 주메모리에서 읽었을 때 캐시 라인을 할당하고, write-allocate 방식은 주메모리에 쓸 때 캐시 라인을 할당한다.
ARM 에서는 주 메모리의 D 캐시 안에 있는 데이터를 복사한다는 의미로 클린 이라는 용어를 사용한다. 또한 캐시의 내용을 없애버린다는 의미로 플러시라는 용어를 사용한다.
캐시 락다운은 몇 가지 ARM 코어에서 제공되는 특징을 말한다. 락다운 특징은 코드와 데이터를 캐시 쪽에 로드하고 eviction(제거) 로 부터 면제되었다는 것을 표시한다. 락다운 안에 있는 코드나 데이터는 캐시 메모리 안에 저장되어 있기 때문에 보다 빠른 시스템 반응을 제공한다. 캐시 안에 정보를 락(lock) 하는 목적은 캐시 미스 문제를 피하기 위해서다.
메모리를 보호할 수 있는 방법에는 2가지가 있는 데, 첫 번째 방법은 비보호(unprotected) 로 태스크 상호 작용을 위한 규칙들을 관리하기 위해 소프트웨어 제어 루틴을 사용한다.
두 번째 방법은 보호(protected)로 태스크 상호 작용을 관리하기 위해 하드웨어와 소프트웨어를 둘 다 사용하고 있다. 보호 시스템에서는 접근 권한이 침범받았을 경우 하드웨어적으로 abort 를 발생시켜 메모리의 영역을 보호한다. 그러면 소프트웨어는 abort 루틴을 처리하고 메모리 기반의 자원을 제어하도록 반응한다.
ARM MPU 는 시스템 보호를 위한 기본적인 구조로 영역을 사용하고 있다. 영역이란 메모리의 영역과 관련되어 있는 속성값들의 모임을 말한다. 영역은 오버랩될 수 있기 때문에 현재 실행중인 태스크에 의한 원치 않는 액세스로부터 잠들어 있는 태스크의 메모리 영역을 보호하기 위해 배경 영역을 사용할 수 있다.
다양한 영역의 속성을 설정하기 위한 루틴들을 포함하여 MPU 를 초기화하는 데 필요한 여러 단계가 있다.
보호시스템을 정의한 다음에는 보호 시스템을 실행하는 데 필요한 마지막 단계는 태스크 변환을 하는 동안 다음 태스크로 영역 할당을 변경하는 것이다.
MMU 의 핵심기능은 각 태스크들이 그 자신만의 가상 메모리 공간에서 독립적으로 프로그램을 실행할 수 있도록 관리해주는 것이다. 가상 메모리 시스템의 중요한 특징으로는 주소 재배치를 들 수 있다. 주소 재배치란 프로세서 코어가 사용하려는 주소를 주 메모리 안의 다른 주소로 변경해주는 것이다. 이 변환 작업은 MMU 하드웨어에 의해 수행된다.
가상 메모리 시스템에서 가상 메모리는 보통 고정 영역과 동적 영역으로 나누어 진다. 고정 영역에서는 페이지 테이블 안에 매핑되어 있는 변환 데이터가 일반적인 동작이 이루어지는 동안에 변경되지 않는다. 동적 영역에서는 가상 메모리와 물리 메모리 사이에 매핑되어 있는 메모리가 종종 변경된다.
페이지 테이블은 가상 페이지 정보에 대한 설명을 포함하고 있다. 페이지 테이블 엔트리(PTE) 는 가상 메모리 안에 있는 페이지를 물리 메모리 안에 있는 페이지 프레임으로 변환한다. 페이지 테이블 엔트리는 가상 주소에 의해 구조화되어 있으며 한 페이지를 한 페이지 프레임으로 매핑하기 위한 변환 데이터를 포함하고 있다.
ARM MMU 의 기능은 다음과 같다.
ARM MMU 에 추가된 주요 특징으로는 고속 문맥전환 확장(FCSE)이 있다. 고속 문맥 전환 확장은 문맥전환중에 캐시나 TLB 를 플러시할 필요가 없기 때문에 멀티 태스크 환경에서 성능을 향상시켜 준다.
작은 가상 메모리 시스템의 동작 예는 멀티태스킹을 지원하기 위해 MMU 를 셋업하는 자세한 방법을 보여주고 있다. 셋업하는 단계는 다음과 같다.
ARM 아키텍처는 정적 상태에 머물러 있지 않고, 오늘날의 소비가 기기에서 요구되는 어플리케이션에 맞도록 개발되고 개선되어 왔다. ARMv5TE 아키텍처가 ARM 에 DSP 의 일부를 추가하는 데 매우 성공적이었음에도 불구하고, ARMv6 에서는 또 다시 대규모 멀티프로세서 시스템을 위해 DSP 의 지원을 더욱 확장하고 추가로 지원하였다.
다음의 표는 이 새로운 기술들이 다른 프로세서 코어에 어떻게 매핑되었는가를 보여주고 있다.
| 프로세서 코어 | 아키텍처 버전 |
| ARM1136J-S | ARMv6J |
| ARM1156T2-S | ARMv6 + Thumb-2 |
| ARM1176JZ-S | ARMv6J + TrustZone |
ARM 은 주요 장점 중의 하나인 코드 밀집도에 지속적인 관심을 보임으로써, 최근에는 Thumb 아키텍처의 확장 버전인 Thumb-2 를 발표하기에 이르렀다. TrustZone 과 관련된 보안에 대한 새로운 관심은 ARM 이 이분야의 선도적인 위치를 차지할 수 있도록 해주었다.
ARM 에서 제공하는 AMBA 버스를 사용하면 SoC 설계자 간의 의사소통이 용이하여 SoC 설계시간 및 오류를 단축할 수 있고, IP 의 재사용이 용이해 외부의 AMBA 버스 기반으로 설계된 표준 IP 도입으로 SOC 설계시간을 단축할 수 있다.
고속으로 동작하는 입출력 제어기가 연결되는 버스 인터페이스인 AHB(Advanced High-performnce Bus), ASB(Advanced System Bus) 와 저속의 입출력 제어기가 연결되는 버스인 APB(Advanced Peripheral Bus) 가 있으며, 근래에 발표된 AMBA 버스 사양 3.0 에는 낮은 전력소모와 고속 동작을 지원하는 AXI(Advanced eXtensible Bus) 가 있다.
특징은 다음과 같다.
AHB 버스는 마스터, 슬레이브, 중재기, 그리고 디코더로 구성되어 있다.
AHB 버스와 마찬가지로 고속으로 동작하는 입출력 제어기를 연결하기 위한 규격이다. 사실 ASB 버스는 AHB 버스보다 먼저 설계된 고속 버스로 ARM 의 내부 버스의 구조에 기본을 두고 있다. ASB 도 고속으로 동작하기 위하여 파이프라인 동작을 지원하고, 여러 개의 버스마스터가 사용될 수 있다. 하지만 ASB 버스는 AHB 버스와 달리 rising-edge 와 falling-edge 클럭을 모두 사용하도록 설계되었다.
APB(Advanced Peripheral Bus) 버스는 브리지와 슬레이브로 구성되어 있다. APB 브리지는 AHB 버스와 연결되어 AHB 에서 구동된 어드레스를 일시적으로 유지하고, APB 슬레이브를 디코드하는 기능을 가지고 있으며, APB 슬레이브를 제어하기 위한 신호를 생성한다. APB 버스는 비교적 속도가 느린 주변 장치를 제어하고 전력 소모를 줄일 수 있도록 구성하기 위하여 간단한 인터페이스 구조를 가지고 있으며, 래치 타입의 어드레스와 제어신호를 가지고 있어 전력소모를 줄일 수 있도록 설계되어 있다.
AXI(Advanced eXtensible Interface) 버스는 AMBA 버스 사양 3.0 에서 제시된 고속의 버스로, 버스 사이클은 어드레스 구동단계, 제어신호 구동단계와 데이터 구동단계로 분리되어 있으며, 정렬되지 않은 데이터 전송 및 버스트 전송을 지원하고 있다. AXI 버스는 데이터를 읽기 위한 데이터 채널과 데이터를 쓰기 위한 데이터 채널로 분리되어 있고, 고속 동작에 적합하게 설계되어 있으며, 전력소모를 줄이도록 설계되어 있다.
새롭게 알게된 점들을 정리했다.
명령어에서 이해가 안갔던 부분은 '=' 이었다.
'=' 은 ARM 어셈블러에서 사용되는 특수 기능으로서 프로그래밍을 좀 더 간편하게 해주는 용도로 쓰인다.
위의 명령어를 처리하기 위해서는 두 번의 오퍼레이션이 필요한데, 그것을 한 번에 처리할 수 있도록 해준다.