FPGA開発日記

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

Speculative Store Bypass に学ぶ現代プロセッサの高速メモリアクセスユニットの仕組み

Microsoft および Google から、新たなCPUの脆弱性であるVariant 4およびVariant 3aについてのアナウンスがあり、Intel, AMD, Arm が対応に迫られている。

これらの脆弱性は、現代のコンピュータアーキテクチャの飽くなき性能追及の結果発生したものが多く、詳細を調べるとコンピュータアーキテクチャの勉強(復習?)にもなるのである意味重宝している。

今回発表されたのは、Variant3a / Variant 4である。それぞれ、

  • Variant 3a: Rogue System Register Read (CVE-2018-3640)
  • Variant 4: Speculative Store Bypass (CVE-2018-3639)

と名付けられている。Variant 3aについては後日調べるとして、Variant 4について調査してみた。名前の示す通り、"Speculative Store Bypass"というのは、ロードとストアの順番を交換することにより速度向上を図ったものであるが、これにより副作用が発生するという問題だ。

一般論として、ロードとストアの順番を入れ替えるという技法を使うマイクロプロセッサは、デスクトップ向けか、モバイル向けでもかなり性能に重点を置いたものに限定されるだろうと思われる。 ArmについてはCortex-A57以降の世代、IntelはCore世代が中心のようだ。

  • Vulnerability of Speculative Processors to Cache Timing Side-Channel Mechanism

developer.arm.com

  • Q2 2018 Speculative Execution Side Channel Update

INTEL-SA-00115

ちなみに、筆者は例によってセキュリティの専門家ではないし、CPUアーキテクチャにしてもデスクトップクラスの本格的なものは設計経験がないので、いまいち本文から読み取れない部分があったりとか、間違っている部分があるかもしれない。 概念的な部分でしか読み取れないのはいつも悲しいところだ。。。

メモリアクセスのデータフロー

Variant-4 について調査するにあたり、様々な情報を調査してみたのだが、主に参考にしたのは主に以下の資料である。

  • Analysis and mitigation of speculative store bypass (CVE-2018-3639)

Analysis and mitigation of speculative store bypass (CVE-2018-3639) – Security Research & Defense

  • Kernel Side-Channel Attack using Speculative Store Bypass - CVE-2018-3639

Kernel Side-Channel Attack using Speculative Store Bypass - CVE-2018-3639 - Red Hat Customer Portal

(個人的には、Redhatの説明が分かりやすかった)

ちなみに、これらのメモリアクセスのデータフローについては、Mike Johnsonの名著"Superscalar Microprocessor Design" が非常に詳細に説明している。 私もこの本を読み直しながら情報を読み解いていった。

www.amazon.com

ロード・ストアの入り混じった命令シーケンスでの実行順序

ここからは、Mike Johnson本の例を参考にしながら読み進めていく。 例えば、以下のようなロードストアの命令シーケンスが並んでいたとしよう。

STORE v  (1)
ADD      (2)
LOAD  w  (3)
LOAD  x  (4)
LOAD  v  (5)
ADD      (6)
STORE w  (7)

投機実行を行うプロセッサでは、後ろの命令が前の命令を追い越すことが可能であり、メモリアクセスについても基本的にそれは同じであるのだが、ストア命令については、「ストア命令よりも前の命令がすべて完了しないと実際のメモリアクセスを行ってはいけない」という原則がある。 なぜならば、メモリストアを投機的に実行してしまうと、ストア命令よりも前で例外が発生した場合にストア命令を破棄することができないからだ(いったんメモリに書いたものを取り消すのは非常に難しい)。

それからもう一つ、ロード命令とストア命令は基本的に順序の入れ替えは行われない。 なぜならば、最初にストアしたデータを次のロード命令で再びレジスタに持ってくる、というケースがあるからだ。もしも、「ストア[A]→ロード[A]」という順番を「ロード[A]→ストア[A]」と入れ替えてしまうと、誤った動作となってしまう。

1. バイパスをしないロードストアのシーケンス

この前提で、再び上記の命令列を考えてみる。 まず、何も考えずに、上記の命令列をそのままの順番で実行してみる。 デコーダは1サイクルで4命令をデコードすることができ、

  • サイクル1 = (1) - (4) の命令のデコード
  • サイクル2 = (5) - (8) の命令をデコード

とする。以下のような順番でメモリアクセスが実行される。図が長くて分かりにくいが、要するにすべて順番に実行される。

f:id:msyksphinz:20180523020740p:plain:w400
f:id:msyksphinz:20180523020805p:plain:w400
f:id:msyksphinz:20180523020816p:plain:w400

2. バイパスをした場合のロードストアシーケンス

次に、ストア命令よりも先にロード命令を先に発行することで投機的実行を行う方式を考えてみる。ロード命令がストア命令を追い抜かすためには、当然ロード用のリザベーションステーションとストア用のリザベーションステーションが別々に必要になる。

そして、ストアバッファを用意して、ストア命令を追い越すロード命令の発行アドレスを常にチェックしている。もしもストアバッファ内のアクセスアドレスが、ロード命令のアクセスアドレスと被っているならば、先にストア命令を発行しなければ整合性を取ることができない。この例では全体のサイクル数では大きな差はみられないが、それでもロード命令を先に発行することでレジスタにデータを取得するレイテンシが短くなり、速度向上に起因することができる。

f:id:msyksphinz:20180523020836p:plain:w400
f:id:msyksphinz:20180523020847p:plain:w400
f:id:msyksphinz:20180523020858p:plain:w400

3. 一般的な、Load命令の投機実行による弊害

これは今回の問題に限ったことではなく、Load命令を投機実行することによる弊害というものは一般に知られている。例えば、デバイス制御用のメモリマップドレジスタのようなもので、一度Loadアクセスを行うことでデバイスを制御するような装置では、投機的にマップされた当該レジスタにアクセスされると勝手にデバイスが動いてまうという弊害がある。このような場合には、一度メモリアクセスのシーケンスをSynchronizeするか、Pipelineを一度Flushした後に当該Load命令を発行させるなどの工夫を行う必要がある。

Speculative Store Bypass は、ロードストアの順番を入れ替える高速化技術を使った技法

実際、Speculative Store Bypass ではこのロードストアの入れ替えにより機密データを読み取るテクニックであり、Spectre が分岐予測による投機実行での機密データ読み取りであるのに対し、こちらはロードストアの入れ替えのテクニックを活用している。これによりキャッシュに投機的ロードした結果が副作用として残ってしまい、これを検査することで機密データのサイドチャネル攻撃が可能になってしまう。

RedHatの技術解説にも、以下のような記述がある。

The Memory Disambiguator(MD) predicts which of the loads do not depend on an earlier store instruction. Such load(read) instructions are then speculatively executed to load data from L1 data cache, even when the address of the earlier store is not known, thus bypassing the Store instruction. This adds to the overall performance by avoiding load latency. At the end, if the prediction was wrong and conflict between load(read) and store(write) instructions is detected, all instructions since (and including) the speculative load are re-executed.

Amazon AWS F1インスタンス上で動作するRISC-VプロセッサRocket-Chipの環境FireSimがオープンソース化

UCBにて開発されているRISC-VのRocket-Chipを、Amazon AWS F1インスタンス上で動作させるための環境FireSimがISCA 2018にアクセプトされたとのこと。 おめでとうございます。

さらに、FireSimの環境がオープンソースとして公開され、GitHub上でアクセスできるようになったようだ。 論文も公開されているのでチェックしてみよう。

github.com

  • FireSim WebSite

fires.im

  • FireSim Manual

Welcome to FireSim’s documentation! — FireSim documentation

  • FireSim Paper

https://sagark.org/assets/pubs/firesim-isca2018.pdf

# なんか文献を並べただけのつまらない記事になってしまったが、後で追記しよう...

C++で記述された軽量CNN実装 mojo-cnn 試行 (4. RISC-V on FPGAのデバッグ)

f:id:msyksphinz:20170925003926p:plain

FPGAでCNNのプログラムを動かそうとしたのだが、C++のコードをそのままRISC-Vでコンパイルして走らせてもどうもうまく行かない。 そもそもの問題だが、例えばRISC-V on FPGAで何かを動かしたい場合、printfなどは独自のsyscalls.c を使ってPS (=ARM)に表示させるように改造しなければならない。 つまり、std::cout などは現状では未対応というように考えればよい?

つまり、std::cout とすると一応spike で動かすことはできるのだが、これをどのようにしてFPGAで動かすプログラムとしてコンパイルしなおせばよいのか不明なのだ。むう。 ただしspikeでstd::coutを動かすことができているので、出来ないはずはない。spikeがstd::coutをどのように処理しているのか要調査だな...

syscalls.c というのは、以下をそのまま使っている。

github.com

とりあえず、シンプルにriscv64-unknown-elf-g++ を使って簡単なC++のコードをコンパイルして、RISC-V on FPGAで動かしてみようとしたのだが、いろんなライブラリを削除してコンパイルしているため結構つらい。syscalls.cに載っていない関数は基本的に使えないし、C++独特のname manglingに対応させるために色々とてこづった。

結果的に、以下のようなtest.cpp を用意して(まあほぼCのコードなのだが)、g++でコンパイルし、gccコンパイルしたcrt.Sとsyscalls.c とくっつけてバイナリを作ることはできた。

// #include <iostream>
#include <stdio.h>

int main ()
{
  printf ("Hello World\n");

  return 0;
}
test_ideal: test.cpp syscalls.o crt.o makefile
        $(CC) $(CFLAGS) -mcmodel=medany -fno-builtin-printf -static -std=gnu99 -O2 -ffast-math -fno-common -nostdlib -nostartfiles -ffast-math -lgcc $< crt.o syscalls.o -o $@ -T ./common/test.ld

crt.o: ./common/crt.S
        $(CC)  -c $(CFLAGS) ./common/crt.S

syscalls.o: common/syscalls.c
        $(CC) $(CFLAGS) -mcmodel=medany -c $< -o $@
f:id:msyksphinz:20180521015029p:plain
図. C++のコード(というかほぼC)をRISC-V向けにコンパイルしてRocket-Chipで動かした図

でもこれでは実用には全く耐えられないので、どうにかしていろんなライブラリを動かす必要がある。この部分はまだ要調査だ...

RISC-V におけるメモリモデルについて

f:id:msyksphinz:20180421185728p:plain

RISC-Vのコンパイル時に登場する謎のメモリモデルについて調査したのでまとめておく。

以下の資料を参考にした。

  • All Aboard, Part 4: The RISC-V Code Models

www.sifive.com

RISC-Vはコード内をジャンプするための手法としては複数の手段があるのだが、他のプロセッサアーキテクチャと比較すると決しては多いわけではない。

RISC-Vの場合は以下の3種類に限定される。

  • PC相対 (auipc / jal / br*命令)
  • レジスタ相対 (jalr / addi命令)
  • 絶対 (lui命令)

gccによりプログラムがコンパイルされる際には、ジャンプ先やメモリアクセス先はどこに配置されるのかは分からない。 これは、実際にリンクしてみるまで分からないので、オブジェクトを作成する段階では、とりあえずアクセス先の情報を空に設定しておき、リンカによる再設定を行う。

gccでは、オブジェクトのコンパイルされてからリンクされるまでのコードの変遷を見るための便利なオプションが用意されている。--save-tempsというオプションだ。

例えば、上記のサイトで紹介されている以下のようなプログラムをコンパイルしてみる。

  • cmodel.c
long global_symbol[2];

int main() {
  return global_symbol[0] != 0;
}

以下のようにしてコンパイルをしてみる。

$ riscv64-unknown-elf-gcc cmodel.c -o cmodel -O3 --save-temps

結果、以下のようなファイルが生成された。

$ ls -1 | grep cmodel
cmodel
cmodel.c
cmodel.i
cmodel.o
cmodel.s

cmodel.iファイルはプリプロセッサを通過させただけなので面白くない。次にcmodel.sはどのようになっているだろうか。

    .file   "cmodel.c"
    .option nopic
    .section    .text.startup,"ax",@progbits
    .align  1
    .globl  main
    .type   main, @function
main:
    lui     a5,%hi(global_symbol)
    ld      a0,%lo(global_symbol)(a5)
    snez    a0,a0
    ret
    .size   main, .-main
    .comm   global_symbol,16,8
    .ident  "GCC: (GNU) 7.2.0"

オブジェクト形式として保存されたcmodel.oをダンプしてみると、以下のようになっていることが分かった。

$ riscv64-unknown-elf-objdump -dtr cmodel.o

cmodel.o:     file format elf64-littleriscv
...
0000000000000010       O *COM*  0000000000000008 global_symbol
...
0000000000000000 <main>:
   0:   000007b7                lui     a5,0x0
                           0: R_RISCV_HI20 global_symbol
                           0: R_RISCV_RELAX        *ABS*
   4:   0007b503                ld      a0,0(a5) # 0 <main>
                           4: R_RISCV_LO12_I       global_symbol
                           4: R_RISCV_RELAX        *ABS*
   8:   00a03533                snez    a0,a0
   c:   8082

実際にglobal_symbolのアドレスは決まっていないため、アセンブラはこの変数の場所をまだ決めることが出来ず、 その代わりに上記のような目印を配置している。 そしてオブジェクトにリロケーションテーブルを作成し、最終的にリンカがアドレスを決める際にどの命令のどの場所の 変数のアドレス情報を置き換えれば良いかが分かるようになっている。

最終的に生成された実行ファイルを同様にobjdumpすると、以下のようにアドレスが確定されていることが見て取れる。

$ riscv64-unknown-elf-objdump -dtr cmodel
Disassembly of section .text:

0000000000010330 <main>:
   10330:       67c9                    lui     a5,0x12
   10332:       0387b503                ld      a0,56(a5) # 12038 <global_symbol>
   10336:       00a03533                snez    a0,a0
   1033a:       8082                    ret

ここまでが、RISC-Vにおけるデフォルトのコード生成モデルであるmedlowの試行結果である。 一方で、RISC-Vにはもう一つのコード生成モデルであるmedanyというモデルがある。

これも同様にコンパイルして、その結果をダンプしてみよう。

$ riscv64-unknown-elf-gcc -mcmodel=medany cmodel.c -o cmodel -O3 --save-temps
$ riscv64-unknown-elf-objdump -dtr cmodel.o

...

Disassembly of section .text.startup:

0000000000000000 <main>:
   0:   00000797                auipc   a5,0x0
                           0: R_RISCV_PCREL_HI20   global_symbol
                           0: R_RISCV_RELAX        *ABS*
   4:   00078793                mv      a5,a5
                           4: R_RISCV_PCREL_LO12_I .L0
                           4: R_RISCV_RELAX        *ABS*
   8:   6388                    ld      a0,0(a5)
   a:   00a03533                snez    a0,a0
   e:   8082                    ret

先程の結果と少し変わっている。 medlowのコンパイルではR_RISCV_HI20とされていたものが、medanyではR_RISCV_PCREL_HI20と変わっているし、 R_RISCV_LO12_I とされていたものが R_RISCV_PCREL_LO12_I と変わっている。

まとめると、以下のようになる。

  • コードモデルmedlowの場合 lui / ld が生成される
  • コードモデルmedanyの場合 auipc / ld が生成される

-mcmodel=medlowの場合

medium-lowコードモデルであることを示す。アドレス参照の範囲は、絶対アドレスとして-2GBから+2GBまでの間である。 命令生成時には、lui/addi 命令を用いてアドレスが生成される。

-mcmodel=medanyの場合

medium-anyコードモデルであることを示す。アドレス参照の範囲は現在のPCの位置から2GBの範囲に限定される。 命令生成時には、 auipc / ld命令が使用される。

ここで注意しなければならないのは、コードモデルはABIとは異なるということだ。 ABIは関数呼び出しのレジスタ使用などの規約を意味しており、コンパイル時に関数の引数の受け渡しを統一するために必要であるが、 コードモデルの場合は異なるコードモデルの関数同士をリンクして一つの実行ファイルにすることができる。 関数同士のインタフェースとは無関係のため、コンパイル時に自由に決めることが可能となる。

AWS EC2 F1インスタンスを使ったハードウェア開発の勉強 (9. 整数行列計算回路の実装)

AWS F1インスタンス HDK の勉強を続けている。 目標としては、以下の部分にAXIマスタを接続してDRAMにアクセスし、データをフェッチする。

  1. DMAでホストからデータをDDR4メモリに格納する。
  2. AXIマスタデータをフェッチする
  3. 演算し、結果を格納する。

として、例えば行列積のアクセラレータをF1インスタンス上で動作させてみたい。

f:id:msyksphinz:20180516232156p:plain

前回、データを自由にフェッチすることができるようになったので、次にフェッチしたデータを使って計算を行ってみたい。

f:id:msyksphinz:20180520003345p:plain

FIFOを挿入してAXIからのデータを受け取り、FIFOにデータが挿入されるたびに積和演算を実行する。

logic fifo_wr, fifo_empty, fifo_full;
assign fifo_wr = (rcv_state == rcv_state_col) & 
                 cl_axi_mstr_bus.rvalid & cl_axi_mstr_bus.rready;
logic [63: 0] fifo_rd_data;

assign cl_axi_mstr_bus.rready = !fifo_full;

fifo u_fifo
(
 .CLK   (clk),
 .nRST  (pipe_rst_n),
 .D     (cl_axi_mstr_bus.rdata[31:0]),
 .Q     (fifo_rd_data),
 .WR    (fifo_wr),
 .RD    (!fifo_empty),
 .FULL  (fifo_full),
 .EMPTY (fifo_empty)
 );

所定の計算結果である 0x00008a20 が出力されていることが確認できた。次は全要素分これを実行しなければ。

            34762000 : [axi_mstr_cfg_bus W] ADDR=00000500     
            34766000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=7 ADDR=0000000400000000
            34766000 : [axi_mstr_cfg_bus W] ADDR=00000500
            34782000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010004
            34798000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010044
            34814000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010084
            34830000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=00000004000100c4
            34846000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010104
            34862000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010144
            34878000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010184
            34894000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=00000004000101c4
            34910000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010204
            34926000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010244
            34942000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010284
            34958000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=00000004000102c4
            34974000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010304
            34986000 : [Waiting] rvalid = 1, rready = 1
            34986000 : [cl_axi_mstr_bus  R] DATA=000000100000000f0000000e0000000d0000000c0000000b0000000a000000090000000800000007000000060000000500000004000000030000000200000001
            34990000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010344
            34994000 : [Waiting] rvalid = 1, rready = 1
            34994000 : [cl_axi_mstr_bus  R] DATA=000000730000007200000071000000700000006f0000006e0000006d0000006c0000006b0000006a000000690000006800000067000000660000006500000064
            35000000 : [matrix  0] mult = 00000001 x 00000064
            35006000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010384
            35014000 : [Waiting] rvalid = 1, rready = 1
            35014000 : [cl_axi_mstr_bus  R] DATA=000000830000008200000081000000800000007f0000007e0000007d0000007c0000007b0000007a000000790000007800000077000000760000007500000074
            35020000 : [matrix  1] mult = 00000002 x 00000074
            35022000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=00000004000103c4
            35026000 : [Waiting] rvalid = 1, rready = 1
            35026000 : [cl_axi_mstr_bus  R] DATA=000000930000009200000091000000900000008f0000008e0000008d0000008c0000008b0000008a000000890000008800000087000000860000008500000084
            35032000 : [matrix  2] mult = 00000003 x 00000084
            35038000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010404
            35046000 : [Waiting] rvalid = 1, rready = 1
            35046000 : [cl_axi_mstr_bus  R] DATA=000000a3000000a2000000a1000000a00000009f0000009e0000009d0000009c0000009b0000009a000000990000009800000097000000960000009500000094
            35050000 : [Waiting] rvalid = 1, rready = 1
            35050000 : [cl_axi_mstr_bus  R] DATA=000000b3000000b2000000b1000000b0000000af000000ae000000ad000000ac000000ab000000aa000000a9000000a8000000a7000000a6000000a5000000a4
            35052000 : [matrix  3] mult = 00000004 x 00000094
            35056000 : [matrix  4] mult = 00000005 x 000000a4
            35058000 : [Waiting] rvalid = 1, rready = 1
            35058000 : [cl_axi_mstr_bus  R] DATA=000000c3000000c2000000c1000000c0000000bf000000be000000bd000000bc000000bb000000ba000000b9000000b8000000b7000000b6000000b5000000b4
            35064000 : [matrix  5] mult = 00000006 x 000000b4
            35074000 : [Waiting] rvalid = 1, rready = 1
            35074000 : [cl_axi_mstr_bus  R] DATA=000000d3000000d2000000d1000000d0000000cf000000ce000000cd000000cc000000cb000000ca000000c9000000c8000000c7000000c6000000c5000000c4
            35080000 : [matrix  6] mult = 00000007 x 000000c4
            35090000 : [Waiting] rvalid = 1, rready = 1
            35090000 : [cl_axi_mstr_bus  R] DATA=000000e3000000e2000000e1000000e0000000df000000de000000dd000000dc000000db000000da000000d9000000d8000000d7000000d6000000d5000000d4
            35096000 : [matrix  7] mult = 00000008 x 000000d4
            35106000 : [Waiting] rvalid = 1, rready = 1
            35106000 : [cl_axi_mstr_bus  R] DATA=000000f3000000f2000000f1000000f0000000ef000000ee000000ed000000ec000000eb000000ea000000e9000000e8000000e7000000e6000000e5000000e4
            35112000 : [matrix  8] mult = 00000009 x 000000e4
            35138000 : [Waiting] rvalid = 1, rready = 1
            35138000 : [cl_axi_mstr_bus  R] DATA=00000103000001020000010100000100000000ff000000fe000000fd000000fc000000fb000000fa000000f9000000f8000000f7000000f6000000f5000000f4
            35144000 : [matrix  9] mult = 0000000a x 000000f4
            35158000 : [Waiting] rvalid = 1, rready = 1
            35158000 : [cl_axi_mstr_bus  R] DATA=000001130000011200000111000001100000010f0000010e0000010d0000010c0000010b0000010a000001090000010800000107000001060000010500000104
            35164000 : [matrix 10] mult = 0000000b x 00000104
            35174000 : [Waiting] rvalid = 1, rready = 1
            35174000 : [cl_axi_mstr_bus  R] DATA=000001230000012200000121000001200000011f0000011e0000011d0000011c0000011b0000011a000001190000011800000117000001160000011500000114
            35178000 : [Waiting] rvalid = 1, rready = 1
            35178000 : [cl_axi_mstr_bus  R] DATA=000001330000013200000131000001300000012f0000012e0000012d0000012c0000012b0000012a000001290000012800000127000001260000012500000124
            35180000 : [matrix 11] mult = 0000000c x 00000114
            35184000 : [matrix 12] mult = 0000000d x 00000124
            35186000 : [Waiting] rvalid = 1, rready = 1
            35186000 : [Waiting] rvalid = 1, rready = 1
            35186000 : [cl_axi_mstr_bus  R] DATA=000001430000014200000141000001400000013f0000013e0000013d0000013c0000013b0000013a000001390000013800000137000001360000013500000134
            35192000 : [matrix 13] mult = 0000000e x 00000134
            35202000 : [Waiting] rvalid = 1, rready = 1
            35202000 : [cl_axi_mstr_bus  R] DATA=000001530000015200000151000001500000014f0000014e0000014d0000014c0000014b0000014a000001490000014800000147000001460000014500000144
            35208000 : [matrix 14] mult = 0000000f x 00000144
            35218000 : [Waiting] rvalid = 1, rready = 1
            35218000 : [cl_axi_mstr_bus  R] DATA=000001630000016200000161000001600000015f0000015e0000015d0000015c0000015b0000015a000001590000015800000157000001560000015500000154
            35224000 : [matrix 15] mult = 00000010 x 00000154
            35226000 : [matrix] result = 0000000000008a20
            35230000 : [matrix] result = 0000000000008a20
````

「30日でできる!OS自作入門」を読み始めた (27日目 LDT / GDTと環境の改善)

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

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

27日目はLDTの改造と環境の改善だ。GDTに対する理解が甘いので、LDTについて読んでもなんかまだしっくり来ない。復習が必要だなあ。。。

  • Global Descriptor Table[GDT] : 全てのプログラムから共通にアクセスするセグメントを定義する
  • Local Descriptor Table[LDT] : タスク単位に存在する
f:id:msyksphinz:20180518013001p:plain
図. 複数アプリケーションも立ち上げることができるHaribote-OS環境

あと、環境の整理を行った。API群はライブラリとして定義するので、arコマンドを使ってHaribote-OSのAPIをライブラリ化した。

github.com

OBJS_API = api001.o api002.o api003.o api004.o api005.o api006.o \
                        api007.o api008.o api009.o api010.o api011.o api012.o \
                        api013.o api014.o api015.o api016.o api017.o api018.o \
                        api019.o api020.o

apilib.lib : Makefile $(OBJS_API)
        ar rcs $@ $(OBJS_API)

%.o:%.nas Makefile
        nasm -felf32 $< -o $@ -l $@.lst

最終的にディレクトリ構成は以下のようになった。

.
├── Makefile
├── a
│   ├── Makefile
│   └── a.c
├── a_nask.h
├── apilib
│   ├── Makefile
│   ├── api001.nas
│   ├── api002.nas
│   ├── api003.nas
│   ├── api004.nas
...
├── app_common.mk
├── beepdown
│   ├── Makefile
│   └── beepdown.c
├── bootpack.map
├── color
│   ├── Makefile
│   └── color.c
├── color2
│   ├── Makefile
│   └── color2.c
├── haribote
│   ├── Makefile
│   ├── a_nask.nas
│   ├── asmhead.nas
│   ├── timer.c
│   └── timer.h
├── hello
│   ├── Makefile
│   └── hello.nas
├── hello2
│   ├── Makefile
│   └── hello2.nas
├── hello3
....

これでハリボテOSとしてはほぼ完成だ。28日目は日本語関係を対応しよう。

AWS EC2 F1インスタンスを使ったハードウェア開発の勉強 (8. cl_dram_dmaにAXIマスタを追加する)

AWS F1インスタンス HDK の勉強を続けている。 遅いながらにどうにか進めている。cl_dram_dma についてなんとなく分かってきた。 前回はアサーションを追加してAXIバスが動作していることを確認した。

次はAXIをどうにか動かすことはできないだろうか?目標としてはCLモジュール内に新しいAXIマスタを作成して内部からDRAMにアクセスできるパスを作りたい。

目標としては、以下の部分にAXIマスタを接続してDRAMにアクセスし、データをフェッチする。

  1. DMAでホストからデータをDDR4メモリに格納する。
  2. AXIマスタデータをフェッチする
  3. 演算し、結果を格納する。

として、例えば行列積のアクセラレータをF1インスタンス上で動作させてみたい。

f:id:msyksphinz:20180516232156p:plain

まずはテストベクタを作成して、DDR4メモリに対して自由にデータを読み書きしてみよう。

test_dram_matrix テストベクタの作成

サンプルプロジェクトとしてtest_dram_dmaをコピーしてtest_dram_matrix を作成した。これは、

  1. $readmemh で整数行列(16×16)を2つ分、DRAMにロードする
  2. CLに通知し、同じ場所からデータをフェッチする(という回路を作成する)
  3. フェッチしたデータを使って行列積を計算し、計算結果をDRAMに格納する

というシナリオだ。

f:id:msyksphinz:20180517000121p:plain
図. test_dram_matrix の考えるシナリオ

とりあえず書きかけのコードだが、githubに開発中のものをアップロードしている。

github.com

readmemhをしているのは以下で、16×16のデータをまずはローカルメモリに読み込む。

initial begin
  $readmemh ("datasets1.txt", datasets1);
  $readmemh ("datasets2.txt", datasets2);
end

次に、データをDMAでDDR4に転送する。tb.que_buffer_to_cltb.hm_put_bytetb.start_que_to_cl を使って転送する。 行列1はchannel0、行列1はchannel1を使って転送する。

f:id:msyksphinz:20180517000330p:plain
図. 行列積のデータをホストから転送するデータパス
  //Queue data to be transfered to CL DDR
  tb.que_buffer_to_cl(.chan(0), .src_addr(host_memory_buffer_address), .cl_addr(64'h0000_0004_0000_0000), .len(matrix_size) ); // move buffer to DDR 0

  // Put test pattern in host memory
  for (int i = 0 ; i < matrix_size / 4 ; i++) begin
    tb.hm_put_byte(.addr(host_memory_buffer_address+0), .d(datasets1[i][ 7: 0]));
    tb.hm_put_byte(.addr(host_memory_buffer_address+1), .d(datasets1[i][15: 8]));
    tb.hm_put_byte(.addr(host_memory_buffer_address+2), .d(datasets1[i][23:16]));
    tb.hm_put_byte(.addr(host_memory_buffer_address+3), .d(datasets1[i][31:24]));
    host_memory_buffer_address+=4;
  end

  host_memory_buffer_address = 64'h0_0001_0000;

  tb.que_buffer_to_cl(.chan(1), .src_addr(host_memory_buffer_address), .cl_addr(64'h0000_0004_0001_0000), .len(matrix_size) );  // move buffer to DDR 1

  for (int i = 0 ; i < matrix_size / 4 ; i++) begin
    tb.hm_put_byte(.addr(host_memory_buffer_address+0), .d(datasets2[i][ 7: 0]));
    tb.hm_put_byte(.addr(host_memory_buffer_address+1), .d(datasets2[i][15: 8]));
    tb.hm_put_byte(.addr(host_memory_buffer_address+2), .d(datasets2[i][23:16]));
    tb.hm_put_byte(.addr(host_memory_buffer_address+3), .d(datasets2[i][31:24]));
    host_memory_buffer_address+=4;
  end

...
  //Start transfers of data to CL DDR
  tb.start_que_to_cl(.chan(0));
  tb.start_que_to_cl(.chan(1));

  do begin
    status[0] = tb.is_dma_to_cl_done(.chan(0));
    status[1] = tb.is_dma_to_cl_done(.chan(1));
    #10ns;
    timeout_count++;
  end while ((status != 4'hf) && (timeout_count < 4000));

次に、CLに対してデータフェッチをトリガするのは以下だ。CLバス経由で0x500のアドレスに対して1を書き込む。これをCL側のデータフェッチのトリガ信号としよう。

  tb.poke(.addr(32'h0500), .data(32'h1), .size(DataSize::UINT16), .intf(AxiPort::PORT_OCL)); // write register

CL側の記述

CL側のハードウェア構成はファブリックのS1はデフォルトでTie offされており、自由に使用できる、のように書いてあるが実際にはATGのバスが接続されている。 これは取り外して良いのだろうか?とりあえず使わないのであれば外してしまってもよい気がしている。

f:id:msyksphinz:20180517000702p:plain
図. 謎。sh_cl_oclから分岐したバスがなぜかS01ポートに接続されている?これは取り外してよいのだろうか?
///////////////////////////////////////////////////////////////////////
///////////////// Secondary AXI Master module /////////////////////////
///////////////////////////////////////////////////////////////////////
// cl_dram_dma_axi_mstr  CL_DRAM_DMA_AXI_MSTR (
//     .aclk(clk),
//     .aresetn(dma_pcis_slv_sync_rst_n),
//     .cl_axi_mstr_bus(cl_axi_mstr_bus),
//     .axi_mstr_cfg_bus(axi_mstr_cfg_bus)
//   );

その代わりに、空いたcl_axi_mstr_busに対してAXIマスタを接続した。とりあえず、DRAMのデータを書き込んだところに対してデータフェッチを行う記述だ。

f:id:msyksphinz:20180517000843p:plain
図. 謎。sh_cl_oclから分岐したバスがなぜかS01ポートに接続されている?これは取り外してよいのだろうか?
    case (state)
      state_init: begin
        if (axi_mstr_cfg_bus.wr && axi_mstr_cfg_bus.addr[ 7: 0] == 8'h00 &&
            !cl_axi_mstr_bus.arvalid) begin
          cl_axi_mstr_bus.arvalid <= 1'b1;
          cl_axi_mstr_bus.araddr  <= 64'h0000_0004_0000_0000;
          cl_axi_mstr_bus.arid    <= 16'b0;                     // Only 1 outstanding command
          cl_axi_mstr_bus.arlen   <= 8'h00;                     // Always 1 burst
          cl_axi_mstr_bus.arsize  <= 3'b111;                    // Always 128 bytes
        end
        if (cl_axi_mstr_bus.arvalid && cl_axi_mstr_bus.arready) begin
          cl_axi_mstr_bus.arvalid <= 1'b0;
          state <= state_row;
        end
      end
      state_row: begin
        cl_axi_mstr_bus.arvalid <= 1'b1;
        cl_axi_mstr_bus.araddr  <= 64'h0000_0004_0001_0004;
        cl_axi_mstr_bus.arid    <= 16'b0;                     // Only 1 outstanding command
        cl_axi_mstr_bus.arlen   <= 8'h00;                     // Always 1 burst
        cl_axi_mstr_bus.arsize  <= 3'b010;                    // Always 4 bytes
        state <= state_col;
      end
      state_col: begin
        if (cl_axi_mstr_bus.arready) begin
          if (col_counter <= 15) begin
            cl_axi_mstr_bus.arvalid <= 1'b1;
            cl_axi_mstr_bus.araddr  <= cl_axi_mstr_bus.araddr + 64;  // Proceed 64-byte
            state <= state_col;
            col_counter <= col_counter + 6'h1;
          end else begin
            cl_axi_mstr_bus.arvalid <= 1'b0;
            state <= state_init;
          end
          cl_axi_mstr_bus.arid    <= 16'b0;                     // Only 1 outstanding command
          cl_axi_mstr_bus.arlen   <= 8'h00;                     // Always 1 burst
          cl_axi_mstr_bus.arsize  <= 3'b010;                    // Always 4 bytes
        end // if (cl_axi_mstr_bus.arready)
      end // case: state_col
    endcase // case (state)

cl_axi_mstr_busに対するデータロガーも接続して動作を観察しておく。

   always @ (negedge clk) begin
      if (cl_axi_mstr_bus.awvalid & cl_axi_mstr_bus.awready) begin
         $display ("%t : [cl_axi_mstr_bus AW] LEN=%d SIZE=%d ADDR=%x", $time,
                   cl_axi_mstr_bus.awlen, cl_axi_mstr_bus.awsize, cl_axi_mstr_bus.awaddr);
      end
      if (cl_axi_mstr_bus.arvalid & cl_axi_mstr_bus.arready) begin
         $display ("%t : [cl_axi_mstr_bus AR] LEN=%d SIZE=%d ADDR=%x", $time,
                   cl_axi_mstr_bus.arlen, cl_axi_mstr_bus.arsize, cl_axi_mstr_bus.araddr);
      end
      if (cl_axi_mstr_bus.wvalid & cl_axi_mstr_bus.wready) begin
         $display ("%t : [cl_axi_mstr_bus  W] STB=%x DATA=%x", $time, cl_axi_mstr_bus.wstrb, cl_axi_mstr_bus.wdata);
      end
      if (cl_axi_mstr_bus.rvalid & cl_axi_mstr_bus.rready) begin
         $display ("%t : [cl_axi_mstr_bus  R] DATA=%x", $time, cl_axi_mstr_bus.rdata);
      end
   end // always @ (negedge clk)

これで動作を見てみた。 めっちゃ時間はかかるが、どうやらちゃんとDRAMにアクセスしてデータをフェッチできたみたいだ。

[            33462000] : Initializing buffers
[            33462000] : starting H2C DMA channels
            33474000 : [sh_cl_dma AW] LEN= 15 SIZE=6 ADDR=0000000400000000
            33474000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=efe3be658f3f3b25123b5fef95d0dbf2eeee6b4b49aa9135f25b09fb79da57bd9ea0db5f4ea6d904e1ef3abb5e3cf5d888d272fbb6f7bafe70ef716ea7feef1a
            33482000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=cdeb2996cebedacc06c3d8fe51fdf3420225cefba9cf9fddee6ffa0fa70bfa0febbc9e3dfb1ebaee5b0d21dee4a10ee92989facf78cd9b379abb719bcf2a0eea
            33490000 : [sh_cl_dma AW] LEN= 15 SIZE=6 ADDR=0000000400010000
            33494000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=2404db84ad6199a6c37abe9c7cf482e9be6d67ccd40f64cdf2e9e4c8267a7f297bd88dbca72faa04fbbed32f24e1eac3ad3cbfcc9d2ed1a050df8a68b92d4b99
            33498000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=acb3f8a6a6de5ddc950d8cade9d6b9daafb96ebae1a1a4bcc8a2a24ffc9eb0f4bd38e6d75ce4703f7b0ce090d25c7e52be7d3ab7f3a5d0a93eaafe1c2db7c5cf
            33506000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=43bdcf1cba9bccd0d2b8d5e6cbb3dd51fed2553af9541b01230b7fb12bacb2b4c8fcea96b23ea5b1dd4086c9ffdecb8fbfcab8ba2e6b5d99591a5c93aad1e4be
            33514000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=908a71bc8e2dbc6dc818d823af24f74cb42dedb33e8cccfe42e84bde682cb56a76aa5c78a7e1b20cdb263f68a76f2ac8576cedf6dbde1ff9ccd61b8d2d2a29a3
            33522000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=3db619aceddb35fddeefcb6ea8b1a6ae36ff7cb5b55fb2230eba5eea09f559b119319c8fe28ff36feb437cc792fc6498b1dbad5ae492cce7bef8aea48ff4f4ba
            33530000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=59716bea2d1e4dbfc6fbb8abfaea0e16aabdc9cdfedac4eb9f3bef10a9ad4fd48b98eadd8d00eefbda36ddbddfd1cbafde8674437efc2b354fe87ac7c1083fc9
            33538000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=fa8c09af8ccb49e6fb7c8e54d30be8e2a23cfe69ccbd9ccb40debf5acddbd0bafcfaae244eeca080fc4deb95d44cd9723b7fe462f7feb21cebf54e9b3c6f6beb
            33546000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=a8df1175cc16bae62bec11411a14e41e532fb6f433f32d3be0cadc320942237d72afdcb1a1c33c0f6f2fa8c40000dd7bfafcf3b0aaec9cbbbaafe33c6f5e31bb
            33554000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=ddd631659ffe89ea783a31c9a4bf1cdcd89ee92f5cf3acd7fae2dc1eb2abdb5d3ffdec13b8380fadefbf7eea9a67bf1bdcdefd275ae5ba8c29aaa545eda4eb4f
            33562000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=26a3c6cd3fa9d0bdfbb2c08ad72de5c0ca0fd8d00bfa9bb4cef194abe7c28fc4e07029fedd9c22acfe3fa16b01cb140ec8d1e5a8caecbbdfeb9c0c0dd0faf13d
            33570000 : [sh_cl_dma  W] STB=ffffffffffffffff DATA=ceddd41b08ab61bded11af8bb27654fbaa4b105dc8f61b16eeaff71c19cb6b7be8ee39273ca94dcf650fdd76287ef63bdb6dd0fad249cf8c9a689401c3d5bbbe
...
[            33732000] : starting C2H DMA channels
Writing 0xDEAD_BEEF to address 0x00000500
            34762000 : [axi_mstr_cfg_bus W] ADDR=00000500
            34766000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=7 ADDR=0000000400000000
            34766000 : [axi_mstr_cfg_bus W] ADDR=00000500
            34782000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010004
            34798000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010044
            34814000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010084
            34830000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=00000004000100c4
            34846000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010104
            34862000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010144
            34878000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010184
            34894000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=00000004000101c4
            34910000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010204
            34926000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010244
            34942000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010284
            34958000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=00000004000102c4
            34974000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010304
            34986000 : [cl_axi_mstr_bus  R] DATA=efe3be658f3f3b25123b5fef95d0dbf2eeee6b4b49aa9135f25b09fb79da57bd9ea0db5f4ea6d904e1ef3abb5e3cf5d888d272fbb6f7bafe70ef716ea7feef1a
            34990000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010344
            34994000 : [cl_axi_mstr_bus  R] DATA=a622eeadfbdeae582fc8a39e30bacd0ac9daba51caadfdff7dab21b8740b152c2493f4fabeaa3bef8eaeeb3bcd7941a978ffa3fec942a985b0ce9b1de998efbf
            35006000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010384
            35014000 : [cl_axi_mstr_bus  R] DATA=28eacbe767f7f34ff42e9a5c4de1cec44fbe9bcecc064db7fcf1a0f3b234c6d1f26cb0fdf3524d8bcdb917fbb91b4efcea83dfa8a7e5c1ebb12ce8ccdaaf6c4f
            35022000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=00000004000103c4
            35026000 : [cl_axi_mstr_bus  R] DATA=68f25ebf12cc8f5daea31cd4bd2bc9c64cbb656cc918a8bf2afdb3e8b25cedc4c65584db1fecd041fc5a124f93babe1ecd89fcba9edd802918bd676a0ac560cc
            35038000 : [cl_axi_mstr_bus AR] LEN=  0 SIZE=2 ADDR=0000000400010404
            35046000 : [cl_axi_mstr_bus  R] DATA=1cadddac5ca664b3740e3fd2f5caf9ae1e5fb3e99ca3d0c3b2b8a2ff100e75febb65b131e40daef7b95ab6c8f6a5d4e5fff319b6cb208f4aeaeeb108b3ff1fb4
            35050000 : [cl_axi_mstr_bus  R] DATA=1efd6a41ceaf3ff86fdfdbf6b5ab2b1d9258660912ebf13944cd0387bdf9eee5c67a911a7ed6887c48edde0ec87faebae3bd46dc31636a7d5b06e9af5be4787f
            35058000 : [cl_axi_mstr_bus  R] DATA=7cadeaa3470d2f7e3fb1dcbf0fb5ca57d492d6c238b8ffe2a2eefc8cc2dbabdfb66568e5fa704caafc31d0dc1e9ebbb327c5b21fcdf3193d5ab2b53243b1ebfc
            35074000 : [cl_axi_mstr_bus  R] DATA=b301cea0c9bbde7c2594f2f3b7ad0b61ac9b2bd19ca2c9c081d0ce61e6c8cc2cd2d624785a9ea4d49dd3dd5266cd1b1c3d3bc3265cd941fc361aafc26ac2edbd
            35090000 : [cl_axi_mstr_bus  R] DATA=0def5d1ebb3f453e61ff7ea314e1abaafb6ffcbcae48681feb742569fe267bf18b2abbb1bf1b42dbb059cdb0d8b9bd05a531e78dd7af7d31bc2e08af5ff1f365