みねっちょのマイコン関係ブログ

組込開発系フリーソフトやハードの情報発信ブログ

WSL 標準パッケージで RISC-V 32ビット版 RV32E のコンパイルをする

最終更新: 2021-03-29
Windows10 上で WSL (Windows Subsystem for Linux) にインストールされた Ubuntu-20.04 の標準パッケージのクロスコンパイラを使って、RISC-V 32ビット組み込み用途版 RV32E の、OS 無しベアメタルなバイナリを生成する方法を紹介します。

尚、一部の項目は別途記載したARM用ベアメタルの【こちらの記事】と被って居ます。

前提条件:

  • Windows 10 を使用していること
  • Microsoft Store から WSL の Ubuntu-20.04 をインストール済なこと

パッケージのインストール:

sudo apt install crossbuild-essential-riscv64

パッケージ名としては RISCV64 なのですが、実はオプションを付ければ32ビット版のコンパイルも出来ますので、これをインストールします。コマンド群の名前は「riscv64-linux-gnu-*」(* は gcc, as, ld, objdump, objcopy 等) となります。これをインストールすると、ubuntu の「/usr/lib/riscv64-linux-gnu/ldscripts」ディレクトリの下に 32ビット用と64ビット用の両方のリンカスクリプトがインストールされまます。

また、ライブラリ群は「 /usr/lib/gcc-cross/riscv64-linux-gnu」にインストールされますが、こちらは64ビット用 のみの様です。ベアメタル用の32ビット ライブラリを使用したい場合には、newlib 等を別途、野良ビルドする必用が有りそうです。

尚、その辺りの話は、こちらの RISC-V の公式 git レポジトリの頁の下の方に記載されています。

github.com

ベアメタル用スタートアップルーチンの作成:

一般論として、通常のプロセッサで C 言語のスタートアップルーチン (いわゆる、crt0) として必要な最小限の事は、次の2点です。正式な C 言語の仕様には準拠しませんが、他は、やりたい事が増えた時に拡張して行く事にします。

  1. スタックポインタの番地設定
  2. C 言語のメインルーチンのコール

次のコマンドを WSL のターミナル上にコピー&ペーストすると、start.s というファイルが出来ます。適当なディレクトリを作って、その下で実行してください。尚、下に記載されているのは疑似的なアセンブラなので機械語と1対1ではなく、最終的なバイナリになる前にアセンブラとリンカが勝手に書き換えます。

cat <<! > tmp.txt
        .align 2
        .text
        .global _start
_start:
        li sp, 0x1ffffff8
        call main
!
/usr/bin/unexpand tmp.txt > start.s
/usr/bin/rm tmp.txt

疑似的なアセンブラなので、上に記載した動作を各1行で記述出来、非常にスッキリしています。

動作確認用 C 言語ソースコードの作成:

次のコマンドを WSL のターミナル上にコピー&ペーストすると、main.c というファイルが出来ます。

cat <<! > main.c
int iadd(int a, int b);

int main() {
  volatile int a, b, c;
  a=0xffff0000;
  b=4;
  c=iadd(a, b);
  return c;
}

int iadd(int a, int b){
  return (a+b);
}
!

変数宣言の最初に volatile (揮発性) を付けて居るのは、コンパイラが勝手に変数を除去しない様にする為です。volatile を付けておくと、コンパイラはこの変数は揮発性指示なので、最適化はせずに毎回必ず値を代入せねばならないと認識します。このソースコードの目的は、RISC-V のコンパイラがどの様なソースコードを生成するのかを見る為であって、答えが 0xffff0004 である事を知りたい訳では無いという事です。関数呼び出しをしているのも、コーリングコンベンション (関数呼び出しの規約。ABI とも言います) がどの様な物かを見る為です。

オブジェクトファイルの生成:

main.c をコンパイラに -S オプションを指定して、一旦アセンブリ記述の main.s を生成させ、人手で記述した start.s と共にアセンブラに掛けて実行形式の ELF ファイルを一気に生成しています。start.s と main.s は指定した順番通りにリンカが配置しますので、順番は大事です。尚、.text セクションのアドレスは指定しなくとも、自動的に 0 番地になりました。

riscv64-linux-gnu-gcc main.c -S -mabi=ilp32e -march=rv32e -g -gdwarf-2 -Bstatic
riscv64-linux-gnu-as start.s main.s -o main.out -mabi=ilp32e -march=rv32e

生成されたファイルの確認と、ヘキサや Verilog 用出力:

次のコマンドで、生成された実行形式ファイル (ELF) やデバッグ情報 DWARF の確認と、実行形式ファイルから逆アセンブルしたリストの出力、及び、インテル (Intellec) 形式ヘキサ、モトローラ S レコード形式ヘキサ、Verilog 用 メモリファイルの生成を行います。

file main.out
riscv64-linux-gnu-objdump main.out --dwarf=info | head -10
riscv64-linux-gnu-objdump main.out --disassemble-all --source --section=.text --section=.rodata --section=.bss --section=.data > main.txt

riscv64-linux-gnu-objcopy main.out -O ihex main.ihex --only-section=.text --only-section=.rodata
riscv64-linux-gnu-objcopy main.out -O srec main.srec --only-section=.text --only-section=.rodata
riscv64-linux-gnu-objcopy main.out -O verilog main.verilog --only-section=.text --only-section=.rodata

riscv64-linux-gnu-objcopy main.out -O binary main.binary --only-section=.text --only-section=.rodata
od -A d -t u4 -v main.binary | \
awk '{printf("@%06x %08x %08x %08x %08x\n", $1/4, $2, $3, $4, $5)}' > main.v

 file コマンドで「ELF 32-bit LSB relocatable, UCB RISC-V, version 1 (SYSV), with debug_info, not stripped」と出力されますので、32ビットの RISC-V 用実行形式ファイルである事が分かります。

2行目の objdump コマンドで、デバッグ情報 DWARF のバージョンが2である事を確認します。DWARF2 は、各種 ICE (In-CIrcuit Emulaor) やデバッガに読み込み可能な形式です。高級言語でのソースコードデバッグに必要となります。

3行目の objdump コマンドで、ELF 実行形式ファイルをディスアセンブルして、かつ、C のソースコードを間に挟み込んで居ます。

4行目から6行目の objcopy コマンドで、ELF 実行形式ファイルからインテルヘキサ、モトローラヘキサ、Verilogのメモリファイルの、各アスキー形式で人間が可読なファイルに変換しています。

7行目と8行目は、ELF 実行形式ファイルからベアメタル用の機械語だけのバイナリに変換し、それを UNIX の od (Octal Dump) コマンドでアスキー形式にダンプ後、Verilog にも読み込み可能なヘキサ形式に変換して居ます。
2021-03-29 訂正:
32ビット ROM 用にダンプする場合、アドレスを4で割らなければなりません。更に、od コマンドによる連続値の省略で「*」が出力されないよう「-v」オプションが必要です。


【RISC-V 関係の目次へ戻る】   【WSL 関係の目次へ戻る】



[ Google 自動配信広告 ]