개인적으로 C언어를 작성하고 컴퓨터에 대한 로우레벨을 이해하려고 할 때 가장 크게 도움이 되었던 이론이 컴퓨터구조 였다고 생각된다. 수업을 들으면서 내 머릿속에서는 ‘아, C언어에서 함수를 호출하면 이렇게 스택에 쌓이고 Stack Pointer를 통해 파라미터를 가져와 사용하겠구나! 그러고 리턴값이 register에 담겨서 넘어가겠구나’ 라는 생각이 바로바로 들어왔다. 그런데 이런 배경 지식이 없는 상태에서 수업을 들었다면 ‘C언어에서 어셈블리 코드로 변환되고 이걸 기계어로 CPU가 처리하는데, 마법이 일어나서 결과를 전달하겠군’ 정도로 밖에 생각할 수 없었을 것 같다.
ㅤ
그래서 임베디드 공부를 할 때에도 베이스 아키텍처에 대한 지식이 탄탄하게 잡혀있으면 이후에 복잡하고 어려운 컨셉들이 제시되더라도 “아, 대략적으로 이 부분에 들어가는 요소를 말하는 거겠구나!” 라는 생각이 들 것 같아서, 한 번 공부를 해보려고 한다. 이게 범용적인 아키텍처에 대해서 공부를 하는게 더 좋으려나 싶었는데 찾아보니 거의 모든 대부분의 임베디드 보드는 ARM 기반이라고 하기에, 안심하고 덤벼본다!
ㅤ
이번 아티클에서 다룬 내용
RISC vs CISC | 왜 ARM은 RISC를, 인텔은 CISC를 사용하는가?
ARM Cortex의 레지스터
Memory Mapped I/O ↔ I/O Mapped I/O
Bridge
PCI
ISA
컴퓨터의 구성요소
컴퓨터를 단순화하면 크게 CPU, Memory, I/O Device 3가지로 구성된다.
ㅤ
ARM 이란?
ARM : Advanced RISC Machine
ㅤ
ARM 회사에서는 칩에 대한 설계만 한다. ST에서 ARM으로부터 Cortex-M4 설계 라이센스를 구매해서 자체적으로 peripheral과 결합해 만든 칩이 STM32F429ZIT6.
ㅤ
ARM Processor
- Cortex-A : Application Processor(AP)
- OS를 올려서 실행할 수 있는 고성능의 칩
- 스마트폰, 태블릿, 임베디드 리눅스 등에 사용한다.
- 갤럭시랑 아이폰에 들어가는 칩이 AP 기반!
- Cortex-M : Microcontroller
- 저전력에 빠른 인터럽트 응답이 가능한 칩
- 실시간 제어, IoT, 차량 ECU(electronic control unit) 등에 사용한다.
- Cortex-R : Real-time
- 완전 Real-Time에서 활용할 수 있도록 응답시간이 deterministic 한 칩
- TCM (Tightly Coupled Memory) → Cache Miss가 없어 Cache Miss로 인한 딜레이 방지 (물론 Cortex-M7에도 TCM이 있는데 Cortex-R에 훨씬 큰 TCM을 넣어서 응답시간을 유지)
- ECC (Error Correcting Code) → 우주방사선 버그를 방지하는 하드웨어 신뢰성 확보
- 인터럽트 발생 시 원래 하던 작업을 Stack에 넣는 작업 없이 바로 Interrupt 수행해서 즉각반응
ㅤ
ARM의 용어 체계
계속 뭔가 설명이 나오는데, 정확히 내가 키워드를 파악하지 못하고 있어서 개념이 혼동되는 것 같다. 조금만 정리해보자.
- Core
- 가장 핵심적인 연산 부분
- ALU, Register, Control Unit, Pipeline, Load/Store Unit 등으로 구성
- Processor
- Core + 동작에 필수적인 주변 요소
- NVIC(인터럽트 처리와 우선순위 관리), SysTick Timer(OS 시스템 타이머), Debug Component, Bus Interface
- FPU, MPU 등은 옵션
- ARM Cortex-M4 Processor가 여기에 속함
- Microcontroller
- Processor + Memory + Peripheral
- 메모리 시스템, CLK 시스템, 파워 관리, Peripheral (I/O) ← ST사에서 추가함
- STM32F429ZIT6 MCU가 여기에 속함
- (+ Board)
- MCU + Mem I/O 장치
- LED, KEY, LCD, UART 같은 장치
- NUCLEO429Z 개발 보드가 여기에 속함

ㅤ
RISC와 CISC
CISC - Complex Instruction Set Computer
- 복잡한 명령어를 다양하게 제공한다.
- 여러 유형의 동작 → 가변 길이의 명령어를 사용한다. (1Byte ~ 15Byte)
- 동일한 프로그램을 RISC 대비 적은 명령어로 작성할 수 있기 때문에 프로그램의 용량 면에서 효율적이다 (고밀도의 코드).
ㅤ
RISC - Reduced Instruction Set Computer
- 적은 개수의 간단한 명령어들만 제공한다.
- 고정 길이의 명령어를 사용한다. (4Byte / Thumb 에서는 2Byte)
- 더 많은 레지스터를 사용한다. (메모리 RW → 레지스터 횟수가 많아야함)
ㅤ
동일한 동작을 수행하기 위해서 RISC의 명령어 양이 더 많아지게 된다. 그러나 이외로 RISC 방식의 처리속도가 더 빠르다.
CISC -> 5개 명령어 X 1사이클에 4CLK = 20CLK
RISC -> 10개 명령어 X 1사이클에 1CLK = 10CLK
ㅤ
CISC의 단점
- 하나의 명령어에 메모리 RW가 모두 포함될 수 있어서 명령어 수행이 훨씬 느리다.
(’특정 메모리에 값 더하기’ → RISC에서는 ‘메모리에서 값 가져오기 + 값 더하기 + 메모리에 값 쓰기’ 로 분리됨) - 명령어의 길이가 가변 … 명령어 Decode 과정이 훨씬 복잡하다.
- 명령어가 복잡해 파이프라인으로 구조를 설계하기 빡세다. 명령어 길이가 동일했다면 다음 FETCH 대상은 항상 PC+4 였을텐데, 가변이니깐 어디를 가야할 지 복잡하게 계산해야함.
- RISC는 단순한 회로를 사용하기 때문에 더 적은 트렌지스터로 구현할 수 있어 저전력으로 수행할 수 있다. + 더 작은 칩, 더 저렴한 칩, 더 적은 발열
- RISC는 각 명령어의 수행시간이 거의 일정해 RTOS에서 유리하다.
ㅤ
현대의 CISC는 CISC가 아니다!
x86 같은 CISC 아키텍처의 프로세서라고 하더라도 성능 상의 이유로 사실 내부적으로는 RISC로 구현된다. CISC 명령어를 디코딩해서 내부에서 마이크로명령어로 쪼갠 다음 이걸 실행시킨다. 이게 말이 마이크로지 사실상 RISC라고 봐도 되지 않을까?
ㅤ
이렇게 이상하게 구조를 잡은 이유는 기존 CISC 체계에서 이미 작성되어있는 수많은 SW와 레거시 시스템 들을 버릴 수 없어서 어쩔 수 없이 외부 인터페이스는 CISC로, 내부 구현은 RISC로 하는 것임. 하위 호환성이라는게 이렇게나 무섭다.
ㅤ
처음에는 CISC가 당연히 고성능이지만 전력을 너무 많이 써서 PC에서는 CISC 쓰고 모바일에서 RISC를 쓴다고 생각했는데, Apple Silicon을 생각해보면 M3, M4 이런게 성능이 진짜 인텔 CPU 보다 뛰어난데 전기도 덜 먹는걸 생각해보면 RISC가 성능 상 앞선다는 말도 사실인 것 같다. 그런데 인텔이 내부에서는 RISC라는건 처음 알았네. 신기하다 ㅋㅋ.
ㅤ
그렇다면 임베디드에서는 왜 RISC(ARM)을 쓰냐?
- 성능과 전력 효율이 중요함
- 과거 호환성이 필요 없음 (레거시 프로그램을 돌릴건 아니니깐)
- Real-Time의 예측 가능한 처리 속도가 중요
ㅤ
자동차에서도 배터리 제어나 메인 CPU로는 ARM을 사용한다.
- 차량 운행 → 예측 가능한 레이턴시가 중요함 (결국 RTOS!)
- ARM의 간결한 파이프라인과 저전력 성능이 유리
- 사실상 레거시 코드가 안전하다기보다는 안전 표준을 잘 준수해서 작성한 SW면 충분히 안전함
ㅤ
ARM Cortex-A의 레지스터 구조

ARM은 32bit Register를 사용하며, 31개의 General Perpose Register, 6개의 Status Register로 구성된다.
ㅤ
- Unbanked Register / R0 ~ R7
- 완전 범용 레지스터, 가장 많이 사용된다. 모든 모드에서 동일함
- Banked Register / R8 ~ R14
- 프로세서 모드에 따라서 물리적으로 분리된 레지스터를 사용
- → 컨텍스트 스위치 시에 레지스터 값 저장하는게 시간이 오래걸리니깐
- 특수 레지스터
- R13 - SP : Stack Pointer - Stack의 최상단 주소
- R14 - LR : Link Register - 함수 return 시 돌아갈 위치
- R15 - PC : Program Counter - 다음에 실행할 명령어 주소
- 상태 레지스터 (CPSR, SPSR)
- NZCV 같은 프로세서의 상태 Flag
- 프로세서 모드
- 인터럽트의 활성화 여부
ㅤ
내가 사용하려는 Cortex-M4 에서는 이거보다 단순한 Register 구성을 사용한다. M 시리즈 (Microcontroller)의 지향점이 ‘단순함과 효율성’이기 때문.
R0~R12 : 범용 레지스터
R13 - SP : Main Stack Pointer + Process Stack Pointer
R14 - LR
R15 - PC
- CPSR, SPSR, 인터럽트 마스크, Control 등
모드도 Thread / Handler 2가지만 제공한다.
ㅤ
Memory Map
메모리 맵 : 메모리, I/O 장치를 포함하여 시스템의 모든 자원에 메모리 주소를 할당하는 방식. 외부 I/O에 접근할 때에도 해당 장치에게 할당된 주소를 이용해 읽고 쓰기 동작을 수행.
ㅤㅤ
메모리 맵의 장점
- 통일된 방식 : 외부 I/O 에게 메모리 접근 명령어로 제어 가능
- 단순한 HW : 별도로 I/O 명령어를 만들 필요 없음. Load/Store 명령어만 있으면 된다 (RISC style)
ㅤㅤ
[ STM32F429의 메모리 맵 ]
0x00000000 ┌──────────────────┐
│ Alias (코드 영역) │ ← 부팅 시 Flash 또는 SRAM
0x08000000 ├──────────────────┤
│ Flash (2MB) │ ← 프로그램 저장
0x081FFFFF ├──────────────────┤
│ ... │
0x20000000 ├──────────────────┤
│ SRAM1 (112KB) │ ← 일반 RAM [**실제 메인 메모리 공간**]
0x2001BFFF ├──────────────────┤
│ SRAM2 (16KB) │
0x2001FFFF ├──────────────────┤
│ SRAM3 (64KB) │
0x2002FFFF ├──────────────────┤
│ CCM RAM (64KB) │ ← Core-Coupled Memory (CPU 전용)
0x40000000 ├──────────────────┤
│ APB1 Peripherals │ ← 저속 주변장치 (Timer, UART 등)
0x40007FFF ├──────────────────┤
│ APB2 Peripherals │ ← 고속 주변장치 (GPIO, ADC 등)
0x40013FFF ├──────────────────┤
│ AHB1 Peripherals │ ← DMA, USB 등
0x40020000 ├──────────────────┤
│ GPIO A │ ← 0x40020000 ~ 0x400203FF
│ GPIO B │ ← 0x40020400 ~ 0x400207FF
│ ... │
0xE0000000 ├──────────────────┤
│ Cortex-M4 Core │ ← NVIC, SysTick, Debug 등
0xFFFFFFFF └──────────────────┘
ㅤㅤ
메모리 맵 방식에서 I/O를 수행하는 방법
- Output : CPU 입장에서는 메모리에 쓰기
- CPU가 I/O 장치가 할당된 메모리 주소에 특정 값을 Store 명령을 실행한다.
- 해당 메모리 주소로 가던 저장 명령어와 값이 Address Decoder에 의해 I/O 장치에게 보내진다.
- I/O 장치에 해당 값이 전달되면 I/O 장치가 자신이 가지고 있는 레지스터에 값을 쓴다. → Output (Write)
- Input : CPU 입장에서는 메모리에서 읽기
- CPU가 I/O 장치가 할당된 메모리 주소에 Load 명령을 수행한다.
- 해당 메모리 주소로 가던 로드 명령어가 Address Decoder에 의해 I/O 장치에게 보내진다.
- I/O 장치는 Load 명령을 보고 레지스터의 값을 CPU에게 보낸다.
- CPU는 가져온 값을 이용한다. → Input (Read)
ㅤ
나는 지금까지 메인 메모리의 특정 주소에 값을 작성하면 그 값을 I/O가 공유하면서 처리하는게 Memory Mapped 방식이라고 생각했는데, 그게 아니라 실제로 해당 I/O 에게 입출력을 전달하는거였다니… 내 세상이 방금 무너졌다 (Positive)
ㅤ
↔ I/O Mapped I/O
I/O 주소 공간을 Memory 주소와 분리해서 별도의 주소를 사용하는 방식. 명시적으로 I/O 장치에게 접근한다는 것을 코드만 봐도 알 수 있는 형태.
ㅤ
Memory Mapped I/O 방식을 사용하면 메인 메모리를 지정할 때 사용해야 하는 주소를 다른 장치에게 부여해야 한다. 16bit 아키텍처 시절에는 64KB 까지만 주소를 가리킬 수 있었기 때문에, 인텔의 x86은 메모리 공간을 최대한 활용하기 위해서 I/O 장치는 별도의 주소를 지정하도록 I/O Mapped I/O 방식을 사용했다. 그런데 지금은 64bit를 사용하면서 메모리 주소 공간은 충분해졌지만 하위 호환성의 문제로 여전히 I/O Mapped I/O를 사용하고 있다. 😢😢😢
ㅤ
위 방식을 사용할 때에는 Load/Store 명령어 대신에 In/ Out 이라는 새로운 명령어를 사용해야 한다. 그러나 ARM이 사용하는 RISC 방식에서는 최대한 명령어의 개수를 줄여서 간단한 아키텍처를 만드는 것을 지향하기 때문에 새로운 명령어가 추가되는 방식 보다는 메모리 주소를 다루는 Load, Store 명령어를 그대로 사용하는 Memory Mapped I/O 를 사용하기로 하였다.
ㅤ
그놈의 하위호환성이 인텔의 발목을 계속 잡고있는 것 같다. 기존 시스템의 호환성을 놓아버릴 타이밍을 놓친걸까 아니면 기존 시스템을 유지하면서 여전히 RISC 만큼 좋은 성능을 낼 자신감이 계속 있는걸까? 궁금하네.
ㅤ
GPIO 영역이 1KB 단위인 이유?
하위 10개 bit로 I/O 내 주소 표현
상위 나머지 bit로 어떤 I/O 인지 표현
GPIO_A 의 메모리주소 : 0x40020000 ~ 0x400203FF (0x400)
GPIO_B 의 메모리주소 : 0x40020400 ~ 0x400207FF (0x400)
A start : 0b0100 0000 ... 0010 0000 00|00 0000 0000
A end : 0b0100 0000 ... 0010 0000 00|11 1111 1111
B start : 0b0100 0000 ... 0010 0000 01|00 0000 0000
B end : 0b0100 0000 ... 0010 0000 01|11 1111 1111
C start : 0b0100 0000 ... 0010 0000 10|00 0000 0000
C end : 0b0100 0000 ... 0010 0000 10|11 1111 1111
ㅤ
Read-Modify-Write 의 문제와 BSRR
아래와 같은 함수가 있다고 하자. 이 함수는 LED를 켜는 역할을 한다. GPIOB가 가지고 있는 ODR(Output Data register) 에서 특정 bit을 set 하면 불이 켜진다고 가정하자.
void control_leds(void) {
GPIOB->ODR |= (1 << 0); // LED1 켜기
delay_us(10);
GPIOB->ODR |= (1 << 7); // LED2 켜기
}
ㅤ
이걸 Deassem 하면 아래처럼 코드가 쓰여진다. GPIOB로부터 현재 ODR 값을 가져와 register에 올려 값을 수정하고 다시 ODR의 메모리 상 원래 위치에 대입하는 과정(실제로는 I/O 장치에게 전달)을 거친다.
; LED1 켜기
LDR R0, =GPIOB_ODR ; R0 = 주소
LDR R1, [R0] ; R1 = 현재 ODR 값 읽기 (Read)
ORR R1, R1, #(1<<0) ; R1 |= (1 << 0) (Modify)
STR R1, [R0] ; ODR에 쓰기 (Write)
; 딜레이
; LED2 켜기
LDR R1, [R0] ; R1 = 현재 ODR 값 읽기
ORR R1, R1, #(1<<7) ; R1 |= (1 << 7)
STR R1, [R0] ; ODR에 쓰기
ㅤ
근데 만약에 이 asm 실행 중간에 인터럽트가 발생해서 뭔가뭔가 다른 일이 일어났다면 Race Condition으로 인해 LED3이 제대로 꺼지지 않는 문제가 발생할 수 있다. 즉, 여기에서 Atmoic 하게 코드가 동작하도록 해줄 필요가 있다.
// 메인 함수
GPIOB->ODR |= (1 << 0); // LED1 켜기 시작
// 딜레이 중 인터럽트 발생!
// 인터럽트 핸들러
GPIOB->ODR &= ~(1 << 5); // LED3 끄기
// 메인 함수 재개
GPIOB->ODR |= (1 << 7); // LED2 켜기
ㅤ
이 문제를 해결하기 위해 BSRR (Bit Set Reset Register) 를 사용할 수 있다. BSRR를 쓰면 |= Or 연산 대신에 = 대입 연산으로 이를 처리할 수 있고, 대입연산은 단순히 Store 명령어 하나만 호출하면 되기 때문에 단 한 줄의 어셈블리 코드만으로 값을 만들어 넣을 수 있어, interrupt의 위험으로부터 자유로워진다.
// 안전한 방법
GPIOB->BSRR = (1 << 0); // LED1 켜기 (Atomic!)
GPIOB->BSRR = (1 << (7+16)); // LED2 끄기 (Atomic!)
[31:16]: Reset bits (1을 쓰면 해당 핀 LOW)
[15:0]: Set bits (1을 쓰면 해당 핀 HIGH)
// 이게 어셈블리 코드 상에서는 이렇게 한 줄이 된다.
STR R1, [R0, #0x18] ; T0
예: BSRR = 0x00800001
→ 비트 0 SET (HIGH)
→ 비트 7 RESET (LOW)
ㅤ
오늘 수업에서 등장했던 BSRR이 이렇게나 유용한 도구였다니… 이거는 문서 안 읽었으면 절대로 몰랐을 것 같다. 이게 문서의 중요성인가봐.
함께 등장한 키워드
Bridge
그리고, CPU-Memory-I/O 는 큰 공유 데이터 통로인 버스(Bus) 를 통해서 연결된다. (Bus Architecture) 덕분에 만약 새로운 장치가 추가되었을 때, CPU나 메모리와 개별적으로 연결하는 대신에 이미 깔려있는 Bus 에 연결함으로 시스템에 장치를 추가할 수 있다.
ㅤ
이때, 만약 하나의 Bus를 여러 장치가 동시에 접근해서 사용하고자 한다면 충돌이 발생해서 간섭이 일어나 정상적으로 데이터를 주고받을 수 없게 된다. 그렇다면 이런 동시 사용에 따라 발생할 수 있는 문제를 어떻게 처리해야할까?
ㅤ
만약 장치들의 우선순위를 기준으로 BUS 접근에 대한 점유권을 준다면?
- 우선순위가 높은 장치가 자원을 독점적으로 사용하면서 사용성이 나빠진다. (CPU가 버스를 통해 데이터를 전송하는 동안 화면 리랜더링 못함 / USB 읽고쓰기 동작 못함)
ㅤ
Bus의 속도를 높인다면?
- 초당 10GB/s → 너무 빠르면 CPU의 처리능력보다 빨라서 낭비
- 초당 50MB/s → 너무 느리면 Memory의 처리능력보다 느려서 낭비 / 그런데 I/O 장치들에게는 50MB/s로 충분. 얘네에게는 오히려 빨라서 낭비
ㅤ
장치마다 필요로 하는 Memory Bus의 처리 속도가 달라 발생할 수 있는 Gap 에 따른 문제를 해결하기 위해서 Memory Bus를 고속 / 저속으로 분리해 사용
- North Bridge : 빠른 데이터 접근, 대량의 데이터 전송을 위해 사용
- CPU, Memory, GPU
- South Bridge : 느린 장치들을 위해 사용
- HDD, USB, Audio, KBD, …
처음에는 CPU와 브릿지 등의 모든 구성요소를 하나의 칩 내에 넣기 어려웠기 때문에 브릿지를 외부에 배치하여 구조를 설계하였음. 그런데, HW가 발전하면서 점점 CPU와 장치 간에 데이터를 주고받는 과정이 병목이 되어감 + 트랜지스터 집적 능력 좋아짐 + 빠른 성능 필요 에 따라서 점차 North Bridge를 CPU 내부로 통합하였다 (이제는 완전히 흡수된 듯).
ㅤ


STM32F429ZIT6 칩에서는 BUS가 이렇게 구성된다. 요런 계층별 버스 구성 방법을 AMBA(Advanced Microcontroller Bus Architecture 이라고 부른다)
- AHB : Advanced High-Performance Bus
- APB : Advanced Peripheral Bus
- AXI : Advanced eXtentible Interface

ㅤ
SRAM과 DRAM in MCU
SRAM : Static Random Access Memory
- 1bit 표현을 위해 트랜지스터 6개 사용
- 전원만 공급하면 계속 데이터가 유지됨 (관리해줄 필요 없음)
- 빠른 데이터 엑세스 타임 + 저전력 + 레이턴시 낮음
- 셀 크기가 커서 대용량으로 만들기 어렵고 비쌈
ㅤ
DRAM : Dynamic Random Access Memory
- 1bit 표현을 위해 트랜지스터 1개 + 커패시터 1개 사용
- 전원 공급 안하면 데이터가 없어져서 리프레시 필요함
- 데이트 엑세스 타임이 비교적 느림 + 리프레시가 필요해 제어 로직 복잡 + 레이턴시 불안정 (리프레시 타이밍과 겹치면 늦게 값 엑세스)
- 대용량으로 만들기 좋고 저렴
ㅤ
일반적인 PC에서는 DRAM으로 메인 메모리를 만드는데, MCU (특히 내가 사용하는 보드) 에서는 메인 메모리를 SRAM 으로 만든다.
MCU에서 메인 메모리로 필요로 하는 용량이 작아 비용이 크게 발생하지 않고, (특히 RTOS 등에서는) 레이턴시가 중요하기 때문에 SRAM 사용.
ㅤ
MCU 에서는 빠른 접근을 위해 메인 메모리까지 MCU 칩 내에 포함된다. 물론 원한다면 외장으로 DRAM을 메모리로 추가할 수도 있다. ((Flexible Memory Controller 인터페이스 이용)
- 덕분에 외부 메모리 크기를 필요에 따라 사용자가 선택할 수 있다.
- 메모리가 없더라도 내부 SRAM 만으로 동작할 수 있다.
- 메모리를 칩에 포함시켰다면 칩 크기 증가 + 비용 증가를 피할 수 없었을 것이다.
ㅤ
나는 당연히 메인 메모리는 DRAM으로 구성되어야 한다고 믿고 있었어서, MCU에서는 메인 메모리가 SRAM으로 구성될 수 있다는 것도 놀라웠고, MCU 칩 내부에 메인 메모리가 있다는 것도 놀라웠다. 당연히 PC에서처럼 보드에 외장으로 박혀있을 거라고 생각했는데… 완전 헛다리를 짚었었네
ㅤ
내가 사용하는 STM32F429 에서는 이게 SRAM의 형태로 CPU 칩 내부에 포함되어 있다.

ㅤ
PCI : Peripheral Component Interconnect
프로세서와 주변 장치가 데이터를 주고받기 위해서 공유 데이터 버스인 PCI BUS를 이용하였다.
ㅤ
기존에는 ISA(Industry Standard Architecture) 라는 것을 사용했는데, 이게 CPU의 종류에 호환되는 장치만 연결할 수 있는 문제 + CPU의 성능을 그대로 따라가는 문제가 있어서 PCI로 개선하였다.
ㅤ
PCI를 사용하면 여러 I/O 장치들을 CPU와 독립된 PCI BUS를 통해서 연결해 데이터를 주고받을 수 있다. 덕분에 CPU의 CLK과 무관하게 데이터를 주고받거나 CPU 종류에 상관없이 장치를 연결할 수 있게 되었다. 또 PCI 소켓을 BUS에 추가함으로서 손쉽게 장치 연결 개수를 늘릴 수 있었다. 다만 PCI BUS를 연결된 모든 장치가 공유하게 되는데 한 번에 하나의 장치만 BUS를 사용할 수 있다보니 성능의 문제가 조금씩 생겼다.
ㅤ
시대가 발전하면서 더 많은 데이터를 주고받을 니즈(GPU 데이터 연산량의 증가 등)가 생겼고, BUS 대신에 직접 CPU와 각 장치를 연결할 수 있도록 하는 PCI express 선을 연결 + 더욱 큰 대역폭을 제공하여 훨씬 많은 데이터를 빠르게 주고받을 수 있도록 하였다. (PCIe = PCI express)

ㅤ
이렇게 구조를 변경하면서 HW 선이 매우 복잡해지는 문제가 있었지만, 그만큼 성능이 우수해서 PCIe 방식이 살아남았다.
ㅤ
그런데 iPhone 등에 들어가는 ARM 칩셋에서는 PCIe를 굳이 사용하지 않는다. (빠르다면서…!!!😱) 이게 하나의 다이(보드) 위에 모든 핀이 있음 + 한 번 설계해서 부품을 붙이기 때문에 굳이 확장성을 고려하지 않아도 됨 → 그래서 그냥 더 빠르고 단순하게 데이터를 주고받을 수 있는 인터페이스를 사용한다.
그러나 외부 확장을 고려하거나 표준화, 모듈화를 위한 임베디드 장치에서는 PCIe를 사용한다. 예를 들자면 차량용 자율주행. 이 친구는 외부 GPU나 AI 가속기 등과 연결하여 데이터를 빠르게 주고받기 위해서 PCIe를 사용할 수 있다.
ㅤ
HW Abstraction Layer
STM32에서 LED에 불이 들어오도록 하기 위해서는 아래같이 명령어를 작성해 수행할 수 있다.
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
ㅤ
이 한 줄의 코드가 실제로 LED에 불이 들어오도록 하기 위해서는 어떤 계층을 거쳐야 하나?
- HAL로 작성한 C 코드
- HAL Library
- [개발자를 위한 추싱화]
- Operating System
- Instruction Set Architecture
- [SW을 위한 추상화]
- Hardware Implementation (실제 하드웨어 구현)
ㅤ
ISA - Instruction Set Architecture
ISA는 컴파일러, 어셈블러, 링커 등의 소프트웨어 도구와 하드웨어 사이의 표준 인터페이스이다.
ㅤ
ISA는 다음의 것들을 보장한다.
- 내부 HW 회로가 어떻게 생겼는지 전혀 몰라도 된다.
- 어떤 ARM 프로세서에서든 실행할 수 있으며, 성능은 다를 수 있지만 결과가 동일하다.
ㅤ
ISA는 SW도구들에게 요런 정보들을 제공한다.
- 컴파일러
- 레지스터의 개수 (컴파일러가 레지스터 개수를 알아야 메모리와 데이터를 이용할 수 있음)
- 사용 가능한 명령어의 개수
- 메모리 접근 방식
- 함수 호출 규약
- OS
- 인터럽트 처리 방법
- 메모리 보호 (MMU, MPU 규격)
- 개발자
- HAL 사용하지 않고 성능을 위해 ISA 명령어를 직접 호출하고 싶을 때 참고가능
ㅤ
ISA가 다르다면 호환되지 않는다. (ARMv7-A와 ARMv7-M 아키텍처는 다르기 때문에, 호환되지 않음)
'Embedded System > ARM 프로세서 아키텍처' 카테고리의 다른 글
| [ARM] ARM Processor Architecture - 2 : ARM 레지스터 구조와 Exception (0) | 2025.10.23 |
|---|