ぐだぐだ低レベルプログラミング(40) RISC-V、div、RV32M拡張その3

Joseph Halfmoon

前回の乗算に比べると今回の除算命令は分かり易いです。商と余りのどちらか結果はひとつだけという割り切りの効果でしょう。C書いていたら、どちらか一つを求めるのが普通だし、プログラミング言語的にもあっている感じ。そんなこんななのでサクッとやりたいと思います。割り算だよ。

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

参照資料へのリンクの再掲載です。

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

ビルドなどはWindows上のVSCode+PlatformIOで行っています。ただし上記のボードはPlatformIOのターゲットボードではリストされません。これは「Longan Nano」ボードなのだ、と言い聞かせ、Frameworkは GigaDevice GD32V-SDK と設定すればビルドして使うことができます。当然、同じことは「お求めやすくてより小型な」Longan Nanoボードでも出来る筈。どちらも同じRISC-V搭載。

RV32Mの除算および剰余命令

除算と剰余命令は珍しく直交的?なので迷うことはありますまい。

    • 32ビットを32ビットで割った結果の32ビットの商を求める命令2種
    • 一方が符号付きのdiv、他方が符号無のdivu
    • 32ビットを32ビットで割った結果の32ビットの剰余を求める命令2種
    • 一方が符号付きのrem、他方が符号無のremu

このくらいであれば、記憶力が減退しているこの年寄りでも覚えられますぞ(本当か?)

実験用のアセンブラ関数

今回も、1命令=1関数的な手抜きのパターンなので、スタックフレームを用意することもなく、誠に単純、並べただけ。被テスト関数は以下です。

.section    .text
.align      2
.globl      divSigned, divUnsigned, remSigned, remUnsigned

divSigned:
    div     a0, a1, a2
    ret

divUnsigned:
    divu    a0, a1, a2
    ret

remSigned:
    rem     a0, a1, a2
    ret

remUnsigned:
    remu    a0, a1, a2
    ret

罪滅ぼし?にラベルは読めば分かるお名前といたしました。

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

アセンブラ関数の呼び出しに使う関数プロトタイプも、今回は、符号付きはint32_t、符号無はuint32_t と綺麗に分けて書くことができます。前回乗算のように、「とりあえず全部符号無」にしておいて後で必要に応じてキャストみたいな強引な方法をとる必要も無しです。

int32_t  divSigned(int32_t a0, int32_t a1, int32_t a2);
uint32_t divUnsigned(uint32_t a0, uint32_t a1, uint32_t a2);
int32_t  remSigned(int32_t a0, int32_t a1, int32_t a2);
uint32_t remUnsigned(uint32_t a0, uint32_t a1, uint32_t a2);

さて、C言語のmain()関数でアセンブラの被テスト関数を呼び出してテストする部分が以下に。符号付きは被除数がマイナスのケースと、除数がマイナスのケースで、分かり易い(暗算できる)数字を入れてみました。符号無は、符号付とは明らかに結果に差異がでるケースということで、殊更に16進表記としてみました。

int32_t  a0i, a1i, a2i, di1, ri1;
uint32_t a0u, a1u, a2u, du1, ru1;

a0i = 0; a1i = -3334; a2i = 1111;
di1 = divSigned(a0i, a1i, a2i);
ri1 = remSigned(a0i, a1i, a2i);
printf(" %ld / %ld = %ld\n", a1i, a2i, di1);
printf(" %ld %% %ld = %ld\n", a1i, a2i, ri1);

a0i = 0; a1i = 3334; a2i = -1111;
di1 = divSigned(a0i, a1i, a2i);
ri1 = remSigned(a0i, a1i, a2i);
printf(" %ld / %ld = %ld\n", a1i, a2i, di1);
printf(" %ld %% %ld = %ld\n", a1i, a2i, ri1);

a0u = 0; a1u = 0xFFFFFFFF; a2u = 0xF000;
du1 = divUnsigned(a0u, a1u, a2u);
ru1 = remUnsigned(a0u, a1u, a2u);
printf(" 0x%08lx / 0x%08lx = 0x%08lx\n", a1u, a2u, du1);
printf(" 0x%08lx %% 0x%08lx = 0x%08lx\n", a1u, a2u, ru1);

いろいろテストケースを増やすべきなのですが、疲れるのでこんだけです(手抜きだな。)

実機上の実行結果

さて、RISC-V実機(GD32VF103)上での実行結果は以下です。

LOOP : 1
-3334 / 1111 = -3
-3334 % 1111 = -1
3334 / -1111 = -3
3334 % -1111 = 1
0xffffffff / 0x0000f000 = 0x00011111
0xffffffff % 0x0000f000 = 0x00000fff

ちゃんと計算しておるようです。良かった。当たり前か。

次回はどっちへ?

ぐだぐだ低レベルプログラミング(39) RISC-V、mul、RV32M拡張その2 へ戻る

ぐだぐだ低レベルプログラミング(41) 64bitのRISC-Vでインライン・アセンブラ へ進む