작성
·
572
·
수정됨
1
안녕하세요. xPSR 레지스터와 기타 궁금한 부분들 질문 드려봅니다!
1. mov r0, #0x7fff fffe or mov r0, #0x8000 0002
0x7fff fffe, 0x8000 0002는 mov 명령어가 invalid constant라고 오류가 나옵니다.
강의에서 0x7fff ffff , 0x8000 0000등은 mov 명령어를 썼는데 그것보다 작은 값이 왜 오류인지 궁금합니다.
r0 레지스터에 0x7ffffffe 값이 들어있고 adds r0, #1을 한 경우 xPSR(0x1000 0000)이 나왔습니다. Overflow가 되는 상황이 아닌것 같은데 이유가 궁금합니다.
Carry가 일어나면 xPSR의 C플래그가 1이 되는데 Borrow는 어떤 경우인지 궁금합니다.
작은값에서 큰 값을 빼는 경우 Borrow가 되는건가요? MSB에서 값을 가져올 때에 발생하나요??( 발생하는 예시 하나만 들어주시면 감사하겠습니다)
어셈블리에서 signed unsigned의 구분은 어떻게 이루어지나요?? 이루어지지 않는다면 c언어 한정으로 컴파일러가 변수 타입을 파악하고 자동으로 바꿔주는건가요?
어셈블리 언어는 Arm cortex m3, m4 모두 동일한 명령어를 사용하나요??
좋은 강의 해주셔서 감사합니다. 여기서 어셈블리를 더 잘 쓰기에 필요한 책이나 사이트들 혹은 어떤 데이타시트를 봐야하는지 추천 가능하시면 부탁드립니다 ㅎㅎ
고민해보다가 막혀서 안되는 부분과 궁금한 부분 질문 드려봤는데 답변 부탁 드리겠습니다!
감사합니다.
답변 2
1
질문에 상세한 답변 정말 감사드립니다.
설명해주신 부분 중 한가지 궁금한 점이 있어 재질문 드려봅니다.
1."MOV r0, #0xc000001d" // 0x77을 2비트 반시계 쉬프트 한 값.
2."MOV r0, #70000007" // 0x77을 4비트 반시계 쉬프트 한 값
3."MOV r0, #DC000001" // 0x77을 6비트 반시계 쉬프트 한 값
4."MOV r0, #0x1DC00000" // 0x77을 8비트 반시계 쉬프트 한 값
답변 해주신 내용의 mov 명령 상수 조건
1. 8비트 값 표현
2.짝수 쉬프트
위 두가지 조건을 지켜서 시도해보았는데 8비트 이하에서 모두 컴파일이 되지 않았습니다.
이유를 알고 싶습니다.
감사합니다!
0
안녕하세요. chltnckd7님!
아래와 같이 질문에 대한 답변드립니다.
안녕하세요. xPSR 레지스터와 기타 궁금한 부분들 질문 드려봅니다!
1. mov r0, #0x7fff fffe or mov r0, #0x8000 0002
0x7fff fffe, 0x8000 0002는 mov 명령어가 invalid constant라고 오류가 나옵니다.
강의에서 0x7fff ffff , 0x8000 0000등은 mov 명령어를 썼는데 그것보다 작은 값이 왜 오류인지 궁금합니다.
<ANS>
ARM 명령어에서 상수 값을 로드하는 것은 조금 복잡한 것처럼 보일 수 있습니다. 예를들어 설명하겠습니다.
mov r0, #0xFF <--- 이 명령어는 상수 0xFF를 사용하고 회전은 필요하지 않습니다.
mov r0, #0xFF00 <--- 이 명령어는 0xFF 상수를 오른쪽으로 24비트 회전하여 얻을 수 있습니다.
따라서 mov r0, #0x7fff fffe
또는 mov r0, #0x8000 0002
와 같은 명령어는 사용되는 상수 값을 8비트 상수와 4비트 회전 필드로 인코딩할 수 없기 때문에 오류를 발생시킵니다.
이러한 경우 아래 예시와 같이 mov 명령어 대신 로드(ldr) 명령어를 이용하면 좋습니다.
ldr r0,=0x7ffffffe
ldr r0,=0x80000002
고정 명령 길이의 특징을 갖는 RISC 계열 프로세서에서는 이러한 불편한 점들을 흔히 볼 수 있습니다.
2. r0 레지스터에 0x7ffffffe 값이 들어있고 adds r0, #1을 한 경우 xPSR(0x1000 0000)이 나왔습니다. Overflow가 되는 상황이 아닌것 같은데 이유가 궁금합니다.
<ANS>
아마도 xPSR(0x100 0000) 을 xPSR(0x1000 0000) 으로 잘못 보신 것 같네요. xPSR(0x0100 0000) 이런 식으로 앞에 0을 추가해서 보여주면 사용자가 읽기 쉽습니다. STM32CubeIDE 의 디버깅 도구와 관련 앞으로 이 부분에 대한 개선이 필요하다고 느낍니다.
3. Carry가 일어나면 xPSR의 C플래그가 1이 되는데 Borrow는 어떤 경우인지 궁금합니다.작은값에서 큰 값을 빼는 경우 Borrow가 되는건가요? MSB에서 값을 가져올 때에 발생하나요??( 발생하는 예시 하나만 들어주시면 감사하겠습니다)
<ANS>
네, 이해하신 것이 맞습니다. 두 이진수를 뺄셈할 때, 작은 값을 가진 비트에서 더 큰 값을 가진 비트를 뺄 경우에 borrow가 발생합니다.
결론부터 말씀드리자면, 이 모든 부분이 뺄셈을 2의 보수로 처리한다는 특징 때문에 생깁니다.
SUB R0, R1, R2
이 연산 후에 결과는 R0에 저장되며, C 플래그(Carry flag)는 borrow가 발생했는지의 여부를 나타냅니다.
만약 R1의 값이 R2보다 작다면, C 플래그는 0으로 설정되어 borrow가 발생했음을 나타냅니다. 반대로 R1의 값이 R2보다 크거나 같으면, C 플래그는 1로 설정됩니다.
다시말하면, 컴퓨터는 뺄셈을 할 때 피 연산자에 2의 보수를 더하는(+) 방법으로 계산을 하기 때문에 이런 현상이 설명이 됩니다.
4. 어셈블리에서 signed unsigned의 구분은 어떻게 이루어지나요?? 이루어지지 않는다면 c언어 한정으로 컴파일러가 변수 타입을 파악하고 자동으로 바꿔주는건가요?
<ANS>
CMP 명령어는 두 값을 비교하며, 그 결과에 따라 상태 레지스터의 플래그를 설정합니다. 이 플래그는 후속 조건 분기 명령어들에 의해 해석됩니다. signed와 unsigned 비교는 해석 방식에서 차이가 납니다.
예를 들어, unsigned 비교는 BHI (branch if higher) 또는 BLO (branch if lower)와 같은 명령어를 사용하며, signed 비교는 BGT (branch if greater) 또는 BLT (branch if less than)와 같은 명령어를 사용합니다. 어셈블리 언어에서는 사용자가 비교 대상 값을 부호가 있는 것으로 바라볼 것인지, 아닌지를 결정한 후 BGT, BHI, BLO 등을 사용하겠죠.
한편, 컴파일러는 변수의 타입(unsigned, 혹은 signed)을 보고 부호 여부를 결정할 수 있죠.
5. 어셈블리 언어는 Arm cortex m3, m4 모두 동일한 명령어를 사용하나요??
<ANS>
Cortex-M4는 Cortex-M3의 모든 기능을 포함하면서 추가로 DSP 명령어와 부동 소수점 연산을 위한 선택적인 FPU를 가질 수 있습니다. 따라서 DSP 관련 명령어나 FPU 관련 명령어를 사용하는 경우, 해당 명령어는 Cortex-M3에서는 지원되지 않습니다.
결론적으로, Cortex-M4의 추가 기능에 관련된 특정 어셈블리 명령어는 Cortex-M3에서는 지원되지 않을 수 있습니다
6. 어셈블리를 더 잘 쓰기에 필요한 책이나 사이트들 혹은 어떤 데이타시트를 봐야하는지 추천 가능하시면 부탁드립니다
<ANS>
저 같은 경우는 어셈블리어 책을 따로 챙겨보고 공부하거나, 학원에서 배운 것이 아니고 MCU 매뉴얼, 컴파일러 제조사가 무료로 제공하는 AS 매뉴얼 등으로 학습하였기 때문에, 추천해드릴 만한 책이 금방 생각나지 않네요. 시중에 쓸 만한 책이 없다고 해서 생뚱맞은 x86 어셈블리 책을 가지고 공부하시면 시간적인 손해가 의외로 클 수 있습니다. 꼭 사용하시려는 MCU 의 어셈블리 언어를 학습하시고, 자신에게 맞는 책을 읽으시라고 조언드리고 싶습니다.
답변 감사합니다! 제 실수로 잘못 보았던 부분(xPSR 비트)도 예상하고 잘 알려주셔서 감사드립니다. 1번과 4번에 대해 다시 한번 질문 드려봅니다.
1번 질문)
mov r0, #0xFF <--- 이 명령어는 상수 0xFF를 사용하고 회전은 필요하지 않습니다.
mov r0, #0xFF00 <--- 이 명령어는 0xFF 상수를 오른쪽으로 24비트 회전하여 얻을 수 있습니다.
위의 답변해주신 내용에서 mov r0, #0xFF00를 오른쪽으로 24비트가 아니고 8비트 쉬프트를 말씀하신건가요??
8비트 상수와 4비트 회전 필드로 인코딩할 수 없다는 의미를 잘 모르겠습니다.
4번 질문)
< mov r0, #80000000 명령어를 실행 결과 사진 >
CMP 명령어 이후 BLT BHI등으로 부호(signed, unsigned) 여부를 결정한다고 말씀해 주셨는데 mov r0, #80000000 명령어를 실행하고 레지스터 값을 보면 위 사진처럼 Decimal이 음수로 표시가 됩니다. SIGNED로 인식을 한 것 같은데 unsigned로 인식하게 해서 0x7fffffff(양수)값보다 더 큰 양수를 표현하고 싶으면 어떻게 해야할까요?
감사합니다!
1번 질문)
mov r0, #0xFF <--- 이 명령어는 상수 0xFF를 사용하고 회전은 필요하지 않습니다.
mov r0, #0xFF00 <--- 이 명령어는 0xFF 상수를 오른쪽으로 24비트 회전하여 얻을 수 있습니다.
위의 답변해주신 내용에서 mov r0, #0xFF00를 오른쪽으로 24비트가 아니고 8비트 쉬프트를 말씀하신건가요??
8비트 상수와 4비트 회전 필드로 인코딩할 수 없다는 의미를 잘 모르겠습니다.
<ANS>
ARM(CORTEX-M) 의 레지스터는 32비트의 크기를 갖습니다.
때문에 표현은 mov r0, #0xFF00 로 적었더라도 그 의미는 mov r0, #0x0000FF00 로 생각하는 것이 좋겠습니다.
mov에서 직접 사용할 수 있는 상수는 8비트 값을 0, 2, 4,..., 30번 반시계 방향으로 회전(짝수비트 회전만 가능)시켜 만든 값입니다.
#0x0000ff00 이라는 상수를 살펴보면:
8비트 값: 0xFF 이 값을 24비트 반시계 방향으로 회전하면: 0x0000FF00
즉, 0xFF 값을 24비트 반시계 방향으로 회전하면 0x0000FF00가 되기 때문에, 이 상수는 mov r0, #0x0000ff00 명령어에서 직접 사용할 수 있습니다.
예로, #0x0000FF80 은 8비트 값인 0xFF를 반시계 방향으로 짝수 비트만큼을 회전하여 얻을 수 없는 값입니다. 따라서, 이 값을 직접 사용하는 것은 불가능합니다:
아래와 같이 예들을 더 보여드릴 수 있습니다.
MOV R0,#0X23 @ GOOD! 0x23 는 총 8비트 내로 표현
MOV R0,#0X123 @ BAD! 0x123 은 총 9비트로 표현(1비트 초과함)
MOV R0,#0XFF @ GOOD! 0xff 는 총 8비트로 표현
MOV R0,#0XFF000000 @ GOOD! 0xff 을 8비트 반시계 회전으로 표현(8비트는 짝수)
MOV R0,#0X1FF @ BAD! 0x1ff 는 총 9비트로 표현(1비트 초과함)
MOV R0,#0X1FE @ BAD! 0xff 을 31비트 반시계 회전으로 표현(31비트는 홀수)
MOV R0,#0X3FC @ GOOD! 0xff 을 30비트 반시계 회전으로 표현(30비트는 짝수)
MOV R0,#0X001F80000 @ GOOD! 0x7E 을 18비트 반시계 회전으로 표현(18비트는 짝수)
이러한 경우 아래 예시와 같이 mov 명령어 대신 로드(ldr) 명령어를 이용하면 좋습니다.
ldr r0,=0X1FE
상수 표현의 제약 혹은 불편함? 은 32비트의 고정 명령 길이의 특징을 갖는 RISC 계열 프로세서인 ARM(Cortex-M)의 특징(단점)으로 받아들여야 하겠습니다.
4번 질문)
< mov r0, #80000000 명령어를 실행 결과 사진 >
CMP 명령어 이후 BLT BHI등으로 부호(signed, unsigned) 여부를 결정한다고 말씀해 주셨는데 mov r0, #80000000 명령어를 실행하고 레지스터 값을 보면 위 사진처럼 Decimal이 음수로 표시가 됩니다. SIGNED로 인식을 한 것 같은데 unsigned로 인식하게 해서 0x7fffffff(양수)값보다 더 큰 양수를 표현하고 싶으면 어떻게 해야할까요?
<ANS>
아래와 같이 예제를 만들어 보았습니다.
#if 1
{
/* actual initialization */
volatile unsigned int a1=0xffffffff; // -1
volatile int a2=0xffffffff; // -1
printf("a1 는 양수인가요? %d\n", (a1 > 0) ? 1 : 0);
printf("a2 는 양수인가요? %d\n", (a2 > 0) ? 1 : 0);
}
#endif
위와 같은 테스트 코드를 실행한 결과는 아래와 같습니다.
HELLO, STM32
a1 는 양수인가요? 1
a2 는 양수인가요? 0
각각의 디버깅 중 디스어셈블 내용을 보시면 아래와 같습니다
0xffffffff 가 양수일까요? 음수일까요? 라는 것은 우리가 그 숫자를 바라보는 관점에 따라 결정되는 것을 잘 보여주고 있습니다.
임의의 숫자를 부호가 포함된 것으로 처리하고 싶을 때는 이렇게 하면 될 것이고
int a2=0xffffffff
임의의 숫자를 부호가 포함되지 않은 것으로 처리하고 싶을 때는 이런식으로 하는 것이죠
unsigned int a1=0xffffffff;
앞서의 제가 했던 답변에 부족함이 있어 이해하시는데 조금 어려움이 있으셨던 같습니다.
이번에는 좀 자세히 설명한다고 해보았는데, 이제는 궁금한 점이 해결되셨을까요?
잘 이해가 안되는 부분이 있다면 질문의 창은 항상 열려있습니다. 댓글 남겨주세요. ^^
안녕하세요. chltnckd7님!
질문주신 내용을 검토해본결과 제가 과거에 드렸던 답변이 정확하지 않다는 사실을 확인하였습니다. 설명에 앞서서 죄송한 말씀드립니다.
앞으로 설명드릴 내용은 장황하고 복잡한데, 아쉽게도 우리가 이것을 학습하는 데 들이는 노력 만큼의 큰 이익은 없다는 점도 꼭 말씀드리고 싶네요.
바로 본론으로 들어갑니다.
CORTEX-M 의 상수 표현 구조
CORTEX-M 과 전통적인 ARM(ARM7, ARM9, Thumb2 을 사용하지 않는 CORTEX-A 계열)는 상수를 다루는 방식에서 차이가 있습니다.
8비트 값을 짝수비트 회전 제약을 갖고 반시계 방향으로 회전하여 이용한다?. 이것은 ARM방식.
그럼 CORTEX-M 은 어떤 방식을 쓰는가? 입니다.
ARM v7-M Architecture Application Level Reference Manual 271쪽을 인용해보겠습니다.
mov 명령의 인코딩 형식을 보입니다. 3가지 방식(T1, T2, T3)으로 인코딩 될 수 있습니다.
비슷한 명령어라도 사용한 상수값의 크기에 따라 명령어의 인코딩은 다르게 취급됩니다
movs r0, #1 @ T1 방식(thumb16)으로 인코딩(20 01 movs r0, #1)된다
mov r0, #1 @ T2 방식으로 인코딩(f0 4f 00 01 mov.w r0, #1)된다
mov r0, #0x1f @ T2 방식으로 인코딩(f0 4f 00 1f mov.w r0, #0x1f)된다
mov r0, #0x7fff @ T3 방식으로 인코딩(f6 47 70 ff mov.w r0, #0x7fff)된다
분명 코드는 mov r0,#0x7fff 로 작성했지만, 디스어셈블리 에서의 명령어는 movw r0, #32767 로 변경된 것과 머신코드 중 f6 패턴을 통해 T3 인코딩 했다는 것을 확신할 수 있습니다.
또한 흥미로운 것은
movs r0, #1 와 mov r0, #1 가 다르게 처리된다는 것이죠. 하나는 thumb16 으로 또 다른 하나는 thumb32 로 만들어진것을 볼 수 있습니다.
그럼 다음 내용에 답해보시기 바랍니다
mov r0, #0x3ff @ T2 방식으로 인코딩될까요?
mov r0, #0xfff @ T2 방식으로 인코딩될까요?
정답은 다음 그림에서 확인 가능합니다
mov r0, #0x3ff @ T2 방식(머신코드 보면 f2 가 발견됨)
mov r0, #0xfff @ T3 방식(머신코드 보면 f6 가 발견됨)
그럼 이제 이런 걸 한번 시도해보죠. 이런?%^ 컴파일 오류가 나고 맙니다.
왜 안될까요? 그 해답은 여기서 찾을 수 있어요. i 와 imm3 을 이용하여 imm8 을 회전시켜서 표현할 수 있습니다. 그런데 위의 코드를 보시면 3ff 나 fff 는 8비트를 초과한 숫자이죠. 그리고 T3 구조는 T2 구조와 달리 비트를 회전시킬 수 있는 기능이 없어요.
그럼 이 명령어는 처리될까요?
mov r0, #0x3f000000
mov r0, #0xff000000
네, 컴파일도 정상적으로 수행되며, T2 로 인코딩된 것을 확인할 수 있어요.
머신코드내에서 f0 를 참고해보시면 알 수 있죠.
그렇다면 8비트를 회전시키는 원리는 무엇인가요?
수정된 상수(Modified immediate constants)란 무엇인가?
머신코드를 보기 쉽게 다시 정리하면 다음과 같습니다.
mov r0, #0x3f000000 @ f0 4f 50 7c
mov r0, #0xff000000 @ f0 4f 40 7f
DDI0403D_armv7m_arm.pdf 매뉴얼 166쪽을 보겠습니다.
적색부를 먼저 봐주세요
상수를 사용자가 원하는 레지스터내의 새로운 위치로 재배치하는 방법을 보여줍니다.
이 명령을 해독해 보죠. mov r0, #0x3f000000 @ f0 4f 50 7c
i=0, imm3=5, a=0
위 표에 대입하면 아래처럼 만들어집니다
01010 00111111 00000000 00000000 00000000
이게 바로 0x3f000000 아닌가요? 잘 만들어짐을 확인할 수 있어요.
이 명령어도 한번 호기심에 해보실 수 있어요. mov r0, #0xff000000 @ f0 4f 40 7f
CORTEX-M 에서의 상수 표현 중 특징 중 하나
그럼, 이제 재미난 것을 볼까요?
mov r0, #0x3f003f00 @ 컴파일이 될까요?
mov r0, #0x003f003f @ 컴파일이 될까요?
결과보시죠
이 명령어를 전통적인 ARM(ARM7, ARM9, Thumb2 을 사용하지 않는 CORTEX-A 계열)에서 시도했다면 실패했을겁니다.
위 그림에서 녹색으로 테두리 친 부분을 보시면 됩니다.
CORTEX-M 은 이런것을 가능하게 만들어 놓았습니다.
결론
어떤가요? 정말 복잡하죠. 다행스러운 점은 실제 코딩할 때는 이러한 규칙을 몰라도 크게 불편하지 않다는 거에요.
예를 들어볼게요.
mov r0, #0x78f30000 을 코딩해요
컴파일하죠
에러나죠?
그럼, 즉시 이렇게 고치면되요
ldr r0,=0x78f30000
이것은 우리가 원래하려고 했던 것과 동일합니다
앞서 질문 글에서도 밝힌것처럼 RISC 프로세서 환경의 어셈블리 프로그래밍에서 상수 사용은 이처럼 난해합니다.
다행인 점은 우리가 이러한 내용을 들여다 본것은 꼭 손해는 아니라는 거에요. 그만큼 ARM 이나 CORTEX-M 에 대해 더 잘 이해하게 되었다고 생각되시지 않나요? ^^