RISC-Vの実装sodorを見てみる

はじめに

chiselの勉強がてらに何か実装してみようと考えたところ、当然のごとくRISC-Vが引っ掛かったので色々調べてみた。

RISC-VのISAなどは眺めてみたもののCPUを1から実装したことがなく、全体像が見えなかったためすでにある実装を参考にさせてもらった。

rocket chip など有名な実装は規模の大きいため、まずは簡潔な実装をしているSodorを参考に実装しながら勉強することにした。

github.com

この記事ではそのSodorについて調べて分かったことをまとめていく

 

Sodorについて

Sodorは教育用に考案されたRISC-VのCPUの一種(subset?)
パイプラインが1段~3段の実装があり、1段の実装は処理内容が2つのファイルにほとんど収まっているのでまずはこれを参考にした。

 

動作環境を作る

Sodor自体はmakeコマンドでテストケースを走らせることができるが、
どんなことをしているか調べるため1段パイプラインのテストケースを個々に実行するためにソースコード等を抜き出して調査環境を整える。

で、調べてみた結果テストケースを走らせるために必要なコマンドは以下の流れになっていた。

 

RISCV_ISA=$HOME/workspace/risc-practive/riscv-sodor/riscv-isa-sim
RISCV_LIB=$HOME/workspace/risc-practive/tmp/tests1/emulator/fesvr/libfesvr.a
ELF_FILE=$RISCV/target/share/riscv-tests/isa/rv32ui-p-add

sbt "run -td generated-src" 
g++ -O1 -std=c++11 -g -I$PWD/emulator/common -I$RISCV_ISA -I$VERILATOR_ROOT/include -c -o emulator/common/SimDTM.o emulator/common/SimDTM.cc
(cd emulator/fesvr; ../../riscv-isa-sim/configure --enable-sodor )
make -C emulator/fesvr libfesvr.a &&
verilator --cc --exe  --top-module Top +define+PRINTF_COND=1 --assert --output-split 20000 --x-assign unique \
	  -I$PWD/vsrc -O3 \
	  -CFLAGS "-O1 -std=c++11 -g -I$PWD/emulator/common -I$RISCV_ISA -DVERILATOR -include $PWD/emulator/common/verilator.h" \
	  -LDFLAGS "$RISCV_LIB -lpthread" \
	  --trace -o $PWD/emulator/emulator-debug \
	  $PWD/generated-src/Top.v \
	  $PWD/vsrc/SimDTM.v \
	  $PWD/emulator/common/emulator.cpp \
	  $PWD/emulator/common/SimDTM.o

make -C obj_dir -f VTop.mk

set -x
./emulator/emulator-debug -voutput/rv32ui-p-add.vcd +max-cycles=30000 $ELF_FILE  | tee result1.log
set +x

前提としてSodorではchisel環境ではRTLの生成のみを行い、
シミュレーションは生成したRTLを改めてVerilatoでコンパイルして実行していた。

コマンドの流れとしてはまずsbtでVerilogを生成。

次にSimDTMのオブジェクトファイルを生成する。これはDPI-Cと呼ばれる機能(フレームワーク?)を使ってSystemVerilogから呼び出すC++の関数をコンパイルしている。

次にriscv-isa-simにあるRISC-Vデバッグ用のサーバライブラリをビルドしている。内容については後述するがsodorではriscv-isa-simの特定バージョンのみに含まれる--enable-sodorオプションを使ってsodor用にビルドしている。

最後に生成したRTLやライブラリをVerilatorに食わせて実行ファイルを生成後、テストプログラムとしてriscv-testsに含まれるrv32ui-p-addを実行している。

 

シミュレーションの構成

次に全体の動作を見てみる。
シミュレーション実行時の構成は以下の図のようになっている。

f:id:tear0202:20200714225419p:plain

Sodor全体図

main関数ではchiselからVerilogに変換されたCPUモジュールにクロックを供給し、Topモジュールのsuccess信号がONになるかタイムアウトするまでクロックを回す。

dtm_tはメソッド自体はDPI-Cを介して呼び出される。
主な処理内容はテストプログラムの書き込みで今回はRISC-Vの命令セットテスト用のプログラムを下記のriscv-testsからビルドして実行させた。

GitHub - riscv/riscv-tests

dtm_tからのメモリ書き込みはRISC-Vのデバッグしように基づいて行われている。
がSodorのデバッグモジュールは完全とは言えないようなのでsodor用のオプションなしでlibsvr.aをビルドした場合、書き込みがうまくいかないようだ。

Debug Specification - RISC-V International

 

CPUコアの構成について

次に上のSodor全体図Coreの構成についても大まかにまとめる

Tileはcore, memory, debugで構成されておりCPUとしての処理はcore, memoryで実行する。
その一方で外部からはdebugを介してcoreのレジスタやmemoryのデータにアクセスできるようになっている。

memoryはdebug用と命令フェッチ用とデータR/W用の3つのポートの構成になっている。
memory自体の記述としてはポートの数をパラメータで容易に増減させられて、メモリのどのポートも遅延なしでリードできる記述となっている。

Coreはcpathとdpathに分かれている。
CPUとしての主な処理はdpathで行われており、cpathは入力された命令をパースした結果をdpathに通知している。

 

 まとめ

今回はRISC-Vを実装してみる前に既存の実装としてSodorの実装がどうなっているかを見ていった。とりあえず全体の構成をみれて、シミュレーションの方法も見当がついたので実装をしながらRISC-Vの理解を深めていきたいと思う。
3段にした場合やオリジナルの命令を組み込んだ場合の構成についても後々踏み込んでいきたいと思う。

 



Cyclone V Soc でLチカ

 

前回の記事ではDE10-nanoの起動用SDカードを作成した

今回の記事ではFPGAをコンフィグしてFPGA側のLEDをC言語のプログラムで操作する

何番煎じだというかんじですが)

設定手順

今回は下記の手順で設定を行う

  1. FPGAデータの作成
  2. u-bootからのFPGAコンフィグ
  3. Lチカプログラムを作成、実行する

1.FPGAデータの合成

FPGAへの書き込みに使用するデータのプロジェクトには今回もTerasicが出している
DE10_NANO_SoC_GHRDを利用する

makeファイルがあるため合成にはコマンドラインmake rbfを実行することで、
FPGAの書き込みに使うoutput_files/DE10_NANO_SoC_GHRD.rbfが生成される

ただし、Quartus 19.xで合成しようとするとQsysコンポーネントで使用しているPIOのIPが見つからないためエラーになってしまう。なので、合成にはQuartus 18.1で合成した。

 

 2.u-bootからのFPGAコンフィグ

作成したRBFファイルをFPGAに書き込む。

FPGAのコンフィグはLinuxから行う方法とLinuxのブート前にu-bootのスクリプトから行う方法があるが、今回はu-bootからのコンフィグを行う。

まずは、RBFファイルをu-bootから読めるようにFATパーティション(zImageとかを入れたパーティション)に入れる。

次にu-bootのコンソールからFPGAのコンフィグスクリプトを登録する
u-bootのコンソールに入るには起動時にシリアルコンソールで下記のメッセージが表示された際にキー入力を行う。

In:    serial
Out:   serial
Err:   serial
Model: Altera SOCFPGA Cyclone V SoC Development Kit
Net:   eth0: ethernet@ff702000
Hit any key to stop autoboot:

 

FPGAをコンフィグするには1度メモリ上にデータを展開して、FPGAにそのデータを流す
そのためのコマンドは下記のようになる

fatload mmc 0:1 ${loadaddr} DE10_NANO_SoC_GHRD.rbf
fpga load 0 ${loadaddr} 2082088;

fatloadではDE10_NANO_SoC_GHRD.rbfを${loadaddr}のアドレスに展開している
この${loadaddr}は後のLinuxの展開に使われるアドレスだがその前に借りる形になる

 fpga loadでは展開したデータをFPGAに書き込んでいる。2082088はFPGAデータのサイズで本来ならfatloadしたサイズを取得するべきだが、今回は固定値にしている。
FPGAが変わらなければ固定値でも問題ないはず)

 このコマンドがうまくいっていればLEDが点灯して一番端のLEDが点滅する

 

次にこれらのコマンドを自動で実行するように環境変数を追記する

通常、ブートの際にキー入力が行われなければmmc_bootのスクリプトからzImageのロード等が行われLinuxが立ち上がる

FPGAのコンフィグをこのブートの流れに組み込むために、setenv, editenvコマンドを使って下記の通り変数を追記、編集する

callscript=if fatload mmc 0:1 ${loadaddr} soc_system.rbf; then fpga load 0 ${loadaddr} 2082088; bridge enable; echo done config FPGA; else echo not found DE10_NANO_SoC_GHRD.rbf;fi;
mmc_boot=run callscript; if mmc dev ${devnum}; then devtype=mmc; run scan_dev_for_boot_part; fi
saveenv

この変更でmmc_bootでboot前にcallscriptが実行される

callscriptではFPGAデータのロードに成功すればFPGAのコンフィグを行う

また、bridge enableというコマンドを実行しているが、これはFPGA-CPU間の通信のためのポートに関して、レジスタ設定を行っている

(このあたりのu-bootのコマンド一覧についてはこちらで調べられている)

最後にsaveenvすることで再起動しても今回の変更が反映される

 

3.Lチカプログラムの作成実行

最後にFPGAレジスタを操作してLEDを点灯(消灯)させる

LEDを制御するモジュールはQsysの設定でlw_hps2fpgaの0x0000_3000番地に接続されている。lw_hps2fpgaのベースアドレスは0xff20_0000(ここでCyclone Vのアドレスマップが確認できる)

なので、0xff20_3000番地に値をかきこむことでLEDのON/OFFが切り替えられる。

C言語でこの物理アドレスにアクセスするプログラムは下記のようになる

 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

int main()
{
  volatile unsigned int *address;
  unsigned int lw_h2f_led = 0xff203000U;
  unsigned int map_size = 0x1000U; //map_sizeは適当(小さすぎるとページングの関係でマッピングに失敗するため)
  int fd;

  /* メモリアクセス用のデバイスファイルを開く */
  /* キャッシュを無効化するためO_SYNCフラグをたてる*/
  if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
    perror("open");
    return -1;
  }

  /* lw_h2f_ledをマッピングした仮想アドレスを取得する*/
  address = (int*)mmap(NULL,  0x1000U, PROT_READ | PROT_WRITE, MAP_SHARED, fd, lw_h2f_led);
  if (address == MAP_FAILED) {
    perror("mmap");
    close(fd);
    return -1;
  }

  printf("led status check.\n");
  printf("led status : 0x%08x.\n", address[0x00]);  /* FPGAのレジスタからデータを取得*/
  address[0x0] = 0x00;  /* LEDは初期状態では点灯しているので消灯 */
  printf("led status : 0x%08x.\n", address[0x00]);  /* 変更が反映されていることを確認*/

  munmap((void*)address, map_size);
  close(fd);

  return 0;
}

このプログラムをarmのクロスコンパイラでコンパイルした後、生成されたa.outをDE10-nanoで実行するとLEDが消灯する(0を書き込んだので)

 

 

これで自分の書いたHDLにCPUからアクセスするための環境ができた(AvalonとかAXIとかのInterfaceの話は残っているが)ので、今後はHDLを書いたり、それ用のLinuxドライバも作っていきたいと思う。

 

間違っているところ等あれば教えていただけると嬉しいです。

 

DE10-Nanoブート用のイメージの作成手順について

DE10-nanoで色々試す(遊ぶ)ためにLinuxカーネルブートローダディストリビューションのビルドを行ったのでその手順を備忘録としてまとめる

背景

今回、DE10-nanoで遊んでみようと思ったのはHDLを色々かいて勉強する際にFPGAで実際に動かしてみようと思ったのがきっかけ

Cyclone V SoCを選んだのは、SoC FPGAデバイスドライバとかの足回りについても調べてみようかなと思ったのと、Cyclone Vなら数年前に少し触ったのが理由
(時間とお金があればZynq, Zynq MPとかにも挑戦したい)

 

参考文献

今回、カーネル等のビルドの手順を調べてみたところmacnicaのサイトにビルド手順がまとめられていたのでこれらを参考にした。

 

1.SoC EDS v19.1 std / v19.3 pro から変更された新しいブートローダー生成フローの動作確認(Cyclone® V SoC / Arria® V SoC 編) – 株式会社マクニカ アルティマカンパニー

2.インテル® SoC FPGA 向け Linux ビルド方法 - 半導体事業 - マクニカ

 
手順はほぼリンク先に載っている通りに行ったが今回は備忘録として実行した手順をまとめる
 

ビルド手順

これから行うビルドの手順は以下の通り

 

1.Linuxカーネルのビルド

2.u-bootのビルド

3.Angstromのビルド

4.SDカードへの書きこみ

 

AngstromのビルドでLinuxカーネルやu-bootも生成されるが今回はデバイスドライバのクロスコンパイルも考えているので別にビルドを行う

また、ビルドにかかる時間を短縮するためにAWSのt2.xlargeインスタンス(ubuntu16.04)でビルドを行った

 

1.Linuxカーネルのビルド

まずはビルドに使うコンパイラをダウンロードし、環境変数に登録する
参考にしたサイトではバージョン2015.06のコンパイラであったが、最新のものを試してみる


$ wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
$ tar xf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
$ export PATH=`pwd`/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/:$PATH
$ export ARCH=arm
$ export CROSS_COMPILE=`pwd`/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
 
次にビルドするLinuxgithubのaltera-opensourceのリポジトリからクローンしてビルドする

$ git clone https://github.com/altera-opensource/linux-socfpga
$ cd linux-socfpga
$ git checkout -t -b test origin/socfpga-4.14.130-ltsi
$ make socfpga_defconfig
$ make -j 24 zImage dtbs
 
これでカーネルイメージがビルドされる。生成された
linux-socfpga/arch/arm/boot/zImage (圧縮イメージ)
linux-socfpga/arch/arm/boot/dts/socfpga_cyclone5_socdk.dtb (デバイスツリー)
の2つのファイルを今回利用する

2.u-bootのビルド

u-bootのビルドでは別のコンパイラを使うのでダウンロードして、環境変数を設定しなおす。こちらでも最新のコンパイラを使ってみる


$ wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-eabi/gcc-linaro-7.5.0-2019.12-x86_64_arm-eabi.tar.xz
$ tar xf gcc-linaro-7.5.0-2019.12-x86_64_arm-eabi.tar.xz
$ export PATH=`pwd`/gcc-linaro-7.5.0-2019.12-x86_64_arm-eabi/bin:$PATH
$ export ARCH=arm
$ export CROSS_COMPILE=`pwd`/gcc-linaro-7.5.0-2019.12-x86_64_arm-eabi/bin/arm-eabi-

 

参考にしたサイトではu-bootのビルドにはintel SoC EDS内のスクリプトやサンプルプロジェクトを利用しているが、今回はTerasicのサイトにDE10-nanoのサンプルプロジェクトを利用する

$ ~/workspace/opt/intelFPGA/19.1/embedded/embedded_command_shell.sh
$ cd DE10_NANO_SoC_GHRD
$ rm -rf software
$ mkdir -p software/bootloader
$ bsp-create-settings --type spl --bsp-dir software/bootloader --preloader-settings-dir "hps_isw_handoff/soc_system_hps_0" --settings software/bootloader/settings.bsp
altera-opensourceのリポジトリからu-bootをクローンして、生成したFPGA側の設定を反映してビルドする

$ cd software/bootloader
$ git clone https://github.com/altera-opensource/u-boot-socfpga
$ cd u-boot-socfpga
$ git checkout -t -b test origin/socfpga_v2019.04
$ ./arch/arm/mach-socfpga/qts-filter.sh cyclone5 ../../../ ../ ./board/altera/cyclone5-socdk/qts/
$ make socfpga_cyclone5_defconfig
$ make -j 24

これでu-bootがビルドされる。今回使うのはSPLとu-bootが一体化したu-boot-with-spl.sfpファイル

 

3.Angstromディストリビューションのビルド

今回はRocketboard.orgのリポジトリがあるAngstromのディストリビューションを使う

バージョンとしてはAngstom-v2019.06-warriorをビルドしていく
とりあえずおまじないとして以下のコマンドを実行する


$ mkdir angstrom-build-v2019.06
$ cd angstrom-build-v2019.06
$ wget http://commondatastorage.googleapis.com/git-repo-downloads/repo
$ chmod 777 repo
$ ./repo init -u git://github.com/Angstrom-distribution/angstrom-manifest -b angstrom-v2019.06-warrior
$ wget http://releases.rocketboards.org/release/2019.10/src/altera.xml
$ mkdir -p .repo/local_manifests
$ mv altera.xml .repo/local_manifests/
$ ./repo sync
$ 
ここで、ダウンロードされた layers/meta-altera-refdes/conf/layer.conf の12行目にあるLAYERSERIES_COMPAT_meta-altera-refdes = "zeus"コメントアウトする
(macnicaのサイトでは"zeus"を"warrior"に変更している)
 
現状meta-altera-refdesの最新版はAngstrom-v2019.12-zeusを指定しているのに対し、meta-alteraの最新版はAngstrom-v2019.06-warrior用になっているのでこのままではビルドできない。
そこでmeta-altera-refdes のバージョン指定をコメントアウトすることでとりあえずビルドできるようになる
 
次に環境変数のセットアップと設定ファイルの修正を行う
(設定ファイルの変更内容を入れるのは冗長な気がするがこれもまとめとして入れておく)

$ MACHINE=cyclone5 . ./setup-environment
$ sed -i '/meta-altera/a \ \ ${TOPDIR}\/layers\/meta-altera-refdes \\' conf/bblayers.conf
$ sed -i '/meta-atmel/d' conf/bblayers.conf
$ sed -i '/meta-freescale/d' conf/bblayers.conf
$ echo "DISTRO_FEATURES_remove = \" wayland \"" >> conf/local.conf
$ echo "DISTRO_FEATURES_remove = \" alsa \"" >> conf/local.conf
$ export KERNEL_PROVIDER=linux-altera-ltsi
$ export KERNEL_TAG=refs/tags/ACDS19.3_REL_GSRD_PR
$ export KBRANCH=socfpga-4.14.130-ltsi
$ export BB_ENV_EXTRAWHITE="$BB_ENV_EXTRAWHITE KBRANCH KERNEL_TAG UBOOT_TAG KERNEL_PROVID
最後に次のコマンドを実行することでディストリビューションのビルドが始まる
今回使ったawsのそこそこのインスタンスでも完了までに数時間かかる処理になる
 

$ bitbake gsrd-console-image  
 
このコマンドで生成された以下のファイルを今回は使う
deploy/glibc/images/cyclone5/gsrd-console-image-cyclone5.tar.gz
 

4.SDカードへの書きこみ

最後に生成した以下の4つのファイルをSDカードに書きこむ
zImage
socfpga_cyclone5_socdk.dtb
u-boot-with-spb.sfp
gsrd-console-image-cyclone5.tar.gz
 
まずは書き込むためのパーティションを設定する
作成するのは
  1. linuxカーネルなどのブート用のファイルを配置するFATパーティション
  2. linuxファイルシステムを配置するext3パーティション
  3. ブートローダバイナリを配置するA2タイプのパーティション
作成にはfdiskコマンドを使う
sudo fdisk -lでsdカードのデバイス名を調べて下記の手続きを行う
(下記は/dev/sdbの場合)
 

$ sudo fdisk /dev/sdb
コマンド (m でヘルプ): o
選択 (既定値 p): p
パーティション番号 (1-4, 既定値 1): 3
最初のセクタ (2048-124735487, 既定値 2048):
最終セクタ, +セクタ番号 または +サイズ{K,M,G,T,P} : +16MB
コマンド (m でヘルプ): t
16 進数コード (L で利用可能なコードを一覧表示します): a2

コマンド (m でヘルプ): n
選択 (既定値 p): p
パーティション番号 (1,2,4, 既定値 1): 1
最初のセクタ (32768-124735487, 既定値 32768):
最終セクタ, +セクタ番号 または +サイズ{K,M,G,T,P} : +128M
コマンド (m でヘルプ): t
16 進数コード (L で利用可能なコードを一覧表示します): b

コマンド (m でヘルプ): n
選択 (既定値 p): p
パーティション番号 (2,4, 既定値 2): 2
最初のセクタ (294912-124735487, 既定値 294912):
最終セクタ, +セクタ番号 または +サイズ{K,M,G,T,P} :
コマンド (m でヘルプ): w
 
次にそれぞれのパーティションにファイルを配置する
1番目のfatパーティションにはzImageとデバイスツリーを配置し
ブート時の引数にあたるファイルをextlinuxディレクトリに配置する

$ sudo mkdir /mnt/sdcard1
$ sudo mount /dev/sdb1 /mnt/sdcard1
$ sudo cp zImage /mnt/sdcard1/.
$ sudo cp socfpga_cyclone5_socdk.dtb /mnt/sdcard1/.
$ sudo sh -c "echo 'LABEL Linux Defaul' > /mnt/sdcard1/extlinux/extlinux.conf"
$ sudo sh -c "echo ' KERNEL ../zImage' >> /mnt/sdcard1/extlinux/extlinux.conf"
$ sudo sh -c "echo ' FDT ../socfpga_cyclone5_socdk.dtb' >> /mnt/sdcard1/extlinux/extlinux.conf"
$ sudo sh -c "echo ' APPEND root=/dev/mmcblk0p2 rw rootwait earlyprintk console=ttyS0,115200n8' >> /mnt/sdcard1/extlinux/extlinux.conf"
$ sudo umount /mnt/sdcard1
2番目のextパーティションにはgsrd-console-imae-cyclone5.tar.gzを展開する

$ sudo mkfs.ext3 /dev/sdb2
$ sudo mkdir /mnt/sdcard2
$ sudo mount /dev/sdb2 /mnt/sdcard2
$ sudo tar xf gsrd-console-image-cyclone5.tar.gz -C /mnt/sdcard2/
$ sudo umount /mnt/sdcard2
 
3番目のA2パーティションにはu-boot-with-spl.sfpを書きこむ
$ sudo dd if=u-boot-with-spl.sfp of=/dev/sdb3
 
 これでDE10-nanoを起動するためのSDカードが出来上がった
FPGAコンフィギュレーションするためにはさらに設定を行う必要があるがそれは次の記事でまとめようと思う
 
 

非同期回路の2段FFについて

FPGAで非同期入力に対してはFFを2段挿入することは知っていたが
その理由をよくわかっていなかったので調べて分かったことを
(投稿の練習も含めて)備忘録として書いておく

 

主に参考にしたのは以下の2つのサイトです

1 . 非同期クロック と 検証手法−1 - 半導体事業 - マクニカ

2. 電気回路/HDL/非同期信号を扱うための危ういVerilogライブラリ - 武内@筑波大

 

まず、非同期の入力をFPGAの内部クロックでラッチしようとすると
FPGAのセットアップホールドに違反するタイミングで入力される場合がある。

違反した入力を受けたレジスタはメタステーブルと呼ばれる不安定な出力状態になる。

このメタステーブルへの対策としてFFを2段(以上)挿入する。

肝心のFFを2段挿入する理由は、1段目のFFがメタステーブル状態になっても次のクロックまでには安定した状態に戻るため、2段目のFFは安定した状態をラッチすることができる。

もし、1段目のFFと2段目のFFの間に組み合わせ回路があったりして遅延時間があると
2段目のFFは不安定な入力をラッチしていしまい誤動作のもとになる。

 

とりあえず、FFを2段挿入する理由は上記のイメージで理解することができたが
参考にしたマクニカのサイトによると3段以上挿入する場合も増えてきたらしい。

3段にするとメタステーブル状態をより抑えられる理由が

・単純に1段目と2段目の遅延が短く仕切れないから

なのか

・複数クロックにわたるメタステーブルを抑制するため

なのか分かれば調べてみたいなと思った。

 

また、Quartusで段数をどのくらいにすればいいかを調べられるみたいなのでそれも使えるようになってみたいなとも思った。

 

以上、間違っているところなどあれば教えてくださるとうれしいです。