Overview
Datasheet에 의하면, AVR은 Harvard Architecture를 사용한다고 한다. 일반적으로 아키텍처 시간에 배운 Von-Neumann Architecture와는 무엇이 다를까? 바로 메모리와 버스 측면에서 다르다.
- 메모리 측면
- Von Neumann architecture: 프로그램 코드와 데이터를 동일한 메모리 공간에 저장 -> 설계가 단순하지만 명령어와 데이터를 동시에 접근할 수 없기 때문에 성능 저하
- Harvard architecture: 프로그램과 데이터를 독립된 메모리 공간에 저장(Overview 파트에서 봤듯, 프로그램같이 큰 데이터는 Flash에, 데이터의 경우 빠른 SRAM에 저장한다.
- 버스 측면
- Von Neumann architecture: 명령어와 데이터가 같은 버스를 통해 전송
- Harvard architecture: 명령어와 데이터를 위한 별도의 버스가 존재해서, 두 가지를 동시에 전송 가능
이렇게 보면 그냥 Harvard architecture가 우수한 것 같은데, 실제로 그렇다. 그러나, 설계 복잡성이 증가하고 비용이 증가한다는 단점이 있어 특수한 목적이 있을 경우에 harvard architecture를 사용한다.
또한, 32개의 8비트 GPR(General Purpose working Register)를 제공하고, 이 레지스터를 이용하면 ALU 연산을 1 cycle 안에 처리 가능하다. ALU는 register file에서 2개의 레지스터를 받아서 연산 후 하나의 result를 register file에 다시 저장할 수 있고, 이 모든 과정을 1 cycle 안에 이루어진다. 만약 메모리에서 로드하고 저장한다면 1 cycle 안에 절대 이루어질 수 없을 것이다. 32개의 GPR 중 6개는 2개씩 합쳐서 16비트의 indirect address register pointer로 사용될 수 있다. 주로 flash program memory를 탐색하는 address pointer로 이용된다. 이러한 16비트 레지스터를 X-, Y-, Z- 레지스터라고 한다.
서브루틴이나 인터럽트의 호출이 발생하면, return address를 비롯한 정보들이 stack에 쌓여야 한다 stack은 SRAM에 할당되어 있고, 이로 인해 stack size는 전체 SRAM 크기 및 사용중인 SRAM에 의해 제한된다. 모든 프로그램은 reset routine에서 SP(Stack Pointer)를 초기화해야 하고, SP는 read/write할 수 있어야 한다.
flexible interrupt module은 i/o space에 control register를 가지고 있고, status register에서 global interrupt를 enable하는 bit를 가지고 있다. 모든 인터럽트는 Interrupt vector table에 자신의 interrupt vector를 가지고 있으며, 자신의 position에 따른 priority가 있다. address(position)이 작을 수록, priority가 높다.
I/O memory space는 ATmega328p의 각종 peripheral을 제어하기 위한 레지스터들이 모여 있는 영역으로, 총 64개의 주소에 SPI, 타이머, UART등을 제어하는 데 사용되는 제어 레지스터(control register)가 할당되어 있다.
Status Register
status register라는 레지스터를 AVR에서 도입했는데, 이는 ALU의 결과값과 관련이 있다. status register는 "가장 최근에 실행된" arithmetic instruction의 결과를 저장하는데, 이 정보는 conditional operation을 수행하기 위해 필요하다. 예를 들어 ISA에서 beq를 하기 전 cmp 명령어를 수행해서 flag 정보를 갱신하고 가져오는데, 이 정보를 status register에 바로 넣어뒀다가 확인하면 되므로 더 빠른 코드를 만들 수 있다. 여기서 interrupt가 일어날 경우 이 정보는 자동으로 저장되었다가 복구되지 않으므로, 정보를 stack에 넣든 해야 한다.
구체적인 AVR Status Register = SREG는 다음과 같다.
- Bit 7 : Global Interrupt Enable : Interrupt가 실행 중일 때 유용하다. 이걸 clear하면 다른 interrupt가 실행될 수 없다. 하지만 이걸 Set하면 NETI(인터럽트 중첩 실행)이 가능하다.
- Bit 6 : Bit Copy Storage : BLD, BST로 T의 bit를 register file의 register에 대해 가져오거나, 저장할 수 있다.
- Bit 5 Half Carry Flag
- Bit 4 Sign Flag
- Bit 3 Two's Complement Overflow flag
- Bit 2 Negative Flag
- Bit 1 Zero Flag
- Bit 0 Carry Flag
5~0 flag는 간단하므로 패스하자.
General Purpose Register File
overview에서 확인했듯, 6개의 GPR이 special한 역할을 수행한다. 두 개의 레지스터가 16비트 레지스터의 low, high를 담당한다.
Stack Pointer
Stack은 SRAM에 구현되고, High address에서 초기화된 후 push를 할 때 Low address로 이동한다. 위에서 밑으로 내려오는 방식이다. Stack은 Data 뿐만 아니라 Return Address역시 저장해야 한다. 따라서 Stack은 다음과 같은 instruction을 지원해야 한다.
여기서 재미있는 점은, 데이터를 push pop 할때는 1씩만 줄이지만, CALL RET 할때는 2씩 줄인다는 것이다. 이유는 무엇일까? 다음과 같이 추론이 가능하다.
DATA는 8bit 이지만, Address는 8bit로는 부족하기 때문이다.
뒤에 Memory에서 나오겠지만, ATmega328p는 14비트 주소를 가지고 있다. 따라서 Stack Pointer는 2개의 8bit register를 사용해서 나타내야 하고, 따라서 다음과 같이 SPH, SPL 레지스터가 있으면 된다.
Instruction Execution Timing
AVR CPU는 CPU clock으로만 동작하고, 다른 clock division은 사용되지 않는다(단순한 MCU다!) AVR은 파이프라이닝을 지원함과 동시에, ALU에서는 단일 사이클 명령어 실행을 지원하고 있어 높은 throughput을 보여줄 수 있다.
위 그림은 파이프라이닝을 적용했기 때문에 parallel instruction을 돌리면 N개의 time slice 동안 N개를 처리할 수 있다. 반면, 아래의 그림의 경우 single arithmetic instruction이 온다면 레지스터 파일에 대한 데이터 로드, 저장 및 ALU가 최적화되어 있어서 이러한 arithmetic instruction은 단일 사이클 처리가 가능하다고 한다. 따라서 이 때문에 높은 throughput을 보인다.
Reset and Interrupt Handling
Reset는 Interrupt 중 가장 우선순위가 높다. 당연히 껐다 키는 버튼이 컴퓨터에서도 가장 중요한 우선순위를 가지듯이.. 11장에 등장하는 인터럽트 벡터 테이블을 미리 가져오면 다음과 같다.
여기서 질문, 인터럽트 안에서 인터럽트를 호출하는게 가능한가? 가능하지만 인터럽트는 빨리 처리되어야 하는 루틴이기 때문에 일반적으로는 인터럽트 안에서 인터럽트를 받을 수 없도록 되어 있다. 예를 들어, 다음 설명처럼 인터럽트가 일어나면 global interrupt enable l-bit가 clear되어서 다른 모든 인터럽트가 비활성화된다. 이후 인터럽트가 끝나고 리턴하면서 l-bit는 자동으로 set된다.
하지만 user software가 nested interrupt가 가능하도록 I-bit를 수정할 수 있다. 이 경우에는 Nested Interrupt가 가능하다.
인터럽트의 유형으론 두 가지가 있다.
- 이벤트가 발생하여 인터럽트 플래그가 설정되고 -> PC가 interrupt vector 값으로 이동 -> 인터럽트 플래그 클리어 하면서 처리됨. -> 만약 인터럽트 플래그가 클리어되었을 때 인터럽트가 발생하면, 하드웨어는 interrupt가 가능해지거나(software에 의해) 또는 실행중이던 인터럽트가 끝나 비트가 clear될 때 인터럽트를 실행한다(우선순위 순으로)
- 두 번째 타입은, 마치 bus 연결처럼 인터럽트가 현재 실행중이면 그냥 요청된 인터럽트를 버리는 방법이 있다.
이 중 AVR은 첫 번째 타입의 인터럽트 처리 방식, 즉 인터럽트가 끝난 이후에 요청된 인터럽트 중 우선순위 높은 것을 실행하는 방식이다. 이를 위해서 SREG(status register)를 설정한다.
인터럽트를 disable하는 명령어로 CLI가 있고,
인터럽트를 enable하는 명령어로 SEI가 있다.
인터럽트 반응 시간 역시 작성해 두었다. AVR에서는 인터럽트가 일어나서 PC를 vector에 옮기기 까지 4 cycle이 걸린다고 한다. 이 과정에서 원래 있던 PC는 stack에 담긴다(복귀해야 하므로). vector에서 interrupt routine으로 jump하는데에 3 cycle이 걸리고, 인터럽트는 N cycle(인터럽트마다 다름)이 걸리고, 다시 main으로 복귀하는 데에 4 cycle이 걸린다. 이 과정에서 stack에서 RET을 통해 원래 하고있던 pc값으로 복귀하고, SREG의 I bit를 set하는 과정이 있다.
'Embedded SW > ATmega328p Datasheet' 카테고리의 다른 글
[Datasheet] 2. Overview (1) | 2025.02.23 |
---|---|
[Datasheet] 1. Pin Configurations (0) | 2025.02.23 |
[Datasheet] 0. Datasheet는 최고의 과외선생이다. (0) | 2025.02.22 |