lynxeyedの電音鍵盤

MBDとFPGAと車載で使うデバイスの備忘録

自作RISC-V向けにCライブラリの整備やPlatformDesigner連携をする

前回の記事でCが使えるようになりました。ですが、標準入出力もない乗除算もできない不便なものでした。
組み込みマイコンレベルで使えるように整備していきます。

libgccをリンクする

RV32Iは算術演算命令セットとしては加減算とシフトくらいしかありません。
乗除算は上記機能を組み合わせることによって実装しますので、libgccをリンクする必要が生じます。
uint同士の乗算など一部例外はありますが、大抵の場合libgccを用意するか、必要とされるルーチンを自作しないとできないはずです。
電子計算機の授業じゃあるまい自作してたら時間が足りません。

参考:
stackoverflow.com

これを参考に以下のようにMakefileを修正しました。

COPS = -Wall -march=rv32i -mabi=ilp32 -O2 -nostartfiles -ffreestanding
COPS2 = -Wall -march=rv32i -mabi=ilp32 -O2 -nostartfiles -ffreestanding -Xlinker -T -Xlinker $(MEMMAP)


# -------------中略-------------

$(ASM_DIR)/main.o : $(ASM_DIR)/main.c
	$(RISCVGNU)-gcc $(COPS) -c $(ASM_DIR)/main.c -o $(ASM_DIR)/main.o

$(ASM_DIR)/blinker.elf : $(ASM_DIR)/linker.ld $(ASM_DIR)/utils.o $(ASM_DIR)/main.o
	$(RISCVGNU)-gcc $(COPS2) $(ASM_DIR)/utils.o  $(ASM_DIR)/main.o -lgcc -o $(ASM_DIR)/blinker.elf

修正したMakefile全体はこちらをご覧ください
github.com

まともなprintfを準備する

まとも= int型/float型の表示かつ32文字以上は一度にprintfできるもの、くらいに捉えてもらえれば。
ChaNさんのxprintfを使わせていただきました。移植まで5分かかりませんでした。
ありがとうございます。
移植のためにしたことは、

  • 上記ファイルのダウンロード、プロジェクトへのコピー
  • int64_t型をprintfすることが多いのでxprintf.hのXF_USE_LLIを1に変更
  • Makefileにxprintf.cの追加

使う際は、すでに似非printfを実装するのに使っていた1文字出力関数uart_putc()関数をオーバーライドするだけで済みます。

xdev_out(&uart_putc);

楽なうえに超軽量です。riscv32-unknown-elfでlong longを有効にしてもxprintfは1kB弱です。
なお、標準関数のsprintfを使うと10CL025の場合、On-Chip RAMの大半を食い尽くす現象が発生したので試していません。お前のためにOn-Chip RAM用意したわけじゃないんだよsprintf君。

サンプルコードは以下。
github.com

例外を実装する

アセンブラレベルではmtvecに例外番地を指定すると、何らかの例外発生時に指定番地に飛ぶように記述できます。なお、指定していない場合、今回作成したRISC-Vは0xDEAD番地にジャンプします。(SignalTapで観察したときにわかりやすいと言う理由でこのような実装にした)
スタートアップコードを見ていただければわかりますが、__expr()関数にジャンプするようにしています。ですのでCソース側で__expr()を作成すると、例外発生時に任意のコードを実行します。現在の実装では

// exception
void __expr(void) {
    xprintf("program exception....\r\n");
    xprintf("cpu stop.\r\n");
    while(1);
}

としています。適宜書き換えてください。全体のソースコードは以下から。
KyogenRV/krv_utils.c at 0.2.6 · panda5mt/KyogenRV · GitHub

  • 例外を発生させてみる

main.c 内でLoad Address Misaligned*1例外を発生させてみます。lw命令(4バイトロード命令)は32bitのアラインがあります。わざとアラインメントを跨ぐ命令を記述します。

int main(int argc, char *argv[]) {
    xdev_out(&uart_putc);       // override xprintf

   // (中略)

    xprintf("KyogenRV (RV32I) Start...\r\n");

    get32(0x01); // <- ミスアライン発生

    // .......

}

UARTから出力されるメッセージを見ます。


f:id:Lynx-EyED:20210129085730p:plain

PlatformDesignerで作成したペリフェラル番地をCヘッダに書き出す方法

NiosII/e /f用に作成されたAPIを他の自作Avalon-MMマスタのために利用することはHALのライセンス違反となりできません。
しかしPlatformDesignerで作成したペリフェラル番地をヘッダファイルとして取得し利用することは可能です。
下記のように実行すると、ヘッダファイルqsys_mem_map.hが得られます。

sopcinfo2swinfo.exe --input=${DESIGN_NAME}.sopcinfo
swinfo2header.exe --swinfo ${DESIGN_NAME}.swinfo --module ${Avalon-MMモジュール名} --master ${モジュールに複数のマスタがある場合、使用するマスタ名を指定} --single ${SWDIRW}/qsys_mem_map.h

このヘッダファイルをCのプロジェクトで使うことができます。

SDRAMコントローラの実装

Avalon-MMスレーブは、マスタ側がリード命令を発行した次サイクルでデータを用意します*2。ですがスレーブ側が有効なデータを準備できないときがあります。このときスレーブはマスタヘwaitrequestを発行しリード動作を待ってもらうのですが、タイミング次第で、発行されたwaitrequestが有効か否かがわからない状態でした。SDRAMではこの状況がCASレイテンシとの兼ね合いで多発します。この部分を修正し、無事SDRAMのリードライトが正しく動作するようになりました。

SDRAMのRWコードはこんなふうに書けます。上記のqsys_mem_map.hをインクルードしていることが前提となります。

// SDRAM test
int sdram_test(void) {
    uint32_t   data,length;

    length = SDRAM_0_END - SDRAM_0_BASE;

    xprintf("SDRAM write start\r\n");
    for (int k = 0 ; k < length ; k = k + 4){
        put32(SDRAM_0_BASE + k, k);
    }

    xprintf("SDRAM read start\r\n");
    for (int k = 0 ; k < length ; k = k + 4){
        data = get32(SDRAM_0_BASE + k);
        if(data != k) {
            xprintf("error fount at 0x%x: expecting %d but got %d\r\n",k,k,data);
            return -1;
        }
    }
    return 0;
}
    

プロジェクト全体のダウンロード

以下からCloneできます。

git clone http://github.com/panda5mt/KyogenRV -b 0.2.6 --depth 1 

github.com

*1:参照: https://riscv.org/wp-content/uploads/2017/05/riscv-privileged-v1.10.pdf p.35

*2:PlatformDesignerで指定したレイテンシに依存