ぐだぐた低レベルプログラミング(28) RISC-Vでアセンブラ再開、環境のレストア

Joseph Halfmoon

昨年は結構RISC-Vマイコンを動かしていたのですが、RISC-V debuggerをボードから外してしまった後「疎遠」となっておりました。今回開発環境をレストア(といってデバッガ取り付けるだけですが)し、再び取り組んでみたいと思います。目標はRV32I全命令を動かしてみること。全命令というと大変そうですがRISC-Vだもんね。

※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら

(今回実験に使用のソース全文を末尾に並べました。)

表裏2ページのリファレンス・カード(グリーン・カードとも言うらしい。グリーンじゃないけど)に全命令が収まるので有名なRISC-V(リスク・ファイブ)、その中でも32ビットの基本整数命令RV32Iはその中心であります。「RISC-V原典」から1行引用させていただくと、

RV32Iは凍結されていて、変更されることはない。

そうです。拡張で追加されることはあってもRV32Iは変更されることがないので、これを前提にしてよい、ということみたいです。64ビットのRV64とも命令は「ほぼほぼ」一致なので、これを覚えておけば良いみたい。

今回から実験に使用するハードウエア

「お手軽」で「お求めやすい」デバイス指向なので、昨年の以下の投稿で使わせていただいたハードウエアを使用します。

鳥なき里のマイコン屋(97) GD32開発ボードにRISC-Vデバッガ接続

購入元はSWITCH SCIENCE社なので、そちらの通販サイトへのリンクを貼っておきます。

SWITCHSCIENCE SeeedStudio GD32 RISC-V 開発ボード

SWITCHSCIENCE Sipeed RISC-V デバッガ

なお、マイコン・ボードはSeeedStudio社製で、GD32VF103VBT6というGiga Device社のRISC-V搭載デバイスを搭載しています。実は同じマイコンの小ピンデバイスを搭載している Sipeed社製の Longan Nanoという超小型マイコンボードがあり、

    • Longan Nanoは小ピンで使えない周辺あり、GD32VF103VBT6は多ピンで皆使える
    • Longan Nanoは漏れなく超小型のディスプレイを背負っているが、GD32VF103VBT6ボードのディスプレイはオプション

同じSDKで開発できるので、ソフトウエア・プロジェクトの作成時にはLongan Nanoのつもりでやれます。

開発環境

ソフトウエアのビルドはWindows 10のPC(x64)上で以下の環境でやっております。

VS Code + PlatformIO

VS CodeのExtensionからPlatformIOを一度インストールすれば、ターゲットのマイコンボードとフレームワークを指定するだけで、クロスコンパイル用のツールチェーンからSDKから必要なものを自動でかき集めてくれる(ダウンロードに時間かかりますが)ので超便利です。

今回のようにGD32VF103VBT6用にプロジェクトを作成する場合、Project Wizardを開いて、ボードはSipeed Longan Nano、フレームワークはGigaDevice GD32V SDKを指定します。ツールチェーンやSDKは1度ダウンロードすれば2度目からはインストール済のものを使います(アップデートの際には再ダウンロードされるケド。)

PlatformIO_Projectさて、空のプロジェクトを作成したのは良いですが、最初は幾つか準備しないとなりません。

RISC-Vデバッガのドライバの変更

USB接続だけでもマイコンボードにオブジェクトファイルを書き込むことはできるのですが、デバッグするためにはデバッグ用のハードウエアを接続しないとなりません。その場合USB接続は電源供給のためです。アイキャッチ画像の左下の黒い小さいものが Sipeed社のRISCV debuggerというものです。ぶっちゃけFTDI社のデュアルCH USBシリアルチップ。Windows 10 PCに差し込むと単なるUSBシリアル2チャンネルと認識されるので、デバッガで使うためにドライバを入れ変えておかないとなりません。

定番ツールの Zadig.exe で以下でできます。DualのInterface 0の方を切り替えないといけません。Interface 1の方はマイコンボードの標準出力を向けられるのでprintfなどで使えます。この作業は「最初に1回だけ」の筈ですが、私の場合、昨年やったのに、その後、USBのポートを整理してつなぎ変えたためなのか、Windowsのアップデートのためなのか、Dual USBシリアルに戻っていたので今回再度やり直しました。

zadig

プロジェクトの設定

さて、PlatformIOをお使いであればご存じのとおり、プロジェクトの各種設定は自動生成されるplatformio.iniファイルに書き込まれています。作成した状態ではボードとframeworkの設定しかないので、以下のように何行か追加しないとRISC-Vデバッガでのオブジェクトの書き込みやデバッグ、そしてprintfなどの出力の表示ができません。なお、monitor_portは、Interface 1が割り当てられているWindowsの仮想シリアルポートに向けておかないとなりません、念のため。

ProjectSettings
なお、プロジェクトのディレクトリとファイル構成が上記画面の左側に表示されています。

アセンブラ関数駆動用のメインプログラム(C言語)

全部をアセンブラで書くのはシンドイので、メインプログラムはCで書いています。後ろの方にCのコードを示しました。メインプログラムではボードの動作状況が分かるように

    • ボード上のLED3個の点灯、消灯
    • printfできる標準出力

を使えます。なお、表示などで「待ち」が必要な場合向けにSDKから systick.c をコピーして同じディレクトリに配置(systick.hもincludeディレクトリに)しています。マイコン周辺回路を使う場合はSDK内に各種ドライバが用意されていますがメンドイです。アセンブラのトライアルであれば systick だけ使えるようにしておくのがお手軽、という判断です。

それからボード上のLEDにはBLUE, RED, GREEN見たいな回路図上の名前が付いているのですが、手元のボードではすべて青色でした。場所でしか識別できないので念のため。

被テストアセンブラ関数

アセンブラ関数は、gasでアセンブルする .s ファイルに書き込みます。また、cソースからアセンブラ関数が見えるように、.s ファイルに対応するCのヘッダファイルも必要です。今回、アセンブラファイルが2つあるのは、

    • 1本は、RISC-Vで定義されているパフォーマンスカウンタ類を使って実行速度や命令数などを数えるためのユーティリティ関数。
    • もう1本が、アセンブラの練習に使うソース。現状は nop命令10個(本当はRISC-Vにnop命令は無いのだが)を実行するだけの関数。

と分けてあるためです。なお、パフォーマンスカウンタ類は昨年いろいろやってみたので、必要になったら適宜リンクを貼り付けます。

実行例

ビルド後、ボードにオブジェクトを書き込むと、LEDがチカチカするとともに、毎秒、何度目のループかを言った後に、cycNop10関数の中身の実行に何サイクルかかったか、が報告されます。こんな感じ。

LOOP : 6
cycNop10 : 0000000b

0xb=11サイクルなので、NOP命令10個+パフォーマンスカウンタの読み出しにかかる1サイクルで1命令1サイクルの割で実行されていることが分かります。

次回からは nop を他の命令で置き換えて、ステップ実行など行いながら動作を勉強していきたいと思います。

ぐだぐだ低レベルプログラミング(27) IARのIDEでArmのアセンブラを へ戻る

ぐだぐだ低レベルプログラミング(29) RISC-VでMV(MOVE)命令、本当は無い へ進む

実験で使用したC言語の上位層プログラムのソース
#include "gd32vf103.h"
#include "systick.h"
#include <stdio.h>
#include "testCSR.h"
#include "testPFC.h"
#include "riscv_encoding.h"

#define LED_RED (0x20)
#define LED_GREEN (0x01)
#define LED_BLUE (0x02)

void initializePeriph(void) {
    // Enable Peripheral clock
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOB);     // initialize GPIO
    rcu_periph_clock_enable(RCU_GPIOC);     // CONTROL
    rcu_periph_clock_enable(RCU_USART0);
    rcu_periph_clock_enable(RCU_PMU);
    //rcu_periph_clock_enable(RCU_BKPI);
    // PB5  ... LED_RED
    // PB0  ... LED_GREEN
    // PB1  ... LED_BLUE
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_5 | GPIO_PIN_1 | GPIO_PIN_0); // LED
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); // USART0 TX
    gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10); // USART0 RX
}

void contLED(int onFlag, int offFlag) {
    if ((onFlag & LED_RED) != 0) {
        gpio_bit_reset(GPIOB, GPIO_PIN_5);
    }
    if ((onFlag & LED_GREEN) != 0) {
        gpio_bit_reset(GPIOB, GPIO_PIN_1);
    }
    if ((onFlag & LED_BLUE) != 0) {
        gpio_bit_reset(GPIOB, GPIO_PIN_0);
    }
    if ((offFlag & LED_RED) != 0) {
        gpio_bit_set(GPIOB, GPIO_PIN_5);
    }
    if ((offFlag & LED_GREEN) != 0) {
        gpio_bit_set(GPIOB, GPIO_PIN_1);
    }
    if ((offFlag & LED_BLUE) != 0) {
        gpio_bit_set(GPIOB, GPIO_PIN_0);
    }
}

void usart0_config(void) {
    usart_deinit(USART0);
    usart_baudrate_set(USART0, 115200U);
    usart_word_length_set(USART0, USART_WL_8BIT);
    usart_stop_bit_set(USART0, USART_STB_1BIT);
    usart_parity_config(USART0, USART_PM_NONE);
    usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
    usart_enable(USART0);
}

int main( void ) {
    int loopCount = 0;
    unsigned int cntDiff;

    write_csr(0x320, 0); //mcountinhibit
    initializePeriph();
    usart0_config();
    delay_1ms(1000);

    while(1) {
        printf("LOOP  : %d\n",loopCount++);
        //RED
        contLED(LED_RED, (LED_GREEN | LED_BLUE));
        delay_1ms(500);
        // GREEN
        contLED(LED_GREEN, (LED_RED | LED_BLUE));
        delay_1ms(500);
        // BLUE
        //--- DUT ---
        write_csr(0x320, 0); //mcountinhibit
        cntDiff = cycNop10();
        printf("cycNop10       : %08x\n",(unsigned int)cntDiff);
        contLED(LED_BLUE, (LED_RED | LED_GREEN));
        //--- END OF DUT---
        delay_1ms(500);
    }
}
サイクル数等計測用のアセンブラルーチン
.section    .text
.align      2
.globl      rdcycX, rdtimeX, rdinstretX

rdcycX:
    addi    sp,sp,-16
    sw      ra, 12(sp)
    xor     a0, a0, a0
    rdcycle  a0
    lw      ra, 12(sp)
    addi    sp,sp,16
    ret

rdtimeX:
    addi    sp,sp,-16
    sw      ra, 12(sp)
    rdtime  a0
    lw      ra, 12(sp)
    addi    sp,sp,16
    ret

rdinstretX:
    addi    sp,sp,-16
    sw      ra, 12(sp)
    rdinstret a0
    lw      ra, 12(sp)
    addi    sp,sp,16
    ret
上記アセンブラプログラムのヘッダファイル
#ifndef TEST_CSR_H
#define TEST_CSR_H

#include <stdint.h>

uint32_t rdcycX(void);
uint32_t rdtimeX(void);
uint32_t rdinstretX(void);

#endif /* TEST_CSR_H */
練習用のアセンブラソース
.section    .text
.align      2
.globl      cycNop10

cycNop10:
    addi    sp,sp,-16
    sw      ra, 12(sp)

    rdcycle  t0
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    rdcycle a0
    sub a0, a0, t0

    lw      ra, 12(sp)
    addi    sp,sp,16
    ret
上記のヘッダファイル
#ifndef TEST_PFC_H
#define TEST_PFC_H

#include <stdint.h>

uint32_t cycNop10(void);

#endif /* TEST_PFC_H */