FPGAを使ったRISC-V SoC(第1回)環境構築~Linux起動

FPGAを使ったRISC-V SoC(第1回)
環境構築~Linux起動

はじめに

RISC-Vの充実したOSSエコシステムを使えば、ソフトウェアエンジニアでも手軽に独自CPU(RISC-V softcore processor)/SoCを作ることができます。

ここでは、弊社がEdgeTech+ West 2023でデモ展示を行った、Litex(+Migen)によるFPGA SoCの合成、BuildrootによるLinuxディストリビューションの構築方法を解説します。

全体の流れ

  • 開発環境の構築
  • LitexでRisc-VSoCのbitstream生成とDeviceTree生成
  • Linuxディストリビューションの生成
  • RISC-V向けSBI(Supervisor Binary Interface)の生成

使用機材

  • ubuntu 20.04(メモリ8G、ストレージ256G以上)
  • Arty A7-100T
  • SDカード
  • Pmod MicroSD

開発環境の構築

必要なパッケージのインストール

$ sudo apt-get update
$ sudo apt-get install openocd dtc fakeroot perl-bignum json-c-devel verilator ¥
                 python3-devel python3-setuptools libevent-devel ¥
                 libmpc-devel mpfr-devel meson expat-devel ¥
                 gcc-10 texinfo bison flex expat libexpat-dev ¥
                 g++ m4 zlib1g-dev make p7zip libgmp-dev python3-pip curl
$ pip install migen
$ pip install -U meson 

Xilinx開発ツール(Vivado ML エディション 2022.2)のインストール

AMDのダウンロードページよりダンロードしてインストールする。

環境変数を登録

$ export PATH=$PATH:/tools/Xilinx/Vivado/2022.2/bin 

※˜/.bashrcへ記載することで、起動時に自動的にPATH設定を行うことが可能

RISC-V GNU Compiler Toolchainのインストール

インストール先のディレクトリを作成

$ cd /opt/
$ sudo mkdir riscv
$ sudo chmod 777 riscv
$ cd ˜ 

作成したディレクトリにToolchainをインストール

$ git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
$ pushd riscv-gnu-toolchain
$ ./configure --prefix=/opt/riscv --enable-multilib
$ make newlib linux
$ popd
$ export PATH=$PATH:/opt/riscv/bin 

※˜/.bashrcへ記載することで、開発環境起動時に自動的にPATH設定を行うことが可能

linux-on-litex-vexriscvとLitexの開発環境のインストール

linux-on-litex-vexriscvのインストール

$ mkdir litex-vexriscv
$ cd litex-vexriscv
$ git clone https://github.com/litex-hub/linux-on-litex-vexriscv
$ cd linux-on-litex-vexriscv 

Litexのインストール

$ wget https://raw.githubusercontent.com/enjoy-digital/litex/master/litex_setup.py
$ chmod +x litex_setup.py
$ ./litex_setup.py --init --install --user --tag 2023.04 

※issues 1717(https://github.com/enjoy-digital/litex/issues/1717)によりbitstreamの生成時にエラーとなるためtagを固定

bitstreamの生成

Arty A7 bitstreamの生成

$ ./make.py --board=arty_a7 --variant=a7-100 --build --cpu-count 2 

以下のファイルが生成されたら成功

生成物ファイル
デバイスツリーbuild/arty_a7/arty_a7.dts
build/arty_a7/arty_a7.dtb
FPGA bitstreambuild/arty_a7/gateware/arty_a7.bit

bitstreamをFPGA書き込み用のファイルフォーマット(mcs)に変換

$ cd build/arty_a7/gateware/
$ cat > arty_a7_mcs.tcl <<EOF
write_cfgmem -force -format mcs -interface spix4 -size 16 -loadbit "up 0x0 arty_a7.bit" -file arty_a7.mcs
EOF
$ vivado -mode batch -source arty_a7_mcs.tcl

Linuxディストリビューションの生成

buildrootセットアップ

Buildroot - Making Embedded Linux Easyから取得し展開

$ cd ˜
$ wget https://buildroot.org/downloads/buildroot-2023.05.1.tar.gz
$ tar xzvf buildroot-2023.05.1.tar.gz
$ mv buildroot-2023.05.1 buildroot
$ cd buildroot 

コンフィグレーションの読み込み

$ make BR2_EXTERNAL=<Litexの開発環境のインストールディレクトリ>/linux-on-litex-vexriscv/buildroot/ litex_vexriscv_defconfig

注)フォルダとファイル名の間のスペースは必須

Linuxカーネルとアプリ層で使用するヘッダファイルのバージョンに差異があるためコンフィグレーションを変更

$ make menuconfig
  -> Toolchain
    -> Custom kernel headers series[6.1.x] 

ビルドの実行

$ make 

以下のファイルが生成されたら成功

生成物ファイル
Linux カーネルoutput/images/Image
rootファイルoutput/images/rootfs.cpio
output/images/rootfs.tar

RISC-V向けSBI(Supervisor Binary Interface)の生成

opensbiを取得

$ cd ˜
$ git clone https://github.com/litex-hub/opensbi --branch 0.8-linux-on-litex-vexriscv
$ cd opensbi 

環境変数の変更
platform/litex/vexriscv/config.mk

PLATFORM_RISCV_ISA = rv32ima_zicsr_zifencei

ビルドの実行

$ make CROSS_COMPILE=riscv64-unknown-elf- PLATFORM=litex/vexriscv 

※習得したソースコードではmoddi3やdivdi3でコンパイルエラーが出るため、「./lib/sbi/sbi_console.c」に公開されている以下のコードをインクルードすることで対応した。
https://raw.githubusercontent.com/glitchub/arith64/master/arith64.c

以下のファイルが生成されたら成功

生成物ファイル
fw_jump.binbuild/platform/litex/vexriscv/firmware/fw_jump.bin

起動メディアの作成

SDカードに起動イメージを書き込み

$ sudo fdisk /dev/sdb
デバイス 開始位置 最後から セクタ サイズ タイプ
/dev/sdb1 2048 307199 305152 149M Linux ファイルシステム
/dev/sdb2 307200 61863902 61556703 29.4G Linux ファイルシステム

$ sudo mkfs.vfat /dev/sdb1
$ sudo mkfs.ext4 /dev/sdb2

$ sudo mount /dev/sdb1 /mnt
$ sudo cp ˜/buildroot/output/images/Image /mnt/.
$ sudo cp ˜/litex-vexriscv/linux-on-litex-vexriscv/build/arty_a7/arty_a7.dtb /mnt/.
$ sudo cp ˜/buildroot/output/images/rootfs.cpio /mnt/.
$ sudo cp ˜/opensbi/build/platform/litex/vexriscv/firmware/fw_jump.bin /mnt/.
$ cat > ˜/litex-vexriscv/linux-on-litex-vexriscv/images/boot.json <<EOF
{
    "Image":       "0x40000000",
    "arty_a7.dtb":    "0x40ef0000",
    "rootfs.cpio": "0x41000000",
    "fw_jump.bin": "0x40f00000"
}
EOF
$ sudo cp ˜/litex-vexriscv/linux-on-litex-vexriscv/images/boot.json /mnt/. 

bitstreamの書き込み

Vivadoを起動し、USBケーブルで接続する。
以下の画面通りに設定

bitstreamの生成において準備したmcsをConfiguration fileとして指定し、bitstearmを書き込む。

起動確認

起動メディアの作成において準備したSDカードをPmod MicroSDに挿入し、ターゲットの12ピンコネクタ(JD) に指し、ターゲットとPCをUSBケーブルで接続する。
TeraTerm等でシリアルコンソールへアクセスすると、Linuxが起動していることが確認できる。

起動時のコンソール出力

       / / (_) /____ | |/_/
      / /__/ / __/ -_)> <
     /____/_/¥__/¥__/_/|_|
   Build your hardware, easily!

 (c) Copyright 2012-2023 Enjoy-Digital
 (c) Copyright 2007-2015 M-Labs

 BIOS CRC passed (cb03754a)

 LiteX git sha1: 3ab7ebe5

--=============== SoC ==================--
CPU:            VexRiscv SMP-LINUX @ 100MHz
BUS:            WISHBONE 32-bit @ 4GiB
CSR:            32-bit data
ROM:            64.0KiB
SRAM:           6.0KiB
FLASH:          16.0MiB
Couldn't read SDRAM size from the SPD, defaulting to 256 MB.
SDRAM:          256.0MiB 16-bit @ 800MT/s (CL-7 CWL-5)
MAIN-RAM:       256.0MiB

--========== Initialization ============--
Ethernet init...
Initializing SDRAM @0x40000000...
Switching SDRAM to software control.
Read leveling:
  m0, b00: |00000000000000000000000000000000| delays: -
  m0, b01: |00000000000000000000000000000000| delays: -
  m0, b02: |11111111111110000000000000000000| delays: 06+-06
  m0, b03: |00000000000000001111111111111000| delays: 21+-06
  m0, b04: |00000000000000000000000000000000| delays: -
  m0, b05: |00000000000000000000000000000000| delays: -
  m0, b06: |00000000000000000000000000000000| delays: -
  m0, b07: |00000000000000000000000000000000| delays: -
  best: m0, b03 delays: 21+-06
  m1, b00: |00000000000000000000000000000000| delays: -
  m1, b01: |00000000000000000000000000000000| delays: -
  m1, b02: |11111111111110000000000000000000| delays: 06+-06
  m1, b03: |00000000000000001111111111111000| delays: 22+-06
  m1, b04: |00000000000000000000000000000000| delays: -
  m1, b05: |00000000000000000000000000000000| delays: -
  m1, b06: |00000000000000000000000000000000| delays: -
  m1, b07: |00000000000000000000000000000000| delays: -
  best: m1, b03 delays: 22+-06
Switching SDRAM to hardware control.
Memtest at 0x40000000 (2.0MiB)...
  Write: 0x40000000-0x40200000 2.0MiB
   Read: 0x40000000-0x40200000 2.0MiB
Memtest OK
Memspeed at 0x40000000 (Sequential, 2.0MiB)...
  Write speed: 169.4MiB/s
   Read speed: 88.1MiB/s

Initializing S25FL128L SPI Flash @0x01000000...
Enabling Quad mode...
SPI Flash clk configured to 100 MHz
Memspeed at 0x1000000 (Sequential, 4.0KiB)...
   Read speed: 22.1MiB/s
Memspeed at 0x1000000 (Random, 4.0KiB)...
   Read speed: 4.6MiB/s

--============== Boot ==================--
Booting from serial...
Press Q or ESC to abort boot completely.
sL5DdSMmkekro
             Timeout
Booting from SDCard in SD-Mode...
Booting from boot.json...
Copying Image to 0x40000000 (7726264 bytes)...
[########################################]
Copying arty_a7.dtb to 0x40ef0000 (5862 bytes)...
[########################################]
Copying rootfs.cpio to 0x41000000 (3566592 bytes)...
[########################################]
Copying fw_jump.bin to 0x40f00000 (49544 bytes)...
[########################################]
Executing booted program at 0x40f00000

--============= Liftoff! ===============--

OpenSBI v0.8-2-ga9ce3ad
   ____                    _____ ____ _____
  / __ ¥                  / ____| _ ¥_ _|
 | |  | |_ __   ___ _ __ | (___ | |_) || |
 | | | | '_ ¥ / _ ¥ '_ ¥ ¥___ ¥| _ < | |
 | | | | |_) |  __/ | | |____) | |_) || |_
  ¥____/| .__/ ¥___|_| |_|_____/|____/_____|
        | |
        |_|

Platform Name       : LiteX / VexRiscv-SMP
Platform Features   : timer,mfdeleg
Platform HART Count : 8
Boot HART ID        : 0
Boot HART ISA       : rv32imas
BOOT HART Features  : time
BOOT HART PMP Count : 0
Firmware Base       : 0x40f00000
Firmware Size       : 120 KB
Runtime SBI Version : 0.2

MIDELEG : 0x00000222
MEDELEG : 0x0000b101
[    0.000000] Linux version 6.1.0-rc2 (developer@developer-VirtualBox) (riscv32-buildroot-linux-gnu-gcc.br_real (Buildroot 2023.02-267-g1ace31aec5) 11.3.0, GNU ld (GNU Binutils) 2.38) #1 SMP Wed Jul 12 17:52:12 JST 2023
[    0.000000] earlycon: liteuart0 at I/O port 0x0 (options '')
[    0.000000] Malformed early option 'console'
[    0.000000] earlycon: liteuart0 at MMIO 0xf0001000 (options '')
[    0.000000] printk: bootconsole [liteuart0] enabled
[    0.000000] Zone ranges:
[    0.000000]   Normal   [mem 0x0000000040000000-0x000000004fffffff]
[    0.000000] Movable zone start for each node
[    0.000000] Early memory node ranges
[    0.000000]   node   0: [mem 0x0000000040000000-0x000000004fffffff]
[    0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x000000004fffffff]
[    0.000000] SBI specification v0.2 detected
[    0.000000] SBI implementation ID=0x1 Version=0x8
[    0.000000] SBI TIME extension detected
[    0.000000] SBI IPI extension detected
[    0.000000] SBI RFENCE extension detected
[    0.000000] SBI HSM extension detected
[    0.000000] riscv: base ISA extensions aim
[    0.000000] riscv: ELF capabilities aim
[    0.000000] percpu: Embedded 8 pages/cpu s11732 r0 d21036 u32768
[    0.000000] Built 1 zonelists, mobility grouping on. Total pages: 65024
[    0.000000] Kernel command line: console=liteuart earlycon=liteuart,0xf0001000 rootwait root=/dev/ram0
[    0.000000] Dentry cache hash table entries: 32768 (order: 5, 131072 bytes, linear)
[    0.000000] Inode-cache hash table entries: 16384 (order: 4, 65536 bytes, linear)
[    0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
[    0.000000] Memory: 243304K/262144K available (5848K kernel code, 571K rwdata, 906K rodata, 215K init, 254K bss, 18840K reserved, 0K cma-reserved)
[    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=2, Nodes=1
[    0.000000] rcu: Hierarchical RCU implementation.
[    0.000000] rcu:     RCU restricting CPUs from NR_CPUS=32 to nr_cpu_ids=2.
[    0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
[    0.000000] rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=2
[    0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[    0.000000] riscv-intc: 32 local interrupts mapped
[    0.000000] plic: interrupt-controller@f0c00000: mapped 32 interrupts with 2 handlers for 4 contexts.
[    0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention.
[    0.000000] riscv-timer: riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [0]
[    0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x171024e7e0, max_idle_ns: 440795205315 ns
[    0.000015] sched_clock: 64 bits at 100MHz, resolution 10ns, wraps every 4398046511100ns
[    0.009906] Console: colour dummy device 80x25
[    0.013610] Calibrating delay loop (skipped), value calculated using timer frequency.. 200.00 BogoMIPS (lpj=400000)
[    0.023765] pid_max: default: 32768 minimum: 301
[    0.031331] Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.037697] Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear)
[    0.069453] ASID allocator using 9 bits (512 entries)
[    0.075391] rcu: Hierarchical SRCU implementation.
[    0.079151] rcu:     Max phase no-delay instances is 1000.
[    0.090641] smp: Bringing up secondary CPUs ...
[    0.105314] smp: Brought up 1 node, 2 CPUs
[    0.115009] devtmpfs: initialized
[    0.153306] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
[    0.162225] futex hash table entries: 512 (order: 3, 32768 bytes, linear)
[    0.187286] NET: Registered PF_NETLINK/PF_ROUTE protocol family
[    0.348629] pps_core: LinuxPPS API ver. 1 registered
[    0.352547] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
[    0.361937] PTP clock support registered
[    0.367876] FPGA manager framework
[    0.379906] clocksource: Switched to clocksource riscv_clocksource
[    0.562231] NET: Registered PF_INET protocol family
[    0.569375] IP idents hash table entries: 4096 (order: 3, 32768 bytes, linear)
[    0.587348] tcp_listen_portaddr_hash hash table entries: 512 (order: 0, 4096 bytes, linear)
[    0.595120] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear)
[    0.602710] TCP established hash table entries: 2048 (order: 1, 8192 bytes, linear)
[    0.610548] TCP bind hash table entries: 2048 (order: 3, 32768 bytes, linear)
[    0.617866] TCP: Hash tables configured (established 2048 bind 2048)
[    0.624573] UDP hash table entries: 256 (order: 1, 8192 bytes, linear)
[    0.630317] UDP-Lite hash table entries: 256 (order: 1, 8192 bytes, linear)
[    0.651349] Unpacking initramfs...
[    0.656516] workingset: timestamp_bits=30 max_order=16 bucket_order=0
[    0.819202] io scheduler mq-deadline registered
[    0.822765] io scheduler kyber registered
[    0.866989] No litex,nclkout entry in the dts file
[    0.872690] LiteX SoC Controller driver initialized
[    1.243069] Initramfs unpacking failed: invalid magic at start of compressed archive
[    1.323144] Freeing initrd memory: 8192K
[    1.966135] f0001000.serial: ttyLXU0 at MMIO 0x0 (irq = 0, base_baud = 0) is a liteuart
[    1.973515] printk: console [liteuart0] enabled
[    1.973515] printk: console [liteuart0] enabled
[    1.982171] printk: bootconsole [liteuart0] disabled
[    1.982171] printk: bootconsole [liteuart0] disabled
[    2.028834] liteeth f0002000.mac eth0: irq 2 slots: tx 2 rx 2 size 2048
[    2.037466] i2c_dev: i2c /dev entries driver
[    2.045935] i2c i2c-0: Not I2C compliant: can't read SCL
[    2.050310] i2c i2c-0: Bus may be unreliable
[    2.096250] litex-mmc f0009000.mmc: LiteX MMC controller initialized.
[    2.127548] NET: Registered PF_INET6 protocol family
[    2.149452] Segment Routing with IPv6
[    2.153212] In-situ OAM (IOAM) with IPv6
[    2.157033] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
[    2.172247] NET: Registered PF_PACKET protocol family
[    2.185301] Freeing unused kernel image (initmem) memory: 208K
[    2.190128] Kernel memory protection not selected by kernel config.
[    2.196533] Run /init as init process
[    2.277931] mmc0: new SDHC card at address 59b4
[    2.296847] mmcblk0: mmc0:59b4 USD   7.51 GiB
[    2.320867]  mmcblk0: p1 p2
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Saving 256 bits of non-creditable seed for next boot
Starting network: OK

Welcome to Buildroot
buildroot login:

おわり

次回は、Adafruit 2.8 TFT Touch Shield取り付けて液晶とタッチパネル操作にチャレンジします。

おまけ rootfsをinitrdからSDカードへ変更

SDカードにrootfsを展開

$ sudo mount /dev/sdb2 /mnt
$ sudo tar -vxf ~/buildroot/output/images/rootfs.tar -C /mnt/. 

デバイスツリー(DTB)に格納されているLinuxの起動パラメータ(cmdline)をSDカードへ変更

$ cd ˜/litex-vexriscv/linux-on-litex-vexriscv/
$ ../litex/litex/tools/litex_json2dts_linux.py --initrd disabled --root-device mmcblk0p2 ¥
build/arty_a7/csr.json > build/arty_a7/arty_a7_mmc.dts
$ dtc -O dtb -o build/arty_a7/arty_a7_mmc.dtb build/arty_a7/arty_a7_mmc.dts 

DTBを差し替える

$ sudo mount /dev/sdb1 /mnt
$ sudo cp ~/litex-vexriscv/linux-on-litex-vexriscv/build/arty_a7/arty_a7_mmc.dtb /mnt/. 

SDカードのboot.jsonを以下のように書き換える

{
    "Image": "0x40000000",
    "arty_a7_mmc.dtb": "0x40ef0000",
    "fw_jump.bin": "0x40f00000"
}