Ryuz's tech blog

FPGAなどの技術ブログ

ZynqMP 用の SDイメージを SD カードを使わずに作る考察

概要

以前、[こんな記事]https://ryuz.hatenablog.com/entry/2022/04/30/213444を書きましたが、その際、実 SD カードにイメージを作ってから dd コマンドでイメージを保存していました。

もちろんもっといいやり方はあるだろうなとは思っていたのですが、「Win32DiskImager 使えると便利」とか言ってる時点でもともと Linux には詳しくなく。

そんなときに下記の話を見かけたので、ちょっとだけ挑戦中です。

やってみたこと

まだ動作確認までしていないので備忘録レベルですが。

こんな感じでイメージ作れないかな?

#!/bin/bash

VERSION="2021.1.1"
TAG="v2021.1.1"
IMG_FILE="ultra96v2-debian-v2021.1.1.img"

TGZ_FILE="ZynqMP-FPGA-Linux-${TAG}.tar.gz"

# ZynqMP-FPGA-Linux-v2021.1.1.tar.gz
if [ ! -f $TGZ_FILE ]; then
#   wget -O $TGZ_FILE https://github.com/ikwzm/ZynqMP-FPGA-Linux/archive/refs/tags/$TAG.tar.gz
    wget -O $TGZ_FILE 'https://onedrive.live.com/download?cid=E643EA309C96C6F6&resid=E643EA309C96C6F6%2142121&authkey=ACAH_X1CZF8a6fU'
fi
tar zxvf $TGZ_FILE


DEV_LOOP=`sudo losetup -f`

rm -f $IMG_FILE
truncate -s 6GiB $IMG_FILE

sudo losetup $DEV_LOOP $IMG_FILE
sudo parted $DEV_LOOP -s mklabel msdos -s mkpart primary fat32 1048576B 315621375B -s mkpart primary ext4 315621376B 100% -s set 1 boot
sudo mkfs.vfat ${DEV_LOOP}p1
sudo mkfs.ext4 ${DEV_LOOP}p2

sudo mkdir -p /mnt/usb1
sudo mkdir -p /mnt/usb2
sudo mount ${DEV_LOOP}p1 /mnt/usb1
sudo mount ${DEV_LOOP}p2 /mnt/usb2

cd ZynqMP-FPGA-Linux-${VERSION}
sudo cp target/Ultra96-V2/boot/* /mnt/usb1

sudo tar xfz debian11-rootfs-vanilla.tgz -C /mnt/usb2
sudo mkdir /mnt/usb2/home/fpga/debian
sudo cp linux-image-5.4.0-xlnx-v2020.2-zynqmp-fpga_5.4.0-xlnx-v2020.2-zynqmp-fpga-3_arm64.deb     /mnt/usb2/home/fpga/debian
sudo cp linux-headers-5.4.0-xlnx-v2020.2-zynqmp-fpga_5.4.0-xlnx-v2020.2-zynqmp-fpga-3_arm64.deb   /mnt/usb2/home/fpga/debian
sudo cp fclkcfg-5.4.0-xlnx-v2020.2-zynqmp-fpga_1.7.2-1_arm64.deb                                  /mnt/usb2/home/fpga/debian
sudo cp u-dma-buf-5.4.0-xlnx-v2020.2-zynqmp-fpga_3.2.4-0_arm64.deb                                /mnt/usb2/home/fpga/debian
sudo cp linux-image-5.10.0-xlnx-v2021.1-zynqmp-fpga_5.10.0-xlnx-v2021.1-zynqmp-fpga-4_arm64.deb   /mnt/usb2/home/fpga/debian
sudo cp linux-headers-5.10.0-xlnx-v2021.1-zynqmp-fpga_5.10.0-xlnx-v2021.1-zynqmp-fpga-4_arm64.deb /mnt/usb2/home/fpga/debian
sudo cp fclkcfg-5.10.0-xlnx-v2021.1-zynqmp-fpga_1.7.2-1_arm64.deb                                 /mnt/usb2/home/fpga/debian
sudo cp u-dma-buf-5.10.0-xlnx-v2021.1-zynqmp-fpga_3.2.4-0_arm64.deb                               /mnt/usb2/home/fpga/debian

sudo mkdir /mnt/usb2/mnt/boot
sudo sh -c "cat <<EOT >> /mnt/usb2/etc/fstab
/dev/mmcblk0p1  /mnt/boot   auto    defaults    0   0
EOT"

sudo umount /mnt/usb1
sudo umount /mnt/usb2
sudo losetup -d $DEV_LOOP

(備忘録) SystemC インストール

SystemC を WSL2 の Ubuntu 20.04 に入れてみたのでメモ。

git clone -b 2.3.3 https://github.com/accellera-official/systemc.git
cd systemc

export CXX=g++

autoreconf .

mkdir objdir
cd objdir

../configure --prefix=$HOME/.opt/systemc-2.3.3

make -j
make check
make install

.bashrc に以下を追加

export SYSTEMC="$HOME/.opt/systemc-2.3.3"
export SYSTEMC_INCLUDE="$SYSTEMC/include"
export SYSTEMC_LIBDIR="$SYSTEMC/lib-linux64"

Kria KV260 の 認定Ubuntu で RPU (Cortex-R5) を認識させる

はじめに

タイトルそのままの内容です。

以前、Ultra96V2のDebianイメージで Cortex-R5 を認識させる - Qiita という記事を書きましたが、その Kria K260 版です。

今回は 認定Ubuntu の iot-kria-classic-desktop-2004-x03-20211110-98.img を使っております。

  • Description: Ubuntu 20.04.4 LTS
  • kernel: 5.4.0-1017-xilinx-zynqmp

DeviceTree を取得する

Twitter で教えていただいた方法ですが、

sudo dtc /sys/firmware/fdt 2> /dev/null > system.dts

とすると現在のシステムの dts が取得できるようです。

調べてみると このKernel、どんなKernel? - Qiita が詳しいようなので参考にさせていただきました。

DeviceTree を編集する

取得した system.dts に対して、まず interrupt-controller@f9010000 のセクションを探して "gic:" を追記しました。

        gic: interrupt-controller@f9010000 {

次に symbols の直前に下記を追記しました。

    reserved-memory {
        #address-cells = <2>;
        #size-cells = <2>;
        ranges;
        rproc_0_dma: rproc@0x7ed40000 {
            no-map;
            compatible = "shared-dma-pool";
            reg = <0x0 0x7ed40000 0x0 0x100000>;
        };
        rproc_0_reserved: rproc@0x7ed00000 {
            no-map;
            reg = <0x0 0x7ed00000 0x0 0x40000>;
        };

        rproc_1_dma: rproc@0x7ef00000 {
            no-map;
            compatible = "shared-dma-pool";
            reg = <0x0 0x7ef00000 0x0 0x40000>;
        };
        rproc_1_reserved: rproc@0x7ef40000 {
            no-map;
            reg = <0x0 0x7ef40000 0x0 0x100000>;
        };
    };

    zynqmp-rpu {
        compatible = "xlnx,zynqmp-r5-remoteproc-1.0";
        #address-cells = <2>;
        #size-cells = <2>;
        ranges;
        core_conf = "split";
        r5_0: r5@0 {
            #address-cells = <2>;
            #size-cells = <2>;
            ranges;
            memory-region = <&rproc_0_reserved>, <&rproc_0_dma>;
            pnode-id = <0x7>;
            mboxes = <&ipi_mailbox_rpu0 0>, <&ipi_mailbox_rpu0 1>;
            mbox-names = "tx", "rx";
            tcm_0_a: tcm_0@0 {
                reg = <0x0 0xFFE00000 0x0 0x10000>;
                pnode-id = <0xf>;
            };
            tcm_0_b: tcm_0@1 {
                reg = <0x0 0xFFE20000 0x0 0x10000>;
                pnode-id = <0x10>;
            };
        };
        r5_1: r5@1 {
            #address-cells = <2>;
            #size-cells = <2>;
            ranges;
            memory-region = <&rproc_1_reserved>, <&rproc_1_dma>;
            pnode-id = <0x8>;
            mboxes = <&ipi_mailbox_rpu1 0>, <&ipi_mailbox_rpu1 1>;
            mbox-names = "tx", "rx";
            tcm_1_a: tcm_1@0 {
                reg = <0x0 0xFFE90000 0x0 0x10000>;
                pnode-id = <0x11>;
            };
            tcm_1_b: tcm_1@1 {
                reg = <0x0 0xFFEB0000 0x0 0x10000>;
                pnode-id = <0x12>;
            };
        };
    };
    
    zynqmp_ipi1 {
        compatible = "xlnx,zynqmp-ipi-mailbox";
        interrupt-parent = <&gic>;
        interrupts = <0 29 4>;
        xlnx,ipi-id = <7>;
        #address-cells = <2>;
        #size-cells = <2>;
        ranges;

        // APU<->RPU0 IPI mailbox controller
        ipi_mailbox_rpu0: mailbox@0xff990000 {
            reg = <0x00 0xff990600 0x00 0x20>,
                  <0x00 0xff990620 0x00 0x20>,
                  <0x00 0xff9900c0 0x00 0x20>,
                  <0x00 0xff9900e0 0x00 0x20>;
            reg-names = "local_request_region",
                "local_response_region",
                "remote_request_region",
                "remote_response_region";
            #mbox-cells = <1>;
            xlnx,ipi-id = <1>;
        };
    };

    zynqmp_ipi2 {
        compatible = "xlnx,zynqmp-ipi-mailbox";
        interrupt-parent = <&gic>;
        interrupts = <0 30 4>;
        xlnx,ipi-id = <8>;
        #address-cells = <2>;
        #size-cells = <2>;
        ranges;

        // APU<->RPU0 IPI mailbox controller
        ipi_mailbox_rpu1: mailbox@ff3f0b00 {
            reg = <0x00 0xff3f0b00 0x00 0x20>,
                  <0x00 0xff3f0b20 0x00 0x20>,
                  <0x00 0xff3f0940 0x00 0x20>,
                  <0x00 0xff3f0960 0x00 0x20>;
            reg-names = "local_request_region",
                    "local_response_region",
                    "remote_request_region",
                    "remote_response_region";
            #mbox-cells = <1>;
            xlnx,ipi-id = <2>;
        };
    };

DeviceTree をコンパイルしてコピーする

下記のように dtc でコンパイルして、FAT領域にコピーすれば完了です。

dtc -I dts -O dtb system.dts -o system.dtb
sudo cp system.dtb /boot/firmware/

リブートして /sys/class/remoteproc/ の下にプロセッサが2つ見えていれば成功です。

ちなみに、これも Twitter で教えていただいたもので

cat /boot/firmware/boot.scr.uimg

とすると、スクリプトが出てきます。

(前略)
                if test -e ${devtype} ${devnum}:${distro_bootpart} /Image; then
                        echo "Loading Image"
                        img_addr=0x00200000
                        load ${devtype} ${devnum}:${distro_bootpart} ${img_addr} Image;
                fi
                if test -e ${devtype} ${devnum}:${distro_bootpart} /system.dtb; then
                        echo "Loading system.dtb"
                        dtb_addr=0x70000000
                        load ${devtype} ${devnum}:${distro_bootpart} ${dtb_addr} system.dtb;
                fi
                if test -e ${devtype} ${devnum}:${distro_bootpart} /rootfs.cpio.gz.u-boot; then
                        echo "Loading rootfs.cpio.gz.u-boot"
                        initrd_addr=0x04000000
                        load ${devtype} ${devnum}:${distro_bootpart} ${initrd_addr} rootfs.cpio.gz.u-boot
                fi
(後略)

のように、ファイルがあれば読み込む記述があり、system.dtb という名前でファイルを置いておけば読み込んでくれることがわかります。

おわりに

今回、GW前に急に KV260 が届いたので、Ultra96 の資産をいろいろと移植中ですが、Twitter 上で多くの方に助けられてなんとか動かせる環境ができてきました。

従来の流れであれば下記を試させて頂くところなのですが、今回、認定Ubuntu もあるということなので可能な限りどちらでも使えるように先に勝手のわからない方から取り組んで、案の定、勝手がわからず苦しんだというお話です。

qiita.com

Windows11+WSL2にて、SDカードのext4パーティーションを操作する

はじめに

表題の通りの自分用備忘録です。

最近 Windows11 にアップグレードして、ほとんどのことがWSL2 でできるようになりました。

必要な作業

usbipd-win のインストール

下記の通り、実施すればOKです。

docs.microsoft.com

カーネルビルド

CONFIG_USB_STORAGE=y として Linux カーネルのビルドが必要でした。

これはそのうち標準でONになってほしいですね。

ほぼ下記の記事の通りさせていただきました。

Windows 11 WSL2 USBメモリー接続 - WSL 2 using the USB/IP:Royal Windows:So-net blog

動作

無事にマウントできました。

SDカードのマウント

流れとしては、

usbipd wsl list

でデバイスを調べ

usbipd wsl attach --busid <BUSID>

でアタッチすればよいようです。

私の場合 /dev/sdf に sdカードが表れてくれました。

おまけ

当初、下記の記事の wsl mount という新機能で行けるのかと思いましたが SD カードのマウントはダメでした。

docs.microsoft.com

一方で、Windows 10 の時に作っていた古いWSL2の仮想ディスク(ext4.vhdx) を、「VHDの接続」でディスクとして認識させた後に wsl mount でマウントできたので、これはこれで役に立つ機能でした。

ZynqMP用のDebianイメージをWin32DiskImager用に作成してみる

はじめに

ikwzam 氏のZynqMP 用の Debianイメージにはいつもお世話になっております。

一方で、古い Windows 環境しかないときにSDカードイメージを新規に作ろうとすると何気に苦労するので、Win32DiskImagerなどで書き込めるイメージにできないかと思っていました。

丁度 Windows11 で、WSL2 から SDカードをマウントできる環境も整ったのと、ikwzam 氏の許可もいただけたので、ちょっと試しに作ってみたイメージを試験的においてみたいと思います。

といっても大したことはしていなくて

  • 小さめのパーティーションを作っていつも通りインストールする
  • パーティーションサイズで dd コマンドでイメージを作る

と、やっただけです。

作成してみたイメージその他を私の OneDriveに共有しておきます。

ただし、こちらも私の OneDrive の契約と容量の都合次第なので、急になくなる場合もあることは予めお断りしておきます。

使い方

私が今メインで利用している Ultra96-V2 用の v2020.2.1 イメージを 下記に置いております。

ダウンロードして解凍後に、Win32DiskImager などで SDカードに焼き込んでください。

その後、SD カードを Ultra96-V2 に差し込むと起動します。

特になんの初期設定もできていませんので、wifi の設定などは別途行ってください。

あと、殆ど試してないのですが、v2021.1.1 も作ってみたので、下記に置いておきます。

パーティーション拡張

なお、この段階ではパーティーションサイズは小さいですので、パーティーション拡張を行うことが望ましいです。

いろいろなやり方があるようですが、私は下記のようにやってみました。

まず root でログインします(パスワードは admin)。

ログインしたら

sfdisk -l /dev/mmcblk0

としてセクタ数を確認します。

sfdisk -l /dev/mmcblk0

上の例だと クラスタサイズが 512 bytesで、SDカード全体で 30703616 sectors と知ることができます。また 16G のSDカードなのに 2G しか Linux パーテーションがないこともわかります。

次に

sfdisk -d /dev/mmcblk0 > dump.txt
nano dump.txt

として、dump.txt を編集します。

/dev/mmcblk0p2 の size を、start から換算して 30703616 まで使うように計算して書き直します。

dump.txt 編集

その後、

sfdisk -f /dev/mmcblk0 < dump.txt
rm dump.txt
reboot

として、念のため再起動しておきます。

確認すると、ちゃんとパーティーションが拡張されたようです。

サイズ変更成功

ここで

resize2fs /dev/mmcblk0p2

としてファイルシステムのサイズ拡張を行えば完了です。

パーティーション拡張成功

おわりに

まだ Ultra96V2 の v2020.2.1 の1パターンしか試せておりませんが、手元に Kria KV260 も届きましたので、他のバージョンや、他のターゲット用にもイメージが作れれば順次実験していきたいと思います。

おまけ

以前 Ultra96 の Docker を動かす為に作ったファイルを置いておきます。

ZynqMP-FPGA-Linux-v2020.2.1 のイメージに対して、/mnt/boot 以下に展開してリブートすればよいはず(多分)

Ultra96V2-Debian-v2020.2-Docker.zip

自分用備忘録

SDのパーティーション

fat16:   8192- 532479  (524288) 
ext4:  532480-4726783 (4194304)

トータルサイズが 4726784 クラスタで、2,420,113,408 バイト( 2308M バイト) なので

dd if=/dev/xxx of=XXXX.img ibs=1M count=2308

みたいな感じでダンプして作くれる。

Ultra96V2のDebianでDocker動かしてみた

はじめに

最近いろんなところで Docker の活用を聞きますし、私自身も多少は使ったことはあったのですが、Raspberry PI4 のおかげで aarch64 での利用も増えてきているようですので、Ultra96 でも動かないか挑戦してみました。

例によって、ikwzm氏のDebainイメージで作業を進めております。

Docker のインストール

RaspberryPi4 向けの記事としてこちらを参考にさせて頂きました。

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

とすればインストールでき、

sudo usermod -aG docker <username>

とすればユーザーに実行権限が付けられます。

しかしながら、このままでは Dokcer は動きません。

カーネルコンフィギュレーション

Twitterで呟いていたところ、有難いことに ikwzm氏 から情報を頂きました。

なるほど、カーネルコンフィギュレーションが必要なようです。

さっそく、カーネルコンフィギュレーションに挑戦してみました。

環境は WSL2 上の Ubuntu 20.04 で行い、今回はもろもろあって v2020.2.1 を利用いたしました。

基本的にはこちらの手順に従ってカーネルビルドしていけばよいようです(こんなやり方でいいのかはよくわかりませんが)。

この時に

make xilinx_zynqmp_defconfig

の直前で、 linux-xlnx-v2020.2-zynqmp-fpga/arch/arm64/configs/xilinx_zynqmp_defconfig を書き換える、もしくは、後から

make menuconfig

として、メニュー形式でオプションを変更すれば目的の事がやれそうです。

こちらの記事を参考にさせて頂き必要なオプションを有効にしました。

この際、そのままやると名前に -dirty が付いてしまうようです。正攻法でやると 一度ちゃんとコミットするべきなのでしょうが、私はこちらを参考に

CONFIG_LOCALVERSION_AUTO=n

としたうえで、さらに環境変数 LOCALVERSION を空定義することで暫定回避しました(これがないと末尾に + がついてしまう模様)。

ビルドが終わったら、target/Ultra96-V2/boot 以下のファイルを SD カードの boot パーティーションにコピーし、また、新しくできた deb ファイルもコピーして、debian 起動後に dpkg でインストールすればよいようです。

ちなみに、起動後は

zcat /proc/config.gz

とすることで、カーネルコンフィギュレーションの状況が確認できるようです。

なお、ここで、カーネルビルド時にできた deb ファイルを持ってきて、

sudo dpkg -i linux-image-5.4.0-xlnx-v2020.2-zynqmp-fpga_5.4.0-xlnx-v2020.2-zynqmp-fpga-4_arm64.deb
sudo dpkg -i linux-headers-5.4.0-xlnx-v2020.2-zynqmp-fpga_5.4.0-xlnx-v2020.2-zynqmp-fpga-4_arm64.deb

も実行しておきます。

加えて、 fclkcfg と u-dma-buf もセルフコンパイルして入れなおしてました。

iptables の置き換え

ここまできてもなお起動してくれず

sudo dockerd --debug

などとして調べていると、どうやら iptables がエラーを出しているようです。

調べてみると、こちらの記事にたどりついたので、そのまま参考にさせて頂き

sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy

としたところ、無事に

sudo systemctl restart docker

がエラー無く通りました。

下記のように無事に hello-world が動いてくれました。\(^^)/

Dockerの動作確認

最近は Ultra96 側でもいろいろなセルフコンパイルをするので、docker で環境が作れれば嬉しいシーンがあるかもしれません。

公式ツールが PetaLinux しかなかった時代に、debian イメージのおかげで apt が使えるだけでもすごい衝撃だったのですが、本当になんでもできてしまいますね。ありがたや。

おまけ(イメージの保存場所をNASに変更)

こちらの記事などを参考にすると、Dockerイメージの保存場所を変えることができるようです。

以前の記事で、Ultra96からNASをマウントしていますので、SDカードに置かずにNASにイメージを置くこともできるようです。

しかし実際にやってみるとなぜか run が異様に遅く、使い物になりませんでした。 何か設定に問題もあるのかもしれませんが、結局 デフォルトの場所(/var/lib/docker/)に戻しました。

おまけ2

xilinx_zynqmp_defconfig に追加した部分はこんな感じです。

CONFIG_LOCALVERSION_AUTO=n

CONFIG_NAMESPACES=y
CONFIG_NET_NS=y
CONFIG_PID_NS=y
CONFIG_IPC_NS=y
CONFIG_UTS_NS=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_SCHED=y
CONFIG_CFS_BANDWIDTH=y
CONFIG_CPUSETS=y
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
CONFIG_MEMCG_SWAP_ENABLED=y
CONFIG_KEYS=y
CONFIG_VETH=y
CONFIG_BRIDGE_NETFILTER=y
CONFIG_NF_NAT_IPV4=y
CONFIG_IP_NF_TARGET_MASQUERADE=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
CONFIG_NETFILTER_XT_MATCH_IPVS=y
CONFIG_IP_NF_NAT=y
CONFIG_NF_NAT=y
CONFIG_NF_NAT_NEEDED=y
CONFIG_USER_NS=y
CONFIG_SECCOMP=y
CONFIG_OVERLAY_FS=y
CONFIG_NET_CORE=y
CONFIG_BLK_CGROUP=y
CONFIG_CGROUP_PIDS=y

下記は元々定義されていたので、確認してもし y や m になっていなければ y にすればいいようです。

CONFIG_OVERLAY_FS=y
CONFIG_CGROUPS=y
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y
CONFIG_MEMCG_SWAP_ENABLED=y
CONFIG_BRIDGE=y
CONFIG_NET=y
CONFIG_NETFILTER=y
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m

個人的な都合でついでに書きも付け加えました。

CONFIG_FUSE_FS=y
CONFIG_USB_RTL8152=y

CONFIG_USB_SERIAL=y
CONFIG_USB_SERIAL_FTDI_SIO=y

おまけ3 (docker-compose )

docker-compose を入れようとしたら普通には入ってくれなかった。

こちらを参考にさせて頂き

sudo curl -L --fail https://raw.githubusercontent.com/linuxserver/docker-docker-compose/master/run.sh -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

としてみたところ、どうやら入ったように思えます。

最小セットのRISC-Vコアを作ってみた

概要

前々からやろうやろうと思って全然手が出せてなかった RISC-V ですが、本格的にやると 深みにはまりそうだったので、最小セット(rv32i)をコンパクトに SystemVerilog + Rust で 動くコアが書けることを目標にちょっとやってみました。

感想としては、「え、こんな簡単にできるの?」と言うぐらい、何も考えなくても 「命令セットの方が代りに考えてくれてる」という「良くできてるなぁー」と いう印象でした。

ISAから伝わってくる設計意図をどこまで汲み取れてるかはわかりませんが、 それがそのまま実装に落ちていくのでいろいろな意味で不思議な体験でした。

作ってみたもの

碌にテストもせずにLチカできた段階で放置してますが、一通りのものは github に置いております。

サンプルプロジェクト

「へー、これだけのソース量でRustが使えるCPUが出来ちゃうんだ」と言う程度のご参考までに。

少しだけ中身の説明(4段パイプライン版)

この手のものでシンプルによくあるのは

IF → ID → EX → MA → WB

という5段パイプラインかと思いますが、今回さらにシンプルにして 4段パイプラインで

IF → ID → EX → WB

として、メモリアクセス(MA)ステージをEXに押し込んだものを作ってみました。

なんで、そんなことしたかと言うと、EXとMAが別ステージだと、MAでロードする値を後続の命令のEXで使いたいときにRAWハザードでストールする必要があるからです。

今回きわめて単純かつ乱暴に作っているので、ストールは分岐の時しかしないという、いろいろと予測のしやすい作りです(それでもなんだかんだで200MHzぐらいで合成できてるので最近のFPGAすごいなと思います)。

また、今回は割り込み機能を実装していません。ZynqMPの場合、複雑なことをやるならRPU(Cortex-R5)とか使えばいいので、今のところシンプルにプログラマブルシーケンサが欲しい場合に特化しております。

もっとも割り込み機構を付けること自体はそれほど難しい話ではないので、「必要性を感じなかったのでやらなかった」というのが正しいかもしれません。

割り込みなどのコンテキストスイッチがあるとリアルタイム保証が難しくなるのもありますし、そもそも「インストラクションメモリやスタックメモリ増やして複数コンテキストやるぐらいなら、小さなコアいっぱい並べればいいじゃん」という割り切りで考えています(いまのところは)。

メモリサイズとか深く考えずに設定して合成してますが、下記ぐらいの規模になっており(注:2022-03-06時点のサイズです)、Ultra96V2 の規模からするとちょっとしたシーケンサとしては許容できなくもないレベルじゃないかと思います。

回路規模(グラフ)

回路規模(数値)

今後の話

私の考えるこれからのエッジFPGA像として

  • IoT やるのに、ZynqMP の PSのようなLinuxの動くコアは必須
  • エッジならではでPSでは成立しないアルゴリズムを混ぜないとFPGA使う面白みがない
  • アルゴリズムをSoCにマップするのにCPUが得意な部位をPLにマッピングする意味はあまりない

というのがあります。

では、そういった条件下で「PL で RISC-V を作って嬉しいときはどんな時か?」、という話になってくるわけですが

あたりかなと、個人的に思っています。

今回前者をやってみたわけですが、いずれ気が向いたら後者も手を出すかもしれません。 所謂DSA(domain specific architecture)での活用と言ったところでしょうか?

その場合、特に、キャッシュやMMUが必要になってくるわけではなく

  • PLが得意な専用演算エンジンと同じクロックで動く
  • CPUの所為で専用演算エンジンがストールしたりしない

といったプリミティブなところが問われてくるので、高クロック仕様で、ストール無しで専用エンジンに制御やデータを供給できるアーキテクチャになってなるんじゃないかと思います。

ある程度深いパイプラインで高クロック化しつつ、分岐予測など入れてメインの演算ループだけはストールなく回るみたいなことに注力したコアが作れるといいのかなと思っております。

何にせよ、非常に小型なところから高性能なところまで手広くカバーした命令セットが自由に使える、というのは「コンパイラがそこにある」というだけでものすごくホビーの範囲を広げてくれているように思います。

周回遅れで触ってる感はありますが、なかなか楽しいISAに触れることができてうれしく思います。

おまけの話(Rustからみたソフトコア)

ちょっと違った見方として

  • C + HLSで書いても良いけど、Rust + ソフトコアで置き換えられるケースも稀にあるかもね
  • MicroBlaze って Rust 対応してなくない?
  • Rust で書ける PicoBlaze みたいなのあると面白いかもね

といった、Rust 使いたいからと言う観点でみても面白いかもしれません。

6段パイプライン版を追記(2022/05/07)

よくある 5段パイプラインの構成にさらに1段追加したバージョンを追記しました。

load 命令後に符号拡張するためのステージを加えて 6段にしています。 FPGAのBlockRAM は同期メモリなのでアクセスに1サイクルかかり、そのあとに符号拡張が必要なのですが、 これがフォワーディング用のパスに入り込んでEXステージのクリティカルパスを増やすのを嫌ったためです。

今のところ KV260 で試した範囲で

4段パイプライン版 : 200~250MHz あたりに限界 6段パイプライン版 : 250~300MHz あたりに限界

といった感じです。6段で、いろいろオプションで頑張ると 300MHz 行ける時があるようですが、250MHz ぐらいが実用限界な気はしています。