前回の乗算に比べると今回の除算命令は分かり易いです。商と余りのどちらか結果はひとつだけという割り切りの効果でしょう。C書いていたら、どちらか一つを求めるのが普通だし、プログラミング言語的にもあっている感じ。そんなこんななのでサクッとやりたいと思います。割り算だよ。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
参照資料へのリンクの再掲載です。
ビルドなどは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
ちゃんと計算しておるようです。良かった。当たり前か。
次回はどっちへ?