ぐだぐだ低レベルプログラミング(36) RISC-V、ロードとストア、素直に動かしてみる

Joseph Halfmoon

前回、メモリアドレスをロードする小技をやったので、今回のロード、ストアでそれを使うかと思っていたですが、その影もありません。単純な命令テストだな。だって時間無かったのだもん。すみません。まあ一応RV32Iが定義しているロードストア命令の全種類を網羅、といっても8個ばかりですが。

まずは毎回の参照資料へのリンクの再掲載です。

  • RISC-Vの公式ドキュメンテーションはこちら
  • 実験に使用しているGD32VF103VBT6開発ボードはこちら
RV32Iのロードストアの全命令

前回、RISC-Vのロード、ストア命令にはアドレッシングモードが一つしかない、という件は書きました。それをうまく活用して実質的にいろいろなアドレシングモードがあるかの如くに使えるという感じでした。そのためロード/ストア命令の種類はロードストアするオブジェクトの種類そのものとなります。まずはロードから。

  1. 符号付きバイトのロード
  2. 符号無バイトのロード
  3. 符号付きハーフワード(16ビット)のロード
  4. 符号無ハーフワード(16ビット)のロード
  5. ワード(32ビット)のロード

以上の5種類となります。例のごとくレジスタにロードしてしまえば、バイトもハーフワードもワード幅として扱われるので、メモリ上でのオブジェクトの種類を示す、ということであります。

ストアを見てみます。

  1. バイトのストア
  2. ハーフワードのストア
  3. ワードのストア

ストアの時は、符号拡張の有無は関係なく、レジスタの下1バイトなのか、下2バイトなのか、4バイト全部なのかという区別だけなので3命令となります。

実験用のアセンブラ関数

前回までアセンブラ関数を書き込んでいた .S ファイルがだんだん見通し悪くなってきたので、今回アセンブラソースを分けました。以下が今回テストするアセンブラ関数ファイルの全文です。

.section    .text
.align      2
.globl      loadBYTE, loadBYTEU, loadHWORD, loadHWORDU, loadWORD, storeBYTE, storeHWORD, storeWORD

loadBYTE:
    lb      a0, 0(a0)
    ret

loadHWORD:
    lh      a0, 0(a0)
    ret

loadWORD:
    lw      a0, 0(a0)
    ret

loadBYTEU:
    lbu     a0, 0(a0)
    ret

loadHWORDU:
    lhu     a0, 0(a0)
    ret

storeBYTE:
    sb      a0, 0(a1)
    ret

storeHWORD:
    sh      a0, 0(a1)
    ret

storeWORD:
    sw      a0, 0(a1)
    ret

ぶちゃけシンプルな各1命令だけ。簡単な読み書きテストです。

ただ、上記をアセンブルすると、例によってRV32Cに解釈されて短いコードにアセンブルされるものが出てきます。以下を御覧じろ。

disassemble

上記ではワード幅のロード命令と、ワード幅のストア命令が16ビット幅のRV32Cにアセンブルされていることが分かりますか?符号の有無にかかわらずバイトやハーフワードには短縮形の命令が用意されていない(オペコードスペースイの節約のために冷遇?されている)のが分かります。短縮形が使える条件は、

  • ワード幅のロード、ストア
  • 使用するレジスタがx8-x16の8本のどれかであること

上記のアセンブラではCとのインタフェースのため、a0(実はx10のABI的別名)、a1(x11のABI的別名)しか使っていなかったので、2命令が上記の条件に当てはまっていたわけです。

アセンブラ関数の呼び出し側ソース

Cからアセンブラ関数を「みえる」ようにするためのヘッダファイルが以下に。アセンブラ命令上は、バイト、ハーフワードのように命令を使い分けているのですが、各命令の動作の差が見やすいように、Cからは全てレジスタの全幅を使うuint32_tの引数または返り値に見えるようにしています。後で全ビットを標準出力に表示して動作を確認します。

#ifndef TEST_ASM_H
#define TEST_ASM_H

#include <stdint.h>

uint32_t loadBYTE(uint32_t* adr);
uint32_t loadBYTEU(uint32_t* adr);
uint32_t loadHWORD(uint32_t* adr);
uint32_t loadHWORDU(uint32_t* adr);
uint32_t loadWORD(uint32_t* adr);
void storeBYTE(uint32_t dat, uint32_t* adr);
void storeHWORD(uint32_t dat, uint32_t* adr);
void storeWORD(uint32_t dat, uint32_t* adr);

#endif /* TEST_ASM_H */

Cでの関数呼び出し(テスト・シーケンス)部分は以下のようです。身も蓋もない感じ。

uint32_t loadTarget = 0x1234ABCD;   
printf("loadBYTE   = 0x%08x\n", loadBYTE(&loadTarget));
printf("loadBYTEU  = 0x%08x\n", loadBYTEU(&loadTarget));
printf("loadHWORD  = 0x%08x\n", loadHWORD(&loadTarget));
printf("loadHWORDU = 0x%08x\n", loadHWORDU(&loadTarget));
printf("loadWORD   = 0x%08x\n", loadWORD(&loadTarget));
uint32_t storeTarget = 0;
storeBYTE(0xAB, &storeTarget);
printf("storeBYTE  = 0x%08x\n", storeTarget);
storeTarget = 0;
storeHWORD(0xABCD, &storeTarget);
printf("storeHWORD = 0x%08x\n", storeTarget);
storeTarget = 0;
storeWORD(0x6789ABCD, &storeTarget);
printf("storeWORD  = 0x%08x\n", storeTarget);
実験結果

実機上で動作させてみた様子が以下に

loadBYTE = 0xffffffcd
loadBYTEU = 0x000000cd
loadHWORD = 0xffffabcd
loadHWORDU = 0x0000abcd
loadWORD = 0x1234abcd
storeBYTE = 0x000000ab
storeHWORD = 0x0000abcd
storeWORD = 0x6789abcd

データ幅と符号の有無で動作が変わっています。当然か。

ロード、ストアの次は、いよいよ?サブルーチンコールとかかね?

ぐだぐだ低レベルプログラミング(35) RISC-V、32ビットのアドレスをロードする小技 へもどる

ぐだぐだ低レベルプログラミング(36) RISC-V、無条件JMPもRETも皆CALL へ進む