ARM Applications


 
* Update history

- 2012.11.3 : 초기 Release



 
1. Base of Software Engineering
   1.1 Memory Endian Formats
   1.2 보수 표현
   1.3 논리연산
   1.4 Shift 연산
   1.5 Rotate 연산
   1.6 진수변환
   1.7 Data Types
   1.8 Aligned & Un-Aligned Data Access



 
1장의 내용은 컴퓨터를 전공한 사람이라면 누구나 다 아는 내용이지만 어셈블리어 실습을 하기위해서는 반드시 알아야 하는 내용입니다.
다 아는 내용이겠지만 기억을 되살리는 차원에서 간단하게만 설명 하도록 하겠습니다.

1. Base of Software Engineering

1.1 Memory Endian Formats
워드 데이터 안에서의 바이트 순서로 크게 빅 엔디언과 리틀 엔디언으로 나눌 수 있습니다. 빅 엔디언은 사람이 숫자를 쓰는 방법과 같이 큰 단위의 바이트가 앞에 오는 방법이고, 리틀 엔디언은 반대로 작은 단위의 바이트가 앞에 오는 방법입니다. 다시한번 강조 하지만 이것은 워드 범위 내에서의 바이트 단위의 순서 입니다.
또한 ARM 레지스터에 저장이 될 경우에는 엔디안의 구분이 없고 메모리 접근 측면에서만 구분이 필요 합니다.

엔디안 종류 0x1234 0x12345678 아키텍쳐
Big-endian 12 34 12 34 56 78 모토로라 등
Little-endian 34 12 78 56 34 12 x86


* 몇몇 아키텍처는 빅 엔디언과 리틀 엔디언 중 하나를 선택할 수 있도록 설계되어 있고, 이를 바이 엔디언(Bi-endian)이라 부릅니다. PowerPC, DEC 알파, MIPS, PA-RISC, IA-64 등은 바이 엔디언을 사용하는 대표적인 아키텍처 입이다. 우리가 공부하고 있는 ARM도 엔디언을 선택해서 사용할 수 있도록 설계되어 있습니다.
x86 아키텍처가 리틀 엔디언을 쓰기 때문에, 오늘날 x86 아키텍처를 사용하는 대부분의 데스크톱 컴퓨터는 리틀 엔디언을 쓰며 이를 "인텔 포맷" 이라고 합니다

Example)
32비트 숫자인 0x2A를 Little-endian으로 표시 하면 ?
0x00 00 00 2A = 2A 00 00 00


1.2 보수 표현
컴퓨터에서 보수를 사용하는 이유는 무었을까요 ? 모든 것이 0과 1로 표현되는 컴퓨터에서 음수를 표현하는데 편리 하기 때문에 사용합니다. 물론 이를 이용해서 뺄셈도 합니다. 예를 들면 12-3 의 수식을 컴퓨터(ALU)에서 계산을 하면 실제로는 12에서 3을 빼는 것이 아니라 12 와 3의 2의 보수(3의 음수표현)를 더해서 계산을 하게 됩니다.

1.2.1 "1" 의 보수
주어진 이진수의 모든 자리의 숫자를 반전(0을 1로, 1을 0으로)시키면 1의 보수를 얻을 수 있습니다.

8자리의 이진수 01001011(십진수로 75)의 1의 보수를 구하면 아래와 같습니다.

 01001011 의 모든 자리의 수를 반전시킨다.

 10110100 --> 1의 보수

1.2.2 "2" 의 보수
1의 보수를 구한다음 2진수 "1" 을 더하면 됩니다.

8자리의 이진수 01001011(십진수로 75)의 2의 보수를 구하면 아래와 같습니다.

       01001011 의 모든 자리의 수를 반전시킨다.
       10110100 --> 1의 보수
                
       10110100
  +)  00000001 --> 1의 보수에서 1을 더합니다.
  ---------------

       10110101 --> 2의 보수

보수를 구하할대 한 자리가 더 길어질 경우에는 가장 높은 자리의 숫자는 버립니다.


1.2.3 "2" 의 보수를 이용한 뺄셈

예제) 100-75를 2의 보수를 이용해서 연산하시요.

100-75는 100+(-75)와 같으므로


       01100100 ( 10진수 100 )
   +) 10110101 ( 10진수 75의 2의 보수 )
   ---------------
       100011001


새로 생긴 가장 높은 자리의 숫자를 빼고 남은 00011001은 십진수로 25와 같으며 이는 계산하려던 식의 결과입니다.

예제) 10-75를 2의 보수를 이용해서 연산하시요.

10-75는 10+(-75)와 같으므로


       00001010 ( 10진수 10 )
   +) 10110101 ( 10진수 75의 2의 보수 )
   ---------------
       10111111 ( 이 결과는 음수 : MSB가 1 이므로 다시 2의 보수를 취하면 65 즉 -65 )

10111111 결과는 MSB가 1(음수) 이므로 다시 2의 보수를 취해보면 01000001은 십진수로 65와 같으며 이는 계산하려던 식의 결과 -65가 됩니다.

 

1.3 논리 연산

1.3.1 NOT

2진수 각 자리의 값을 반대로 바꾸는 연산 입니다. 이 연산은 어떤 값의 보수를 구할 때 효과적으로 사용할 수 있습니다.


NOT 0111
  = 1000

C나 C++에는 "~" 연산을 통해 이 값을 구할 수 있습니다.



x = ~y;

1.3.2 OR
두 값의 각 자릿수를 비교해, 둘 중 하나라도 1이 있다면 1을, 아니면 0을 계산합니다.


   0101
OR 0011
 = 0111

C/C++에는 "|" 연산자가 이 기능을 제공 합니다.



x = y | z;


1.3.3 XOR 
두 값의 각 자릿수를 비교해, 값이 같으면 0, 다르면 1을 계산합니다.


    0101
XOR 0011
  = 0110

C/C++에는 "^" 연산자가 이 기능을 제공한다.



x = y ^ z;

1.3.4 AND

두 값의 각 자릿수를 비교해, 두 값 모두에 1이 있을 때에만 1을, 나머지 경우에는 0을 계산 합니다.


    0101
AND 0011
  = 0001

C/C++에는 & 연산자가 이 기능을 제공한다.


    
  x = y & z;


1.4. Shift 연산
1.4.1 왼쪽으로 쉬프트(<<)

"<<" 키워드는 데이터를 왼쪽으로 비트 수만큼 이동시키기 때문에 2의 "이동할 비트수" 를 제곱한 값과 데이터가 곱해진 값이 결과 값으로 도출 됩니다. 5(b00000101)를 왼쪽으로 3비트 이동시키면 5 << 3 = 40(b00101000) 즉 5 * 8 한 것과 같은 효과가 나타 납니다.


가중치 128 64 32 16 8 4 2 1
연산전 0 0 0 0 0 1 0 1
연산후 0 0 1 0 1 0 0 0

 

1.4.2 오른쪽으로 쉬프트(>>)

">>" 키워드는 데이터를 오른쪽으로 비트 수만큼 이동시키기 때문에 2의 "이동한 비트수" 를 제곱한 값으로 데이터를 나눗셈 한 값이 도출 됩니다.
40(b00101000)를 오른쪽으로 1비트 이동시키면 40 >> 1 = 20(b00101000) 즉 40/2 한 것과 같은 효과가 있습니다.


가중치 128 64 32 16 8 4 2 1
연산전 0 0 1 0 1 0 0 0
연산후 0 0 0 1 0 1 0 0


1.4.3 부호가 있는 왼쪽 쉬프트 연산(<<)

79(b01001111) << 1 = -98(b10011110)



가중치 부호 64 32 16 8 4 2 1
연산전 0 1 0 0 1 1 1 1
연산후 1 0 0 1 1 1 1 0

 

부호 비트("1")와 그 다음 비트("0")가 서로 다른 값인 경우 부호가 바뀌고 Overflow가 발생하게 됩니다. 위의 예제에서는 양수에서 음수로 바뀌면서 Overflow가 발생 하였습니다.

1.4.4 부호가 있는 오른쪽 쉬프트 연산(>>)

여기서 65의 2의 보수를 이용해서 -65를 표현 했습니다. -65(b10111111) >> 1 = -33(b11011111)


 

예를 들어 위의 그림과 같이 "1011 1111" 값을 오른쪽으로 한번 이동하면 왼쪽에 생긴 빈 공간은 최상위 비트인 1 값으로 채워지는 것입니다. 만약 "-160 >> 4" 를 연산하면 -160 의 이진수 값인
"1010 0000" 가 오른쪽으로 4비트만큼 이동하고, 왼쪽에 생긴 네개의 빈 공간은 최상위 비트인 1 로 채워지므로 연산 결과는 "1111 1010" 가 됩니다.

1.5 Rotate 연산
데이터의 모든 비트들을 각각 서로 이웃한 비트의 자리로 옮기고 한 쪽 끝에서 밀려 나가는 비트가 다시 반대편 끝으로 들어오게 되는 연산 입니다.

1.5.1 부호 없는(Rotate no carry) 왼쪽 Rotate

23(b00010111) ROL 1 = 46(b00101110)


Left circular shift or rotate

1.5.2 부호 없는(Rotate no carry) 오른쪽 Rotate

23(b00010111) ROR 1 = 139(b10001011)

Right circular shift or rotate

1.5.3 부호 있는(Rotate through carry) 왼쪽 Rotate

ROL과 동일하지만 Carry Flag가 최하위 비트로 이동하고, 최상의 비트의 값이 Carry Flag 로 이동 됩니다.

23(b00010111) RCL 1 = 47(b00101111)

 
Left rotate through carry

1.5.4 부호 있는(Rotate through carry) 오른쪽 Rotate

23(b00010111) RCR 1 = 267(b100001011)


Right rotate through carry


아주 기본적인 내용들 이지만 기본적인것 부터 정확하게 알고 있어야 합니다. 주로 데이터 암호화나, 고속의 그래픽 처리 작업 등에서 Shift, Rotate연산을 많이 사용합니다.

1.6 진수변환

2진수를 16진수롤 변환 할 때 4자리씩 끊어서 계산하면 편리합니다. 자주 쓰이는 수들의 2진수와 16진수들의 예제들 입니다.

2^4 = 16B = b0001 0000 = 0x10
2^5 = 32B = b0010 0000 = 0x20
2^6 = 64B = b0100 0000 = 0x40
2^7 = 128B = 0x80
2^8 = 256B = b0001 0000 0000 = 0x100
2^10 = 1KB = b0100 0000 0000 = 0x400
2^20 = 1MB = b0001 0000 0000 0000 0000 0000 = 0x100000
2^30 = 1GB = 0x40000000
2^40 = 1TB = 0X10000000000
4KB = 0x1000, 64KB = 0x10000, 1MB = 0x100000

Ex)
4GB = 4*1GB = 4 * 2^30 = 2^2 * 2^30 = 2^32 = 0x100000000
256KB = 256*1KB = 2^8 * 2^10 = 2^18 = 0x40000
96KB = 64KB + 32KB = 2^6*2^10 + 2^5*2^10 = 0x10000 + 0x3000 = 0x13000
135KB = 128KB + 4KB + 2KB + 1KB = 2^17 + 2^12 + 2^11 + 2^10 = 0x20000 + 0x1000 + 0x800 + 0x400 = 0x21c00

1.7 Data Types

Byte( 8 bits )
Halfword( 16 bits )
Word ( 32 bits )
Long Word ( 64 bits )

- Value ranges

unsigned char val;
0 <= val <= 2^8(255)

signed char val;
-128(0x80) <= val <= 127(0x7F)

int(signed 32bit integer) val;
0x8000 0000 <= val <= 7FFFF FFFF

1.8 Aligned & Un-Aligned Data Access

1.8.1 Aligned Access
(1) Halfword Data Access : 데이터의 주소 값이 짝수 이어야 합니다.
(2) Word Data Access : 데이터의 주소 값이 4의 배수 이어야 합니다.

Aligned Data Access
char buf[100];

int *p;
int gVar;

p = (int *)&buf[0];
gVar = *p --> Data Abort 가능성 1


p = (int *)&buf[1];
gVar = *p --> Data Abort 가능성 2


- Data Abort 가능성 2 : buf의 데이터형이 char(8 bit) 이기 때문에 0번째 이후 부터는 4의 배수가 아니기 때문에 Data Abort 가능성이 있습니다.
- Data Abort 가능성 1 : 0번째 인덱스인데 어떻게 보면 Data Abort 가능성이 없는것 처럼 보입니다. 보통은 Linker가 알아서 buf 변수의 할당 주소를 4의 배수에 맞추어 주지만 그렇지 않을 가능성도 있기 때문에 링커에 따라서는 Data Abort 가능성이 있는 것입니다. 그렇다면 Data Abort가 발생하지 않도록 char 배열 buf를 선언 하려면 어떻게 해야 할까요 ? 컴파일러 지시자를 사용해서 char 배열 buf 를 강제로 4의 배수로 맞추어 주는 것입니다.

Aligned Data Access
#pragma data_alignment = 2 --> 강제로 4의 배수가 되도록 지시
char buf[100];

int *p;
int gVar;

p = (int *)&buf[0];
gVar = *p --> 정상동작


p = (int *)&buf[1];
gVar = *p --> 1번째 배열 에서는 여전히 Data Abort 가능성 2


여기서 또 한가지 ARM C언어에서 구조체 선언을 할때 어떻게 선언하는 것이 효율적인지를 알아 봅시다.

비 효율적인 구조체 데이터 선언
- 메모리 배치


위의 그림을 보면 총 5byte의 메모리 공간을 낭비 하였습니다. 그렇다면 선언 순서를 short c, int b 의 순서를 바꾸면 어떻게 될까요 ?

비 효율적인 구조체 데이터 선언
- 메모리 배치


단지 선언 순서만 바꾸어도 메모리 낭비가 5-bytes 에서 1-byte로 줄었습니다.

1.8.2 Un-Aligned Data Access
(1) ARM Architecture v6 부터 지원됨
(2) ARM Architecture v4T, v5T(E) 등에서 Un-aligned data를 접근 하려고 하면 Data Abort가 발생 합니다.