ULX3S 튜토리얼

이 튜토리얼에서는 FPGA 개발 도구를 설치하고, RISC-V 코어를 합성하고, 프로그램을 컴파일 및 설치하여 ULX3S에서 실행하는 방법을 설명합니다.
오픈 소스 FPGA 개발 툴체인 설치
시작하기 전에 오픈 소스 FPGA 개발 툴체인(Yosys, NextPNR 등)을 설치해야 합니다. 설치 방법은 여기에 나와 있습니다.
(선택 사항) femtosoc 및 femtorv32 설정
이제 learn-fpga/FemtoRV/RTL/CONFIGS/ulx3s_config.v
파일을 편집할 차례입니다. 이 파일에서는 만들 RISC-V 프로세서의 종류와,
함께 구성되는 시스템 온 칩에 포함할 장치 드라이버를 정의할 수 있습니다.
기본적으로 소형 OLED 화면이 설정되어 있습니다. 그대로 두어도 문제는
없지만, 해당 화면이 없다면 관련 줄(SSD1331)을 주석 처리해도
됩니다. 이에 대해서는 아래에서 자세히 설명합니다.
합성하기
이제 펌웨어를 컴파일하고, 디자인을 합성하여 장치로 전송할 수 있습니다. 장치를 USB 포트에 연결한 다음, 다음 명령을 실행하십시오.
$make ULX3S
처음 실행할 때는 RISC-V 개발 도구를 다운로드합니다(시간이 좀
걸립니다). 기본 펌웨어에는 SD 카드에 저장된 elf 실행 파일을
불러올 수 있는 파일 브라우저가 포함되어 있습니다. 소형 OLED 화면을
연결했다면 SD 카드를 찾을 수 없다는 메시지가 표시됩니다. not-HDMI
커넥터에 모니터를 연결할 수도 있습니다. 제 (허접한) OS는
@ultraembedded의 fat io lib 덕분에
만들 수 있었습니다. 이 라이브러리는 FAT 형식 매체에서 파일을 읽을 수
있게 해 주는 훌륭한 소프트웨어입니다. 가까운 시일 내에 명령줄 버전을
추가할 예정입니다.
예제 프로그램
FIRMWARE/EXAMPLES 및 FIRMWARE/ASM_EXAMPLES
디렉터리에는 장치에서 실행할 수 있는 C 및 어셈블리 프로그램이 들어
있습니다.
프로그램을 컴파일하려면 다음을 실행하십시오
$cd FIRMWARE/EXAMPLES
$make program.elf
(모든 예제를 컴파일하려면 make everything을
사용하십시오.) 그런 다음 모든 .elf 파일을 FAT 형식의
마이크로 SD 카드에 복사하여 ULX3S에 삽입하고, SD 카드 위에 있는 fire
버튼을 눌러 ULX3S를 리셋하십시오.
이제 up 및 down 버튼으로 탐색하고,
right 버튼으로 선택한 프로그램을 실행할 수 있습니다.
프로그램을 종료하려면 (SD 카드 위에 있는) reset 버튼으로
장치를 리셋하십시오.
직접 실행해 볼 수 있는 C 및 어셈블리 프로그램이 여러 개 있습니다(아래 목록 참조). RISC-V 어셈블리에 대해 더 알아보려면 RISC-V 명세, 특히 명령어 집합과 프로그래머 매뉴얼을 참고하십시오.
어셈블리
프로그램(ASM_EXAMPLES/mandelbrot_terminal.S)으로 계산한
ASCII 아트 버전의 망델브로 집합 
| 프로그램 | 설명 |
|---|---|
ASM_EXAMPLES/blinker_shift.S |
시프트를 사용하는 blinker 프로그램 |
ASM_EXAMPLES/blinker_wait.S |
지연 루프를 사용하는 blinker 프로그램입니다. |
ASM_EXAMPLES/test_serial.S |
USB 시리얼로 문자를 읽어 다시 돌려보냅니다. |
ASM_EXAMPLES/mandelbrot_terminal.S |
망델브로 집합을 계산하여 ASCII 아트로 표시합니다. |
EXAMPLES/hello.c |
환영 메시지를 표시합니다. |
EXAMPLES/sieve.c |
소수를 계산합니다. |
그래픽
이제 그래픽을 다루어 봅시다. 이를 위한 방법은 두 가지입니다.
- FGA(Femto Graphic Adapter)는 ULX3S의 HDMI 커넥터로 영상을 출력하는 그래픽 보드입니다.
- ULX3S의
OLED1커넥터에 작은 OLED 디스플레이를 연결합니다.
이 옵션들은 RTL/CONFIGS/ulx3s_config.v에서 선택할 수
있습니다. 원한다면 두 가지를 모두 활성화할 수도 있습니다. 두 가지를 모두
활성화하면, 펌웨어는 OLED 디스플레이에 표시되는 내용을 그대로 HDMI로도
내보냅니다.
FGA(Femto Graphic Adapter)
Femto Graphic Adapter는 다음 모드를 지원합니다.
| 모드 | 설명 |
|---|---|
| 320x200x16bpp | 65536색, RGB |
| 320x200x8bpp | 256색, 컬러맵 방식, 2페이지 |
| 640x400x4bpp | 16색, 컬러맵 방식 |
하드웨어 가속 FILLRECT 연산을 지원하며, 이 연산은 화면을
지우거나 다각형 채우기에서 스캔라인을 그리는 데에도
사용됩니다(소프트웨어 루프보다 7배 빠릅니다).
OLED 화면
필수는 아니지만, 작은 OLED 디스플레이를 추가하면 멋집니다.
SSD1331(아래 이미지 중 위쪽 줄)과 SSD1351(아래쪽 줄) 중에서 고를 수
있으며, 두 가지 모두 하드웨어/펌웨어에서
지원합니다('RTL/CONFIGS/ulx3s_config.v'에서 NRV_IO_SSD1351
또는 NRV_IO_SSD1331을 활성화하십시오).
SSD1331 OLED
디스플레이, ULX3S에 기구적으로 잘 맞습니다
SSD1351 OLED 디스플레이(더 큰
화면), ULX3S에 전선으로 연결했습니다
어느 것을 사용해야 합니까? 선택에 도움이 되도록 두 제품을 나란히 비교했습니다.
| SSD1351 | SSD1331 |
|---|---|
| +작지만 큰 화면입니다! | -비트 단위로 보면 너무 작습니다 |
| -ULX3S에 연결하려면 전선이 필요합니다 | +ULX3S에 잘 맞습니다 |
| -화면 뒤집기/회전이 불가능합니다 | +설정이 유연합니다 |
| -가속 프리미티브가 거의 없습니다 | +fillrect, scroll, copy를 하드웨어로 가속합니다 |
- 공통: 밝고 선명하게 표시되어 LCD보다 훨씬 좋습니다!
- 공통: 마지막으로 중요한 점은 FemtoRV32/FemtoSOC에서 지원한다는 것입니다!!
따라서 ULX3S에는 SSD1331을(다른 보드에는 SSD1351을) 추천합니다. 가장 큰 이유는 SSD1331이 기구적으로 ULX3S에 매우 잘 맞기 때문입니다. ULX3S에 헤더 핀을 납땜해야 합니다(또는 솜씨 좋은 친구에게 부탁하십시오. 저도 그렇게 했습니다. 고맙습니다, @ssloy !!!). SSD1331에는 윗줄 사진처럼 암 헤더를 납땜하면 됩니다(납땜은 14살 된 제 아들 Nathan이 했습니다. 솜씨가 좋습니다!). SSD1351은 ULX3S에 표시된 이름과 디스플레이에 표시된 이름에 맞춰 전선을 연결하십시오. 핀 이름은 비트마다 조금씩 다를 수 있으니 다음 표를 참고하십시오:
| ULX3S의 핀 이름 | 핀 설명 | 디스플레이에서 쓰일 수 있는 다른 이름 |
|---|---|---|
| CS | 칩 선택 | |
| DC | 데이터/명령 | |
| RES | Reset | RST |
| SDA | 데이터 | DIN |
| SCL | 클록 | CLK |
| VCC | +3.3V | |
| GND | 접지 |
설정에서 이것을 제거했다면 RTL/CONFIGS/ulx3s_config.v를
편집하여 NRV_IO_SSD1331 또는 NRV_IO_SSD1351이
있는 줄의 주석을 해제하는 것을 잊지 마십시오. 그런 다음
FemtoRV32 디렉터리에서 make ULX3S를 실행하여
다시 합성하고 장치로 전송하십시오.
테스트 프로그램을 컴파일해 봅시다:
$ cd FIRMWARE/examples
$ make gfx_test.elf
이를 SD 카드에 복사하고, SD 카드를 ulx3s에 삽입한 다음, 장치를 재시작하고 프로그램을 실행하십시오.
모든 것이 잘 진행되면 화면에 움직이는 컬러 패턴이 표시됩니다. 텍스트
모드 데모(hello.c와 sieve.c)도 여전히
동작하며, 이제 화면에 텍스트를 표시한다는 점에 유의하십시오. 이 밖에도
가지고 놀 수 있는 프로그램들이 있습니다:
(검은 대각선 줄무늬는 디스플레이
갱신 때문에 생긴 것이며, 평소에는 보이지 않습니다).
| 프로그램 | 설명 |
|---|---|
ASM_EXAMPLES/test_OLED.S |
움직이는 패턴을 표시합니다. |
ASM_EXAMPLES/mandelbrot_OLED.S |
망델브로 집합을 표시합니다. |
EXAMPLES/cube.c |
회전하는 3D 정육면체를 표시합니다. |
EXAMPLES/mandelbrot.c |
망델브로 집합을 표시합니다(C 버전). |
EXAMPLES/riscv_logo.c |
RISCV 로고를 사용한 로토줌입니다(90년대로 돌아간 느낌입니다). |
EXAMPLES/spirograph.c |
회전하는 사각형들입니다. |
EXAMPLES/gfx_test.c |
움직이는 패턴을 표시합니다(C 버전). |
EXAMPLES/gfx_demo.c |
그래픽 함수 데모입니다(오래전부터 해 오신 분들, EGAVGA.bgi를 기억하십니까?). |
EXAMPLES/test_font_OLED.c |
폰트 렌더링을 테스트합니다. |
EXAMPLES/sysconfig.c |
femtosoc 및 femtorv 구성을 표시합니다. |
| 더 큰 예제(IceStick에는 들어가지 않는 것) | |
EXAMPLES/imgui_xxxx.c |
ImGui challenge에서 가져온 몇 가지 포트입니다. |
EXAMPLES/mandelbrot_float_OLED.c |
망델브로 집합을 표시합니다(gcc의 소프트웨어 FP를 사용하는 부동소수점 버전). |
EXAMPLES/tinyraytracer.c |
TinyRaytracer의 포트입니다. |
LIBFEMTORV32 라이브러리에는 기본적인 폰트 렌더링, 2D 다각형 클리핑, 2D 다각형 채우기 루틴이 포함되어 있습니다.
SD 카드의 파일 접근
다시 한번 말씀드리지만, @ultraembedded의 fat io lib는 정말
훌륭한 소프트웨어입니다. 단 몇 개의 .c 파일만으로 독립적인
라이브러리와 fopen(), fread(),
fwrite(), fclose()의 구현을 제공합니다. 이를
사용하여 ST-NICCC라는
Y2K 데모를 포트하는 방법을 살펴봅시다. 이 데모는 파일에 저장된, 미리
계산된 2D 다각형 스트림을 사용합니다. 데모를 생성하려면 먼저 (원본
ST-NICCC 데모의) FIRMWARE/EXAMPLES/DATA/scene1.dat 파일을
SD 카드에 복사하십시오. 그런 다음 실행 파일을 생성하십시오:
$ cd FIRMWARE/EXAMPLES
$ make ST_NICCC.elf
그런 다음 ST_NICCC.elf를 SD 카드에 복사하고, SD 카드를
ULX3S에 삽입한 뒤 재시작하십시오. 이어서 위/아래 버튼으로
ST_NICCC.elf를 선택하고 오른쪽 버튼으로 실행하십시오.
참고: SPI 플래시(FPGA 구성을 저장하는 것과 같은 부품으로, 추가
데이터를 저장할 공간이 충분합니다)에서 데이터를 읽는
ST_NICCC_spi_flash.c 버전도 있습니다. 이 버전은 IceStick
버전에서 사용됩니다. ULX3S에서도 동작합니다(소스의 안내를
참조하십시오).
not-HDMI와 FGA(Femto Graphics Adapter)에 대한 추가 설명
FIRMWARE/EXAMPLES에는 사용할 수 있는 여러 프로그램이
있습니다:
| 프로그램 | 설명 |
|---|---|
EXAMPLES/test_FGA.c |
움직이는 컬러 패턴을 표시합니다. |
EXAMPLES/tinyraytracer.c |
TinyRaytracer의 포트입니다. |
EXAMPLES/ST_NICCC.c |
ST_NICCC 데모를 포팅한 것입니다 |
cd FIRMWARE/EXAMPLES를 실행한 다음
make xxx.elf로 컴파일하십시오. 여기서 xxx는
프로그램 이름입니다. 생성된 .elf 실행 파일을 SD 카드에
복사하십시오. HDMI 모니터를 ULX3S에 연결하십시오. 위아래 버튼으로
프로그램을 선택하고 오른쪽 버튼으로 실행하십시오. SD 카드 근처에 있는
버튼이 리셋 버튼입니다.
직접 프로그램을 작성하는 것도 매우 쉽습니다.
FGA_setpixel(X,Y,R,G,B)를 사용하기만 하면 됩니다. 여기서
X와 Y는 좌표(해상도는 320x200)이고,
R,G,B는 각각 0~255 범위의 색상
성분입니다.
애니메이션을 만들려면 그래픽 메모리에 직접 써야 할 수도 있습니다.
그래픽 메모리는 FGA_BASEMEM 주소에서 시작합니다. 각 픽셀은
16비트로 인코딩됩니다. GL_RGB 매크로는 r, g, b 성분(각각
0에서 255 사이)으로부터 픽셀을 인코딩합니다. FGA_setpixel
함수는 다음과 같이 작성되어 있습니다:
static inline FGA_setpixel(int x, int y, uint8_t R, uint8_t G, uint8_t B) {
((uint16_t*)FGA_BASEMEM)[320*y+x] = GL_RGB(R,G,B);
}
최적화된 그래픽 프리미티브를 직접 구현하려 할 때 필요한 모든 내용이 여기에 담겨 있습니다. GL_RGB 매크로는 각 성분을 시프트하여 결합합니다. 작은 OLED 화면과 동일한 인코딩을 사용합니다.
에필로그
계속됩니다 / 다음에 이어집니다 (더 깔끔한 FemtOS, 특권 명령어, 시스템 콜, 부동소수점 명령어, PS/2 마우스 및 키보드, ImGui 포트 등...)