본문 바로가기

학교/시스템 프로그래밍

shift 연산자

1. lsl, lsr

lsl부터 보자. 다만, 플래그 값을 보기 위해선 명령어에 S를 붙여야 한다.

lsl 해주고 왜 안되나 한참 생각했네..

  1 .global _start
  2 _start:
  3     mov r0, #0x7FFFFFFF
  4     lsls r0, #1
  5     lsls r0, #1   @ 여기까지만 cpsr이 변한다.
  6     lsls r0, #1
  7     lsls r0, #1
  8     lsls r0, #1
  9     b exit
 10 
 11 exit:
 12     mov r0, #0
 13     mov r7, #1
 14     swi 0

r0에 signed int의 최대값을 넣은 뒤, 왼쪽으로 shift 연산을 할 때마다 레지스터 상태를 볼 것이다.

 

shift 연산 실행 전 cpsr 상태
cpsr 값이 바뀐 걸 볼 수 있다.

여기서 0x80000000 값이 더해진 걸 볼 수 있다.

이는 이진수로 1000 0000 0000 0000 0000 0000 0001 0000 이고, 29번째 비트가 on 된 걸 볼 수 있다.

이는 C(Carry) 플래그가 올라간 것이고, 연산에 자리올림이 된 것을 뜻한다.

 

사진 출처 : https://www.ques10.com/p/41159/arm7-a-32-bit-microcontrollerpart-2-1/

다시, 다음 명령어를 실행해보자.

cpsr의 최상단 비트가 1로 세팅됐다.

이번엔, 최상위 비트인 N(Negative)플래그가 올라간 것을 볼 수 있는데, 이는 연산 결과가 음수란 뜻이다.

왜냐면 signed 자료형에서는, 최상단 비트(MSB)가 1이면 음수, 0이면 양수이기 때문이다.

 

lsl에서 cpsr이 변하는 모습 보는 건 여기까지로 한다.

이로써, lsl 연산의 32비트가 초과된 비트는, Carry 플래그에 담기는 걸 알 수 있다.

lsr도 lsl과 똑같으니 생략한다.

LSL구조. LSR은 반대라고 보면 된다.

 

 

 

2. asr

arm은 왼쪽 shift 명령어는 하난데, 오른쪽은 여러개다.

그 이유는 32비트 기준, 왼쪽 한 번(4bit)과, 오른쪽 7(28bit)번 한 게 같기 때문이다

 

아까 전엔, MSB가 변하는 shift연산이었지만, 이번엔 MSB가 보존되는 연산이다

  1 .global _start
  2 _start:
  3     mov r0,  #0xFFFFFFFF    @ -1
  4     asrs r0, #1
  5     asrs r0, #1
  6     b exit
  7 
  8 exit:
  9     mov r0, #0
 10     mov r7, #1
 11     swi 0

asr 연산 전, cpsr
cpsr의 최상위 비트가 바뀐 걸 볼 수 있다.

원래라면, r0가 0x7FFFFFFF가 되어야 한다. 하지만, asr 연산은 부호를 보존해주기 때문에,

MSB가 1비트로 고정이 돼서, N(negative)플래그가 올라간 것이다.

여기서 계속 asr을 해줘도 부호는 고정이다.

ASR 구조

 

3. ror

이번엔 ror 연산이다.

각 자리 수를 하나씩 뽑아낼 때, 이걸 주로 사용한다.

  1 .global _start
  2 _start:
  3     mov r0, #0x1
  4     rors r0, #1
  5     rors r0, #1
  6     rors r0, #1
  7     rors r0, #1
  8     rors r0, #1
  9     b exit
 10 
 11 exit:
 12     mov r0, #0
 13     mov r7, #1
 14     swi 0

ror 전 cpsr
최상위 비트가 변경돼서 N 플래그 올라감

여기 까진 다른 연산하고 같지만, 그 다음부턴 흥미로운 결과가 나온다.

 

양수로 바뀌면서 N플래그가 내려갔다.

이로써, ror shift는 최상위 비트를 구분하지 않고 그냥 지정한 크기만큼 오른쪽으로  shift해준다는 걸 알 수 있다.

ror 구조

 

4. rrx

다음은 ROR의 확장판인 RRX이다.  캐리 플래그를 포함해, 33비트를 다룰 수 있게 된다.

  1 .global _start
  2 _start:
  3     mov r0, #0x1
  4     rrxs r0, r0
  5     rrxs r0, r0
  6     rrxs r0, r0
  7     rrxs r0, r0
  8     b exit
  9 
 10 exit:
 11     mov r0, #0
 12     mov r7, #1
 13     swi 0

 

rrx를 하기 전 cpsr
r0은 0이 되고, Z, C 플래그가 올라갔다.

여기서 Zero 플래그와 Carry플래그가 올라간 이유는

연산 결과가 32비트를 넘었고(해당 명령어로 0x1을 shift 해주면 캐리로 넘어가니까)

0x1 >> 1을 하면, 0이 되버리기 때문에 Zero 플래그가 올라간 것이다.

 

다음 연산을 보자.

Z, C 플래그가 내려가고 N플래그가 올라갔다.

마찬가지로 Carry에 있던게 나가서, carry 플래그가 내려갔고,
더이상 0이 아니니 Zero 플래그도 내려갔다.

하지만, 0x80000000은 음수이므로, Negative 플래그가 올라갔다.

 

N 플래그가 내려갔다.

연산 결과가 양수이기 때문에, N플래그가 내려갔다.

rrx의 구조를 정리하자면 이런 방식이다.

rrx의 구조

 

 

ALU Barrel Shifter

ALU 구조, op1은 레지스터 고정이지만, op2는 복잡하다.

요약하면, op2에는 barrel shift를 거친 값을 넣을 수 있단 것이다.

이걸 쓰는 이유가 op1은 32bit 값을 넣을 수 있지만,

arm 구조상 op2엔 8bit밖에 못온다, 이러면 문제가, 0xFF를 넘는 값을 넣을 수 없는 것이다.

그렇기 때문에, 큰 수를 넣을려면, lsl 연산을 시켜서 넣어줘야 하는 것이다.

ex) mov r0, 0x27C0은 에서 0x27C0은 0x9f, LSL, #6으로 바꿔줄 수 있다.

 

단, 여기서 제약이 하나 있는데, 짝수 shift연산이 되는 값들만 허용되는 것이다.

ex) mov r0, 0x17A0은 0xBD, lsl #5 이기 때문에 안 된다.

이 외에도 mov r0, 0xFFFFFFFF 같은 경우는 mvn r0, #0 같은 걸로 바꿔줄 수 있다.

요즘 컴파일러가 좋아서, 일일이 변환 안 해도 얘네들이 알아서 바꿔준다.

 

솔직히 저게 무슨 말인지 잘 모르겠다.. 어찌됐든 메모리를 효율적으로 쓸 수 있는 건 맞는데..

https://www.csie.ntu.edu.tw/~cyy/courses/assembly/10fall/lectures/handouts/lec09_ARMisa.pdf

아마 이런 구조 땜에 저게 가능한 거 같기도 하다.

'학교 > 시스템 프로그래밍' 카테고리의 다른 글

Signal  (0) 2020.12.05
PC, LR, SP  (0) 2020.11.17
LDR / STR Indexing  (0) 2020.11.17
section  (0) 2020.11.13
1. LDR/STR/ADR, 논리 연산  (0) 2020.11.10