Thief of Wealth
Published 2019. 4. 18. 00:47
6. ARM 명령어 개발/마이크로컴퓨팅

https://ezbeat.tistory.com/359


위 블로그에도 많은 정보들이 있습니다.



ARM의 명령어의 종류에는


데이터 처리 명령어

분기 명령어

Load/Store 명령어

Software Interrupt 명령어

Program Status Resgiter (PSR) 명령어들이 있습니다.


각각에 대해 살펴보도록 하겠습니다.


1. 데이터 처리 명령어


- ARM은 RISC아키텍쳐이므로 명령어의 길이는 32bit으로 고정입니다.

- 3-address 형식을 지닙니다.

- 각 연산은 1개의 cycle에 실행됩니다.

- 조건부로 실행이 가능합니다.

- 기본적으로 결과가 상태플래그 CPSR에 영향을 주지 않습니다.

- 명령어에 S를 붙여주면 상태플레그 CPSR에 영향을 줍니다. (ADD -> ADDS , SUB -> SUBS)

S접미사를 줄경우 영향을 끼치는 flag는 다음과 같습니다.

(1) N flag : 연산의 결과값이 Negative 입니까?

(2) Z flag : 결과값이 0입니까?

(3) C flag : 캐리값이 발생했습니까?


- 또 데이터 처리 명령어에서 한번 더 데이터의 범주를 분리할 수 있습니다.


(1) 데이터 이동용 (Data Movement)

: 주로 대입할때 사용됩니다


- MOV r7, r5

r7에 r5의 값을 대입합니다. ( r7 = r5 )

- MVN r7, r5

r7에 r5값을 반전시켜서 대입합니다. (r7 = not r5)

- MOVS r7, r5

r7에 r5의 값을 대입하고 해당하는 특성 flag를 바꿔줍니다.


(2) Barrel Shifter (쉬프트 연산용)

: 주로 2의 거듭제곱만큼 곱하거나 나눌때 사용합니다.


쉬프트 연산얘기에 앞서서 쉬프트연산에는 2가지 종류가 있습니다.

Logical Shift냐 Arithmetic Shift냐 가 중요합니다.


먼저 logical shift일때 

ex)  00001000 << 3 == 01000000

00001000 >> 3 == 00000001 좌우가 무조건 0으로 채워집니다.


이번엔 arithmetic shift입니다.

ex) 10000000 >> 3 == 11100000

01000000 >> 3 == 00001000 우측으로 쉬프트했을때 해당 부호비트와 같은 비트로 채워짐을 알 수 있습니다.


그리고 logical shift의 오른쪽 shift는 무조건 음수가 아닐때만 사용할 수 있습니다.

음수에 사용하게되면 0으로 채워지기 때문에 음수가 양수가 되버리기 때문입니다.

음수 오른쪽연산은 Arithmetic shift right 를 이용합시다.


- r1 LSL #1

r1을 logical shift left로 1bit 이동합니다. 무조건 0으로 채워집니다.


- r2 LSR #5

r2를 logical shift right 로 5bit 이동합니다. 무조건 0으로 채워지고, r2는 무조건 음수가 아니어야 합니다.


- r3 ASR #3

r3를 arithmetic shift right로 3bit만큼 이동합니다. 해당 r3의 부호비트와 같은 bit로 채워지고, 

r3는 음수이든 양수이든 0이든 상관없습니다.


- r4 ROR #2

r4를 rotate right로 2bit 이동합니다. 오른쪽으로 shift하는데 나간 bit가 왼쪽으로 다시채워지는 꼴입니다.

회전의 개념입니다.


- r5 RRX #1

r5를 rotate right extended로 1bit 이동합니다. 이떄 extended는 캐리bit를 뜻합니다. 

즉, 최하위 비트가 사실상 r5의 바깥쪽에있는 캐리bit로 들어가게되고, 

캐리bit가 r5의 최상위 bit로 회전하는 꼴이 됩니다.


(3) 산술 연산용 (Arithmetic instruction)


사칙연산과 관련된 명령어들입니다.


- ADD r0 r1 #4

r0에 r1+4를 더한값을 대입합니다.


- ADC r0 r1 #5

r0에 r1+5+c 즉, r1+5에 캐리값까지 더해서 r0에 대입합니다. 


- SUB r0 r1 #1

r0에 r1 - 1 값을 대입합니다.


- SBC r0 r1 #2

r0에 r1 - 2 - (~c) 값을 대입합니다. 즉 r1-2에 carray에 not까지 취해서 뺍니다. 


- RSB / RSC는 그만 알아봅시다.


//논리연산자


- AND r0 r1 r2

r0 = r1 & r2 //and 연산


- ORR r0 r1 r2

r0 = r1 | r2 //or 연산


- EOR r0 r1 r2

r0 = r1 ^ r2 //xor연산


- BIC r0 r1 r2

r0= r1 & ~r2 //bit clear연산


(4) 비교 연산용 (Comparison instruction)


- CMP r0 r9

r0 == r9 이면 0을 반환합니다.


-CMN r0 r9

r0 != r9 이면 0을 반환합니다.


- TEQ r0 r9

r0 != r9 이면 1을 반환합니다. (XOR로 비교)


- TST 

각 bit를 and연산해서 비교한다는데 각자 알아보도록하자.



(5) 곱셈 연산용 (Multiply instruction)


- MLA r0 r1 r2 r3

r0 = r1*r2 + r3 //누적해서 곱함


- MUL r0 r1 r2

r0 = r1*r2




2. 분기 명령어


label은 분기할 곳의 주소(pc)값이다.


- B label

복귀없이 label로 점프!


- BL label

label로 갔다가 다시 복귀, 링크 레지스터 lr를 사용해서 복귀





3. Load/Store 명령어


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ.


이 부분이 이해가 안된다면 아래블로그에서 이해하면된다.

추천 블로그 => http://blog.daum.net/heyjun/15705380


RISC 아키텍쳐의 특징으로. 

메모리 접근은 모두 Load/ Store로 해결한다는 것을 전 글에 적었었다!

속도빨라지고, 단순해지고, 명령어가 적어진다는 장점이 있었다.


- 한번에 1개의 값을 load하거나 store (SIngle)


-LDR r0 mem32[address]

//address인 주소값을 메모리에서 찾아서 값을 r0에 불러옴! read


-STR r0 mem32[address]

// r0값을 address주소를 가지는 메모리의 값으로 내보냄! write


그외에 LDRB, LDRH, LDRSB / STRB, STRH, STRSB 은 LDR, STR뒤에 붙은 접미사가 값의 자료형을 정의하는 거라서 가볍게 

무시하고 지나가도록 하겠습니다.



- 한번에 여러개의 값을 load하거나 store (Mutiple)


load/store로 메모리를 불러오거나 내보낼건데, stack형으로 연속해서 불러오거나 내보낼 수 있습니다. 마치 배열


이때 명령어 키워드들은

LDR은 LDM (load multiple register), STR은 STM (store multiple register) 로 바꿔서 쓸 수 있습니다.

끝자리가 M으로 바꼈다고 생각하면 됩니다.


각 LDR과 STR은 아래와 같은 모드를 4가지씩 가질 수 있습니다.


IA는 Increment After

IB는 Increment Before

DA는 Decrement After

DB는 Decrement Before      의 약자임을 기억합시다.



앞서 배운 개념들을 다시 복습해 봅시다.

 

LDR와 STR의 기능은

 

LDR r0, [r1] 의 의미는 [주소값이 r1인 공간]이 가지는 값을 r0에 저장하겠다. (연산방향이 <=)

STR r0, [r1] 의 의미는 r0 값 자체를 [주소값이 r1인 공간]에 넣겠다. (연산방향이 =>)

 

라고 할수 있다.

 

 

반면

 

 

LDMxx와 STMxx를 보면,  ( xx는 IA, IB, DA, DI 중 택 1 )

 

LDMxx r0, [r1, r2, r3] 의 의미는 [주소값이 r0인 공간]이 가지는 값을 r1, r2, r3에 순차적으로 저장하겠다. (연산방향이 =>)

STMxx r0, [r1, r2, r3] 의 의미는 r0 값 자체를 [주소값이 r1, r2, r3인 공간]에 순차적으로 대입하겠다. (연산방향이 => )

 

!를 연산을 붙여야 모드 xx가 연산됩니다!

 

LDMxx r0!, [r1, r2, r3] 의 의미는 [주소값이 r0인 공간]이 가지는 값을 r1, r2, r3에 연산하며 순차적으로 저장하겠다.

 (연산방향이 =>)

STMxx r0! [r1, r2, r3] 의 의미는 r0 값 자체를 [주소값이 r1, r2, r3인 공간]에 연산하며 순차적으로 대입하겠다.

(연산방향이 => )

 

 

예를 들어보겠습니다.

 

r0 = 0x00080000

r1 = 0x00000001

mem32[0x00080000] = 0x03

mem32[0x00080001] = 0x010

이 있다고 합시다.

 

1) LDR r1, [r0]

이후에는 어떻게 값이 바뀔까?

=> 주소값이 r0인 공간에서의 값을 r1에 저장. 즉 r1 = mem32[r0]

=>

r0 = 0x00080000

r1 = 0x00000003 // 값 바뀜!

mem32[0x00080000] = 0x03

mem32[0x00080001] = 0x010

 

다시 값을 문제와같이 초기화시키고

2) STR r1, [r0]

=> r1값 자체를 주소값이 r0인 공간에 저장, 즉 mem32[r0] = r1

=>

r0 = 0x00080000

r1 = 0x00000001

mem32[0x00080000] = 0x01 // 값 바뀜!

mem32[0x00080001] = 0x010

 

 

 

 

3) LRMIA r0!, {r1,r2,r3}

 

r0 = 0x00080000

r1 = 0x00000004

r2 = 0x00000008

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x0b

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

 

위와같은 상황을 가정한후 연산해보겠습니다.

 

1. [r0] 값을 읽습니다. (0.0a)

2. r1 자체에 저장합니다.

r0 = 0x00080000

r1 = 0x0000000a

r2 = 0x00000008

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x0b

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

3. r0을 4증가시킵니다. // increment After이므로 대입후에 증가!

r0 = 0x00080004

r1 = 0x0000000a

r2 = 0x00000008

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x0b

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

 

4. [r0]값을 읽습니다. (0x0b)

5. r2레지스터 자체에 저장합니다.

r0 = 0x00080004

r1 = 0x0000000a

r2 = 0x0000000b

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x0b

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

6. r0를 4증가시킵니다.

r0 = 0x00080008

r1 = 0x0000000a

r2 = 0x0000000b

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x0b

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

 

7. 한번더 반복합니다.

r0주소공간 읽고

r3레지스터 자체에 저장하고

r0를 증가시킵니다.

 

8. 최종결과는

r0 = 0x0008001c

r1 = 0x0000000a

r2 = 0x0000000b

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x0b

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

가 될 것입니다.

 

4) STMIA r0!, {r1,r2,r3}

 

r0 = 0x00080000

r1 = 0x00000004

r2 = 0x00000008

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x0b

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

 

으로 가정하겠습니다.

 

1. r0값 자체를 [r1]에 덮어씌웁니다. ( r1이 아니라 [r1]입니다. )

r0 = 0x00080000

r1 = 0x00000004

r2 = 0x00000008

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x00080000

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

2. r0레지스터값 자체를 증가시킵니다.

r0 = 0x00080004

r1 = 0x00000004

r2 = 0x00000008

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x00080000

mem32[0x00080008] = 0x0c

mem32[0x0008001c] = 0x0d

3. r0값자체를 [r2]에 저장합니다.

r0 = 0x00080004

r1 = 0x00000004

r2 = 0x00000008

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x00080000

mem32[0x00080008] = 0x00080004

mem32[0x0008001c] = 0x0d

 

4.. 위와 비슷한 과정을 2번더 반복합니다.

5. 최종결과는

 

r0 = 0x0008001c

r1 = 0x00000004

r2 = 0x00000008

r3 = 0x0000001c

mem32[0x00080000] = 0x0a

mem32[0x00080004] = 0x00080000

mem32[0x00080008] = 0x00080004

mem32[0x0008001c] = 0x0000001c

 가 될 것입니다.

 

 

 


지금까지 load/store의 single, multiple 연산을 알아보았는데,

 

multiple연산은 이게 끝이아니라

 

Stack을 사용하는 연산이 또 있습니다.

 

 

 - 일반적으로 할당된 메모리 공간을 Stack이라고 치면

load연산은 읽어서 빼는 연산으로 pop과 같은 역할을 하고,

store연산은 읽어들어와서 저장하는 연산으로 push와 같은 역할을 한다고 볼 수 있습니다.

 

- 또 스택을 어떤 방향으로 저장할 것이냐에 따라서

Ascending (상향)

Descending (하향)

으로 나눌 수 있습니다.

 

- 또 스택포인터 (sp)가 가리키는 지점에 따라서 나눌 수도 있습니다.

sp가 스택의 마지막 값의 위치를 가리키면 full stack

sp가 스택의 마지막값 다음을 가리지면(마지막으로 저장된 아이템의 다음위치를 가리킴(비어있음) ) empty stack 입니다.

 

 

이와 관련 명령어는 2가지만 알면됩니다.

 

1) STMFD r13!, {r4-r7}

STM은 multiple store고 F는 full stack, D는 descending입니다. (연산방향 <=)

 

즉 sp가 항상 마지막 아이템을 가리키고, 저장을 밑 방향으로 하겠다는 의미입니다.

 

1. r7레지스터 자체값을 [r13] (r13이 가리키는 주소값)에 저장합니다.

2. sp는 r13을 가리킵니다.

3. r13에서 4를 뺍니다.

 

4. r6레지스터 자체값을 [r13] (r13이 가리키는 주소값)에 저장합니다.

5. sp는 r13을 가리킵니다. (위 r13에서 4가 줄어든 상태)

6. r13에서 또 4를 뺍니다.

 

... r4까지 반복합니다.

 

2) LRMFD r13!, {r4- r7}

LRM은 multiple load 이고 full stack이면서 Descending 임을 알 수 있습니다.

 

즉, sp가 항상 마지막 아이템을 가리키고,  

 

위와 반대로 r4부터 r7까지를 r13레지스터 주소로부터 아래로 대입하겠다는 뜻입니다.

 

 

 

 

 

- SWP r1, r2, [r3]

swap을 구현하는것이다. [r3]와 r1값을 바꾸는거임

tmp = mem32[r3]

mem32[r3] = r2

r1 = tmp 


4. Software Interrupt 명령어


- SWI SWI_number 꼴로 이루어지며, 프로그램 도중 번호가 SWI_number인 SWI를 호출하여

인터럽트를 호출하는 것


5. Program Status Resgiter (PSR) 


- 프로그램 상태 레지스터를 변경하는 명령어가 있다.


- MRS r1, cpsr //cpsr값을 r1에 복사

  BIC r1, r1, #0x80; //7번 비트 clear

  MSR cpsr_c, r1 // r1값을 복사한다. cprs_r에 복사한다.



주로 인터럽트후 복구할 때 쓰는 것 같다



'개발 > 마이크로컴퓨팅' 카테고리의 다른 글

7. ARM 어셈블리 프로그래밍 TIPS  (0) 2019.04.18
5. ARM 디버깅  (0) 2019.04.18
4. ARM 프로세서에 대해 2  (0) 2019.04.18
3. ARM 프로세서에 대해  (0) 2019.04.17
2. ARM은 뭘까  (0) 2019.04.17
profile on loading

Loading...