ぐだぐだ低レベルプログラミング(42) 64bitRISC-V、単精度浮動小数点add

Joseph Halfmoon

前回から64bitのRISC-V搭載のK210にターゲットを切り替えたのですが、今回はまさかの問題勃発。Flashに書き込めません。というか接続するとPCの挙動が不審。どうしたものか。そこで困ったときのラズパイ頼み、Picoの母艦のラズパイ4機にK210ボードへの書き込みをお願い。OK、単精度浮動小数点add命令動いています。

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

(今回実験に使用したコード全文は末尾に)

前回から、64bitのRISC-VコアのK210を搭載したMAiX BiTボード(マイク搭載版)にターゲットボードを切り替え。それまでと環境が変わりました。ために、32bitのRISC-VではGASで別ファイルに記述したアセンブラ関数を使っていたのを、インライン・アセンブラでの実験にスイッチ。前回は、インライン・アセンブラへの引数の渡し方をおさらいした、というところ。

さて今回は、前回の流れにそって、いよいよRISC-Vの浮動小数点命令セットRVF(単精度浮動小数)、RVD(倍精度浮動小数)に突入と思ったら、まさかの問題勃発であります。

「とりあえず」単精度浮動小数点加算命令を1つをインライン・アセンブラに置き、それにインタフェースをとるための命令を記述いたしました。ビルドはOK,これを走らせれば fadd.s 命令のサンプル動作OK,と思いきや、肝心のMAiX BiTボードに書き込みできません。仮想シリアルポート番号などは変わっていない。それどころか接続するとPCの挙動そのものが怪しいです。何が起こった。PCの電源入れなおして再トライするもダメ。

他のボードを同じUSBポートに接続するとサクサク動くので、どうも先週から今週への1週間でMAiXとの接続に何かがおきた感じです。

困ったときのラズパイ頼み

PCで作業していても煮詰まってツボにハマるばかりに思えたので、ラズパイ4に御出馬願いました。普段は、ラズパイPicoの母艦として活躍しているデバイスです。万が一、MAiXボードが壊れていたなどの事態であればラズパイ4でも動作しない筈。接続しました。

/dev/ttyUSB0 および /dev/ttyUSB1

としてMAiX Bitボードを認識しました。こころみに /dev/ttyUSB0 に端末ソフトで接続すれば、先週書き込んだプログラムが動作しているみたい。よかった、ボードは壊れてなかった。

シリアルポートが正常に動作するのであれば、プログラムの書き込みが出来る筈。GitHubにある以下のKendryteのツールはPythonベースで、Raspberry Pi OS上でも実行可能。

kflash, A Python-based Kendryte K210 UART ISP Utility

その上、PIPでインストールできます。

$ sudo pip3 install kflash

そしてPC上でビルド済のオブジェクトファイル firmware.bin をラズパイ4機へ転送。インストールした kflash ツールで一発。

$ kflash -p /dev/ttyUSB0 firmware.bin

書き込みOK、そして動作も予定通り。困ったときのラズパイ頼み。

今回実験のインラインアセンブラ

単精度浮動小数点 fadd.s 1命令をテストするために書いたインラインアセンブラは以下です。最初、浮動小数点レジスタに直接浮動小数点数を「突っ込もう」としてみたのですが、上手く行きませんでした。整数レジスタに対してはうまく動く%[Rs1]みたいな記述が、どうも浮動小数点レジスタには働かないみたいです。そこで、

整数レジスタを経由して浮動小数点レジスタに突っ込む

スタイルをとったら、インラインアセンブラが処理してくれるようになりました。そのためターゲットのfadd.sの前後に 整数/浮動小数レジスタ間移動のfmvが合計3個。イマイチですが致し方ありません。

TestFloat f0S, f1S, f2S;

f0S.fDat = 0.0;
f1S.fDat = 1.000001;
f2S.fDat = 0.999999;
asm volatile("fmv.w.x f1, %[Rs1]\n\t"
             "fmv.w.x f2, %[Rs2]\n\t"
             "fadd.s f0, f1, f2\n\t"
             "fmv.x.w %[Rd], f0\n\t"
            : [Rd] "=r" (f0S.u32Dat.LowW)
            : [Rs1] "r" (f1S.u32Dat.LowW), [Rs2] "r" (f2S.u32Dat.LowW)
            : );
Serial.printf(" %f + %f = %f\r\n", f1S.fDat, f2S.fDat, f0S.fDat);
Serial.printf("HEX: %08x\r\n", f0S.u32Dat.LowW);
生成されたオブジェクトコードを確認

実際に生成されたオブジェクトを確認しないと、どんな命令が走るのだか大変不安なので何時ものとおり objdump で覗いてみます。こんな感じ。

80000634:	3f800737          	lui	a4,0x3f800
80000638:	0087079b          	addiw	a5,a4,8
8000063c:	373d                	addiw	a4,a4,-17
8000063e:	f00780d3          	fmv.w.x	ft1,a5
80000642:	f0070153          	fmv.w.x	ft2,a4
80000646:	0020f053          	fadd.s	ft0,ft1,ft2
8000064a:	e00007d3          	fmv.x.w	a5,ft0
8000064e:	f0078553          	fmv.w.x	fa0,a5
80000652:	0007841b          	sext.w	s0,a5

浮動小数点数は、予定どおり「整数のつもり」で整数レジスタ上で作られて、そこから浮動小数点レジスタに送り込まれています。取り出すのも同じ。インライン・アセンブラ、結構苦労しておるな。

動作確認

先に述べたとおり、PC上で動作を確認できなかったので、以下は、PC上で生成の上記オブジェクトをラズパイ4から書き込んで動作確認したものです。

Trial: 8
1.000001 + 0.999999 = 2.000000
HEX: 40000000

このままPCで書き込みできないのは困るけれど、場合によっては開発環境から全部、ラズパイ4に移してしまうのもアリですか。。。

ぐだぐだ低レベルプログラミング(41) 64BITのRISC-Vでインライン・アセンブラ に戻る

ぐだぐだ低レベルプログラミング(43) 64bitRISC-V、レジスタとレジスタ間転送 へ進む

実験に使用したコード全文
#include <Arduino.h>

typedef struct {
  uint32_t  LowW;
  uint32_t  HighW;
} HighLowW;

typedef union {
  uint64_t u64Dat;
  HighLowW u32Dat;
  double  dDat;
  float   fDat;
} TestFloat;

int counter;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  counter = 0;
  Serial.begin(9600);
  while (!Serial);
  Serial.printf("MAiX BiT ASM TEST 001.\r\n");
}

void loop()
{
  if ((counter++ % 2) == 0)
  {
    digitalWrite(LED_BUILTIN, HIGH);
  }
  else
  {
    digitalWrite(LED_BUILTIN, LOW);
  }
  Serial.printf("Trial: %d\r\n", counter);
  //--- DUT --------------------------------
  TestFloat f0S, f1S, f2S;

  f0S.fDat = 0.0;
  f1S.fDat = 1.000001;
  f2S.fDat = 0.999999;
  asm volatile("fmv.w.x f1, %[Rs1]\n\t"
               "fmv.w.x f2, %[Rs2]\n\t"
               "fadd.s f0, f1, f2\n\t"
               "fmv.x.w %[Rd], f0\n\t"
              : [Rd] "=r" (f0S.u32Dat.LowW)
              : [Rs1] "r" (f1S.u32Dat.LowW), [Rs2] "r" (f2S.u32Dat.LowW)
              : );
  Serial.printf(" %f + %f = %f\r\n", f1S.fDat, f2S.fDat, f0S.fDat);
  Serial.printf("HEX: %08x\r\n", f0S.u32Dat.LowW);

  //--- END OF DUT--------------------------
  delay(5000);
}