IceBreaker 튜토리얼

이 튜토리얼에서는 FPGA 개발 도구를 설치하고, RISC-V 코어를 합성하고, 프로그램을 컴파일하여 설치한 뒤 IceBreaker에서 실행하는 방법을 안내합니다. IceBreaker에는 소형 FPGA(Ice40UP5k)가 탑재되어 있습니다. 5K개의 로직 엘리먼트를 갖추고 있습니다. IceStick의 Ice40HX1K보다 지원하는 최대 주파수는 낮지만, 다음과 같은 여러 장점이 있습니다.

작은 OLED 화면, VGA, HDMI 등의 장치를 연결하려면 사진처럼 PMOD 커넥터 두 개(PMOD1A와 PMOD1B)를 납땜해야 합니다. PMOD2는 보드에서 분리할 수 있는 오른쪽 부분의 버튼 및 LED와 공유된다는 점에 유의하십시오.

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

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

(선택 사항) femtosoc 및 femtorv32 설정

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

이 파일에서는 만들 RISC-V 프로세서의 종류를 정의할 수 있습니다. IceBreaker에서는 femtorv32-gracilis(RV32IMC)를 사용합니다.

learn-fpga/FemtoRV/RTL/CONFIGS/icebreaker_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 터미널 연결로 시스템과 통신하기 위한 것)를 활성화합니다. Ice40up5K에서 사용할 수 있는 128킬로바이트의 싱글 포트 RAM을 사용합니다. OLED 화면이나 LED 매트릭스 화면이 없다면 파일에서 해당 줄을 주석 처리하면 됩니다.

파일에는 다른 옵션도 있습니다. 보통은 이 옵션들을 변경할 필요가 없습니다. 궁금하시다면 아래 목록을 참고하십시오:

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

기본값(25MHz)은 Yosys/NextPNR에서 검증된 값입니다. 원한다면 약간의 오버클로킹을 시도해 볼 수 있으며, 30 - 35 MHz까지 올려 볼 수 있습니다(다만 그 경우 안정성 문제가 발생할 수 있습니다). 오버클로킹 수준을 조금(약간의 비트만큼) 올린다고 생각하시면 됩니다. 주파수는 RTL/PLL/frequencies.txt에 나열된 몇 가지 미리 정의된 값만 사용할 수 있다는 점에 유의하십시오. 필요하다면 그곳에 원하는 값을 추가할 수 있지만, 그 경우 RTL/PLL/gen_pll.sh를 사용하여 PLL용 보드별 Verilog를 다시 생성해야 합니다.

합성 및 프로그래밍

이제 디자인을 합성하여 장치로 전송할 수 있습니다. 장치를 USB 포트에 연결한 다음, 아래 명령을 실행하십시오:

$make ICEBREAKER

처음 실행하면 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의 PMOD1A 커넥터에 꽂으십시오(그림에는 IceBreaker 대신 IceStick이 나와 있으니, 모든 PMOD에서 동일한 핀 번호를 참고하십시오).

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

FemtoSOC 구성

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

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

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

$make ICEBREAKER

이제 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 어셈블리로 LED 매트릭스에 이미지 두 개를 표시합니다
EXAMPLES/life_led_matrix.c 8x8 토러스 세계에서 동작하는 생명 게임

직접 프로그램을 작성하고 싶다면: C에서는 먼저 MAX7219_init()을 사용하여 디스플레이를 켜야 하며, 그다음 함수 MAX7219(col,data)를 사용할 수 있습니다. 여기서 col은 1..8 범위의 열 인덱스이고(0..7이 아닙니다!!!), data는 어떤 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에 연결하기만 하면 됩니다. 전선 색상이 일치하지 않는다면 오른쪽 회로도를 보고 어떤 전선을 어디에 연결할지 확인하십시오(그림에는 IceBreaker 대신 IceStick이 나와 있으니, 모든 PMOD에서 동일한 핀 번호를 참고하십시오).

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

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

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

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

$make ICEBREAKER

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

$ 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

고전적인 반짝이는 구체와 체커보드 장면을 표시합니다. 약 3분 정도 걸리니 인내심을 가지십시오! 우리 프로세서에는 아직 FPU가 없습니다(IceBreaker에는 아마 하나를 넣을 수 있을 것이며, 앞으로 시도해 볼 예정입니다).