ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Cortex-M3에 대한 정리
    Embedded 2014. 6. 26. 10:34

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

    Cortex-M3 교육 내용 정리

     개요

     ARMv7개열의 ARM12칩으로 나온 것이 Cortex칩입니다. Cortex칩은 크게 3가지 타입으로 구분됩니다. A, R, M타입입니다. A는 Application칩으로써 일종의 범용칩이라고 이해할 수 있습니다. R은 Realtime으로 Realtime system을 위해서 디자인된 칩 설계이구요, 마지막으로 M은 Microcontroller를 위해서 설계된 칩 디자인입니다.

    A나 R Type의 경우 상대적으로 복잡하고 메모리가 많이 요구되는 계열의 Application을 수행할 수 있는 능력이 되지만, M Type의 경우는 가격이 상대적으로 저렴하며, 비교적 간단한 Application을 만들어서 돌리는데 유용하다고 생각하시면 됩니다. 또한 M Type의 계열의 경우 몇가지 제약적인 사항들이 존재합니다. 칩이 디자인된 목적이 Microcontroller이니 그럴수밖에 없습니다.

    이미 다 아시는 내용이고 이러한 내용이 재탕에 불과할 수 있겠지만, 어째든 정리하는 의미에서 적어보도록 하겠습니다.

    소개

    Cortex M3는 MCU(Microcontroller Unit)의 요구사항에 최적화된 플랫폼에 맞추어져 있다고 합니다. 하지만 있을껀 다 있는데요, 다음이 그 구성요성들입니다.

    Cortex-M3 Core

    1. Harvard architecture : Code Bus, Data Bus, System Bus가 각기 따로 존재하여, 이전에 하나였을때 보다 코드 또는 데이터를 읽거나 쓸때의 대기 시간을 줄였습니다.
    2. 3-stage pipeline with branch speculation(prediction) : Fetch, Decode(with branch prediction), Execute의 실행단계를 가집니다.(Write Back도 있습니다.)
    3. Thumb-2 and traditional Thumb : Cortex-M3는 Thumb2명령어만 사용할 수 있습니다. Thumb2는 ARM과 기존의 Thumb을 섞어서 쓸 수 있는 새로운 명령어 체계입니다.
    4. ALU with H/W divide and single cycle multiply : 정수형 나눗셈이 H/W적으로 지원됩니다.
    5. (Processor적으로) Configurable interrupt controller
    6. Advanced debug components(H/W Breakpoint를 8개까지 & 추가된 디버깅 기능(like QXDM))Optional MPU & ETM

    이것외에 변경된 점들은 다음과 같습니다.

    1. Vector Table is addresses, not instructions
    2. Designed to be fully programmed in C
    3. 내장형 System Timer 한개 지원
    4. Bit Banding
    5. 미리 정의된 Memory Map
    6. (위의 5번에 대한) Nested Vectored Interrupt Controller (NVIC) 및 Interrupt를 256개 지원(시스템에서 미리 정의된 16개를 제외하면 실제 240개)

    입니다.

    위의 내용들이 위에서 말한 A Type이나 R Type에 적용되었는지는 이후에 말씀드리겠습니다. 어째든 여기서의 목적은 M Type이니까요.

    3 Stage Pipeline

    3단계 Pipeline을 가집니다. 이는 Fetch, Decode, Execute로 나뉘어 질 수 있습니다.

    Fetch란 실행할 코드를 가져오는 것을 의미하며, Decode란 가져온 명령어를 CPU가 알 수 있는 코드로 바꾸는 것을 의미하며, Execute는 이것을 실행하는 것을 의미합니다. 각 단계는 서로에 영향을 미치지만, 동작은 독립되어 있습니다. 예를 들면, Execute를 하면서, Decode도 동시에 할 수 있고, Decode를 하면서 동시에 Fetch를 할 수 있다는 뜻입니다.

    이것의 장점은 각각의 단계가 놀지 않는 다는 것이고, 단점은 branch와 같이 코드가 실행될때 속도가 오히려 늦어질 수 있다는 것입니다.

    이것을 해결하기 위해서 다양한 방법들이 존재하고 그 중 하나가 branch prediction이 있습니다.

    주의) pipeline을 사용하면, pc의 위치가 약간 바뀌는데,

    0x1000 mov r0, pc
    0x1004 mov r1, r2
    ...

    라고 한다면, r0는 0×1008이 됩니다. 그 이유는 pc는 fetch할 대상을 가르키는데, pipeline의 fetch와 execute가 병렬적으로 일어나므로해서 발생됩니다. 즉, 0×1000번지의 code는 0×1008번지의 코드가 fetch될때(즉, pc값은 fetch를 기준으로 한다고 했으므로, pc는 0×1008입니다.) 실행되고, 이때 pc의 값은 현재의 pc, 즉, 0×1008번지가 됩니다.

    Branch Prediction

    Execute단계에서 branch prediction을 하면, 순차적으로 pipeline에 쌓여있던 코드가 비워지고, branch이후의 코드들이 다시 fetch부터 단계적으로 들어오는 일련의 작업을 해야 합니다. 예를 들어서

    0x1000 mov r0, r2
    0x1004 mov r1, r3
    0x1008 b 0x1020
    0x100c add r0, r0, #1
    0x1010 add r1, r1, #2
    ...
    0x1020 sub r0, r0, #1
    0x1024 mov r2, r0

    이면, pipeline의 관점에 입각해서 다음과 같은 단계를 가집니다.

    Time

    Fetch

    Decode

    Execute

    1

    mov r0, r2

     

     

    2

    mov r1, r3

    mov r0, r2

     

    3

    b 0×1020

    mov r1, r3

    mov r0, r2

    4

    add r0, r0, #1

    b 0×1020

    mov r1, r3

    5

    add r1, r1, #2

    add r0, r0, #1

    b 0×1020

    6

    flush

    7

    sub r0, r0, #1

     

     

    8

    mov r2, r0

    sub r0, r0, #1

     

    라는 식으로 실행된다는 것입니다.

    여기서 6번째의 flush가 되는 과정이 되고, 4cycle이 지난뒤에 비로서 점프한 위치의 코드를 실행시킵니다. 이것은 cpu가 4cycle동안 논다는 것을 의미하고, loop등에서는 심각한 hotspot code가 됩니다. 이것을 방지하기 위해서 다음의 두가지를 적용했습니다.(ARM11도 마찬가지입니다.)

    • Static Branch Prediction : Branch가 실행된 것을 Branch Taken이라고 하고, branch가 실행되지 않은 것을 Branch Not-Taken이라고 합니다. ARM개발자들(굳이 이 사람들만 한 것은 아니겠지만) Branch Taken이 일어나는 경우를 가만히 보니, 보통 branch가 되면, branch의 주소가 상위의 주소가 된다더라 입니다. 예를 들어서
    0x1000 ...
    0x1004 ...
    ...
    0x1030 bl 0x1000

     

    인 경우 위의 0×1030번지의 브랜치는 거의 대부분 일어난다고 가정을 하고 그에 따른 준비를 미리 해놓는 다는 것입니다.

    • Dynamic Branch Prediction : 매 branch할때마다 branch정보들을 메모리의 특정영역에 저장해두고, 다음번에 또 그 branch명령어가 실행되면, 메모리에 저장된 정보를 보고 판단을 하는 것입니다. 이때 branch 정보를 담고 있는 것을 BTB라고 합니다. Branch Target Buffer라고 합니다.

    Harvard Architecture & Bus Matrix

    Harvard Arch.는 Code가 사용하는 버스와 Data가 사용하는 버스가 분리된 Arch를 의미합니다. 이것의 장점은 Code데이터를 읽거나 쓸 때, 동시에 Data를 읽거나 쓸 수 있다는 점이죠. Cortex-M3에서는 여기에 추가적으로 System Bus를 추가하여, 주 Code및 Data는 Code Bus & Data Bus로, 그 외의 통신(즉, 주변부와의 통신이나 외부 RAM과의 통신)은 System Bus를 사용하도록 하였습니다.

    Unaligned data access

    과거의 ARM은 4의 배수 주소에서만 데이터를 Access할 수 있었습니다.(그럼 char형태는 어떻게 했느냐 물어보실텐데, 이건 char가 있는 주소의 4의 배수 조수에서 4bytes데이터를 가져와 char데이터가 있는 부분만을 가져와서 처리하도록 되어 있었기 때문입니다. 속도가 훨 느렸죠) 만약 4의 배수가 아닌 주소에 접근하면, Abort Exception이 발생했습니다. 하지만, Cortex-M3에서는 4의 배수가 아니더라도 이를 지원하게 되었습니다. 즉 0×1001에서 4바이트 길이의 데이터를 가져올 수 있게 되었던 것입니다.(단 속도가 느립니다. 위의 방법을 CPU에서 구현했을 뿐입니다.) 속도를 빠르게 하기 위해서는 4bytes aligne되어 있으면 data access가 빠른 것은 변함이 없습니다. – 아래에 다시 설명했습니다. 중복설명 ㄷㄷ

    NVIC

    Firmware 프로그래밍 하시는 분들에게 가장 중요한 내용인데, 여기선 다음과 같은 내용이 변경되어 습니다.

    • User Interrupt를 240개까지 설정할 수 있다.
    • Nested Interrupt가 지원된다.

    입니다.

    User Interrupt가 240개까지 지원하므로써 변화된 내용은 이전에 Supervisior모드를 시작해서 다양한 모드들이 단 2가지 모드로 줄어들었습니다. Privileged Mode와 Non-Privileged 모드 두개가 있습니다. 단 두개이므로, 기존에 사용하던 레지스터의 갯수가 확 줄었습니다.

    Interrupt의 개수가 많아지면서 Interrupt들이 겹쳐서 발생하는 경우가 잦아질 것입니다. 이것을 해결하기 위해서 Nested Interrupt가 지원됩니다. 이것을 간략하게 설명하면, 각 Interrupt에 Priority(이 우선순위는 또 다음과 같이 두가지 우선 순위로 나뉩니다. Preempting Priority & Subpriority)를 주고, 이를 이용해서 Interrupt가 실행중에 실행 할 건지(Nested), 아님 해당 Interrupt가 샐행이 끝난 뒤에 실행할 건지를 판단합니다.

    cf. 또 변화된 것이 예전에는 다중 레지스터 전송 명령인 LDM/STM과 관련된 명령어를 수행중에는 interrupt가 호출되더라도 pending된 상태였다가 명령어의 실행이 끝났을 때 interrupt를 실행하였습니다. 하지만, Cortex-M3의 경우 LDM/STM중간에도 interrupt가 호출되면 수행되도록 되었습니다.

    Priority에 대해서 설명하자면, 우선 순위이고, Interrupt No가 낮을 수록 높은 Priority를 가집니다.(이때 주의할 점이 Priority가 높을 경우 Priority값은 낮아집니다. 즉, Priority가 -3인 넘이 있고, -2인 넘이 있다면, Priority가 높은 넘은 -3을 가진 넘입니다.) 그러면, Reset이 최고의 Priority를 가지겠죠?(이때 reset은 -3의 priority를 가집니다.)

    이런 식으로 하여, 256개의 interrupt에 priority가 매겨져 있는데, 이때 특정 단말의 경우 external interrupt가 하나의 interrupt에 모여 있는 경우가 있습니다. 이 경우 동일한 priority를 가지는데, 이럴 경우에 처리가 문제입니다. 이 경우 내부에 또 다른 priority를 가지는데, 그것이 위에서 말한 Preempting Priority와 Subpriority입니다. (subpriority는 preempting priority가 동일할때 내부적으로 구분하기 위해서 만든 priority이고, 이것은 cpu에서 지원해줍니다.

    cf. 이것을 group priority라고 하고 EXTI(external interrupt)의 internal priority를 결정할때 사용합니다. group priority는 특정한 register를 변경함으로 속성을 바꿀 수 있습니다.

    Interrupt Handling

    Interrupt가 발생하면 다음과 같은 일을 합니다.

    • Entry
      • Context state automatically saved to the stack over the data bus
        • {R0-R3, R12, LR, PC, xPSR} -> AAPCS

      • In parallel, ISR is prefetched on the instruction bus
        • ISR ready to start executing as soon as stack PUSH complete
      • Late arriving interrupt will restart ISR prefetch, but state saving does not need to be repeated.
        • “stmfd sp!, { … }” in traditional ARM is not interruptible
    • Exit
      • Context state is automatically restored from the stack
      • In parallel, interrupted instruction is prefetched ready for execution upon completion of stack POP
      • Stack POP can be interrupted, allowing new ISR to be immediately executed without the overhead of state saving
        • “ldmfd sp!, { … }” in traditional ARM is not interruptible

    Interrupt Response- Tail Chaining

    IRQ1과 IRQ2가 동시에 발생할 경우(IRQ1의 Priority가 높습니다.)

    ARM7의 경우

    PUSH

    ISR1

    POP

    PUSH

    ISR2

    POP

    26

     

    16

    26

     

    16

    Cortex-M3의 경우

    PUSH

    ISR1

    Tail-Chaining

    ISR2

    POP

    12

     

    6

     

    12

    Clock Cycle의 수가 확실히 줄어든 것을 확인할 수 있습니다. 여기서 Tail-Chaining은 ARM7에서와 같은 과정을 줄입니다.

    Interrupt Response – Preemption

    IRQ2가 발생된 상황에서 IRQ1이 발생하는 경우

    ARM7의 경우

    ISR2

    POP

    PUSH

    ISR1

    POP

     

    16

    26

     

    16

    Cortex-M3의 경우

    ISR2

    PUSH

    ISR1

    POP

    ISR2

    POP

     

    12

     

    12

     

    12

    Nested가 된 것입니다!!

    cf. ARM7, ARM9에서는 nested가 지원되지 않았었습니다!!

    Interrupt Response – Preemption

    IRQ1발생후 IRQ2가 바로 발생하는 경우

    ARM7의 경우

    ISR1

    POP

    PUSH

    ISR2

    POP

     

    16

    26

     

    16

    Cortex-M3의 경우

    ISR1

    POP

    Tail-Chaining

    ISR2

    POP

     

    1-12

    6

     

    12

    두번째에서 왜 1-12가 걸릴까? 그 이유는 다중 레지스터 전송 명령어의 경우 한번에 모든 레지스터의 값을 스택에 던지는 것이 아니라, 레지스터의 순서대로 하나씩 하나씩 전송하게 되어 있습니다. 즉, 지정된 레지스터가 다 전송되기도 전에 irq를 처리하게 된다면, pop하는 과정을 멈추기 때문에, 이전까지 레지스터를 pop하던 cycle로 생각하면 됩니다.

    ex) Cortex-M3에서 두번째 단계에서 r2를 pop하고 있는 중간에 irq가 불리면, tail-chaining이 일어난 후 irq로 진입한다. 이때 pop이 r2까지 전송했으므로, pop은 cycle상으로 3이 걸렸다는 것입니다.

    Interrupt Response- Late Arriving

    IRQ2가 불리자 마자 바로 다음에 IRQ1이 호출되는 경우

    ARM7의 경우

    PUSH

    PUSH

    ISR1

    POP

    ISR2

    POP

    26

    26

     

    16

     

    16

    Cortex-M3의 경우

    PUSH

    ISR1

    Tail-Chaining

    ISR2

    POP

     

     

    6

     

    12

    입니다.

    cf. ARM7의 경우 nested가 없기 때문에 위의 그림이 틀렸다고 생각할 수 있습니다. 이것은 FIQ와 IRQ라면 맞게 됩니다.(다른 mode로 전환할때 stack에 레지스터를 push한다. FIQ와 IRQ는 다른 모드이다.) 단, FIQ와 IRQ가 아니라면,

    PUSH

    ISR1

    POP

    PUSH

    ISR2

    POP

    26

     

    16

    26

     

    16

    가 되어야 합니다. 주의 필요

    Vector Table

    Interrupt의 Callback function을 가지고 있는 것이 Vector Table입니다. ARM7, ARM9, ARM11의 경우 Vector Table은 일종의 Code였지만, Cortex-M3의 경우는 이것이 Address로 바뀌었다는 점이 다릅니다. 또 다른 점은 0번째(즉, reset exception)의 메모리 주소가 0×0이 아닌 0×4라는 것입니다. 0×0에는 초기 stack address가 들어갑니다.

    또한 이 Vector Table의 위치를 바꿀 수 있습니다.(SCB Register사용)

    Programmer’s Model

    어쩌면 Firmware프로그래밍 하시는 분들에게 좋은 이야기가 될 텐데, Cortex-M3의 경우 ARM Assembly Language를 몰라도 C언어만으로도 Firmware를 작성할 수 있게 되었습니다. 그 이유는 위에서 말씀드린 것과 같이 0×0에 stack address가 들어가므로해서, 이전에 assembly로 작업되어야 했던 초기화 작업이 C언어만으로도 가능해진 것입니다.

    cf. 예전 모델의 경우 reset이 호출된 상태(stack address가 초기화 되지 않은 상태)에서 stack을 초기화 하고 약간의 startup code를 실행시킨 후 C로 작성된 코드로 진입해야 해습니다. 그 이유는 C로 진입하기 위해서는 우선 조건으로 stack이 초기화 되어야 하기 때문입니다.(stack은 변수들의 값을 저장하는 장소이자, 함수의 인자, return값들을 보관하는 임시 장소이기 때문이고, C에서는 이러한 것을 사용하기 때문입니다.) 즉, C언어 상태에서는 최초에 Stack Address가 정해지지 않았기 때문에 진입하지 못하기 때문이지요.

    Cortex-M3의 경우 0×0번지의 4bytes 데이터를 가져와서 stack pointer를 초기화 하는 과정 후에 reset을 호출하게 되도록 되어 있습니다.

    Instruction Set (Thumb2를 기준으로)

    대부분의 경우 ARMv6, Thumb에서 코드를 유지하지만, Thumb2는 다음과 같은 차이점이 존재합니다.

    첫번째

    ARM7 & ARM9일 경우

    ADDEQ R2, R2, #1

    Cortex

    IT EQ                ; IT->If then
    ADDEQ R2, R2, #1

    두번째

    ARM7 & ARM9일 경우

    ADDEQ R2, R2, #1SUBNE R2, R2, #1 

    Cortex 

    ITE EQ    ; ITE->If then else
    ADDEQ R2, R2, #1
    SUBNE R2, R2, #1

    즉, Conditional Instruction Set이 따로 존재하고 있다는 것을 주의해야 합니다.(나머지는 ARM과 Thumb의 혼합된 모델을 생각하면 됩니다.)

    System Timer

    Timer를 사용하기 위해서 이전의 ARM 시리지는 외부 타이머를 연결하여 이것을 사용해야 했습니다. 하지만, Cortex-M3에서는 외부의 오실레이터 소스를 받아서 작동하는 내부 Timer를 만들었습니다. Cortex에서는 외부 타이머를 사용하도록 바꿀 수도 있습니다.

    Unaligned data access

    위에서 설명했듯이 이전의 ARM에서는 4바이트 단위의 데이터를 엑세스 할때 4의 배수를 가지는 주소를 가진 것만 엑세스 할 수 있는데, Cortex-M3에서는 4의 배수가 아니더라도 4바이트 단위의 데이터를 엑세스 할 수 있게 된 것입니다.

    이것의 장점은 간단합니다. 메모리 사용량을 줄일 수 있다는 것입니다.(특히 구조체)

    하지만 문제가 있습니다. 예를 들어 다음과 같이 struct가 있다고 합시다.

    struct _Test {
      char a;
      short b;
      int c;
    } Test;

    에서 b와 c는 분명 Address상 홀수번지에 위치하게 됩니다. 이 경우 Source상에서는 단순히 b와 c를 읽으면 되지만, CPU에서는 이를 읽기 위해서 4바이트 데이터를 읽은 후 b를 얻어내고, c의 경우는 경계에 존재하므로 8바이트를 읽고 c를 얻어냅니다. (위의 struct가 memory상에 어떻게 쓰여지는 생각해보면 됩니다.) 이것을 CPU에서 대신해준다는 것입니다.

    그러므로 ARM에서는 UNPACK Struct가 속도가 빠르겠죠?

     

     

     

    출처 : http://dyanos.wordpress.com/2008/11/28/6/

    'Embedded' 카테고리의 다른 글

    프로젝트 정리  (0) 2011.11.20
    PXA255-Tiny 리눅스 포팅  (0) 2010.05.23
    Fedora 12 에 NFS 구축  (0) 2010.05.01
    단위  (0) 2010.04.30
Designed by Tistory.