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

 


  오늘 다룰 명령은 SWP와 SWI 두 가지입니다. Co-Process 명령을  제외하면 이 명령이 마지막이겠군요. 코프로세서는 다루지 않을 생각이니. 참 홀가분한 느낌입니다.


1. Single Data Swap : <SWP>{cond}{B}    Rd,Rm,[Rn]


   레지스터를 3개 지정하도록 되어 있습니다. 실제 동작은 Rd:=[Rn], [Rn]:=Rm이 한번에 일어나는 명령입니다. 여기서 한번이라는 말은 한 Clock을 의미하는 것이 아니라, 명령 도중에 인터럽트 등에 의해 중단되지 않고 계속 이루어진다는 것입니다. 그리고 B접미사는 계속 보아 왔듯이 워드 Operation이 아니라 Byte Operation을 의미합니다.


   예제를 보죠.


        SWP     r0,r1,[r2]

                ; r0:=[r2], [r2]:=r1 입니다.


        SWPB    r2,r3,[r4]

                ; r2:=[r4], [r4]:=r3 입니다. 다만 B가 붙었으므로 Bit 0에서 7까지만 영향을 미칩니다.


        SWPEQ   r0,r0,[r1]

                ; Flag의 상태를 확인하여 r0와 [r1]의 내용을 바꾸는 형태입니다. 보셨듯이 Rd와 Rm이 같을 수도 있군요.


   한가지 더 언급하고 싶은 것은 SWP를 세마포어 구현에 사용하는 방법입니다. 어떤 문서에선가 SWP명령이 세마포어를 구현하는데 유용하다고 읽었는데, 처음에는 이해가 안되더군요. 한참 뒤에 이해할 수 있었습니다.


   인터럽트를 금지시키지 않고 Semaphore 연산을 할 수 있다는 것이 SWP명령의 강점입니다. 여기서 Semaphore를 설명 드리기는 좀 그러니, 간단히 핵심만 말씀드리고, 이해하실 분은 하시고, 안해도 별 수 없겠죠.


   보통 세마포어의 p()연산을 구현하는 순서로, 먼저 세마포어 변수를 읽어오고, 다음에 비교를 하죠. 그 값이 0보다 크다면 감소를 시켜  다시 Write합니다. Test and Set 연산이라고도 하던데요.


   SWP명령에 적용시키려면, [Rn]부분이 변수가 되어야겠군요. 그러면 Rd에는 SWP명령을 통해 세마포어 변수 값이 들어오고요, 물론 세마포어 변수의 상태를 알지 못하므로 어떤 값이 들어올지는 모릅니다. 따라서 이 단계에서는 세마포어 변수에 어떤 값을 다시 Write해야 할지도 모릅니다.


   핵심은 변수에 Write되는 값을 무조건 0으로 설정한다는 것이죠. 즉.


   r3가 Semaphore 변수의 주소를 가리키고 있다고 하고, r2의 값은 0이라고 한다면,


        SWP     r1,r2,[r3]


   와 같은 명령으로 처리할 수 있습니다. 위의 명령을 통해 원래 세마포어 변수의 값이 r1으로 로드됩니다. 0보다 큰 지의 여부는 이제부터 비교해야겠지요. 한가지 특이한 것은 세마포어 값을 읽어옴과 동시에 0을 넣어주었다는 것입니다. SWP명령의 특성상 읽어오는 동작과 0을 넣는 동작 사이에는 인터럽트 등이 걸릴 수 없습니다.


   이제, r1의 세마포어 값을 비교하는 부분으로 넘어가면 되는데, 이 와중에 인터럽트가 걸리고 다른 Task가 세마포어 변수를 Access하려고 하더라도 0이 들어 있으니 자연히 대기상태로 인식되어 더 이상 진행을 하지 못하게 될 것입니다.


   이밖에도 v()연산에서도 뭔가 더 해주어야 하겠지만.

   (괜한 얘기를 꺼냈나 하는 후회가 엄습하는 중입니다...^^)



2. Software Interrupt : SWI{cond}  <expression>


   SWI명령은 소프트웨어 인터럽트 명령입니다. 8086에는 INT 명령이 있죠? Exception 파트를 보시면 언급이 있었습니다.


   SWI명령이 걸리면 동작모드가 변화합니다. Supervisor 상태로 진입을 하게 됩니다. 이런 특성 때문에 pSOS의 경우는 System Call의 진입방법으로 사용합니다.


   명령의 형식을 보시면 <expression>부분이 있는데, 무슨 역할을 하는 것일까요? 8086계열처럼 INT 21H 또는 INT 10H 이런 식으로 인수를 주는 것일까요? 8086이야 Interrupt 벡터를 지정하는 것이었지만, ARM7에는 해당 Vector는 고정이 되어 있습니다.(Software Exception 한가지로)


   CPU는 expression에 대한 아무런 일도 하지 않습니다. 실제 명령어 구조를 보면 총 32비트 중 상위 4비트는 조건 Flag에 사용되고, 다음 4비트가 모두 1111 이면 SWI명령으로 판단됩니다. 이후 24비트는 실제로는 무시되는 내용입니다만...


   위와 같이 써주면 expression의 내용이 SWI명령의 Low 24Bit부분에 인코딩 됩니다. 하지만 말씀 드렸듯이 ARM7은 SWI명령의 Low 25Bit내용이 뭐든 간에 동일하게 동작합니다.


   그럼 왜 그렇게 쓰는걸까요?


   예제를 보면 SWI명령을 만나면 소프트웨어 Exception Handler로 이동하고 복귀번지는 r14_svc로 저장이 됩니다. 그런데 Handler 부분에서 복귀 번지를 이용하여 해당 명령을 직접 읽어다가 Low 24 Bit 부분을 인수로 해석해서 사용할 수 있는 것입니다.


   물론 이런 일들은 ARM7이 해주는 것이 아니라 User가 Handler에서 직접 프로그램 하는 것이죠.


   다음은 Handler 부분의 예제입니다.


        STMFD   r13,{r0-r2,r14}

        LDR     r0,[r14,#-4]

        BIC     r0,r0,#0xFF000000

        ...


   위에서 보시면 결국 r0에 SWI명령의 low 24Bit가 얻어지죠.


  오늘 강좌까지 길고 길었던 정말 길었던 ARM Instruction 부분을 끝냈습니다.


  계획했던 분량은 3회였는데... 결국 8회 분량으로 늘어나고 말았군요. 이것저것 후회가 많았던 부분이었습니다. 이렇게 저렇게 숙제하듯이 여기까지 해치우고는...


  다음엔 무엇을 써야 할지... 고민을 하게 되는군요.


  뭐가 되었든 다음 강좌로 ARM7 강좌를 정리하겠습니다. (마음의 준비를...^^)

  그럼 이만.


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

 


● Block Data Transfer 명령(LDM,STM)


  해당 명령은 개인적인 생각으로 참 독특하다고 생각합니다. 지난 강좌에서 다루었던 LDR, STR과 마찬가지로 실제 메모리에 레지스터의 내용을 전달하거나, 전달받을 수 있는 명령입니다. ARM7에서는 이런 명령이 몇 안되죠...


  LDR명령이 메모리 번지의 내용을 지정된 레지스터로 가져오는 명령이라면 LDM은 가져오긴 하는데, 여러 개의 레지스터의 내용을 한 큐에 가져오는 명령입니다. 가장 많이 사용되는 경우는 스택 연산인 것 같습니다. 전에 말씀드렸었지만, ARM7에는 Push, Pop 명령이 없습니다. 대신 LDR이나 STR을 쓸 수도 있겠고, 또 LDM이나 STM을 쓸 수도 있죠. 후자 쪽이 더 많이 사용되는 듯 합니다.


  1) <LDM|STM>{cond}mode   Rn{!},{reg_list}{^}


    위 명령에서 {cond}는 늘 보아오던 명령어 실행 조건입니다. Rn은 전송에 사용될 베이스 번지를 지정하는 레지스터입니다. !를 붙이면 Wrte Back 기능이죠. LDR과 STR에서 다루었습니다. 자세한 내용은 뒤에서 언급하도록 하겠습니다.


    {reg_list} 부분은 전송하거나 전송 받을 레지스터의 목록을 나타내는 부분입니다. 예를 들어 1000번지에 r1,r2,r3를 저장하고 싶다 했을 경우에, 일단 1000번지 값을 어떤 레지스터에 넣어두고, 여기서는 그 레지스터를 r13이라고 하죠, 그러면 Rn은 r13이 되는 거구요, {reg_list}는 {r1,r2,r3} 이 되는 것입니다. mode 라고 되어있는 부분은 여러 개의 레지스터를 메모리에 넣거나 가져올 때 어떤 방식으로 동작할 지를 지정하는 접미사입니다. 이 접미사 종류가 8가지가 있는데요.


    무지하게 복잡해 보입니다. 일단은 각 요소의 의미만 간단하게 정리하고, 다음으로 넘어가죠.


    마지막으로 {^}부분은... 글쎄요 잘 이해가 안되는 부분입니다만, 제가 보는 책과 데이터 시트에서 예제가 나와 있지 않군요. 그냥 문서의 내용을 그대로 적어보겠습니다.


    {^} if present set S bit to load the CPSR along with the PC, or

        force transfer of user bank when in privileged mode...(???)


    자, 그럼 이제 본격적으로 설명에 들어가겠습니다.


    먼저 1단계, LDM 과 STM의 의미입니다.


       LDM : 베이스 레지스터(Rn)로 지정된 번지에서 레지스터 목록으로 지정된 각 레지스터의 내용을 읽어들이는 명령.

       STM : LDM과 반대.


    여기까지는 별 무리가 없으리라 생각합니다. 혹시 이 명령을 8086등에 있는 Block Data 전송명령 등과 혼동하지 않으시길 바랍니다. 어렴풋이 기억하는데, 8086등에는 메모리에서 메모리로 Block 전송을 할 수 있는 명령이 있죠... 또는 특정 길이만큼 메모리를 어떤 값으로 설정하는 Block 설정 명령도 있었던 듯 합니다.


    ARM7의 LDM과 STM은 Block 전송이 아니라 Multiple Register 전송입니다.

    쉽게 생각하면


        push    ax

        push    bx

        push    cx

        push    dx


    가 8086형태라고 할 때, ARM7에서 Single 데이터 전송 명령을 사용하면


        STR     r0,[sp],#4

        STR     r1,[sp],#4

        STR     r3,[sp],#4

        STR     r4,[sp],#4


     가 되겠고, 또 Multiple 전송명령을 사용하면,


        STMEA   sp!,{r0,r1,r2,r4}


     제가 방금 적어본 것이라 맞는 것인지 확신은 없습니다만, 그냥 개념이 이렇다는 것만 파악하셨으면 합니다.


     그러면 이제 2단계, {Reg_List}를 자세히 다루어 보죠. 위의 예에서 나와있듯이 중괄호 사이에 전송 대상이 되는 레지스터를 넣어주면 됩니다. 그렇다면 몇 개까지 가능한 것일까요? ARM7에서 한 시점에 사용할 수 있는 레지스터의 개수는 r0에서 r15까지 총 16개죠.  LDM이나 STM명령에서 지정할 수 있는 레지스터의 개수는 최대 16개입니다. 즉, 한 명령으로 모든 레지스터를 저장하거나, 가져올 수 있다는 의미입니다.


     참 재미있는 사실은, LDM명령의 니모닉상에 16비트의 공간이 있어서 각 비트가 레지스터 r0-r15와 1:1로 대응이 된다는 사실입니다. 따라서 {reg_list}에는 어떠한 레지스터의 조합도 올 수 있습니다. 감동 !


     그리고 어셈블러의 문제겠지만, {r0,r2} 이런 형식뿐만 아니라, {r0-r5} 와 같은 형식, {r0-r3,r6-r7} 이런 형식도 사용할 수 있습니다.


     마지막으로 확인할 것은, {r3,r2,r1} 이렇게 썼을 때와 {r1,r2,r3} 이렇게 썼을 경우, 메모리에 저장되는 순서가 다를까요. 아닐까요. 말씀드렸듯이 16비트의 비트필드가 존재해서 각각 레지스터와 1:1대응이 된다고 하였으니, 소스코드에서 어떤 형식을 쓰든지 간에 니모닉으로 변환될 때는 그 순서는 아무 의미가 없겠지요. 결국 같다는 말입니다.


     자. 이번에는 3단계입니다. 동작 모드!!!


     위의 STM명령에서 제가 STMEA라고 명령을 적었습니다. EA가 동작모드를 지정하는 부분입니다. 이와 같은 키워드가 8가지가 있고, 동작모드는 4가지가 있습니다.


     먼저 4가지의 동작모드를 말씀드리겠습니다.


     A) Post-Increment Addressing


        여기서 동작모드는 여러 개의 레지스터 값을 메모리로(혹은 로부터) 전송할 경우 해당 메모리 번지를 증가시키면서 저장할지, 혹은 감소시키면서 저장할지를 지정하는 것과, 증/감을 하는데, 저장하기 전에 증/감을 할지, 아니면 저장하고 나서 증/감을 할지를 지정하는 것을 의미합니다.


        처음 설명할 동작모드는 저장 이후 증가하는 방식입니다.


        예를 들어 R10에 0x1000이 들어있다고 가정하고 R10을 베이스레지스터로 사용해서 {r1,r2}를 저장한다면, Post-Increment모드에서는,


                1. 0x1000 번지에 r1이 저장된다.

                2. Base 번지값이 0x1004로 증가한다.

                3. 0x1004 번지에 r2가 저장된다.

                4. Base 번지값은 0x1008로 증가한다.


        만약 !를 사용했다면 r10의 값은 0x1008이 될 것입니다.


    B) Pre-Increment Addressing


        말 그대로 먼저 증가하고 다음에 저장하는 동작 모드입니다. 위와 같은 조건을 가정해 봅시다.


                1. Base 번지값이 0x1004로 증가한다.

                2. 0x1004번지에 r1이 저장된다.

                3. Base 번지값이 0x1008로 증가한다.

                4. 0x1008번지에 r2가 저장된다.


        역시 !를 사용했다면 r10의 값은 0x1008이 됩니다.


    C) Post-Decrement Addressing


        이번에는 베이스 번지가 감소하는 경우죠. 좀 특이한 것은, 감소 모드로 저장(로드)을 할 경우 아까는 레지스터번호가 빠른 것부터 저장하거나 불러들였는데, 이번에는 거꾸로 라는 것입니다. 결과적으로, 메모리에 저장되는 레지스터의 순서는 항상 동일하다는 것이죠. 마찬가지로 같은 조건에서 예제를 들겠습니다.


                1. 0x1000번지에 r2가 저장된다.(!!! r1이 아니라 r2)

                2. Base번지값이 0x0FFC로 감소된다.

                3. 0x0FFC번지에 r1이 저장된다.

                4. Base번지값이 0x0FF8로 감소된다.


        만약 !를 사용했다면 r10의 값은 0xFF8이 된다.


    D) Pre-Decrement Addressing


                1. Base번지값이 0x0FFC로 감소된다.

                2. 0x0FFC번지에 r2가 저장된다.

                3. Base번지값이 0x0FF8로 감소된다.

                4. 0x0FF8번지에 r1이 저장된다.


        만약 !를 사용했다면 r10의 값은 0xFF8이 된다.


    여기까지 4개의 동작모드를 설명했습니다.


    동작모드를 나타내는 키워드는 8개인데요. 각각의 동작모드에 대해서 스택처럼 사용할 경우, 혹은 아닐 경우 2가지로 나누어서 나타내기 때문입니다.


    다음 표는 각 동작모드에 대한 명령어입니다.


    ==================================================================

           동작                         Stack           Other

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

        pre  increment load             LDMED           LDMIB

        post increment load             LDMFD           LDMIA

        pre  decrement load             LDMEA           LDMDB

        post decrement load             LDMFA           LDMDA


        pre  increment store            STMFA           STMIB

        post increment store            STMEA           STMIA

        pre  decrement store            STMFD           STMDB

        post decrement store            STMED           STMDA

    ==================================================================


    Stack인 경우와 아닌 경우 키워드가 다른 것은, 단지 User의 편의를 위한 배려라고 생각됩니다. 즉, 키워드를 LDMED로 썼을 경우나 LDMIB로 썼을 경우, 동작상에 차이는 없는 듯 합니다.


    키워드를 무작정 붙인 것 같지는 않고요. 먼저 Other의 키워드를 살피면, I는 increment를 의미하고요.  D는  decrement겠죠. 그리고 B는 Before를, A는 After를 의미합니다.


    그러므로 만약 LDMDA 는 post decrement 모드를 의미하는 것입니다.


    Stack의 경우엔, E는 Empty를 F는 Full을 의미한답니다. 스택을 구현하는 경우 현재 sp가 가리키는 번지의 내용이 차있는지, 비어있는지를 의미한다고 보시면 될 듯 합니다. 무슨 이야기인가 하면 만약 Post 모드를 사용한다면, 스택을 구현할 경우, 어떤 내용을 넣고 다음에 번지를 증/감하므로 결국 어떤 시점에서 스택포인터가 가리키는 번지는 비어있게 됩니다. 이 경우 Empty가 되겠죠.


    Load의 경우엔 Empty 형태의 스택이라면 sp가 가리키는 공간에 아무 내용도 없으므로 먼저 sp를 변화시키고 데이터를 가져와야겠죠. 그래서 Load는 Pre가 Empty 와 대응이 됩니다. 하지만 반대로 Store의 경우엔 Empty 형태의 스택을 위해서는 먼저 데이터를 넣고 sp를 변화시켜야 합니다. 그렇다면 post모드가 Empty와 대응이 되겠군요!


    Full은 더 이상 말 안해도 되리라 믿습니다.


    다음으로 D는 Decending, A는 Ascending을 의미합니다. 이것은 스택이 거꾸로 커지는지 아니면 반대인지와 관련이 있습니다. 8086에서는 Push를 하면 sp값이 작아지죠? 그렇다면 decending Stack이라고 볼 수 있습니다. push와 STM이 대응되므로 STM의 Decrement 모드는 D 라는 키워드를 사용했군요. Pop의 경우는 LDM과 대응되고  decending stack에서 Pop을 할 경우 sp값은 증가되어야겠죠. 그래서 LDM에서는  increment모드가 D입니다.


    A는 반대이겠죠. 결국 스택관련 명령에서는 LDM이나 STM에서 같은 접미사를 사용하면 되는 것입니다. 물론 동작 방식은 LDM과 STM에서 각기 다르지만요.


 자.. 이제 예제를 하나 보이고 오늘 강좌를 정리하려 합니다.


     STMED      sp!,{r0-r3,r14}


     BL         somewhere


     LDMED      sp!,{r0-r3,r15}


     첫 번째 명령에 STMED는 Empty Decending Stack Operation이므로 실제로는 post-decrement 동작모드를 의미하죠.. sp(r13)가 가리키는 번지에 r0,r1,r3,r14를 저장합니다. BL로 이 루틴에 들어왔다면 r14에 복귀 번지가 들어가 있다는 것을 감안하십시요.


     BL에서 뭔가 처리를 하고, 마지막으로 LDMED명령에서 레지스터를 복구합니다. 이번에는 pre-Increment 동작모드입니다.


     한가지 주의하실 점은 r14대신에 r15로 복구를 시켰다는 것입니다. ARM7에서는 ret명령 대신 mov r15,r14를 사용한다고 말씀드렸죠.


     LDMED 명령에서 레지스터 복구와 서브루틴 복귀를 동시에 처리하는 부분입니다.


  자. 오늘 강좌는 이렇게 정리할까 합니다. 거의 1달만에 강좌를 썼군요. 그동안 메일이나 게시판을 통해서 질문과 격려를 보내주신 분들에게 죄송하다는 말씀을 드려야겠군요.


이제 강좌도 종반을 향해 달리고 있습니다. 아마도 2회 정도면 계획했던 모두가 끝이 날것 같군요. 다음 강좌를 기약하며 이만 줄이겠습니다.

ARM7 강좌 [12] : Instruction Set (6)

 


  정말 오랜만에 강좌를 쓰는군요. 생활에 여유가 좀 있어서 강좌를 시작했는데 예상보다 강좌가 길어지고 더불어 점점 다른 일들이 생겨서 요즘에는 통 시간을 낼 수가 없었습니다.  혹, 강좌를 기다리신 분이 계시다면 죄송하다는 말씀을 드려야겠군요. 아무래도 강좌를 빨리 정리해야겠다는 생각이 듭니다. 자꾸 늘어지니까, 저도 부담스럽고,  읽으시는 분들에게도 폐를 끼치는 것 같아서.


  남은 부분에 대해서는 최대한 핵심만 얘기하려 합니다.


  사실 명령어 하나하나에 대해 하나하나 설명한다는 것이... 뭐 그다지 의미 있는 일 같지도 않고, 이쯤까지 왔다면 대충 어떤 명령어들이 있는지 파악하시고, 나중에 필요하실 때 데이터시트 찾아보시면 될 것 같아요.


  정리를 해보자면, 이제 말씀드릴 명령은

        곱하기 명령 (MUL,MLA),

        데이터 전송 명령 (LDR, STR),

        블록 데이터 전송명령 (LDM, STM),

        기타 명령(SWP, SWI)

  정도입니다. Coprocessor 명령은 생략하도록 하고, 명령이 끝나면 실전 케이스로 간단한 예제 설명을 하나 드리려고 하고, 마지막으로 StrongARM의 Spec 정도를 간단히 언급하겠습니다.


  남은 부분이 앞으로 몇 번에 걸쳐서 매듭지어질지는 해봐야겠지만, 최대한 스피디한 진행을 하려 합니다.


  차후에 기회가 되면 MMU 부분 같은 것들을 짧게 다룰 수도 있겠지요.


자, 이제 시작하겠습니다.


● 곱하기 명령(MUL, MLA)


  ARM7에서의 곱하기 명령은 크게 두 종류가 있습니다. 하나는 그냥 곱하는 것이고, 다른 하나는 곱해서 더하는 것입니다. ^^ 명료하죠?


  1) MUL{cond}{S}  Rd,Rm,Rs


     cond 부분은 해당 명령의 실행 조건입니다. S플래그는 결과가 플래그 레지스터에 영향을 미칠지의 여부라고 계속 말씀 드렸죠? 다음으로 레지스터 3개를 지정하도록 되어있는데, Rd는 결과가 저장될 레지스터이고, Rm과 Rs는 곱해질 두개의 레지스터입니다. 보셨듯이 이 명령에서는 레지스터만을 사용해야 합니다.


     Ex) MUL    R1,R2,R3                ; R1:=R2*R3


     # 참고로 곱하기의 결과가 32비트를 넘는다면, 하위 32비트만 결과 레지스터에 남습니다.


  2) MLA{cond}{S}  Rd,Rm,Rs,Rn


     위와 비슷합니다만, 이번에는 레지스터가 4개입니다. 단순 명료하게 의미를 말씀드리면,


        Rd :=  Rm * Rs + Rn 의 의미입니다.


● Single Data Transfer (LDR, STR)


  해당 명령은 레지스터와 외부 메모리와의 데이터 전송을 담당하는 명령입니다. 무척 사용빈도가 높은 명령입니다. 86에서는 MOV하나로 레지스터간의 데이터 전송과, 외부메모리와의 데이터 전송의 두 가지 목적으로  사용하지만, ARM7 에서는 구분이 되어있습니다.


  즉, MOV명령은 레지스터간의 전송명령으로 분류도 데이터 처리명령으로 분류되고, 데이터 전송명령으로는 LDR과 STR이 있습니다.


  여기에 사용되는 옵션이 많은데요, 우선, LDR은 로드라는 의미이므로 외부 메모리로부터 레지스터로 데이터를 읽어오는 명령이고, STR은 반대의 기능을 하는 명령입니다.


  - 명령 형식


  LDR{cond}{B}  Rd, address{!}          Rd:= contents of address

  LDR{cond}{B}  Rd, =expression         Rd:= expression

  STR{cond}{B}  Rd, address{!}          contents of address := Rd


  LDR과 STR은 Single Data 전송 명령입니다. 비슷한 명령으로 LDM, STM은 여러 개의 레지스터 내용을 전송할 수 있는 명령입니다. 이밖에 SWP라는 스왑 명령이 있는데, 해당 5개의 명령만이 외부 메모리와 레지스터간의 전송을 가능하게 하는 명령입니다.


  LDR과 STR의 경우 전송 단위를 바이트, 혹은 워드(32Bit)단위로 수행 할 수 있습니다. 바이트 단위 전송의 경우 해당 레지스터의 어떤 부분이 사용될지의 여부는 해당 프로세서의 Endian에 달려 있습니다. 또, 워드 전송 명령을 사용할 경우, 메모리 어드레스는 Word align이 되어야 합니다.


  다음 표는 LDR과 STR에서 사용하는 전송 모드를 나타낸 것입니다.


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

   Mode                 Effective address               Indexing

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

  [Rn]                  Rn                              none

  [Rn,+- expression]    Rn +- expression                Pre-indexed

  [Rn,+- Rm]            Rn +- Rm                        Pre-indexed

  [Rn,+- Rm, shift cnt] Rn+-(Rm shifted by cnt)         Pre-indexed

  [Rn],+-expression     Rn                              Post-indexed

  [Rn],+-Rm             Rn                              Post-indexed

  [Rn],+-Rm,shift cnt   Rn                              Post-indexed

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


      # Rn : base 주소를 가지고 있는 레지스터

        Rm : r15를 제외한 레지스터(Offset), 부호 값 사용

        expression : -4095 - +4096 범위의 Immediate 값(12Bit)

        shift : LSL, LSR, ASR, ROR, RRX

        cnt : 1..31 사이의 값.


  상당히 복잡한 내용입니다. 표에서 Mode 부분이 실제 어셈블러에서 사용하는 명령의 형식이라고 보시면 됩니다. 각 모드별로 예제를 들어서 설명하도록 하죠.


  1) Pre-Indexed Addressing Mode


    이 모드에서는 Rn을 베이스 주소로 사용합니다. 여기에 더해서 Offset을 지정하거나, 혹은 지정하지 않을 수 있습니다. Offset을 사용할 경우에는 해당 Offset을 베이스 주소에서 더하도록 하거나 뺄 수 있는데, 위의 표에서 +, - 기호가 그것을 의미합니다.


    Offset은 부호가 있는 12Bit Immediate값을 사용하거나 레지스터를 지정해서 해당 레지스터의 내용으로 사용할 수 있습니다. 또한 레지스터를 Offset으로 사용하는 경우에는 Shift 시켜서 적용시킬 수 있습니다. 관련 내용을 다시 확인하고 싶으신 분들은 데이터 전송명령의 오퍼랜드 2에 대한 설명을 다시 확인하시길 바랍니다.


    Ex) LDR     r0,[r1]          ; r1을 베이스로 사용하고 Offset은 지정하지 않는 형태입니다. r0에 r1을 번지로 하는 워드(4바이트)를 읽어드립니다.

        LDR     r0,[r1,#132]     ; 이번에는 Offset으로 132를 지정한 형태입니다.

        STR     r0,[r1,r2]        ; 베이스는 r1, Offset은 r2를 사용한 형태입니다. 즉

                                    *(r1+r2)=r0 와 같은 의미.

        LDR     r0,[r1,r2,LSL #2] ; 베이스 r1, Offset은 r2<<2 의 형태.


    이번에는 Write-Back 기능에 대해서 알아보겠습니다. LDR명령의 경우 옵션으로 Write-Back을 지정할 수 있는데, 지정할 경우 원래 베이스에 Offset을 더한 값을 다시 베이스 레지스터로 넣는 기능을 합니다. 제 생각에는 이 기능이 C에서 ++와 같은 의미라 생각되는데요. 글쎄, 쓰기 나름이겠지요. 다음은 예제입니다.


    Ex)

        1:              LDR     r0,=table_end

        2:              LDR     r1,=table

        3:

        4:              MOV     r2,#0

        5:loop          STR     r2,[r0,#-4]!

        6:

        7:              ADD     r2,r2,#1

        8:              CMP     r0,r1

        9:              BNE     loop

        10:

        11:             ...

        12:

        13:             ALIGN

        14:table        %       table_length*4

        15:table_end


      예제가 좀 길군요. 주의해서 보실 부분은 5번 라인입니다. 해당 명령은 STR    r2,[r0,#-4]! 이죠. r2를 r0를 베이스로 삼고  Offset으로 -4값을 사용했군요. r0가 가리키는 포인터가 실제 버퍼로 사용할 공간이 끝난 바로 다음을 가리키고 있으므로 -4를 하면 마지막 워드 엔트리 포인트가 됩니다. 그리고 주의하실 점은 !를 사용한 것인데요. 이것이 바로 Write-Back기능입니다. 위의 설명에 나와있듯이 베이스 값(여기서는 r0가 되겠군요.)을 갱신하는 것인데요...


      예를 들어 1000번지부터 2개의 entry를 가정한다면, table은 1000이 될 것이고 table_end는 1008 번지가 될 것입니다. 이때 STR명령에서 1008을 베이스로 -4를 Offset으로 설정했으므로 실제로 r2값은 1004번지부터 4바이트에 기록이 될 것입니다. 이후 Write-Back 옵션이 적용되어 해당 명령이 끝나면 r0값은 1004로 (1008-4) 바뀌게 됩니다.


      참, 여기서 Pre-Index Mode이기 때문에 실제 주소를 구할 때 Offset을 먼저 빼 주었군요. 아무쪼록 이해 하셨기를 바라며 다음으로 넘어가렵니다.


  2) Post-Indexed Addressing Mode


    Post인덱스 모드의 경우에는 Pre인덱스 모드에 비해 Effective Address는 항상 Rn, 즉 베이스 어드레스를 나타내는 레지스터의 값입니다. 그리고 한가지 주의하실 점은 Post인덱스 모드의 경우엔 따로 !를 사용 하지 않더라도 디폴트로 Write-Back 모드가 사용됩니다. 그렇겠죠? Post인데 Write-Back이 안된다면, Offset을 사용할 이유가 없군요.


    Ex) LDR     r0,[r1],r2      ; 대괄호의 사용이 Pre 인덱스의  경우와

                                  다릅니다. 실제 수행되는 것은  r0에다

                                  r1을 주소로 하여 값을 읽어오고  다시

                                  r1에는 r2를 더해주는 일을 합니다.


        STR     r0,[r1],#20     ; [r1]:=r0, r1:=r1+20


        1:              LDR     r0,=table

        2:              LDR     r1,=table_end

        3:

        4:              MOV     r2,#0

        5:loop          STR     r2,[r0], #4

        6:

        7:              ADD     r2,r2,#1

        8:              CMP     r0,r1

        9:              BNE     loop

        10:

        11:             ...

        12:

        13:             ALIGN

        14:table        %       table_length*4

        15:table_end


        마지막 예제는 위에서 나왔던 예제와 비슷하니 잘 분석해 보시길 바랍니다.


  3) Relative Addressing Mode


    해당 Addressing 모드는 어셈블러가 적절히 지원하여 변환해 주는 모드라고 생각하시면 됩니다.


    Ex)         LDR     r5,ThreeCubed

                ...


     ThreeCubed DCD     27


     위와 같은 경우 실제로는 LDR r5,[PC,#constant] 형태의 코드로 번역해 줍니다. 즉 PC를 베이스로 삼아서 코드를 만드는 것이지요. 만약 해당 Symbol이 지정하는 범위가 너무 커서 상수로 지정할 수 없다면 어셈블을 할 때 에러를 내게 됩니다.


     또 많이 사용되는 표현으로 다음과 같은 표현이 있습니다.


     Ex)        LDR     r0,=0x12345678


     사실은 위의 예에서도 사용했었는데요. 이 경우 어셈블러가 해당 상수 값을 특정 공간에 모아서 삽입 해 주고 (이공간을 Literal Pool이라 하는군요) 역시 마찬가지로 PC를 베이스로 해서 명령코드를 만듭니다.




  자.. 오늘은 이만 쓰렵니다. 사실 열두번째 강좌는 쓰기 시작한지 몇 주만에 끝내게 되는군요. 중간에 계속 일이 생겨서. 미뤄두고 있다가, 메일로 강좌를 재촉하시는 분들이 계셔서.


  아무튼, 다음강좌에 뵙도록 하겠습니다.

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이 들어갑니다.


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

+ Recent posts