FPGA開発日記

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

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

結構昔にRISC-VのPMP機構(Physical Memory Protection)について調査したのだった。

msyksphinz.hatenablog.com

改めて復習しているが、PMPのNAPOTについてあまり明確に理解していなかった。 PMPのアドレス範囲選択については、3つのモードがある:

  • pmpcfg[*].A == 0 : 無効
  • pmpcfg[*].A == 1 : TOR(Top of Range)
  • pmpcfg[*].A == 2 : NA4(Naturally aligned four-byte region)
  • pmpcfg[*].A == 3 : NAPOT(Naturally aligned power-of-two region, >=8)

TORについては、2つのpmp領域を利用してマッチングアドレスの上下を設定する。

NA4とNAPOTは似ていて、NAPOTはアドレスの範囲の指定領域が2の累乗バイトに限定されるアドレス範囲の指定方法である。

アドレス範囲はpmpaddr[*]を使って指定するのだが、ここで注意するのはpmpaddr[*]のアドレスは4バイトアラインであり、下位の2ビットは右に2ビットシフトされて節約されている。

`pmpaddr[*]`レジスタの定義。addressの下位2ビットは削られていることに注意。

ここで、NAPOTのアドレス指定方式について見ていく。仕様書の中では G という変数が使われているが、これはおそらくハードウェアで指定されたアドレス領域の粒度(Granularity)の意味だと思う。ここでは、例えば8バイト粒度だと G=log2(8/4)=1として取り扱うことにする(/4としているのはNAPOTが4バイト粒度以下をサポートしていないから)。

まず、pmpcfgを書き込んでNAPOTモードにすると、当該pmpaddrレジスタの G-2 ビットの位置までがすべて1に設定される(G=1のときはどこも1に設定されない)。これはNAPOTモードを解除すると解放される。

これはどういう意味なんだろうととちょっと考えていたのだが、普通に考えるとマッチングのアドレス領域を簡単に指定しようとしているのだと気が付く。

例えば、上記の図で16-byteの粒度の場合、NAPOTのアドレスマッチング範囲はyyyy...yy00 から yyyy...yy01までとなる(実際には下位にビットが省略されているので、 yyyy...yy0000 から yyyy...yy0111までとなる。

TORと同じ機構でアドレスの比較ができるため、このような仕組みになっているのか、と思っている。

ただ、上記の例だと0x0~0x7までしかチェックできないため1ビット分足りないな?どういう仕組みになっているんだろう。

Binary Hacks Rebootedを購入しました

結構昔のBinary Hacksがまだ本棚に残っているかどうか不明だけど、Rebootedということで買いました。 自分はハードウェア屋さんなので、もうBinary Hacksの中身も忘れてしまいましたね。

内容は面白そうです。実作業にすぐに役に立つかというとそうでもないけど、こういう本はパズル的な感覚で楽しく読むといいかもしれない。

まだ時間が無くて中身を読み込んだわけではないけど、前回のBinary Hackと同様にみたいに各サブセクションで話がつながらず、各サブセクションを著者がバラバラに書いているんだと思うので、短編小説的な感じで進めていけばいいのかな。

ちょっとずつ流し読みしながら「ホーンなるほど」と思えればいいと思う。気軽に読んでいくことにしよう。

Rebootedといえば、私が書いたLLVMの本も、LLVMのバージョンアップがあるとすぐに情報が古くなるので、改版したり情報アップデートに興味がある方は手伝ってくれるととてもありがたいです。 もう一回同じレベルの本を書くかというと分からないけど。とても時間がかかったので、今はそんな時間が取れそうもない。

オープンソース・アウトオブオーダCPU NaxRiscvを概観する (11. NaxRiscvで生成されるデザインの比較)

NaxRiscvの様々なコンフィグレーションでのVivado論理合成結果を見て比較してみる。 次はRV64の構成で試してみた。

Slice LUT Slice Registers WNS (ns)
rv32ima 13478 7899 3.408
rv32imaf 18071 10655 3.150
rv32imafd 17669 9786 2.708
rv32imac 13702 8189 3.394
rv32imafc 18543 10948 3.052
rv32imafdc 21159 12934 3.022
rv64ima 17669 9786 2.708
rv64imaf 22956 12808 2.555
rv64imafd 25383 14431 2.836
rv64imac 17930 10137 3.018
rv64imafc 23078 13047 2.849
rv64imafdc 25870 14805 2.846

RV64の場合は面積は順当に変化している気がする。周波数に関してはRV32と同様に、ちょっと相関がないかな。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

オープンソース・アウトオブオーダCPU NaxRiscvを概観する (10. NaxRiscvで生成されるデザインの比較)

NaxRiscvの様々なコンフィグレーションでのVivado論理合成結果を見て比較してみる。 riscv32での構成で試してみた。

普通は浮動小数点演算器をつけると周波数が落ちそうなものだが、そうはなっていない。ここには誤差が載っていそうな気がする。

rv32imafとrv32imafdが面積が逆転してるのも気になるんだよな。一応生成し直してみても結果は一緒だ。もうちょっと見てみたい。

Slice LUT Slice Registers WNS (ns)
rv32ima 13478 7899 3.408
rv32imaf 18071 10655 3.150
rv32imafd 17669 9786 2.708
rv32imac 13702 8189 3.394
rv32imafc 18543 10948 3.052
rv32imafdc 21159 12934 3.022

(追記)もうちょっと調べると、rv32imafdはちょっと実装がおかしい気がするなあ?FPU演算器が実装されていない気がする。

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

オープンソース・アウトオブオーダCPU NaxRiscvを概観する (8. オプションで生成されるVerilogデザインを制御できるか?)

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

もうちょっとまともに読めるSystemVerilogは出力できないものかと、Spinal-HDLのオプションを見てみる。

  • SpinalHDL/core/src/main/scala/spinal/core/Spinal.scala
/**
 * Spinal configuration for the generation of the RTL 
 */
case class SpinalConfig(mode                           : SpinalMode = null,
                        flags                          : mutable.HashSet[Any] = mutable.HashSet[Any](),
                        debugComponents                : mutable.HashSet[Class[_]] = mutable.HashSet[Class[_]](),
                        keepAll                        : Boolean = false,
                        defaultConfigForClockDomains   : ClockDomainConfig = ClockDomainConfig(),
                        onlyStdLogicVectorAtTopLevelIo : Boolean = false,
                        defaultClockDomainFrequency    : IClockDomainFrequency = UnknownFrequency(),
                        targetDirectory                : String = SpinalConfig.defaultTargetDirectory,
                        oneFilePerComponent            : Boolean = false,
                        netlistFileName                : String = null,
                        dumpWave                       : DumpWaveConfig = null,
                        globalPrefix                   : String = "",
                        var privateNamespace           : Boolean = false,
                        var formalAsserts              : Boolean = false,
                        anonymSignalPrefix             : String = null,
                        device                         : Device = Device(),
                        inlineRom                      : Boolean = false,
                        romReuse                       : Boolean = false,
                        genVhdlPkg                     : Boolean = true,
                        verbose                        : Boolean = false,
                        mergeAsyncProcess              : Boolean = false,
                        asyncResetCombSensitivity      : Boolean = false,
                        anonymSignalUniqueness         : Boolean = false,
                        inlineConditionalExpression    : Boolean = false,
                        nameWhenByFile                 : Boolean = true,
                        var genLineComments            : Boolean = false,
                        noRandBoot                     : Boolean = false,
                        randBootFixValue               : Boolean = true,
                        noAssert                       : Boolean = false,
                        fixToWithWrap                  : Boolean = true,
                        headerWithDate                 : Boolean = false,
                        headerWithRepoHash             : Boolean = true,
                        removePruned                   : Boolean = false,
                        allowOutOfRangeLiterals        : Boolean = false,
                        dontCareGenAsZero              : Boolean = false,
                        phasesInserters                : ArrayBuffer[(ArrayBuffer[Phase]) => Unit] = ArrayBuffer[(ArrayBuffer[Phase]) => Unit](),
                        transformationPhases           : ArrayBuffer[Phase] = ArrayBuffer[Phase](),
                        memBlackBoxers                 : ArrayBuffer[Phase] = ArrayBuffer[Phase] (/*new PhaseMemBlackBoxerDefault(blackboxNothing)*/),
                        rtlHeader                      : String = null,
                        scopeProperties                : mutable.LinkedHashMap[ScopeProperty[_], Any] = mutable.LinkedHashMap[ScopeProperty[_], Any](),
                        private [core] var _withEnumString : Boolean = true,
                        var enumPrefixEnable           : Boolean = true,
                        var enumGlobalEnable           : Boolean = false,
                        bitVectorWidthMax              : Int = 4096,
                        var singleTopLevel             : Boolean = true

例えば、oneFilePerComponentを追加しても、NaxRiscvの場合は何も変わらない。

  • NaxRiscv/src/main/scala/naxriscv/Gen.scala
    val spinalConfig = SpinalConfig(inlineRom = true, anonymSignalPrefix = "_zz", oneFilePerComponent = true)

keepAll=trueを追加してみた。これでも何も変わらない。

RISC-V Advanced Platform Interrupt Controller (APLIC) の概観 (3. RTL実装の概観)

RISC-VのAdvanced Platform Interrupt Controller (APLIC) について勉強してみようと思う。

以下においてある実装をチェックしてみることにした。

github.com

APLICはいくつかのモードによって実装が切り替わるようになっているらしい。

  • MSI_MODE=true
    • AIA_EMBEDDED=true
    • AIA_DISTRIBUTED=true
  • DIRECT_MODE=true

まずはMSI_MODE && AIA_EMBEDDEDの時。この時はIMSIC(Incoming Message Signaled Interrupt Controller )というモジュールが接続されて割り込みを送出する機能まで内包されている。

次にMSI_MODE && AIA_DISTRIBUTEDの時、この時はaxi_liteのマスタ・ポートが付いており、これでMSI通知をAXIに対して送出する仕組みになっている。

最後にDIRECT_MODEでは、そのまま専用の信号線として割り込みが通知される仕組みになっている。

RISC-V Advanced Platform Interrupt Controller (APLIC) の概観 (2.必要なレジスタ群)

RISC-VのAdvanced Platform Interrupt Controller (APLIC) について勉強してみようと思う。

まずは以下のブログを読んで概観を掴もう。

blog.stephenmarz.com

今回はレジスタの構成についてまとめてみた。必要なのはだいたいこんな感じかなと思う。

Name Address Description
domaincfg 0x0000 - [0] : Big-Endian
- [2] Delivery mode: 0=direct delivery mode, 1=MSI delivery mode
- [8] IE Interrupt Enable
sourcecfg[1-1023] 0x0004 - 0x0ffc - [2 : 0] source mode (SM): Inactive/Detached/Edge1/Edge0/Level1/Level0
- [10] : delegate:
mmsiaddrcfg 0x1bc0
mmsiaddrcfgh 0x1bc4
smsiaddrcfg 0x1bc8
smsiaddrcfgh 0x1bcc
setip[0-31] 0x1c00 - 0x1c7c Sourceの割り込み要因に明示的に割り込みを挿入するためのレジスタ
setipnum 0x1cdc setipと同様のことが可能だが、割り込み要因IDを用いて割り込みを挿入することができるレジスタ
clrip 0x1d00 - 0x1d7c Sourceの割り込み要因を明示的にクリアすることができるレジスタ
clripnum 0x1ddc clripと同様のことが可能だが、割り込み要因IDを用いて割り込みをクリアすることのできるレジスタ
setie[0-31] 0x1e00 - 0x1e7c 割り込みEnable Bitを設定するためのレジスタ
setienum 0x1edc 割り込みSource番号を使ってEnable Bitを有効化するためのレジスタ
clrie[0-31] 0x1f00 - 0x1f7c 割り込みEnable Bitをクリアするためのレジスタ
clrenum 0x1fdc 割り込みSource番号を使ってEnable Bitをクリアするためのレジスタ
setipnum_le 0x2000
setipnum_be 0x2004
genmsi 0x3000 - [10: 0] EIID: External interrupt identifier
- [31:18] Hart Index: Hart index that wants to send an MSI.
target[1-1023] 0x3004 - 0x3ffc Direct delivery modeの場合:
- [31:18] 割り込みを送出するHartのインデックス
- [ 7: 0] Priority
MSI Delivery Mode:
- [31:18] 割り込みを送出するHartのインデックス
- [17:12] Guest Index
- [10:0] EIID

Starting from 0x4000, "interrupt domain control" (IDC) region.

Name Address Description
idelivery 0x0000 - [0] : 1の場合は割り込みを通知する。0の場合は通知しない
iforce 0x0004 - [0] : APLICの割り込みが有効なとき、このレジスタに書き込むと#0の割り込みを強制的に送出する
ithreshold 0x0008 割り込みのスレッショルドを設定する
topi 0x0018 現在ペンディングの最も高いPriorityの割り込み番号を格納する
claimi 0x001c topiと同じものが参照できるが、読むことによって当該割り込みのpendingビットを下げることができる