ARM7 강좌 [11] : Instruction Set (5)

 


● PSR Transfer 명령


  기억하실지 모르겠지만 PSR이란 Program Status Register로서 플래그 비트와 Control 비트들로 구성된 레지스터입니다. 해당 레지스터의 값을 일반 레지스터로 옮기거나 반대의 일을 하는 명령이 PSR Transfer 명령입니다.


  PSR은 32비트 레지스터입니다. CPSR과 SPSR 5개를 합쳐서 총 6개가 있습니다. 여기서 SPSR 은 Exception모드에 따라 여분으로 존재하는 PSR을 말합니다. 실행 모드에 따라서 User가 접근할 수 있는 PSR은 한 개, 혹은 2개로 제한됩니다. 예를 들어 User모드(보통모드)에서는 CPSR에만 접근 할 수 있고, IRQ모드에서는 CPSR과 SPSR_irq 에 접근할 수 있습니다.


  여기서 잠깐 지난 강좌의 내용을 떠올려 보죠. CPSR이라고 함은 일반적인 플래그 레지스터라고 생각하시면 됩니다. 그런데, ARM7에서는 6개의 동작 모드가 있고, Exception(IRQ, FIQ, ABORT 등)에 의해 동작 모드가 전환됩니다. 각 동작모드에 따라서 몇몇 레지스터들이 교체되고, 이 덕분에 각 모드별로 스택을 따로 관리할 수 있는 기능이 제공됩니다.     Exception Call에 따르는 수행번지 저장도 스택을 통해 하지 않고 r14(lr)을 통해 수행합니다. 플래그 레지스터 역시 Exception에 따라 스택에 저장하지 않아도 되도록 하는 기능을 제공해 주는데, 이런 역할을 하는 것이 SPSR 입니다. SPSR은 총 5개가 있습니다. User모드를 제외하고 나머지 5개의 동작 모드마다 하나씩 존재하는데요.


  예를 들어 IRQ가 걸렸다고 하면, 우선 CPU동작모드 IRQ 모드로 바뀌고 이에 따라 r13_irq, r14_irq 로 레지스터 2개가 대치됩니다. 다음  r14_irq에 기존의 pc(r15)값을 저장합니다. 다음 지정된 벡터로 이동하겠죠. 이때 플래그레지스터, 즉 CPSR의 값도 보전할 필요가 있죠. 그래서 하는 일이 CPSR값을 pc와 비슷한 메커니즘으로 SPSR_irq로 넣어두는 것입니다. r13_irq와는 좀 차이가 있죠. r13과 같은 범용 레지스터들은 각 수행모드 별로 그 값이 계속 유지가 됩니다만, SPSR이나 r14는 Exception시에 기존 레지스터 값의 보관을 위해 사용되는 버퍼 역할을 합니다.


  어떻든 간에... 오늘 설명할 명령어는 두가지입니다. 사실 그다지 쓰일 일은 없고, CPU동작모드를 임의로 설정하거나, 현재  동작모드를 확인하기 위해서 사용되는 경우가 있습니다.


1) MRS{cond} Rd,<psr>   : Transfer PSR contents to a register

   MSR{cond} <psr>,Rm   : Transfer register contents to PSR

   MSR{cond} <psrf>,Rm  : Transfer register contents to PSR

                          flag bits only


   MRS명령과 MSR명령의 의미가 헛갈릴 수 도 있는데요, M을 Move로, R을 레지스터로, S를 PSF로 파악하면, MRS의 경우엔 Move Reg,PSR이 정도로 생각할 수 있습니다. 즉, 레지스터에 PSR값을 넣는 명령이죠. MSR의 경우엔 반대로 생각 할 수 있습니다.


   참고로 CPSR의 컨트롤비트를 바꾸면 CPU의 동작모드를 임의로 설정할 수 있습니다만, USER모드에서는 해당 기능을 사용할 수 없습니다.


Ex1) MSR        CPSR_all,r0


     : 해당 명령이 USER모드에서 수행되었다면 Control Bit들은 영향을 받지 않고 Flag Bits[31:28]만 영향을 받습니다.


       만약 USER모드를 제외한 다른 모드에서 수행되었다면 CPSR의 모든 비트들이 변화하게 됩니다.


Ex2) MSR        CPSR_flg,r0


     : CPSR에 접미사 _flg를 붙여주어서 모드에 상관없이 Flag 비트들만 영향을 받도록 합니다.


Ex3) MRS        r0,CPSR


     : CPU동작모드에 상관없이 CPSR의 모든 비트들을 r0로 복사합니다.


Ex4) MSR        SPSR_all,r0


     : r0의 값을 SPSR의 모든 비트에 반영합니다. 해당 명령은 USER모드에서는 수행 불가능합니다. USER모드에서는 접근 가능한 SPSR 자체가 없기 때문입니다.


Ex5) MSR        SPSR_flg,#0xC0000000


     : SPSR의 N, Z플래그를 세트하고 C, V플래그를 클리어 하는 명령입니다. 위와 같이 MSR명령을 Flag비트에 제한을 두어 사용할 경우엔 Immediate 값을 사용할 수 있다고 합니다.


  오늘 강좌는 간단하게 마치려고 합니다. 오랜만의 강좌라 기대를 많이 하신 분들이 혹(?) 계시다면 죄송하고요. 요즘 들어서 주변상황이 좀 어수선해서 조만간 다시 올리도록 하겠습니다.



ARM7 강좌 [10] : Instruction Set(4)

 


  오늘 강좌에서는 Data Processing Instruction에 대해서 계속 이어나가도록 하겠습니다. 되도록이면 오늘 이 부분을 다 커버하고 싶습니다만 해봐야겠지요. 지난 강좌에서 다루었던 내용 중 오퍼랜드쪽 부분은 계속 겹치는 내용이므로 의문이 생기시면 이전 강좌로 돌아가서 확인해 보시길 바랍니다.


1) MVN{cond}{S}       Rd,Op2

   

   해당 명령은 Rd:=NOT Operand2 의 의미를 가진 명령입니다. 기존 어셈블러에서는 볼 수 없었던 낯선 명령이네요. 기능은 써 있듯이 MOV처럼 값을 넣기는 넣는데 NOT를 해서 넣는 명령입니다. 이런 명령이 왜 있는지에 대해서는 확실히 모르긴 몰라도, 아마도 ARM7에서 8비트Resolution Immediate 오퍼랜드만 다룰 수 있기 때문인 듯 합니다. 해당 명령으로 어느 정도 기능을 확장한다고 할까요?


   바로 예제로 들어가겠습니다.


   Ex1) MVN   r0,#0          ; r0 := -1

        : 사실 MVN의 용도로 위의 경우 밖에는 사용된걸 보지 못했습니다. 만약 MOV  r0,#0xFFFFFFFF 이렇게 하면 에러가 발생합니다. 이것은 매번 설명 드리지만 ARM7의 모든 명령이 32비트 한 워드이고 Immediate 오퍼랜드처리를 8비트 값을 Rotate시키는 방식으로 사용하기 때문인데, MVN을 사용함으로서 해당 값을 넣을 수 있습니다.


   다른 부분은 MOV와 동일하므로 그다지 이슈가 될 것이 없을 듯 하군요.


2) ADD{cond}{S} Rd, Rn, Op2

   ADC{cond}{S} Rd, Rn, Op2


   더하기 명령입니다. 8086과 큰 차이는 오퍼랜드를 3개 지정한다는 것입니다. Rd는 결과가 저장될 레지스터로, Data Processing 명령 모두는 그 결과가 레지스터(Rd)로 들어가야만 합니다. 첫 번째 Rd가 결과가 저장 될 레지스터이고, 나머지 두개가 서로 더해질 오퍼랜드입니다. 두개의 오퍼랜드 중 하나는 또 항상 레지스터여야만 합니다. Op2는 거듭 말씀드리지만 Shifted Register 혹은 Rotated Immediate Value 중 하나입니다.


   ADD와 ADC는 널리 사용되는 대로 더하기, 더하는데 캐리플래그랑 같이 더하기 정도입니다.


   Ex1) ADD   r0,r0,#1 ; r0:=r0+1


   Ex2) ADD   r0,r1,r2 ; r0:=r1+r2


   Ex3) ADDS  r0,r1,r1, LSL #2 ; r0:=r1*5

        : 위의 명령을 보면 ARM 명령어의 강력함을 알 수 있죠?  위에서 Rd는 r0, Rn은 r1, Op2 부분이 r1,LSL #2 입니다. r1값 더하기 r1을 두 번 쉬프트 한 값, 즉 r1*4값이므로 결과적으로는 *5가 되겠지요. S 옵션이 사용되었으므로 플래그 레지스터에 영향을 미칩니다. 그러면? 위의 1번 2번 예제는 수행하더라도 플래그 레지스터는 영향을 안받는다는 의미지요. 사용하기에 따라서 강력한 역할을 할 수 있을 듯 합니다.


   Ex4) in C

        

        int addints(int a,int b)

        {

            int c;

            c=a+b;

            return c;

        }


        in ARM7 Assembly


        ADD       r0,r0,r1     ; r0:=r0+r1

        MOV       pc,lr       ; return


        사실 인수 전달에 관련된 코드가 더 들어가지만, 코어부분은 대강 위와 같습니다.


3) SUB{cond}{S}       Rd, Rn, Op2

   SBC{cond}{S}      Rd, Rn, Op2

   RSB{cond}{S}       Rd, Rn, Op2

   RSC{cond}{S}      Rd, Rn, Op2


   위의 네 명령은 빼기 명령입니다. SUB와 SBC는 그런데로 알만 한데, R로 시작하는 두개가 더 있네요. R은 감수(?) 와 피감수를 바꾸는 역할을 합니다. 즉, SUB 명령이 Rd=Rn-Op2 라면 RSC는 Rd=Op2-Rn 입니다. 이런 명령이 왜 필요할 까요? 글쎄요. 우선 Op2가 Rn에 비해서 상대적으로 유연성이 좀더 많아서일까요? 어떻든... 개념은 어렵지 않습니다.


   SBC와 RSC는 캐리플래그가 관련되어 있는 명령입니다. 그런데 명령 해석을 보면 다음과 같이 나와 있네요.


      SBC Rd, Rn, Op2 = Rd=Rn-Op2+Carry-1


   아마도 8086에서의 Carry와는 좀 다른 모양입니다. 뺄셈을 했을 때 자리 내림이 발생을 하면 Carry가 클리어 되는 구조이군요. 아마도 2의 보수 연산을 하기 때문인 듯 합니다. 즉 1을 빼고 싶은 경우 1의 2의 보수를 취해 더해주죠. 해당 값은 FFFFFFFF 이고, 대상이 0이 아니라면 더하기를 통해 Carry가 발생하겠군요. 근데 원래 하고자 했던 것이  뺄셈이었으니까... 피감수가 0인 경우에 Carry가 발생하지 않았고, 나머지의 경우에는 모두 Carry가 발생합니다. 기계적 사고방식에는 그게 맞나 봅니다.


   어떻든 중요한 것은 뺄셈 연산을 통해 borrow가 발생한다면 Carry는 0이고 발생하지 않는다면 Carry는 1이 되는 것이죠. 따라서 SBC와 같은 명령에서는 Carry를 오히려 더해주고 다시 1을 빼 줌으로써 8086 등에서 사용되는 것과 동일한 효과를 얻게 되는군요.


   Ex) 64Bit 뺄셈


       SUBS   r4,r0,r2

       SBC    r5,r1,r3


       : 이렇게 하면 [r5:r4]=[r1:r0]-[r3:r2]가 되겠군요. {S}옵션에 주의하시길 바랍니다.


4) AND{cond}{S} Rd, Rn, Op2

   EOR{cond}{S} Rd, Rn, Op2

   ORR{cond}{S} Rd, Rn, Op2


   : 이번에는 Bit연산에 관련된 명령어들입니다. AND는 그대로 AND, EOR은 XOR쯤으로, ORR은 OR로 생각하시면 됩니다. B명령을 제외하고는 웬만한 명령어는 모두 3글자인데, 그래서 ORR로 표기하는 듯합니다. 역시 오퍼랜드는 같은 의미입니다.


   Ex1) AND   r0,r0,#0xFF

       : r0의 8비트만 남기는 명령입니다.


   Ex2) ANDCSS       r0,r1,r2,ASR r3

       : 만약 캐리 플래그가 설정되어 있다면...(CS) r1 AND (r2>>r3) 정도를 하는데, 해당 명령의 결과가 플래그 레지스터에 영향을 미칩니다. ASR과 LSR의 차이는 ASR의 경우 최상위 비트가 1이라면 해당 비트는 계속 1로 유지를 하는 것이죠. LSR이라면 새로 들어오는 비트는 항상 0입니다. 이것은 음수를 2의 보수로 사용하는 시스템에서 필요한 방법입니다. 참 결과는 r0에 넣는군요.


   Ex3) EORS  r0,r0,r0

       : 해당 명령을 수행하면 r0는 0이 될 것이고, N 플래그도 0이 되고 Z플래그는 1이 됩니다.


   Ex4) MOV   r0,#0xFF

        ORR   r0,r0,#0xFF00

       : r0에 0xFFFF를 넣는 방법입니다.


5) BIC{cond}{S}       Rd, Rn, Op2

   

   : Rd := Rn AND (NOT Op2) 로 설명이 되어 있는 명령입니다. 별별 명령이 다 있다는 생각이 드는군요. AND하기 전에 NOT을 한번 해서 처리하는 명령입니다. 의미는 비트를 클리어 하는 것이고, Op2에 지정한 비트가 0으로 지워지는 결과가 나타납니다.


   Ex) BIC     r0,r1,#3

       : r0 := r1 and 0xFFFFFFFC 의 의미입니다.


6) CMP{cond} Rn, Op2

   CMN{cond} Rn, Op2


   : 비교 명령입니다. MOV, MVN처럼 인수가 2개인데요, 이번에는 Rd가 빠졌습니다. CMP는 흔히 보던 그 CMP입니다. Rn에서 Op2를 빼 보는 명령이죠. 아시다시피 레지스터 값에는 영향을 미치지 않고 플래그에만 영향을 미칩니다. 그러고 보니 {S}옵션도 없네요. 이 명령들은 디폴트로 {S}를 준 효과가 나타납니다. (일설에는 어셈블러가 그렇게 해 준다고 하던데, 기계어 코드로는 S옵션을 꺼놓은 CMP도 가능하지 않을까 싶네요.)


     CMN은 좀 새롭군요. 이 명령은 CMP가 빼기를 하는데 반해, 두 오퍼랜드를 더해 보는 명령입니다. 역시 생소한 개념인데요. 그럴 필요도 있을까요? 어떻든 CMP가 빼기를 해보는 거라면 CMN은 더하기를 해 보는 명령어랍니다.


   Ex1) CMP   r2,#23

        MOVEQ r2,#45

       : 만약 r2가 23이라면 45를 넣어라.


   Ex2) CMP   r0,#0

        CMPEQ r1,#0

        CMPEQ r2,#0

        CMPEQ r3,#0


        MOVEQ r4,#12


        : r0부터 r3까지 모두 0이라면 r4에 12를 넣는 명령입니다. 참 재미있는 구조라고 생각됩니다.


   Ex3) CMN   r1,r2

        MOVEQ r0,#0

        MVNNE r0,#1


        : r0 = ((r1+r2)==0) ? 0:-1;


   위에서 조건부 명령의 강력함을 느낄 수 있나요? 아니라면 다음을 한번 보시죠.


   in C

        while (a!=b) {

            if (a>b) a-=b;

            else     b-=a;

        }


   in ASM with no conditional Instruction


        gcd    CMP   a,b

                JZ     end

                JNZ    less_than


                SUB   a,a,b

                JMP    gcd


        less_than

                

                SUB   b,b,a

                JMP    gcd

        

        end    

                ...


        ※ ARM도 아니고 8086도 아니고, 명령어가 이상하게 되었네요. 대충 의미만 파악하시길.


   in ARM7


        gcd    CMP   a,b

                SUBGT a,a,b

                SUBLT b,b,a

                BNE    gcd


        직접 보시고, 소감을.


7) TEQ{cond} Rn, Op2

   TST{cond} Rn, Op2


   : CMP와 거의 비슷한 구조를 갖는 명령입니다. 기능도 거의 유사합니다. CMP가 -, CMN이 + 라면, TEQ는 XOR, TST는 AND 연산을 통해 같은 일을 합니다. 하지만 특징이라면 Logical 연산이 일어날 경우 플래그 중에서 V 플래그(오버플로우 플래그)는 영향을 받지 않습니다. 이걸 잘 응용하면 다음과 같은 처리가 가능합니다.



  Ex1) CMP   r0,#31         ; test r0<=31 ?

        TEQ    r0,#127         ; test r0==127 ?

        MOVLS r0,#'.'          ; if either then r0='.'


  Ex2) TST    r1,#3          ; is r1 word aligned?

        MOVEQ r0,#1          ; r0:=1 if so

        MOVNE r0,#0          ; else r0:=0



  지난번에 다루었던 MOV 명령까지를 합치면 총 16개의 명령을 설명 드렸습니다. 이로서 가장 비중이 큰(분량이 많은?) Data Processing 명령의 설명을 마쳤군요.


참고로 다루고 있는, 혹은 앞으로 다룰 명령의 종류는 다음과 같습니다.


   (1) Branch 명령

   (2) Data Processing 명령

   (3) PSR Transfer 명령

   (4) Multiply 명령

   (5) Single Data Transfer 명령

   (6) Block Data Transfer 명령

   (7) Swap 명령

   (8) SWI 명령


  이밖에도 코프로세서 관련 명령이 있기는 하지만, 그 정도까지는 좀 무리인 듯 싶습니다.

위에 써 놓은 것을 보니 무척 많아 보이는데요, 막상 살펴보면 분류 당 1개~2개정도의 명령밖에는 없습니다.


  자, 그럼 오늘 강좌는 여기에서 줄이겠습니다.


ARM7 강좌 [9] : Instruction Set (3)

 


  지난번 강좌를 쓰고 나서 이것저것 생각을 해봤는데, 내용이 좀 어렵지 않았나 싶네요. 명령 코드를 가지고 접근하는 방식은 디지털 쪽의 기초 지식과 어셈블리에 대한 보편적인 지식을 갖고 접근하는 방법이어서, 혹 그런 기본지식이 없으신 분들에게는 딱딱하고 어렵게 느껴졌을지도 모른다는 생각을 혼자(?) 했습니다.


  강좌에 대한 피드백이 없어서. 그냥 나름대로 생각하고 쓰고 있는데요. 이럴 경우엔 좀 어렵던지. 방향이 틀린 것 같다 라는 식의 의견을 들을 수 있다면 강좌의 방향을 정하는데 도움이 될 것 같다는 생각을 했습니다.


  그간 강좌를 보시고 ARM에 관한 몇 가지 질문들을 주신 분들이 계신데요. 될 수 있으면 답변을 해드리려고 노력중이고, 또 그렇게 함으로써 보람도 느낄 수 있는 것 같습니다.


  제 email 주소는 zartoven@secsm.org입니다. 강좌에 관련된 의견이나 질문이 있으신 분들은 해당 주소로 질문을 주세요. 제가 어떤 사람인지에 대해서도 말씀을 못 드렸는데, 모 전자회사에서 RTOS 업무를 담당하고 있습니다. 아직까지는 주로 pSOS 관련업무를 하고 있고요. 처음에는 pSOS관련 강좌를 운영해 볼까 생각도 해 봤지만, 과연 얼마나 많은 분들이 pSOS를 사용 하실지 의문이 들어서 포기했습니다. 관련해서 제가 알려드릴 부분이 있다면 아는 한도 내에서 최대한 노력해 보겠습니다.


  오늘은 안하던 사설을 길게 늘어놓았군요. 아무래도 지난 강좌에 다루었던 내용이 좀 부실했던 것 같아서, 오늘은 좀 다른 접근 방식으로 명령에 대한 내용을 다루려고 합니다. 자, 그럼 시작하겠습니다.


  오늘 다룰 내용은 주로 "The ARM RISC Chip. A Programmer's Guide"라는 책의 내용을 위주로 하겠습니다.


1) MOV{cond}{S} Rd,Op2

   

   Assembler에서 가장 기본적으로 사용되는 데이터 전송명령입니다. 해당 명령은 8086에서의 MOV, Z80이나 196에서의 LD 명령과 같은 역할을 합니다. 다만 차이라면 ARM7에서의 MOV는 그 Target이 되는 부분이 항상 레지스터라는 점입니다.


   예를 들어 8086에서는 MOV AX,[BX] 의 형태와 MOV AX, BX와 같은 명령의 형태를 같은 MOV명령을 통해 해결합니다만, ARM에서의 MOV는 후자의 것만을 의미합니다. 즉, 레지스터나 Immediate 값 같은 것들을 레지스터에 넣는 명령입니다.


   전자의 경우와 같이 외부 메모리를 액세스하는 명령은 따로 있고, 분류 자체를 다르게 합니다.


   제목에 써놓은 것은 MOV명령의 형식인데요. {cond}부분은 지난번 말씀드렸던 실행 조건을 제한하는 옵션입니다. ARM7에서는 모든 명령어에 해당 옵션을 적용시킬 수 있다고 말씀드렸습니다. 구체적으로는 EQ, NE, 등등의 2자 짜리 접미사 형태이고, 해당 조건에 맞도록 플래그 레지스터가 설정이 되어 있어야 MOV명령이 수행됩니다.


   {S}옵션은 해당 명령의 결과가 플래그 레지스터에 영향을 미칠지 여부를 결정하는 옵션입니다. ARM에서의 MOV는 Data Processing 명령으로 분류되어 Rd에 들어가는 값에 따라 플래그 레지스터가 영향을 받을 수 있습니다.


   하지만, 특별히 사용되는 경우가 있습니다. 좀 어려울지 모르겠지만, Target 레지스터가 PC인 경우 S옵션을 사용하면, Exception모드에서 보통 상태로 빠져 나오는 역할을 하게됩니다. 지금 다루기는 좀 그렇고요. 강좌의 마지막쯤 해서 한꺼번에 다시 다루겠습니다. 지금은 그러려니 하시길 바랍니다.


   Rd는 Target이 되는 레지스터입니다. MOV명령의 Target은 항상 레지스터이어야 합니다. 물론 MOV 뿐만이 아니라 Data Processing 명령으로 분류된 모든 명령들(지난 강좌에서 언급했던)이 다 그렇습니다.


   Op2는 MOV명령에 의해 Rd로 들어갈 내용을 가리키는 오퍼랜드입니다. 지난 강좌에서 이것저것 복잡하게 말씀드렸었는데요, Op2 부분에는 단순히 레지스터가 들어갈 수도 있고, 어떤 immediate값이 들어갈 수도 있고, 또 Shift 오퍼레이션을 가한 레지스터 내용이 들어갈 수도 있습니다.  예제를 통해 확인하시길 바랍니다.


   Ex1) MOV     r0,r1

        : 단순히 r0:=r1을 하는 명령입니다.


   Ex2) MOV     r0,#0

        : r0에 상수 0을 넣는 명령입니다. ARM에서는 196에서와 같이 상수에는 #을 붙여서 사용합니다. 또 진법 표현은 C에서의 방법을 사용합니다. 즉 MOV   r0,#0x30 이런 식으로 사용할 수도 있습니다. 혹은 &를 붙여서 16진수를 나타낼 수도 있습니다.


   Ex3) MOV     r0,#0xfc000003

        : r0에 상수 값 0xfc000003 을 넣는 명령입니다. 지난 강좌에서 말씀드렸지만 해당 값은 8비트 값 0xFF를 32비트로 확장하고 오른쪽으로 두 번 Rotate 시킨 값입니다. 그래서 에러가 나지 않는 것이지요.


   Ex4) MOV     r0,r1,LSL #1

        : 이 명령은 r0:= r1 <<1 을 수행하는 명령입니다. LSL은 logical Shift Left를 의미하는 키워드이고, Op2 부분에 위와 같은 형식으로 올 수 있습니다.


   Ex5) MOV     r0,r1,LSR r2

        : 이 명령은 r0:=r1 >> r2 에 해당하는 명령이고, LSR은 Logical Shift Right를 의미합니다.


   Ex6) MOV     r0,r0,ASR #24

        : 이 명령은 r0:=r0 >> 24 를 의미합니다. ASR은 Arithmetic Shift Right를 의미하며 LSR과의 차이는 최 상위 비트가 1인 경우 새로 계속 해당 비트 값을 유지시킨다는 것입니다.


   Ex7) MOVS    r0,r1,LSR #1    : C(flag) := r1[0]

        MOVCC   r0, #10         : if C=0 then r0:=10

        MOVCS   r0, #11         : if C=1 then r0:=11


   Ex8) MOVS    r0,r4           : if r4=0 then r0=0

        MOVNE   r0,#1           : else r0=1

        

        첫 번째 명령에서 r4값을 r0로 넣을 때 {S}옵션에 따라서 플래그 레지스터 값이 변화합니다. 다음 명령에서 NE 조건을 사용했기 때문에, 참고로 NE는 Not Equal, Z플래그가 0일 경우입니다., r4값이 0이었다면 NE는 False가 되어 두 번째 명령을 수행하지 않습니다. 결과적으로는 r0에 0이 들어간 셈이지요. 만약 r4가 0이 아니었다면 두 번째 명령이 수행되어서 r0에는 1이 들어갑니다.


  오늘 강좌는 이것으로 정리하려 합니다. 다음에 뵙겠습니다.

ARM7 강좌 [8] : Instruction Set (2)

 


  이번 강좌에서는 데이터 프로세싱 명령에 대해 다루려 합니다. 해당 명령은 ARM7의 50%정도에 해당하는 명령입니다. 실제 개수는 16개이고, 연산명령과 비교명령, 비트 연산명령, 데이터 전송 명령 등이 포함됩니다.


● Data Processing Instruction


  데이터 전송명령의 형식은 다음과 같습니다.


+--------------------------------------------------------------------+

| Cond | 00 | I | OpCode | S |    Rn    |    Rd    |   Operland 2    |

+--------------------------------------------------------------------+

   4      2   1     4      1      4          4            12


  전에 언급했듯이 모든 인스트럭션의 크기는 32 Bit 입니다. 위의 그림에서 숫자는 해당 필드의 비트 수를 의미합니다.


  1) <Cond> [31:28] 4 Bit


     해당 명령의 조건 실행 플래그입니다. 지난 강좌에서 언급했었던 부분인데, 모든 명령어에 포함되므로 데이터 프로세싱 명령에도 포함됩니다.

     혹시나 해서 다시 말씀드리는데, 해당 플래그를 통해 명령을 현재 플래그 레지스터(CPSR)의 상태에 따라 실행 여부를 결정하는데 사용되는 플래그입니다.


  2) <I> [25] 1 Bit


     Operland 2로 지정되어 있는 부분이 Immediate Operand 인지 아닌지 여부를 나타내는 비트입니다. Immediate Operand라 함은, 예를 들어 8086에서 MOV AX,01234h 라고 했을 경우, 1234h를 가리키는 말입니다. 자세한 내용은 Operland2를 설명하면서 자세히 다루겠습니다.


  3) <OpCode> [24:21] 4 Bit


     데이터 프로세싱 명령 중 어떤 명령인지를 나타내는 필드입니다. 해당 필드와 명령어는 다음과 같습니다.


     <OpCode>   Instruction        Description

     -----------------------------------------------------

       0000       AND              Rd:=Op1 AND Op2

       0001       EOR              Rd:=Op1 XOR Op2

       0010       SUB              Rd:=Op1 - Op2

       0011       RSB              Rd:=Op2 - Op1

       0100       ADD              Rd:=Op1 + Op2

       0101       ADC              Rd:=Op1 + Op2 + C

       0110       SBC              Rd:=Op1 - Op2 + C - 1

       0111       RSC              Rd:=Op2 - Op1 + C - 1

       1000       TST              Op1 AND Op2 -> CPSR

       1001       TEQ              Op1 XOR Op2 -> CPSR

       1010       CMP              Op1 - Op2   -> CPSR

       1011       CMN              Op1 + Op2   -> CPSR

       1100       ORR              Rd:=Op1 OR  Op2

       1101       MOV              Rd:=Op2

       1110       BIC              Rd:=Op1 AND (NOT Op2)

       1111       MVN              Rd:=NOT Op2


     명령어들의 간단한 설명만으로도 어느 정도는 이해할 수 있으리라 생각합니다. 각각의 명령에 대해서는 자세히 다루지 않도록 하겠습니다. 나중에 예제를 보시고 이해할 정도라면 족하다고 생각합니다.


  4) <S> [20] 1 Bit


     S 비트가 1인 경우는 데이터 프로세싱 명령의 결과가 CPSR(플래그레지스터)에 영향을 미칩니다. 즉, 0인 경우에는 CPSR은 변하지 않습니다.


  5) <Rn> [19:16] 4 Bit


     ARM 데이터 프로세싱 명령은 그 결과와 첫 번째 오퍼랜드는 항상 레지스터로 지정해야 합니다. Rn은 첫 번째 오퍼랜드를 가리키는 것으로 위에서 Op1으로 표기한 것에 해당합니다. ARM에서 한번에 볼 수 있는 범용 레지스터는 sp, lr, pc 등을 포함해서 r0에서 r15까지라고 말씀 드렸습니다. 즉, 4Bit를 통해 레지스터를 나타내게 됩니다. 해당 필드는 명령에 따라 사용되지 않기도 합니다. MOV나 MVN등이 이에 해당합니다.


  6) <Rd> [15:12] 4 Bit


     오퍼레이션의 결과가 저장될 레지스터를 의미합니다. 역시 레지스터를 가리키므로 4비트를 사용하고 모든 명령에서 디폴트로 사용되는 필드입니다. 말씀드렸듯이 ARM의 데이터 프로세싱 명령의 결과는 항상 레지스터로 들어갑니다.


  7) Operand 2 [11:0] 12 Bit


     드디어 오늘의 가장 험난한 산인 Operand 2 필드까지 왔군요. 설명 드려야 할 부분이 좀 많거든요. 어떻게 설명을 드려야 할지, 막막하긴 하지만, 용기를 가지고 차근차근 설명해 보도록 하겠습니다.


● 데이터 프로세싱 명령의 Operand 2 필드


  오퍼랜드 2의 의미 자체는 별게 아닙니다. 아마도 다들 아시겠지만, ALU에 연산명령을 내릴 경우 한 쪽 입력은 오퍼랜드 1, 다른 쪽은 오퍼랜드 2 그리고, 결과가 나오고... 앞서 설명한 내용에 의거하면 다음과 같이 표현 할 수 있겠죠?



                Op1(Rn)             Operand 2

                --------------     ---------

                 |           |    /        /

                 |           |   /        /

                  |          |  /        /

                  |                     /

                   |        ALU        /

                   |                  /

                    |                /

                     ----------------

                            Rd


  예를 들어 AND r1,r2,r3라는 명령에서 Rd = r1 이고 Rn=r2 입니다. 그리고 r3가 Operand2에 해당하는 부분이 되겠죠. 의미는 r1=r2 & r3 라는 의미입니다.


  여기서 설명하고자 하는 ARM7의 데이터 프로세싱 명령의 Operand2의 타입은 두 가지 종류가 있습니다. <I> 필드의 내용에 따라 구분이 되는데, Immediate Operand 혹은 레지스터 Operand입니다.


  1) Register Operand


     <I> 필드가 0일 경우엔 Op2가 레지스터임을 의미합니다. 레지스터를 나타내기 위해서는 아시다시피 4비트만 있으면 되죠? 그런데 Op2를 위한 비트 수는 총 12 비트입니다. 남는군요.


     구체적으로 레지스터 오퍼랜드2의 형식을 보여드리겠습니다.


    +----------------------------------------------+

     |          Shift [11:4]             | Rm [3:0] |

     +----------------------------------------------+


     위의 그림을 보시면 Rm이라고 레지스터를 지정하는 4비트가 포함되어 있습니다. 그러면 Shift 부분은 뭘까요?


     강좌의 앞부분에서 언급했나요? 무슨 Shift가 ALU의 한쪽에 달려 있다고. 결론적으로는 레지스터를 ALU로 그냥 집어넣는 것이 아니라 Shifter를 통해서 넣어주도록 되어 있습니다. 따라서, Shift라는 필드는 (8비트죠?) 그런 역할을 해주는 것이겠군요. 그럼 왜 8비트일까요? 여기서 다시 한번 Shift를 구체적으로 살펴보겠습니다.


     우선 그전에 Shift 필드에는 Shift 회수를 지정하는 방법이 두 가지가 있는데, 첫 째는 Shift 필드 안에 그 회수를 포함시키는 방법이고, 두 번 째는 Shift 회수가 들어있는 레지스터를 Shift 필드 안에서 지정하는 방법입니다. 경우의 수가 자꾸 늘어가는군요. 해당 타입 두 가지는 Shift 필드의 최하위 비트인 4번 비트에 따라 결정됩니다. 다시 그림을 그려보겠습니다.


     +-------------------+               +-------------------+

     |  count  |type | 0 |               | Rs  | 0 |type | 1 |

     +-------------------+               +-------------------+

         5        2    1                    4    1    2    1


     최하위 비트의 값이 0이냐 1이냐에 따라 두 가지로 구분이 되었습니다. 왼쪽의 경우가 직접 Shift 회수를 지정하는 방법으로, count 부분이 총 5 비트로서 그 값을 의미합니다. ARM7의 레지스터는 32비트이므로 5비트는 있어야 Shift 회수를 표현할 수 있겠지요.


     그러면 Type은 뭘까요? 2비트인 Type의 의미는 다음과 같습니다.


        00 : logical left       (LSL)

        01 : logical right      (LSR)

        10 : arithmetic right   (ASR)

        11 : rotate right       (ROR)


     그렇군요. 단순히 Shift라고 말씀드렸지만 실제로는 좌, 우, 부호의 여부와 Rotate까지 형식을 지정할 수가 있습니다. (무지 복잡해지는 듯한 느낌입니다.^^)


     오른쪽에 레지스터를 지정하는 방식은, 1비트가 남아서 0으로  처리한 것만 제외하고는 별 문제가 없겠죠?


     원래는 여기서 쉬프트 방식 4가지에 대한 설명이 뒤를 이어야 하겠지만. 이러다 보면 한도 끝도 없을 것 같네요.


  2) Immediate Operand


     강좌를 정리하려다 생각하니 Immediate Operand를 설명 안했군요. 여기까지는 해야할 것 같아서.


     레지스터 오퍼랜드의 경우 총 12 비트 중에서 4비트는 레지스터를 지정하고, 나머지 8비트는 해당 레지스터 값을 Shift하는 방법을 지정하도록 되어 있었습니다.


     Immediate 오퍼랜드는 몇 가지 문제가 있습니다. 32비트 레지스터에 값을 써넣고 싶은데 공간은 12비트 밖에는 없다는 것이지요. 8086등에서는 그냥 여러 바이트로 명령을 처리했었으니 그런 문제가 없었지만, ARM에서는 이것 때문에 모든 명령이 32비트라는 규칙을 깰 수는 없었겠지요. 다음은 구체적인 형태입니다.


     +----------------------------------------------------------------+

     |  Rotate [11:8]    |              Imm [7:0]                     |

     +----------------------------------------------------------------+


     이전과 비슷한 모양이군요. 이번엔 Rotate 필드(4비트)가 있고, 또 8비트의 Imm필드가 있습니다. Imm은 말 그대로 Immediate Value를 넣는 공간입니다. 이 공간이 8비트라서, 결국 ARM7에서 어떤 상수 값을 레지스터로 전송하거나 할 경우 8비트까지는 아무런 문제가 없이 넣을 수 있습니다. 그러면 Rotate 필드의 하는 역할은 무엇일까요?


     좀 복잡한데요... ARM7문서에는 다음과 같이 써 있습니다.


“Immediate Operand rotate 필드는 부호 없는 4비트 정수로서 8비트  Immediate 값에 Shift 오퍼레이션을 할 수 있도록 사용되는 값이다. rotate필드 *2 값만큼 8Bit Immediate Value 값을 Right Rotate 시킨다. 결국 8비트 값을 32비트로 확장시키는 역할을  하며 이를 통해 자주 사용되는 많은 상수들을 사용 가능하도록 한다."


     8비트의 값이 ALU로 들어가기 전에 Shifter로 들어가면, 이미 32비트로 확장이 됩니다. 이후 그 값을 그냥 쓰면 8비트 영역만 표현할 수 있지만 Rotate필드 값을 * 2 한만큼 Rotate Right 시킨다 그랬으니, 어떤 변화가 생기겠죠?


오늘 분량이 좀 많았네요... 차근차근... 담에 또 말씀드리죠.. 뭐. 몇 가지 예를 들어 보이면서. 오늘 강좌를 정리할까 합니다.


     ADDEQ      r2,r4,r5                : 제로 플래그가 설정되어 있다면 r2=r4+r5

     SUBS       r4,r5,r7,LSR r2         : r4=r5-(r7>>r2) & effect CPSR

ARM7 강좌 [7] : Instruction Set (1)

 


● ARM7 명령어의 특징


  AMR7은 32 Bit 코어입니다. 특징적인 것은 모든 명령어가 32Bit 하나의 Word로 구성된다는 것입니다. 8086의 경우엔 명령어에 따라 1바이트 명령부터 5바이트까지 있나요? 그런데, ARM7은 모든 명령어를 한 워드로 처리합니다. 일단은 명령어의 개수가 몇 안되고, 주소는 상대주소 방식을 사용하며, 심지어는 Immediate 상수 값도 32비트 값은 그대로 넣을 수 없습니다.


  무슨 이야기인가 하면, 만약 r0에 32비트 상수를 넣고 싶다면, 몇몇 예외를 제외하고는 메모리에 미리 넣어두고 해당 메모리를 상대 주소로 참조해서 얻어 와야 한다는 뜻입니다.


  역시 장단점이 있겠지만, 장점으로는 모든 명령어를 같은 사이즈로 처리함에 따라 파이프라인 구현이 용이하다는 점이 있습니다. 그리고 명령어 해석기를 설계할 경우 예외 처리부분이 없으므로, 쉽고 고속으로 처리할 수 있겠지요. 단점은 앞에서도 간단히 언급했지만 코딩 시에 몇몇 제한이 따른다는 점입니다. 상대 주소 지정 방식은, 이때 사용하는 Offset이 24+2=26 비트이므로, 상대주소라고는 하지만 거의 불편이 없고, 다만 Immediate  오퍼랜드를 지정할 경우에 좀 번거롭다는 점이 있습니다. 그러나 8비트의  해상도를 가지는 오퍼랜드라면 한 워드 내에서 처리 가능합니다. 자세한 내용은 다시 말씀드리겠습니다.


  ARM 명령어의 다른 특징으로는 모든 명령어를 조건적으로 실행시킬 수 있다는 것입니다. 저의 경우엔 이 부분에서 대단히 감명을 받았는데요 예를 들어, 8086에서는 jz, jc 와 같은 점프 명령을 사용합니다. 그 의미는 제로 플래그가 설정되어 있으면 점프를 해라, 혹은 캐리 플래그가 설정되어 있으면 점프를 해라.. 라는 의미임을 아실 것입니다. 그냥 무조건 점프의 경우엔 jmp를 쓰지요.


  ARM의 경우엔 그런 플래그의 사용이 점프명령에 국한되지 않고, 예외 없이 모든 명령어에 사용할 수 있습니다.


        ex)  BEQ        jmp_1   ; Branch if Z flag set to jmp_1

             MOVEQ      r0,r1   ; r0 := r1 if Z flag set...


  위의 경우를 보시면 jmp에 해당하는 B명령뿐만 아니라 MOV 와 같은 데이터 전송명령에도 플래그 옵션을 사용했음을 볼 수 있습니다. 이런 기능의 장점은 잠깐만 생각해 보아도 알 수 있습니다. 예를 들어 C 연산자 중에 ? 연산자를 컴파일 한다고 생각해 봅시다.

   

        ex) a=(b==c) ? d:e;

        (편의상 변수를 레지스터로 바꾸어 생각하겠습니다.)


            CMPS        r2,r3

            MOVEQ       r1,r4

            MOVNE       r1,r5


  아직 명령어를 안 다루었으므로 대충 의미는 추측 해 보세요. 8086이랑 대충은 비슷하니까 어려운 일은 아닐 것입니다. r2와 r3를 비교해서, 그 결과가 같다면 r1에 r4를 넣고, 그렇지 않다면 r1에 r5를 넣는 코드입니다. 만약 같은 일을 8086등에서 하려면 점프 명령이 한 개 이상은 들어가야 하겠죠.


  다시 한번 강조하지만 ARM7에서는 이와 같은 조건 옵션을 모든(!) 명령어에 사용 가능합니다. 실제로 모든 OP 코드의 상위 4비트는 이런 조건옵션을 나타내는데 사용되는 비트입니다. 각 조건에 사용되는 접미사 목록입니다.


        0       EQ      Z Set                   equal

        1       NE      Z Clear                 not equal

        2       CS      C Set                   unsigned higher or same

        3       CC      C Clear                 unsigned lower

        4       MI      N Set                   negative

        5       PL      M Clear                 positive or zero

        6       VS      V Set                   overflow

        7       VC      V Clear                 no overflow

        8       HI      C Set and Z Clear       unsigned higher

        9       LS      C Clear or Z Set        unsigned lower or same

        10      GE      N Set and V Set or      greater or equal

                        N Clear and V Clear     

        11      LT      N Set and V Clear or    less than

                        N clear and V set

        12      GT      Z clear and             greater than

                        ether N set and V set or

                        N Clear and V clear

        13      LE      Z Set or                less than or equal

                        N set and V clear or

                        N clear and V set

        14      AL      always

        15      NV      never


  뒷부분에 가서는 무지 복잡하죠? 저도 치면서 이걸 꼭 쳐야 할 필요가 있을까 생각을 했습니다만, 이왕 시작하는 거.. 하면서 다 쳤군요. 자세하고 정확한 내용은 다른 문서를 참조하시길 바라고 여기서는 그냥 이런 것들이 있구나 하는 정도만 알아두세요.


  다음으로 말씀드릴 명령어의 특징은 대부분의 명령어에 S라는 접미사를 사용하여 플래그 레지스터에 영향을 줄지 여부를 결정할 수 있다는 것입니다. 특히 연산명령을 수행할 때 'S'를 붙이면 해당 결과에 따라서 플래그 값들이 변하게 되고, 붙이지 않으면 영향을 미치지 않도록 할 수 있습니다. 8086에서는 연산의 결과에 따라 항상 플래그 값이 영향을 받죠? 여기서는 받지 않게 할 수도 있다는 점을 말씀드렸습니다. 자세한 내용은 명령어의 세부사항을 참조하세요.


  명령어의 특징을 말씀드리는 중인데, 앞으로도 오퍼랜드 사용 등에 대해 말씀드릴 것이 많이 남아있네요... 그런데 분량은 점점 늘어나고...


  ARM은 지금 말씀드리는 규칙들이 거의 예외 없이 적용됩니다. 명령어가 간단한 대신 옵션이 많거든요...그리고 지금 그 옵션 두 가지를  말씀드렸고요.


  오늘은 그냥 끝내기는 서운하니까 점프 명령 하나만 말씀드리고 마치겠습니다.


  ● Branch and Branch with link (B, BL)


  +------------------------------------------------------------------+

  | Cond | 101 | L |                                                 |

  +------------------------------------------------------------------+

  

  Cond 부분은 위에서 말씀드린 조건 옵션입니다. 101은 B 명령 코드입니다.


  L 부분은 1일 경우 BL 이 되는 것이고 0이면 B 명령입니다. B는 JMP라고 생각하시면 되고, BL은 CALL로 생각하시면 됩니다. 다만 BL의 경우엔 PC값을 스택에 넣는 것이 아니라 r14(lr)에 넣는다는 것이 차이가 있습니다. 나머지 하위 24비트가 Offset으로 사용되는데, ARM7은 모든 명령어들이 Word단위이므로 총 +/- 32메가 바이트 영역을 커버합니다.


  좀 어수선한 느낌입니다만. 오늘은 이만.

ARM7 강좌 [6] : Exception (2)

 


● Exception Overview


  Exception을 처음부터 완전히 이해 할 필요가 있다고는 생각하지 않습니다. 대부분의 것들이 그러하듯 우선은 이런 것이 뭐다 라고 파악만 하고 있다면, 정작 필요할 경우에 다시 자세히 살펴보아도 좋을 것입니다.


  그런 취지에서 Exception을 바라본다면, ARM 7에는 여섯 가지의 CPU 동작 모드를 지원한다는 사실과, 차후 MMU 같은 것에도 그 동작모드가 관여한다는 것. 그리고 각 모드마다 스택을 따로 설정해 준다는 것... 등.


  꼭 알아야 할 것이 있다면 IRQ나 FIQ 정도가 되겠지요. 흔히 말하는 인터럽트 관련 부분이 바로 이 부분입니다. 실제 코딩을 할 경우에는 CPU에서 정해져 있는 IRQ, FIQ관련 레지스터를 설정하고, 해당 벡터 번지를 바꾸는 정도... 그리고 초기화 시에 해당하는 스택 공간을 따로 잡아주는 정도만 신경을 쓰면 됩니다.


  원래는 각 Exception 모드마다 종료 방법이 각기 다름을 설명 해 드리려고 했는데, (구체적으로는 FIQ는 서비스 종료 시에 SUBS pc,r14_fiq,#4)라는 명령을 사용해야 합니다, 혹은 소프트웨어 인터럽트의 경우엔 MOVS pc,r14_svc 를 사용합니다.) 굳이 그런 내용까지 다룰 필요는 없을 듯 하다는 생각입니다. 필요한 경우 도큐먼트를 참조하면 자세히 나와 있습니다. 참고로 그렇게 각각 경우에 따라 복귀 방법이 다른 이유는 추측하자면 파이프라인 기능과 관련이 있는 것 같습니다.


  그러면, 다시 앞으로 돌아가서, Exception과 CPU동작모드의 연관성에 대해 마저 얘기해 보려 합니다. CPU동작 모드는 일반적으로는 Exception에 의하여 바뀌게 되고, User모드가 아니라면 프로그램에 의해 강제로 바꿀수도 있습니다.


  전에 살펴보았던 CPSR(스테이터스 레지스터)과 관련되어, CPSR의 모드비트 부분에 현재 CPU의 동작모드를 나타내는 비트들이 있는데, 해당 비트를 변경시킴으로서 프로그램에서 동작모드를 전환할 수 있습니다. OS를 포팅 할 경우 동작모드와 관련하여 메모리 보호기능 등도 구현될 수 있습니다.


  마지막으로 몇 가지 사항만 말씀드리고 오늘 강좌를 마치려 합니다. 우선, Exception간의 우선순위 문제인데, 기본적으로 ARM7은 IRQ, FIQ를 지원하지 않습니다. 해당 인터럽트가 발생하면 CPU는 CPSR의 인터럽트 금지 플래그를 설정합니다. 만약 도중에 인터럽트를 받고 싶다면 사용자가 해당 플래그를 클리어 해주어야 합니다. 또, 동시에 여러 개의  Exception 이 발생한 경우라면 그 우선순위는 다음과 같습니다.


        1) Reset (가장 높은 우선순위)

        2) Data abort

        3) FIQ

        4) IRQ

        5) Prefetch abort

        6) Undefined Instruction, Software Interrupt


  Exception 부분을 설명하다 보니 강좌가 자칫 너무 딱딱해지는 감이 있어서 그냥 이쯤에서 마무리를 지으려 합니다.


  다음 강좌부터는 좀더 구체적인 ARM7의 인스트럭션에 대해 다루겠습니다. 명령어가 몇 개 되지 않기 때문에 3회 정도로 다룰 생각입니다..


  그럼 다음 강좌에 뵙겠습니다.


+ Recent posts