IceStick 튜토리얼

이 튜토리얼에서는 FPGA 개발 도구를 설치하고, RISC-V 코어를 합성하고, 프로그램을 컴파일 및 설치하여 IceStick에서 실행하는 방법을 설명합니다. 이를 통해 가장 저렴한 FPGA 장치 중 하나(약 40달러)로 FPGA 설계와 RISC-V를 경험할 수 있습니다.

오픈 소스 FPGA 개발 툴체인 설치

시작하기 전에 오픈 소스 FPGA 개발 툴체인(Yosys, NextPNR 등)을 설치해야 합니다. 설치 방법은 여기에 나와 있습니다.

(선택 사항) femtosoc 및 femtorv32 설정

learn-fpga/FemtoRV/RTL/CONFIGS/icestick_config.v 파일을 편집하십시오.

이 파일에서 만들 RISC-V 프로세서의 종류를 정의할 수 있습니다. IceStick에서는 femtorv32-quark를 사용합니다. 이 프로세서는 LUT 사용량이 최소이며 SPI 플래시에서 코드를 실행할 수 있습니다. 이 프로세서는 LUT 수가 적고(1380개) BRAM이 매우 작은(8 kB이지만 2 kB는 레지스터에 쓰이므로 RAM으로 사용할 수 있는 것은 6 kB뿐인) IceStick에 가장 적합합니다.

learn-fpga/FemtoRV/RTL/CONFIGS/icestick_config.v 파일에서는 해당 시스템 온 칩에 포함할 장치 드라이버도 선택할 수 있습니다.

장치 설명 비고
NRV_IO_LEDS LED 5개 그대로 두십시오
NRV_IO_UART USB를 통한 시리얼 연결 그대로 두십시오
NRV_IO_SSD1351 소형 OLED 화면(128x128) 없으면 주석 처리하십시오
NRV_IO_SSD1331 소형 OLED 화면(96x64) 없으면 주석 처리하십시오
NRV_IO_MAX7219 LED 매트릭스(10x10) 없으면 주석 처리하십시오
NRV_MAPPED_SPI_FLASH 메모리 공간에 매핑된 플래시 그대로 두십시오

LED(시각적 디버깅용)와 UART(USB 터미널 연결을 통해 시스템과 통신하기 위한 용도)를 활성화합니다. RAM은 6144바이트를 사용합니다. 많은 양은 아니지만, IceStick에서는 이보다 더 늘릴 수 없습니다. 6k의 RAM으로도 멋지고 흥미로운 RISC-V 데모를 충분히 프로그래밍할 수 있다는 것을 알게 되실 것입니다. OLED 화면이나 LED 매트릭스 화면이 없다면, 파일에서 해당 줄을 주석 처리하면 됩니다.

파일에는 다른 옵션도 있습니다. 보통은 이를 변경할 필요가 없습니다. 궁금하시다면 아래에 정리해 두었습니다:

옵션 설명
NRV_FREQ 프로세서 주파수(MHz 단위)
NRV_RAM RAM 용량(최대 6KB)
NRV_RESET_ADDR 시작 및 리셋 시 점프할 주소(SPI 플래시 + 64KB 오프셋)
NRV_IO_HARDWARE_CONFIG 코드에서 조회하여 구성된 장치를 확인할 수 있는 하드웨어 레지스터
NRV_RUN_FROM_SPI_FLASH BRAM을 펌웨어로 초기화하지 않음(펌웨어는 SPI 플래시에서 읽음)

기본값(66 MHz)은 Yosys/NextPNR에서 검증된 값에 해당합니다. 원한다면 80 - 90 MHz까지 약간의 오버클로킹을 시도해 볼 수 있습니다(다만 그 경우 안정성 문제가 생길 수 있습니다). 오버클로킹으로 1~2 비트 정도의 여유를 얻는다고 생각하면 됩니다. 주파수는 RTL/PLL/frequencies.txt에 나열된 미리 정의된 값만 사용할 수 있다는 점에 유의하십시오. 필요하다면 그곳에 원하는 값을 추가할 수 있지만, 그 경우 RTL/PLL/gen_pll.sh를 사용하여 PLL용 보드별 Verilog를 다시 생성해야 합니다.

IceStick에서는 femtorv32-quark 구성을 사용합니다. 이 구성은 일부 기능(하드웨어 곱셈, 카운터)을 비활성화하고 약간 더 느리지만(더 높은 CPI를 대가로 단순화된 상태 기계를 사용합니다), 비트스트림과 함께 저장된 코드를 SPI 플래시에서 직접 실행할 수 있는 능력이 있습니다. IceStick에는 큰 프로그램을 저장하는 데 사용할 수 있는 2MB의 SPI 플래시가 있으므로, 이 기능이 매우 유용합니다.

합성 및 프로그래밍

이제 설계를 합성하여 장치로 전송할 수 있습니다. 장치를 USB 포트에 연결한 후 다음을 실행하십시오:

$make ICESTICK

처음 실행하면 RISC-V 개발 도구를 다운로드합니다(시간이 좀 걸립니다).

이제 터미널 에뮬레이터를 설치하십시오:

$sudo apt-get install python3-serial

(또는 sudo apt-get install screen을 사용해도 되며, 둘 다 동작합니다). 출력을 보려면 (터미널 에뮬레이터를 사용하여) 장치에 연결해야 합니다:

$make terminal

(python3-serial 대신 screen을 설치했다면, 먼저 Makefile을 그에 맞게 수정하십시오. 그곳에서 ttyUSBnnn도 변경해야 할 수 있습니다).

... 하지만 아무 일도 일어나지 않습니다. 이는 정상입니다. 아직 시스템에 어떤 프로그램도 로드하지 않았기 때문입니다.

종료하려면 <ctrl> ](python-3-serial/miniterm)를 누르거나, <ctrl> a를 누른 다음 '\'(screen)를 누르십시오.

시리얼 터미널(UART)을 사용하는 예제

FIRMWARE/EXAMPLES 및 FIRMWARE/ASM_EXAMPLES 디렉터리에는 장치에서 실행할 수 있는 C 및 어셈블리 프로그램이 들어 있습니다. IceStick에서는 6K 이하를 사용하는 프로그램만 동작합니다(아래 목록 참조).

프로그램을 컴파일하려면 다음을 실행하십시오:

$cd FIRMWARE/EXAMPLES
$make hello.prog

.prog 타깃은 프로그램을 생성하고 iceprog를 사용하여 장치의 SPI 플래시로 전송합니다. 결과를 보려면 다음을 사용하십시오:

$cd ../..
$make terminal

직접 실습해 볼 수 있는 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 소수를 계산합니다

LED 매트릭스를 사용하는 예제

더 재미있게 즐기려면 8x8 LED 매트릭스를 추가할 수 있습니다. 가격이 저렴하고(1달러 미만) 구하기도 쉽습니다(max7219 8x8 led matrix로 구글에서 검색하기만 하면 됩니다). 핀 레이블(CLK,CS,DIN,GND,VCC)이 이미지와 일치하는지 확인한 다음, 이미지에 나온 대로 IceStik의 J2 커넥터에 꽂으십시오. OysterPMOD를 만들 수도 있습니다(PMOD 커넥터 하나로 LED 매트릭스와 OLED 화면을 모두 사용합니다).

FemtoSOC 구성

이제 LED 매트릭스에 대한 하드웨어 지원을 활성화해야 합니다(그리고 UART는 비활성화해야 합니다). 이를 위해 FemtoRV/RTL/CONFIGS/icestick_config.v에서 장치를 다음과 같이 구성하십시오:

...
`define NRV_IO_MAX7219    // Mapped IO, 8x8 led matrix
...

그런 다음 다시 합성하고 비트스트림을 IceStick으로 전송해야 합니다:

$make ICESTICK

이제 hello world 프로그램(FIRMWARE/EXAMPLES/hello.c)을 컴파일할 수 있습니다. 파일을 편집하여 다음 줄의 주석을 해제하십시오:

femtosoc_tty_init();

이 줄은 printf()를 구성된 장치(여기서는 LED 매트릭스)로 자동으로 리디렉션합니다. 이제 프로그램을 컴파일하여 장치로 전송하십시오:

$cd FIRMWARE/EXAMPLES
$make hello.prog

LED 매트릭스가 구성되어 있으면 printf()는 스크롤러 디스플레이 루틴으로 자동으로 리디렉션됩니다. sieve.c 프로그램도 같은 방식으로 동작합니다.

다음과 같은 다른 예제도 사용해 볼 수 있습니다:

프로그램 설명
ASM_EXAMPLES/test_led_matrix.S ASM으로 LED 매트릭스에 두 개의 이미지를 표시합니다
EXAMPLES/life_led_matrix.c 8x8 토러스 형태의 세계에서 실행되는 생명 게임

직접 프로그램을 작성하고 싶다면, C에서는 먼저 MAX7219_init()을 사용해 디스플레이를 켜야 하며, 그다음 함수 MAX7219(col,data)를 사용할 수 있습니다. 여기서 col은 1..8 범위의 열 인덱스이고(0..7이 아닙니다 !!!), 데이터는 어떤 LED를 켤지 나타내는 8비트 정수입니다. 참고로 FIRMWARE/EXAMPLES/life_led_matrix.c를 살펴보십시오.

OLED 화면을 사용하는 예제

64개의 픽셀로 이루어진 우리의 LED 매트릭스는 다소 제한적이고 색상도 없습니다... 좀 더 멋진 그래픽을 만들어 봅시다. 이를 위해서는 SSD1351 128x128 OLED 디스플레이가 필요합니다. 가격은 약 15달러입니다(ST7789로 구동되는 240x240 IPS 화면처럼 더 저렴한 화면도 있지만, 보기에 정말 그만큼 좋지 않고 호환되지도 않습니다. 제 말을 믿으십시오. SSD1351은 그 가격만큼의 가치가 있습니다). 반드시 품질이 좋은 제품을 구하십시오(5달러보다 저렴하다면 저는 의심해 볼 것입니다. 그런 저가형 제품에서 고장이 났다고 보고한 사용자들이 있습니다). 저는 Waveshare에서 구입했습니다. Adafruit 제품도 잘 동작한다는 보고가 있습니다.

이 작은 화면에는 전선 7개가 필요합니다. 좋은 소식은 납땜이 필요 없다는 점입니다. 사진에 있는 것과 같은 2x6 핀 커넥터를 구해서, 그림처럼 전선을 커넥터에 연결한 다음 커넥터를 IceStick에 연결하기만 하면 됩니다. 전선 색상이 일치하지 않으면 오른쪽의 회로도를 보고 어느 전선이 어디로 가는지 확인하십시오.

OysterPMOD를 만들 수도 있습니다(PMOD 커넥터 하나로 LED 매트릭스와 OLED 화면을 모두 사용합니다).

이제 icestick_config.v를 다음과 같이 다시 설정해야 합니다:

...
`define NRV_IO_SSD1351    // Mapped IO, 128x128x64K OLed screen
...

그런 다음 다시 합성하고 비트스트림을 IceStick으로 전송해야 합니다:

$make ICESTICK

테스트 프로그램을 컴파일해 봅시다:

$ cd FIRMWARE/EXAMPLES
$ make gfx_test.prog

모든 것이 잘 되면 화면에 움직이는 컬러 패턴이 나타납니다. 텍스트 모드 데모(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 구성을 표시합니다.

LIBFEMTORV32 라이브러리에는 기본적인 폰트 렌더링, 2D 다각형 클리핑, 2D 다각형 채우기 루틴이 포함되어 있습니다. 이 모든 것이 사용 가능한 6KB 메모리 안에 들어갑니다!

SPI 플래시에 데이터 저장하기

IceStick으로 할 수 있는 것은 이것이 전부입니까? 아닙니다. 더 많은 것을 할 수 있습니다! ST-NICCC라는 Y2K 데모를 포트하는 방법, 즉 다른 플랫폼으로 포팅하는 방법을 살펴봅시다. 이러한 3D 그래픽은 우리의 펨토 머신으로는 실시간으로 렌더링할 수 없으므로(Atari ST의 8MHz 68000도 마찬가지였습니다!), 파일에 저장된 미리 계산된 2D 다각형 스트림을 렌더링합니다. 이 파일의 크기는 640KB인데, 기억하시다시피 우리에게는 6KB밖에 없습니다. 그러면 어떻게 해야 합니까? SD 카드 어댑터를 연결하고 파일을 그곳에 저장할 수도 있지만, 훨씬 더 쉬운 해결책이 있습니다. IceStick은 FPGA의 구성을 플래시 메모리에 저장하는데, 그 안에는 사용되지 않는 공간이 충분히 있습니다. 칩 하나에 다 들어가지 않으면, 옆 칩으로 넘치게 하면 됩니다! 이 플래시 메모리는 다리가 8개인 작은 칩으로, 직렬 프로토콜(SPI)을 사용해 외부와 통신합니다. 사실 우리는 이미 이것을 사용하고 있습니다! 우리의 프로그램이 이곳에 저장되며, FemtoRV32는 프로그램을 이곳에서 직접 실행합니다. 여기에 데이터도 저장할 것입니다.

데이터를 SPI 플래시에 복사합시다:

$ iceprog -o 1M FIRMWARE/EXAMPLES/DATA/scene1.bin

이 명령은 1MB 오프셋부터 데이터를 복사합니다(그보다 낮은 주소는 FPGA의 구성과 우리의 프로그램을 저장하는 데 사용되므로 덮어쓰지 마십시오). 데이터 파일 scene1.bin은 ST_NICCC 데모에서 가져온 원본 파일입니다.

이제 데모 프로그램을 컴파일하여 IceStick으로 전송할 수 있습니다:

$ cd FIRMWARE/EXAMPLES
$ make ST_NICCC_spi_flash.prog

이 작은 시스템으로 더 많은 것을 할 수 있습니까? 네, 레이트레이싱도 할 수 있습니다!

$ cd FIRMWARE/EXAMPLES
$ make tinyraytracer.prog

고전적인 반짝이는 구체와 체크무늬 바닥 장면을 표시합니다. 약 20분 정도 걸리니 인내심을 가지십시오! 이는 프로세서가 그다지 빠르지 않을 뿐만 아니라 하드웨어 곱셈 기능도 없고, 부동소수점 소프트웨어 루틴을 SPI 플래시에서 실행하기 때문입니다! 이 루틴들을 RAM에 복사하면 더 빠르겠지만, 공간이 부족해 들어가지 않습니다(기억하시다시피 RAM은 6KB밖에 없습니다).

더 작은 프로그램이라면 루틴 일부를 RAM에 올릴 수 있습니다. FIRMWARE/EXAMPLES/riscv_logo_2.c를 살펴보십시오(LED 매트릭스와 SSD 화면이 모두 있으면 프로그램이 무엇을 하고 있는지 알려 줍니다). 이 프로그램은 마법의 키워드 RV32_FASTCODE의 효과를 보여 줍니다. 이 키워드는 어노테이션으로 확장되어, 해당 함수를 SPI 플래시 대신 RAM에 복사해야 한다는 것을 링커에게 알려 줍니다. RAM은 SPI 플래시보다 접근 속도가 훨씬 빠르므로, 자주 호출되는 일부 작은 함수에 대해 이렇게 할 수 있습니다. 예를 들어 우리 버전의 ST_NICCC 데모에서 사용하는 일부 그래픽 함수가 이에 해당합니다.

하지만 우리의 작은 시스템은 금방 한계에 부딪힙니다. 더 나아가고 싶다면 ULX3S를 구하는 것이 손쉬운 방법입니다. 가격이 조금 더 비싸지만($130) 그만한 가치가 있습니다(보드에 탑재된 ECP5 FPGA는 IceStick의 FPGA에 비해 비트 수준에서도 엄청나게 큽니다). 이제 ULX3S 튜토리얼을 읽어 볼 차례입니다!

이해를 돕기 위해 덧붙이면, 이 이미지는 우리 코어를 ICE40HX1K(IceStick에 탑재된 FPGA)와 ECP5 85K(ULX3S에 탑재된 FPGA)에 각각 설치한 모습을 보여 줍니다. ULX3S에는 공간이 넉넉합니다!