FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

複数精度の浮動小数点を取り扱うための手法NaN Boxingについて

RISC-Vのアーキテクチャでは、浮動小数点を扱うレジスタは整数レジスタとは別に用意されている。 基本的にFloat / Double / Quad も1種類の浮動小数レジスタを利用して格納されるので、ハードウェア的にレジスタ長がどのくらいの大きさを持っているのかについては、FLENという値でもって定義されている。 FLEN=32の場合は単精度を格納することができ、FLEN=64の場合は単精度と倍精度、FLEN=128の場合は単精度、倍精度、4倍精度までデータを格納することができる。

f:id:msyksphinz:20180412010726p:plain

このとき、注意しなければならないのは、単精度の命令であろうが倍精度の命令であろうが、オペランドは"f"レジスタという名前で指定する。 識別可能なのは、オペコードの表記であるs / dだけである。 つまり、基本的に単精度浮動小数点命令であろうと、倍精度浮動小数点命令であろうと、指定されるレジスタオペランドは同一のものである。

fadd.s  ft1,ft0,ft0    // 単精度
fadd.d  ft1,ft0,ft0    // 倍精度

このあたりのテストパタンを調査していて気がついたのだが、RISC-Vの浮動小数点命令では、各浮動小数点命令の精度が異なるなかでそれらをうまく共有するために"NaN Boxing"という手法が使われている。 私は初めて知った手法なのだが、どうやら一般的な手法なようだ。

constellation.hatenablog.com

tanakahx.hatenablog.com

FLEN=64で単精度浮動小数点のデータ操作をする場合

例えば、FLEN=64つまり浮動小数レジスタの長さが64ビットであった場合、単精度と倍精度の両方が格納されるわけだが、格納されているデータが単精度である、倍精度であるということを区別するためにはどのようにすればよいのであろうか。

この時に使われる N-Boxingという手法であるが、例えば、FLEN=64の中でFLEN=32の単精度のデータを格納する場合、上位の32ビットをすべて1で埋めてしまう。

以下 RISC-V ISA Specification V2.2 より抜粋。

When multiple floating-point precisions are supported, then valid values of narrower n-bit types,
n < FLEN, are represented in the lower n bits of an FLEN-bit NaN value, in a process termed
NaN-boxing. The upper bits of a valid NaN-boxed value must be all 1s. Valid NaN-boxed n-bit
values therefore appear as negative quiet NaNs (qNaNs) when viewed as any wider m-bit value,
n < m ≤ FLEN.

例えば、FLEN=64で単精度浮動小数点の1.0を表現する場合は、以下のようにして、上位の32ビットを埋めてしまう。これがNaN Boxingだ。

FFFFFFFF_3F800000   // 1.0 

これ、何がNaNなのかというと、FLEN=64である倍精度浮動小数からしてみるとこの値はNaNとして認識される。 つまり、上位がすべて1ならば、本来表現したい精度の浮動小数点よりも大きな精度の浮動小数点では、常にNaNとなる。

3F800000    // 1.0
FFFFFFFF_3F800000  // 単精度では1.0 倍精度ではNaN
FFFFFFFF_FFFFFFFF_FFFFFFFF_3F800000   // 単精度では1.0、倍精度ではNaN、4倍精度ではNaN

これにより、レジスタに付加情報を持たせることなく、どの精度の値が格納されているかということを検証することができる。 これがNaN Boxingの基本的な考え方だ。

f:id:msyksphinz:20180412014134p:plain

RISC-V における NaN-Boxingの利用

さて、これらの技法は、RISC-Vではどのようにして使われているのだろうか? (というか実際にはテストパタンでNaN Boxingが発生して、何故だろう?と調査した結果NaN Boxingについて初めて知ったわけだが...)

  • 浮動小数点数移動命令 FMVx : 浮動小数点数移動命令FMVでは、FLENよりも小さな値を移動するとき、その値はNaN Boxingされる。
  • 浮動小数点符号挿入命令 FSGNJx : RISC-Vには浮動小数点の符号を扱うための命令がある (だいたいこれらはFABS やFNEGのベースの命令として使用される)。これらの命令も、扱うデータがFLENよりも小さい場合 (例えば、FSGNJ.S 命令をFLEN=64の環境で実行する場合)、入力値がNaN-Boxingされているかどうか (つまり上位の32ビットがすべて1になっているか)をチェックし、レジスタに書き込まれる値も常にNaN-Boxingされている

RISC-V における Nan-Boxingの実装

逆に、これらのソースコードを追いかけている間に、NaN-Boxingについて知ったわけだが...

riscv-isa-sim の場合

例えば fsgnj.s 命令の場合には、以下のような実装となっている。

  • riscv-isa-sim/riscv/insns/fsgnj_s.h
require_extension('F');
require_fp;
WRITE_FRD(fsgnj32(FRS1, FRS2, false, false));
  • riscv-isa-sim/riscv/decode.h
/* Convenience wrappers to simplify softfloat code sequences */
#define isBoxedF32(r) (isBoxedF64(r) && ((uint32_t)((r.v[0] >> 32) + 1) == 0))
#define unboxF32(r) (isBoxedF32(r) ? (uint32_t)r.v[0] : defaultNaNF32UI)
#define isBoxedF64(r) ((r.v[1] + 1) == 0)
#define unboxF64(r) (isBoxedF64(r) ? r.v[0] : defaultNaNF64UI)
typedef float128_t freg_t;
inline float32_t f32(freg_t r) { return f32(unboxF32(r)); }
inline float64_t f64(freg_t r) { return f64(unboxF64(r)); }

Rocket-Chipの場合 (Chisel)

Rocket-Chipの場合も、入力値に対してNaN-Boxingを適用する論理が挿入されている。

  val fsgnjMux = Wire(new FPResult)
  fsgnjMux.exc := UInt(0)
  fsgnjMux.data := fsgnj

  when (in.bits.wflags) { // fmin/fmax
    val isnan1 = maxType.isNaN(in.bits.in1)
    val isnan2 = maxType.isNaN(in.bits.in2)
    val isInvalid = maxType.isSNaN(in.bits.in1) || maxType.isSNaN(in.bits.in2)
    val isNaNOut = isnan1 && isnan2
    val isLHS = isnan2 || in.bits.rm(0) =/= io.lt && !isnan1
    fsgnjMux.exc := isInvalid << 4
    fsgnjMux.data := Mux(isNaNOut, maxType.qNaN, Mux(isLHS, in.bits.in1, in.bits.in2))
  }

というわけで、例えばfsgnj命令の実装する場合も、単純に上位の符号のみ書き換えればよいわけではないので注意。

「30日でできる!OS自作入門」を読み始めた (17. 18日目)

遅まきながら、「30日でできるOS自作入門」を読み始めた。

30日でできる! OS自作入門

30日でできる! OS自作入門

18日目はコマンドの導入だ。memコマンドとclsコマンドを導入した。 dirコマンドは、どうもファイルフォーマットをFAT32に合わせないといけないらしい。これはちょっと改造が必要だなあ。

f:id:msyksphinz:20180414004836g:plain
図. memコマンドとclsコマンドを導入した。

2018/04/14追記。mformatmcopy を追加してアップデートした。

$(TARGET).img: ipl10.bin $(TARGET).sys
        mformat -f 1440 -C -B ipl10.bin -i $(TARGET).img ::
        mcopy $(TARGET).sys -i $(TARGET).img ::
        mcopy ipl10.nas -i $(TARGET).img ::
        mcopy Makefile  -i $(TARGET).img ::
f:id:msyksphinz:20180414124037p:plain
図. dir コマンドを実装

RISC-V の メモリプロテクション機構について (PMP : Physical Memory Protection)

RISC-V はどのようなターゲットにも適用することの出来るCPUアーキテクチャを目指している。 それはMPU(Micro Processor Unit) だけでなく MCU(Micro Controller Unit)としても活用することができる。

MCUで多く使用される(MPUもだろうけど) メモリ保護の機構についても、RISC-Vで定義されている。 大きく分けて、 PMA(Physical Memory Attribute) と PMP(Physical Memory Protection) である。 PMAはハードウェアで設定されているメモリマップ属性、PMAはソフトウェアで設定することのできるメモリマップ属性だ。

まずは、PMP(Physical Memory Protection) について仕様を見てみよう。 RISC-V Privileged Mode Manual Ver.1.10 を参照しながら、読み解いていこう。

物理メモリのプロテクション (Physical Memory Protection: PMP)

PMA(Physical Memory Attribute)が、ハードウェア的にメモリアクセスの属性を定義する機構であることに対し、PMP(Physical Memory Proctection)はソフトウェアによるメモリ領域の保護機構である。 PMPによるチェックはPMAと同時にチェックされる。

PMPアクセスもPMAと同様に、すべてのメモリアクセス(Read/Write/命令フェッチ)に適用される。 ただし、いくつか制約があり、

  • S-Mode/U-Modeの場合はすべてのメモリアクセス(Read/Write/命令フェッチ)に対してPMPが適用される。
  • mstatusレジスタのMPRVビットが設定されており、mstatusレジスタのMPPフィールドにS-Mode/U-Modeが設定されている場合に、ロード・ストア操作に対してPMPが適用される。

PMPチェックはすべてのページテーブルアクセスに対してもチェックされる。

また別のケースとして、PMPレジスタ自体がロックされている場合にはM-Modeでも適用されるケースがある。

PMPCSRレジスタ構成

PMPの設定は大きく分けて2種類のレジスタで構成される。

  • pmpcfg : PMPのコンフィグレーション
  • pmpaddr : PMPの設定アドレス

まずはpmpcfgから見ていこう。RV32とRV64では構成が異なるので要注意である。

PMPは現在の構成では16種類設定できるようになっている。

f:id:msyksphinz:20180410004114p:plain
図. pmpcfg レジスタのレイアウト(RV32)
f:id:msyksphinz:20180410004255p:plain
図. pmpcfgレジスタのレイアウト(RV64)

このpmpcfgレジスタレジスタレイアウトだが、以下のようになっている。

f:id:msyksphinz:20180410004134p:plain
図. pmpcfgレジスタのビットレイアウト
  • Lビット : Lockビット。pmpcfgレジスタのRWを設定する。
  • Aビット : アドレスマッチングのためのモード
  • X/W/R ビット : メモリアクセスのためのフィールド

これらについては詳細を後に見ていく。

次にpmpaddrだ。これはpmpcfgと対になって使用される。こちらもRV32とRV64で構成が異なっている。

f:id:msyksphinz:20180410004154p:plain
図. pmpaddrレジスタのレイアウト(RV32 / RV64)

アドレスマッチングの方法

PMPレジスタを使ったページアクセス許可の判定では、

  • アドレスをどのようにして判定するか
  • アクセス許可の判定方法

の2つに大別される。まずはアドレスマッチングの方法について見ていく。

アドレスマッチング方式の種類

アドレスマッチング方式についてはpmpcfg のAビットで設定される。Aビットは2ビット長であり以下のエンコーディングで設定される。

A 名前 説明
0 OFF Null領域(PMP無効)
1 TOR Top領域
2 NA4 4バイト領域にアラインするモード
3 NAPOT 8バイト以上で2の累乗の領域でアラインするモード

TORの意味は、そのレジスタで設定されたアドレスは、アドレスマッチングの上位のアドレスであることを示している。例えばpmpcfg[i]レジスタでTORを設定すると、pmpaddr[i]pmpaddr[i-1] で指定される領域でアドレスマッチングが行われる。もしi=0の領域でTORを設定した場合、0<addr<pmpaddr[0] までがアドレス設定領域として指定される。

一方でNAPOT / NA4はアドレスマスクを使ってアドレス領域を設定する。以下のようなイメージだ。つまり、NA4はNAPOTの1種類ととらえることができるだろう。

f:id:msyksphinz:20180410004212p:plain
図. NAPOT/NA4のアドレス領域指定方法。NA4は4バイトでアラインする。それ以外はマスクとして指定。

PMPレジスタのアドレスマッチングは、下位のレジスタ番号から順に適用される。M-Modeの場合、すべてのレジスタアドレスにマッチングしなかった場合に、アクセスはスルーされ、そのままバスに流される。S-Mode/U-Modeにおいて、PMPレジスタエントリが実装されている場合は、レジスタアドレスにマッチングしないアドレスはアクセスはブロックされる。

Lockビットの役割

pmpcfgのLビットは、Lockビットの意味を持っている。このビットを設定するとM-ModeでもProtection判定が適用され、R/W/Xのビット設定もM-Modeでもチェックされるようになる。

Lビットの解除は、RISC-Vのシステムリセットでしか解除することができない。したがってこの設定は半永久的である。

「30日でできる!OS自作入門」を読み始めた (16. 17日目)

遅まきながら、「30日でできるOS自作入門」を読み始めた。

30日でできる! OS自作入門

30日でできる! OS自作入門

17日目はいよいよコンソールを導入した。カーソルを表示してキー入力をできるようにするために、FIFOの位置を変えてキーがタスク毎に割り振られるようにしている。 いろんな仕組みを入れないといけないので大変だ。

タスク毎にキーを割り振らないといけないのは大変なので、キー入力のための専用のFIFOを用意してもいいんじゃないかという気がしてきた。 まあ、その辺りは今後改善されていくんだろう。

f:id:msyksphinz:20180409003409p:plain
図. コンソール画面を表示した。
f:id:msyksphinz:20180409003421g:plain
図. コンソール画面と別のウィンドウをTabキーで切り替えることができるようにする。
f:id:msyksphinz:20180409003523g:plain
図. タブキーで切り替えたウィンドウで、文字が入力できるようにする。
f:id:msyksphinz:20180409003544g:plain
図. タブキーで切り替えたウィンドウで、大文字小文字や記号の入力ができるようになった。

「30日でできる!OS自作入門」を読み始めた (15. 16日目)

遅まきながら、「30日でできるOS自作入門」を読み始めた。

30日でできる! OS自作入門

30日でできる! OS自作入門

タスク管理の続き。タスク管理の高速化と、タスクの優先順位、さらにタスクグループを作成してタスクの優先度によりマウスやキーボードの反応が遅くなることを防いでいる。

f:id:msyksphinz:20180407215426p:plain
図. タスク自動管理システムを導入
f:id:msyksphinz:20180407215503p:plain
図. タスクを4つ定義して同時に動かす。
f:id:msyksphinz:20180407221839p:plain
図. 4つのタスクに対して優先度を付加して、タスクの割り当て時間を変える。

関連記事

APSにRISC-Vの記事を寄稿しました (第2回 RISC-Vの命令セット)

APSさんにRISC-Vの記事を寄稿しました。第2回。たぶん1か月に1回くらいの更新です。

www.aps-web.jp

初心者向けとか言いながら、ISAの少し深いところにも立ち寄っています。まあこういう記事に興味を持つ人って、ぶっちゃけあまり初学者じゃないですよね。

APSさんの記事では、ISAのSIMD命令とか、カスタム命令にも少し触れています。概要をつかむにはもってこいです。

というわけで、たぶん全8~10回くらい。よろしくお願いします!

RISC-Vの仮想アドレス→物理アドレス変換機構の構成と実装について (Ver.1.10版)

RISC-Vはシンプルな構成を特徴としているISAではあるが、一つの特徴としてMMUの構成があまりシンプルではないということがある。 シンプルなRISC-Vの構成の中で、ハードウェアページテーブルウォークを採用したRISC-Vは、それも一緒に実装しないと仮想アドレスを導入することができない。

仮想アドレスの変換方式やシステムレジスタの構成は、RISC-V Privilegeモードのバージョンアップに基づいて変更が行われている。

特にRISC-V Privilege Mode Ver.1.7で大きく変更が行われている印象だ。昔Sv39の仕様について書いた気がするのだが、この情報はちょっと古い。

msyksphinz.hatenablog.com

というわけでもう一度まとめてみよう。

RISC-Vの仮想アドレスモード

RISC-Vの仮想アドレスモードは複数用意されている。特にRV32とRV64では使用できる仮想アドレスの種類が違うので注意が必要だ。

  • RV32 の場合
    • Bare : アドレス変換なし。物理アドレスをそのまま使用する。
    • Sv32 : ページベース 32ビット仮想アドレッシング
  • RV64 の場合
    • Bare : アドレス変換なし。物理アドレスをそのまま使用する。
    • Sv39 : ページベース 39ビット仮想アドレッシング
    • Sv48 : ページベース 48ビット仮想アドレッシング
    • Sv57 : ページベース 57ビット仮想アドレッシング (実装仕様なし。予約済み)
    • Sv64 : ページベース 64ビット仮想アドレッシング (実装仕様なし。予約済み)

となっている。これらのモードはどのシステムレジスタのどのビットを参照すれば良いのだろう?それは、Ver1.7から導入された新しいシステムレジスタsatpを参照する。 これまではspbtr と呼ばれていたのだが、名称とビットアドレスが変更されている。

(RISC-V Privileged Mode Ver.1.10 の変更点要約より抜粋)
The supervisor virtual memory configuration has been moved from the mstatus register to the sptbr register. Accordingly, the sptbr register has been renamed to satp (Supervisor Address Translation and Protection) to reflect is broadened role.

SATP システムレジスタは以下のような構成になっている。

f:id:msyksphinz:20180408194618p:plain
f:id:msyksphinz:20180408194820p:plain
図. SATPレジスタの構成。RV64構成時

SATPシステムレジスタの中でMODEレジスタが存在しており、MODEレジスタの値で仮想アドレッシングの構成が決められている。

0 1 2-7 8 9 10 11 12-15
RV32 Bare Sv32 - - - - - -
RV64 Bare - - Sv39 Sv48 Sv57 Sv64 -

仮想アドレッシングでのアドレス変換の方法

仮想アドレッシングとして定義されているSv32 / Sv39 の両方とも基本的な考え方は一緒である。 ハードウェアページテーブルウォークの方式を採用しており、以下のような手段でアドレス変換を行う。

  • 仮想アドレスを VPN[N], PageOffset に分割する。
  • (変換後の)物理アドレス を PPN[N], PageOffset に分割する。
  • PTE(Page Table Entry) と呼ばれるメモリ中に配置されたテーブルをウォークしていき、アドレス変換を行う。

という仕組みになっている。Sv32 と Sv39 のVPN / PPN / PTE の構成について、資料から抜粋しておく。

f:id:msyksphinz:20180408200249p:plain
図. Sv32 でのアドレス変換テーブルの構成
f:id:msyksphinz:20180408200302p:plain
図. Sv39 でのアドレス変換テーブルの構成

実際のアドレス変換の方法だが、以下に概要を示す。

f:id:msyksphinz:20180408212913p:plain
図. RISC-Vでのアドレス変換方式の概要 (Sv39の例。Sv32では最大でも2回のアドレス参照に限定される)
  1. 最初に参照するページテーブルのアドレスは、 SATP.PPN×PAGESIZEとなる。これをAとする。Sv39はi=2, Sv39はi=1とする。
  2. 最初に参照するページテーブルのアドレスを計算する。A + VA.VPN[i]×PTESIZE がそのアドレスであり、PTE(Page Table Entry)を参照する。
  3. PTEエントリを参照し、PTE.V(valid信号) / PTE.R / PTE.W などを参照してページテーブルの有効性をチェックする。
  4. PTE.R = 1でPTE.X =1 であれば、そのエントリは最終エントリである。PTE参照を中止し、5. に移動する。そうでなければ、次のPTEを参照する。iを1つデクリメントし、A = PTE.PPN × PAGESIZE とし、2. に移動する。
  5. 最終PTEエントリを発見すると、そのエントリにアクセスできるかどうかをチェックする。アクセス許可が得られなければ、ページアクセス例外を発生する。
  6. i > 0 かつ PA.PPN[i-1] != 0 であれば、ページテーブル参照のMisAlignである。ページアクセス例外を発行する。
  7. PTE.A および PTE.D に基づくアクセスチェックを行う。
  8. 変化は終了である。以下のようにして物理アドレスを生成する。
  9. PA.PageOffset = VA.PageOffset
  10. i > 0 であれば、PA.PPN[i-1:0] = VA.PPN[i-1:0] とする。つまり、途中までのページテーブルを使って物理アドレスを生成する。
  11. のこりのアドレス領域を変換する。 PA.PPN[LEVELS-1:i] = PTE.PPN[LEVELS-1:i] とする。

注意事項としては、PTEのアクセス判定チェックの方式だ。Ver.1.7ではかなり複雑なアクセスチェック方式が採用されていたが、Ver.1.7ではアクセスチェック方式が簡略化されている。

f:id:msyksphinz:20180408214229p:plain
図. Ver.1.7でのPTEのアクセス判定方式。
f:id:msyksphinz:20180408214258p:plain
図. Ver.1.10でのPTEのアクセス判定方式。

例外発生時アドレスを格納するTVALレジスタ

Ver.1.7以降で新設されたTVALレジスタ (MTVAL / STVAL / UTVAL) は、これまでは MBADADDR / SBADADDR レジスタなどと呼ばれていたが、名称が変更された。

これはメモリアクセス時の例外時に設定されるレジスタで、似たようなレジスタとしてはSEPCレジスタがあるのだが、

  • TVALレジスタ : 例外が発生したメモリアドレス (つまり、ロード・ストア命令ではアクセスを行ったアドレス)が格納される。
  • EPC レジスタ : 例外が発生した命令のアドレス(つまり、ロード・ストア命令では、アクセスを行った命令のアドレス)が格納される。
f:id:msyksphinz:20180408215601p:plain
図. STVALレジスタの構成。