글에 틀린 부분이 있을 수도 있습니다. 피드백은 언제나 환영입니다. 감사합니다.
1. Intro
지난 포스트에서는 MPU system의 MPcore가 FPGA 영역의 peripheral에 접근하기 위해 어떤 경로를 거치게 되는지 알아봤었다. MP core에서 출발한 데이터와 명령어는 L2 cache → L3 interconnect → Lightweight HPS to FPGA bridge
를 통해 전달되고 결국에 FPGA의 peripheral들을 제어할 수 있게 된다. 이번 포스트에서는 리눅스 운영체제에서 LED를 제어하는 C 프로그램을 통해, mmap함수를 사용했을 때 내부적으로 어떤 동작이 발생하는지 운영체제 입장에서 작성하려고 한다. LED 제어 소스 코드는 source code 에서 확인할 수 있다.
2. 개념
LEDR(Red Led) 을 제어하는 예제를 이해하기 위해 몇가지 개념에 대한 이해가 필요하다.
2.1 file descriptor
file descibtor
는 하나의 프로세스 내에서 그 고유성이 유지되는 각 파일에 대한 값이다. file describtor 값은 파일을 열 때 즉, open()함수가 호출되었을 때 운영체제에 의해 각 파일에 할당된다. 이후 프로그램은 이 값을 통해 file에 접근할 수 있다. 이 source code에서는 /dev/mem 파일에 대한 file describtor 값을 fd 라는 변수에 저장하고 있다.
2.2 Linux에서의 file
리눅스에서 파일이라고 하는 것은 우리가 일반적으로 통칭하는 그 파일하고는 개념이 조금 다르다. 좀 더 확장된 개념을 갖는다. 리눅스에서는 일반적인 파일뿐만 아니라 디렉토리 혹은 디바이스 같은 것들도 전부 다 파일로서 취급을 한다. 그래서 source code의 /dev/mem 은 리눅스의 메모리 전체를 칭하는 파일로 생각해야 한다.
2.3 mmap 함수
맨 왼쪽의 사진은 지난 포스트에서도 소개한 mpu system의 4GB 주소공간의 일부이고, 오른쪽 사진은 mmap 함수에 관해 설명한 그림이다. 오른쪽 사진에서 offset에서부터 시작하여 length bytes 만큼의 공간이 왼쪽 사진의 Lightweight FPGA Slaves
공간이라고 할 수 있겠다. mmap 함수를 사용할 때 void *start
인자를 0 또는 Null로 지정하면, Process virtual memory의 stack 영역과 heap 영역 사이의 임의의 빈 공간에 매핑된다.
DE1-Soc에 FPGA 영역이 있고, 이를 이용하여 custom hardware를 만들 수도 있지만, 기본적으로 FPGA의 peripheral들을 제어할 수 있도록 Intel에서 제공되는 computer system을 DE1 soc computer
라고 한다. DE1 soc computer의 memory layout은 아래 사진과 같다.
LW_BRIDGE_BASE 는 0xFF200000, LW_BRIDGE_SPAN 은 0x00005000, LEDR_BASE 는 0x00000000 인 것을 확인할 수 있다.
3. LEDR 제어 C 프로그램 예제
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "address_map_arm.h"
int main(void)
{
int fd;
void *lw_virtual;
volatile int *ledr;
int cnt = 0;
fd = open("/dev/mem", (O_RDWR | O_SYNC));
lw_virtual = mmap(NULL, LW_BRIDGE_SPAN, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, LW_BRIDGE_BASE);
ledr = (volatile int *) (lw_virtual + LEDR_BASE);
*ledr = 0;
while(cnt<8){
*ledr = *ledr + 1;
cnt++;
usleep(1000000);
}
munmap(lw_virtual, LW_BRIDGE_BASE);
close(fd);
return 0;
}
/dev/mem 장치파일을에 대한 file describtor 값을 할당받은 후, mmap함수에 LW_BRIDGE_SPAN, LW_BRIDGE_BASE와 함께 fd를 인자로 넣고 또 설정값들을 넣어주면 lw_virtual에는 process virtual memory에 매핑된 physical address가 반환된다. 그 후 lw_virtual + LEDR_BASE 주소값을 volatile int *
형으로 형변환하여 ledr에 반환한다. 여기서 volatile이 사용된 이유는 컴파일 과정에서 최적화를 수행하지 않기 위해서다. 하드웨어의 값은 외부에서 계속 변경될 수 있다. 예를 들면 pushbutton을 눌렀다가 뗐다가 하는 과정을 생각해 볼 수 있겠다. 이처럼 하드웨어의 값은 외부환경으로 인해 계속 바뀌는데, cpu에서 데이터를 load 할 때, 캐싱된 데이터(=캐시에 저장된 데이터)를 사용하면 그때그때 최신으로 업데이트된 값들을 사용하지 못하게 된다. 이를 막기 위해 캐시가 아니라 항상 메모리에서 데이터를 load 하도록 volatile 키워드를 사용하는 것이다. 즉 컴파일러가 캐싱을 통해 변수접근을 최적화하는 것을 막는 것이다.
지난 포스트에서 설명했듯이 LEDR의 레지스터는 위의 사진처럼 생겼다. DE1-soc에는 10개의 RED LED가 있는데 1비트를 이용하여 1개의 LEDR를 제어한다. 그래서 위의 프로그램을 실행하면 1초의 간격을 두고 차례대로 LEDR이 하나씩 켜지는 동작을 하게 된다.
다음포스트에서는 Slide switch와 Hex display를 이용하여 학번을 출력하는 banner scroll 에 대해 적어보려고 한다.
[출처]
https://www.clear.rice.edu/comp321/html/laboratories/lab10/
https://people.ece.cornell.edu/land/courses/ece5760/DE1_SOC/SoC-FPGA%20Design%20Guide_EPFL.pdf
'과제 및 프로젝트 > 임베디드 시스템 설계' 카테고리의 다른 글
Polled I/O를 이용한 Calculator (0) | 2024.12.23 |
---|---|
Memory-Mapped IO in Linux-Based Systems(3) (0) | 2024.12.23 |
Memory-Mapped IO in Linux-Based Systems(1) (0) | 2024.12.22 |