FPGA開発日記

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

NVDLAの内部構成調査(4. NVDLA_compiler と Caffeデータの変換)

NVDLAの動かし方についてもう少しプログラミングモデルを解き明かしていきたいが、いかんせん内部レジスタの仕様を読み進めながらでは効率が悪くて良くない。

それに実際のニューラルネットワークを使いながらインタフェースを見ていかないと、意味が分からないことになってしまう。 sanityテストと、わずかなConvolutionテストだけを使って内部構成を把握するのはかなり厳しい気がしてきた。

そこで、テストパタンやC言語で書かれたプログラムを用いて、NVDLAを実際に動かして推論を行うコードを見ながら調査を進めていくことにした。 真っ向から解析していっても全く埒が明かないので、少し視点を変えてみようという訳だ。

NVDLAにはハードウェアのデザインだけでなく、ソフトウェアのリポジトリも用意されている。

  • NVDLA Open source Hardware 1.0

github.com

  • NVDLA Open source softwea

github.com

  • NVDLA Open source Virtual Platform

github.com

ソフトウェアのリポジトリの使い方をいろいろと調べていた。nvdla_compilernvdla_runtimeというのが気になっている。

  • NVDLA - Test applications

Test applications — NVDLA Documentation

nvdla_compilerというコマンドを使用すれば、NVDLAに食わすことができるバイナリを生成できそうな気がする。 その時に必要な入力は、prototxtcaffemodelというものらしい。どうやら、Caffeで生成されるもののようだ。

CaffeどころかTensorFlowもろくに使いこなせていないが、バイナリだけ作ってCaffeのモデルを入力するだけでバイナリが作られるのならば、やりようがあるのかもしれない。

./nvdla_compiler [-options] –prototxt <prototxt_file> –caffemodel <caffemodel_file> -o <outputpath>

Caffeの構築

まずはCaffeをダウンロードして手元の環境でモデルを作ることができる環境を整えた。

環境の構築は、 https://gist.github.com/nikitametha/c54e1abecff7ab53896270509da80215 を参考にした。OpenCVやら各種ライブラリのインストールに時間がかかってしまった。

make all -j8

CaffeのLenetサンプルプログラムのビルド

Caffeのサンプルプログラムである、MNISTをLeNetで動かすプログラムは以下でコンパイルできるらしい。

./data/mnist/get_mnist.sh
./examples/mnist/create_mnist.sh
./examples/mnist/train_lenet.sh

すると、examples/mnistに目的のファイルが生成される。これをnvdla_compilerに食わせてみた。

f:id:msyksphinz:20180902235659p:plain
nvdla_compiler --caffemodel lenet_iter_10000.caffemodel --prototxt lenet.prototxt
creating new wisdom context...
opening wisdom context...
parsing caffe network...
libnvdla<3> mark prob
Marking total 1 outputs
attaching parsed network to the wisdom...
compiling profile "basic"...
libnvdla<3> compile network
libnvdla<3> compile349
libnvdla<2> Prototxt input batch size (N = 64) != Profile batch size (N = 1). Preferring N from profile for compiling.
profile compiled "basic"...
closing wisdom context...

output.protobufbasic.nvdlaが生成された。 ProtoBufってことは、Protocol Bufferが使われているのかなあ。ということはバイナリを見ただけでは分からないな。

実際にこの生成されたファイルを使用するためには、NVDLAのVirtual Platformの環境を使用しなければならないらしい。 これにはnvdla_vpとnvdla_runtimeを使用しなければならないのだが、これの解析は後日とする。

Freedom-U-SDKで生成したLinuxバイナリを自作RISC-Vシミュレータで実行 (4. 命令セットシミュレータの高速化検討)

RISC-Vの自作シミュレータでLinuxを立ち上げることに成功したが、まだかなりシミュレーション速度が遅いことが心配だ。

原因はすでに解析済み。メモリアクセスが何度も発生しているためにそこが最適化できていない。 以下は前回Google Perftoolsで解析したシミュレータのボトルネック部分。 LoadMemoryと言う、メモリアクセスをつかさどっている部分にかなりのアクセスが集中している。 WalkPageTableと言うこちらも仮想アドレスから物理アドレスへの変換を行う機構もやはりかなりのアクセスが必要になっている。

この辺りを改良するためには、TLBを実装するとよい。 という訳で、非常に簡単なTLBを実装してメモリアクセスの高速化の検討を行った。

(pprof) top
Total: 19415 samples
    3750  19.3%  19.3%     5083  26.2% Memory::LoadMemory
    3088  15.9%  35.2%     6920  35.6% RiscvPeThread::WalkPageTable
    1457   7.5%  42.7%    19198  98.9% RiscvPeThread::StepExec
    1384   7.1%  49.9%     9034  46.5% RiscvPeThread::FetchMemory
    1185   6.1%  56.0%     1185   6.1% CsrEnv::Riscv_Read_CSR
    1173   6.0%  62.0%     1173   6.0% RiscvDec::DecodeInst
     872   4.5%  66.5%      872   4.5% __memmove_avx_unaligned_erms
     768   4.0%  70.4%      768   4.0% TraceInfo::RecordTrace
     543   2.8%  73.2%     2056  10.6% RiscvPeThread::CheckInterrupt
     397   2.0%  75.3%     3561  18.3% EnvBase::LoadMemoryDebug

RISC-VにTLBはある?

誤解を招きそうなのだが、RISC-VにTLBの仕様は明記されていない。 それはRISC-VがISAの仕様だからであり、わざわざTLBの構成まで規定する必要はないからだ。 なので、Spikeを含めTLBの実装方法はかなりバリエーションがある。 ここでは、Spikeの実装を参考にしながらTLBを実装した。

  static const uint32_t tlb_width = 1 << 8;
  bool   m_tlb_en[tlb_width];
  Addr_t m_tlb_tag[tlb_width];
  Addr_t m_tlb_addr[tlb_width];

ここで、さらにTLBの参照及びアップデートの機能を追加していく。 基本的には、上記で実装しているページWalkをつかさどる関数 WalkPageTable() に追加してけば良い。

  //===================
  // Simple TLB Search
  //===================
  Addr_t  vaddr_vpn = (vaddr >> 12);
  uint8_t vaddr_tag = vaddr_vpn & (tlb_width-1);
  if (m_tlb_en[vaddr_tag] && m_tlb_tag[vaddr_tag] == vaddr_vpn) {

TLBにヒットしなかったときはTLBをアップデートする。

  //==========================
  // Update Simple TLB Search
  //==========================
  DebugPrint("<Info: TLB[%d] <= 0x%016lx(0x%016lx)>\n", vaddr_tag, vaddr_vpn, *paddr & ~0x0fff);
  m_tlb_en  [vaddr_tag] = true;
  m_tlb_tag [vaddr_tag] = vaddr_vpn;
  m_tlb_addr[vaddr_tag] = (*paddr & ~0x0fff) | (pte_val & 0x0ff);

Spikeの実装はどのようになっているのか

Spikeも似たような実装になっている。 Spikeの方は少し複雑で、命令フェッチ、Load、Storeで別々のタグが用意されている。

  • riscv-isa-sim/riscv/mmu.h
  // If a TLB tag has TLB_CHECK_TRIGGERS set, then the MMU must check for a
  // trigger match before completing an access.
  static const reg_t TLB_CHECK_TRIGGERS = reg_t(1) << 63;
  tlb_entry_t tlb_data[TLB_ENTRIES];
  reg_t tlb_insn_tag[TLB_ENTRIES];
  reg_t tlb_load_tag[TLB_ENTRIES];
  reg_t tlb_store_tag[TLB_ENTRIES];

測定結果

Linuxのブートプロセスで、どれくらい高速化されるか観測した。まあまあ速くなった。

1000000000命令実行時間 実行時間
TLB実装前 195.823
TLB実装後(256-entries) 128.412
TLB実装後(512-entries) 128.101
f:id:msyksphinz:20180902183620p:plain

NVDLAの内部構成調査(3. プログラミングシーケンス, Register Description Language)

NVDLAの内部構成調査の続き。前回はNVDLAのBDMAについて調べたが、次は実際にConvolutionの操作を見ていかなければならない。

sanity3のテストパタンを見ると、大量のレジスタを設定してるのだが、それをいちいち調べていくのは大変だ。

まずは、NVDLAの基本的な実行方法について調べていく。

f:id:msyksphinz:20180901013918p:plain
図. NVDLAコアの内部構成 (http://nvdla.org/primer.html より抜粋)

NVDLAのプログラミングシーケンス

以下を参考にした。 Hardware Architectural Specification — NVDLA Documentation

NVDLAサブモジュールをプログラムするための基本的なシーケンスは以下のとおりである。 NVDLAサブモジュールは、すべて同じレジスタを持っており、このシーケンスではCDMAサブモジュールを使ってプログラムを動作させるための手法を説明している。

  1. リセット後、Group0とGroup1はどちらともIDLE状態である。CPUはCDMA_POINTERレジスタを読み出し、PRODUCERレジスタに対してCONSUMERレジスタの値を設定する必要がある(リセット後、CONSUMERレジスタは0に設定されている)。
  2. レジスタグループ0に、1番目のハードウェアレイヤのパラメータを設定する。設定が完了すると、D_OP_ENABLEレジスタenableビットフィールドに1を設定する。
  3. ハードウェアが最初のハードウェアレイヤの処理を始める。
  4. レジスタグループ1がIDLE状態であることを確認するためにS_STATUSレジスタを確認する。
  5. CPUはPRODUCERに1を設定し、グループレイヤ1に対して2番目のハードウェア寧屋のパラメータを設定し始める。これのレジスタに対するプログラムが完了すると、グループ1のD_OP_ENABLEレジスタのenableビットを設定する。
  6. CPUはS_STATUSレジスタを参照して、レジスタグループ0の状態を確認するまだ実行中であれば、CPUは割り込みの発生を待つ。
  7. 現在のハードウェアレイヤについて、ハードウェアが処理を完了する。S_STATUSレジスタ内のアクティブだったグループの値をIDLEに設定し、D_OP_ENABLEレジスタENABLEに設定する。
  8. ハードウェアはCONSUMERフィールドを次のレジスタグループ(今回の場合は1)に設定する。CONSUMERフィールドを進めると、新しいグループ内のEnableビットの位置が決まる。そして次のハードウェアレイヤが即時に開始される。そうでなければ、Enableビットが設定されるまでハードウェアは待ち状態になる。
  9. 実行が完了したハードウェアに対して"done"割り込みが発生する。CPUがdone割り込みの発生をブロックしているならば、上記の処理が継続される。
  10. 必要に応じて上記が繰り返される。

NVDLAのRegister Description Language

NVDLAには大量のレジスタが定義されており、どのレジスタのどのビットフィールドに何が設定できるのかが良く分からない。 一応レジスタ一覧表はあるが、ざっくりと解説してあるだけで具体的な値については説明されていない。

ここで役に立つのが、Register Description Language(=RDL)という気泡で記述されたレジスタの一覧表だ。 このRDLで記述されたレジスタ定義から、VerilogPythonC言語などのシステムレジスタの実装が生成されている。

Register Definition Languageは統一された規格のようだ。以下に仕様が掲載されている。

  • SystemRDL 2.0 Register Description Language

http://www.eda.org/images/downloads/standards/systemrdl/SystemRDL_2.0_Jan2018.pdf

例えば、NVDLAのCDMAで定義されているレジスタを見てみよう。masterブランチ(NVDLA v2)でRDLが定義されている。

  • spec/manual/NVDLA_CDMA.rdl
...
    reg {
        name = "D_MISC_CFG";
        enum D_MISC_CFG_CONV_MODE_enum {
            DIRECT = 1'd0;
            WINOGRAD = 1'd1;
        };
        field {
            encode          = D_MISC_CFG_CONV_MODE_enum;
            sw              = rw;
            hw              = r;
            spec_access     = rw;
            reset           = 0x0;
            reset_mask      = 0x1;
            spec_sw_default = 0x0;
            sw_default_mask = 0x0;
        } CONV_MODE[0:0];
        enum D_MISC_CFG_IN_PRECISION_enum {
            INT8 = 2'd0;
            INT16 = 2'd1;
            FP16 = 2'd2;
        };
        field {
            encode          = D_MISC_CFG_IN_PRECISION_enum;
            sw              = rw;
            hw              = r;
...

これをコンパイルする。NVDLAのルートディレクトリに移って、makeでtree.makeを作成してからの./tools/bin/tmakespec/manualの中身がコンパイルされる。

$ make  # nv_largeを指定する。
$ ./tools/bin/tmake -build vmod
[TMAKE]: building nv_large in spec/defs
[TMAKE]: building nv_large in spec/manual
[TMAKE]: building nv_large in spec/odif
[TMAKE]: building nv_large in vmod/vlibs
[TMAKE]: building nv_large in vmod/include
...
[TMAKE]: building nv_large in vmod/nvdla/retiming
[TMAKE]: building nv_large in vmod/nvdla/top
[TMAKE]: Done nv_large
[TMAKE]: nv_large: PASS

outdir/spec/manualに移動してみる。NVDLA_CDMA.*に関するファイルだけでも以下が生成されていた。

$ ls -1 NVDLA_CDMA*
NVDLA_CDMA.h
NVDLA_CDMA.py
NVDLA_CDMA.rdl
NVDLA_CDMA_reg.sv
NVDLA_CDMA_reg.v
NVDLA_CDMA.vh
NVDLA_CDMA.xml

NVDLA_CDMA_reg_c:
ordt_pio_common.cpp
ordt_pio_common.hpp
ordt_pio.cpp
ordt_pio.hpp

例えば、NVDLA_CDMA.vhには以下のようなレジスタフィールドが定義されている。 S_STATUS_0レジスタはアドレス0x3000に定義されており、以下の2つのフィールドが定義されていることが分かりますね。 (ちなみに同じようなレジスタが2つ生成されているのが、各パイプラインでグループが指定されており、Foreground / Backgroundの指定ができるものと思われる)

  • STATUS_0[1:0] : IDLE=0, RUNNING=1, PENDING=2
  • STATUS_1[17:16] : IDLE=0, RUNNING=0, PENDING=2

  • NVDLA_CDMA.vh

// Register NVDLA_CDMA_S_STATUS_0
#define NVDLA_CDMA_S_STATUS_0                                   32'h3000
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_RANGE                    1:0
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_SIZE                             2
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_IDLE                     2'h0
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_RUNNING                  2'h1
#define NVDLA_CDMA_S_STATUS_0_STATUS_0_PENDING                  2'h2
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_RANGE                    17:16
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_SIZE                             2
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_IDLE                     2'h0
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_RUNNING                  2'h1
#define NVDLA_CDMA_S_STATUS_0_STATUS_1_PENDING                  2'h2

NVDLAの内部構成調査(2. BDMAの使用方法について)

RISC-VのNVDLAがコラボしたり、NVDLAの調査も再度実施しなければならないと思ってきた。

riscv.org

テストパタンの中で、sanity1 / sanity2 BDMAのテストパタンだ。 BDMAはいわゆるDMAのことなのだが、ディープラーニング向けのDMAよろしく、いろんな設定ができるようになっている。

まずは、sanity2のテストパタンのテストの内容をチェックしていこう。 Feature Dataという入力データを外部から取り込んで、別のメモリ場所に格納するというDMAのテストである。 Feature Dataはsample_surf.datというデータに格納されている。これはreadmemhでcsb_master_seqというモジュール経由であらかじめ外部メモリに格納されているものとする。

load_mem 0x80000000 0x1000 sample_surf.dat

テストパタンの内容としては、0x80000000から格納されているsample_surf.datのデータを0x81000000にDMA転送するというものである。 この時に幾つかパラメータを設定するのだが、その前にFeature Dataの構造について理解しておく必要がある。

パラメータには大きく分けて"LINE"向け、"SURFACE"向けのパラメータが存在している。 マニュアルを読んだ感じだと、

  • LINE : Feature DataのW(Width)×H(Height)に相当するデータ群
  • SURF : Feature DataのC(Channel)に相当するデータ群

であると理解した。これらについて、BDMAでは転送ストライドをそれぞれ設定しながらデータを流すことができるというわけだ。

f:id:msyksphinz:20180830230204p:plain:w400
図. Feature DataとLINE / SURFACEの関係(想像)

転送パラメータについて確認しよう。

  • SURF向けの設定
    • CFG_SRC_SURF_0 : SURFのSource Addressのストライド数。この1SURF(=Channel)は、ここで設定したバイト数の間隔で配置されているものとする。
      • テストパタン : 0x800 (つまりSURFは0x800バイト毎に配置されている)
    • CFG_DST_SURF_0 : SURFのDestination Addressのストライド数。
      • テストパタン : 0x800 (つまりSURFは0x800バイト毎に転送する。)
    • CFG_SURF_REPEAT_0 : SURFを何ブロック分転送するかを示している。ここでは0が設定されているので1回分転送する。
  • LINE向けの設定
    • CFG_SRC_LINE_0 : LINEのSource Addressのストライド数。この1LINE(=W×H)は、ここで設定したバイト数の間隔で配置されているものとする。
      • テストパタン : 0x100 (つまりLINEは0x100バイト毎に配置されている)
    • CFG_DST_LINE_0: LINEのDestination Addressのストライド数。
      • テストパタン : 0x100 (つまりLINEは0x100バイト毎の場所に転送される)
    • CFG_LINE_0 : 1LINEのサイズを指定する。ここでは7が設定されているので8(=7+1)×32-byte(256-bit)が転送される。
    • CFG_LINE_REPEAT_0 : LINEを何ブロック転送するかを示している。ここでは7が設定されているので8(=7+1)回LINEが転送される。

それぞれの関係図を示すと以下のようになる。3次元の入力データに対して、このようにして自由に転送できる領域を変えている。

f:id:msyksphinz:20180830225447p:plain:w1000
図. NVDLAのBDMA機能の各種パラメータと転送データの関係

NVDLAの内部構成調査(1. NVDLAのテストパタンの調査)

RISC-VのNVDLAがコラボしたり、NVDLAの調査も再度実施しなければならないと思ってきた。

riscv.org

しかしNVDLAの内部構成やインテグレーションの方法など、あまり情報が整理されていない。 というわけで、NVDLAのリポジトリのテストパタンを解析して、どうやって動いているのか見ていこう。

NVDLAには、主に以下のテストパタンが付属しており、Verilator, Vivado Simulator, VCSで動作させることができる。

verif/traces/traceplayer/sanity0
  • sanity0
  • sanity1
  • sanity1_cvsram
  • sanity2
  • sanity2_cvsram
  • sanity3
  • sanity3_cvsram
  • sdp_relu_int16
  • cc_alexnet_conv5_relu5_int16_dtest_cvsram
  • conv_8x8_fc_int16
  • googlenet_conv2_3x3_int16
  • pdp_max_pooling_int16

まずはsanityテストからチェックしようかな。

csr制御のシーケンサ

NVDLAのテストベンチは、read_reg, write_regなどのCSRを制御するシーケンスで構成されている。 これを実際に制御するのはNVDLAモジュールの傍にインスタンスされているcsr_master_seqというモジュールなのだが、このモジュールがそれぞれの命令をシーケンサで管理している。

表. NVDLAテスト用の制御シーケンサのステートマシン
コマンド名 番号
MSEQ_IDLE 8'h00
MSEQ_REG_RD 8'h01
MSEQ_REG_RD_WAIT_RESP 8'h02
MSEQ_REG_RD_POLL_WAIT 8'h03
MSEQ_REG_RD_MISMATCH 8'h04
MSEQ_REG_RD_TIMEOUT 8'h05
MSEQ_REG_WR 8'h08
MSEQ_REG_WR_WAIT_RESP 8'h09
MSEQ_REG_WR_TIMEOUT 8'h0a
MSEQ_MEM_RD 8'h10
MSEQ_MEM_WR 8'h18
MSEQ_MEM_LD 8'h20
MSEQ_MEM_DMP 8'h28
MSEQ_WAIT 8'h30
MSEQ_WAIT_TIMEOUT 8'h31
MSEQ_BOOT_1 8'hfd
MSEQ_BOOT_2 8'hfe
MSEQ_DONE 8'hff

sanity0

レジスタアクセスに関するテスト。

3回のレジスタアクセスを起こす。

  1. CFG_DST_SURF_0の初期値読み込み。
  2. CFG_DST_SURF_0に値を書き込み。
  3. CFG_DST_SURF_0に書き込んだ値が読み込めるか。

sanity1

BDMAに関するテスト。

tb.slave_mem_wrap.dbb_mem.memoryにsurf_memの内容をコピーする。このメモリは32ビット幅で、surf_data.txtの中身をそのまま格納している。

0x80000000に格納されているデータを0x81000000にコピーしている。

CFG_LINEを7に設定している。CFG_LINE_REPEATが0x7に設定されている。このパラメータは1surfaceにつき何ラインを移動するかを指定している。

最後にCFG_LAUNCH0に書き込みして実行開始。

64bit×32回のバーストが発生している。NVDLA側からリードリクエストを発生させ、読み込んだものをそのまま書き込み。

sanity2

BDMAに関するテストでsanity1と同じだが、最後に割り込みが発生するのを待つ。最後にwaitコマンドを実行し、割り込みが発生するまで待機する。

sanity3

sample_surf.datのデータと、weight.datのデータを使って畳み込み演算を実行している。

sample_surfのデータは0x80000000-0x80001000(4KB)、0x80100000から18KBに格納されている。といっても、かなり大量にレジスタを設定しているので、もう少し調査しないといけない。

NVDLAの扱えるデータ構造

sanity3は実際に畳み込みをするテストなのだが、どのようにデータを格納して扱うのだろうか?

色々調べると、詳細な情報を以下に見つけた。

  • NVDLA : In-memory data formats

In-memory data formats — NVDLA Documentation

どうやら、重みと入力データでデータタイプをいろいろ指定できるらしい。 NVDLAの精度としてはint8, int16, fp16をサポートしている。

入力データの形は、Width, Height, Channelの3次元構造を入力することができる。 これに対して、Line Stride, Surface Strideを指定することで畳み込みのサイズを指定している。

http://nvdla.org/_images/format_packed_feature_diagram.svg
図. 特徴データのデータ構造 (http://nvdla.org/hw/format.html#basic-weight-for-winograd-convolution より抜粋)
http://nvdla.org/_images/format_unpacked_feature_diagram.svg
図. 特徴データのデータ構造 (http://nvdla.org/hw/format.html#basic-weight-for-winograd-convolution より抜粋)

Intel Software Guard Extensionのチュートリアル サンプルプログラムを実行(Part-3 PasswordManagerCore)

Intel SGX Tutorialを読み進めているのだが、サンプルプログラムにも触れておきたい。

まずはPart-3のサンプルコードから。いわゆる普通のソフトウェアとか、GUIのソフトウェアとか触る機会がないので扱い方に少し戸惑うが...

software.intel.com

ページの一番最後にダウンロードのリンクがあるのでそれを経由してダウンロードする。

f:id:msyksphinz:20180827230914p:plain

Visual Studio 2017で開いた。メインプログラムはC#で記述されている。

f:id:msyksphinz:20180827231037p:plain

実行するとこのようになる。CUIのプログラムなのでテキストがパラパラと出てきて終了である。 が、最後FAILと表示されているじゃないか。何だこりゃ。

f:id:msyksphinz:20180827231131p:plain
=========================================================
NEW VAULT TESTS
=========================================================
Deleting test vault C:\Users\msyksphinz\Documents\test1.vlt
Creating new test vault C:\Users\msyksphinz\Documents\test1.vlt
    Expected 0, received 0...>>> PASS <<<
Unlock before creating a password
    Expected 8, received 8...>>> PASS <<<
Random password
    Expected 0, received 0...>>> PASS <<<
Add account before creating password
    Expected 8, received 8...>>> PASS <<<
Set master password
    Expected 0, received 0...>>> PASS <<<
Get number of accounts
    Expected 0, received 0...>>> PASS <<<
Found 0 accounts
    Expected 0, received 0...>>> PASS <<<
Set/add info at account index 3
    Expected 0, received 0...>>> PASS <<<
Get number of accounts
    Expected 0, received 0...>>> PASS <<<
Found 4 accounts
    Expected 4, received 4...>>> PASS <<<
Account 0:
    Expected 0, received 0...>>> PASS <<<
    Name =
   Login =
     URL =
    Expected 0, received 0...>>> PASS <<<
Password =
Account 1:
    Expected 0, received 0...>>> PASS <<<
    Name =
   Login =
     URL =
    Expected 0, received 0...>>> PASS <<<
Password =
Account 2:
    Expected 0, received 0...>>> PASS <<<
    Name =
   Login =
     URL =
    Expected 0, received 0...>>> PASS <<<
Password =
Account 3:
    Expected 0, received 0...>>> PASS <<<
    Name = IRC
   Login = jellybean
     URL = n/a
    Expected 0, received 0...>>> PASS <<<
Password =
Locking vault
Unlocking vault
    Expected 0, received 0...>>> PASS <<<
Get number of accounts
    Expected 0, received 0...>>> PASS <<<
Found 1 accounts
    Expected 1, received 1...>>> PASS <<<
Account 0:
    Expected 0, received 0...>>> PASS <<<
    Name = IRC
   Login = jellybean
     URL = n/a
    Expected 0, received 0...>>> PASS <<<
Password =
=========================================================
EXISTING VAULT TESTS
=========================================================
Checking for reference vault C:\Users\msyksphinz\Documents\reference.vlt
    Expected True, received False...>>> FAIL <<<
Hit ENTER to exit...

何をしているのか

  • TestSuite.cs
        public bool RunAll()
        {
           try {
                this.Initialize();
                this.RunNewVault();
                this.RunExistingVault();
           }
           catch {                
                return false;
           }
        }

mgr.vault_createによりパスワード管理クラス(PasswordManagerCore)を作成しているらしい。

  • TestSuite.cs
        PasswordManagerCore mgr;
...
            // Create a new vault
            Console.WriteLine("Creating new test vault {0}", path);
            rv= mgr.vault_create(path);

一つ一つ追っていくことにした。 コメントを追いかけると次のようなテストをしていることになる。

RunNewVault

  1. テスト用のvaultを作成する。
  2. vaultをアンロックする。
  3. ランダムなパスワードを作成する。
  4. パスワードなしでアカウントを追加する。これは失敗するはずである。
  5. スターパスワードを設定する。マスターパスワードは3.とは異なる。ここでは"12345@#$%asdfg"としている。
  6. アカウント数を確認する。ここでは4.で失敗したので0のままである。
  7. 再びアカウントを追加する。今回はマスターパスワードを追加したので成功する。
  8. アカウントを確認する。ここでは(何故か)4つのアカウントが追加された。最後のアカウントにのみ情報が入っている。
    Expected 0, received 0...>>> PASS <<<
    Name = IRC
   Login = jellybean
     URL = n/a
    Expected 0, received 0...>>> PASS <<<
  1. vaultのロックとアンロックを試行する。すると(これもなぜか)1つのアカウントが生き残る。上記のアカウントだ。

ここまでが、アカウントの作成テスト。

RunExistingVault

まずこのファイルの実行でFailしてしまうのだが、reference.vltを参照仕様として失敗している。これはサンプルプログラムのsample vaultというディレクトリに入っているので、C:\Users\msyksphinz\Documents\reference.vltに移動して実行してみる。

再び実行してみると、最後まで実行されてPassしたようだ。

まずはreference.vltだ。これは暗号化されている。これをどのようにしてテストするか、というところだが、

  1. 既存のreference.vltに対して、さらに新しいvaultファイルを作成する。
  2. スターパスワードを作成し、vaultのパスワードを更新する。
  3. アンロックせずにアカウント情報を読み込む。これはNoPermissionによりエラーが発生すr。
  4. アンロックせずにアカウントのパスワードを読み込む。これはNoPermissionによりエラーが発生すr。
  5. 間違ったパスワードでアンロックする。これはエラーとなる。
  6. 正しいパスワードでアンロックする。これは成功する。
  7. スターパスワードを設定する。
  8. パスワードやアカウントの情報などをいろいろ変更する。変更しても問題ないことを確認する。
f:id:msyksphinz:20180828001205p:plain
=========================================================
EXISTING VAULT TESTS
=========================================================
Checking for reference vault C:\Users\msyksphinz\Documents\reference.vlt
    Expected True, received True...>>> PASS <<<
Backing up original vault file C:\Users\msyksphinz\Documents\reference.vlt to C:\Users\msyksphinz\Documents\reference_orig.vlt
Create new vault on top of existing vault C:\Users\msyksphinz\Documents\reference.vlt
    Expected 5, received 5...>>> PASS <<<
Opening vault C:\Users\msyksphinz\Documents\reference.vlt
    Expected 0, received 0...>>> PASS <<<
Generate password
    Expected 0, received 0...>>> PASS <<<
Password: 3dxy6u6gTAiH5lbx
Set master password
    Expected 6, received 6...>>> PASS <<<
Change master password
    Expected 6, received 6...>>> PASS <<<
Read accounts without unlocking
    Expected 6, received 6...>>> PASS <<<
Read account password without unlocking
    Expected 6, received 6...>>> PASS <<<
Read account without unlocking
    Expected 6, received 6...>>> PASS <<<
Update account without unlocking
    Expected 6, received 6...>>> PASS <<<
Unlock with wrong password
    Expected 13, received 13...>>> PASS <<<
Unlock vault
    Expected 0, received 0...>>> PASS <<<
Set master password
    Expected 8, received 8...>>> PASS <<<
Change master password to itself
    Expected 1, received 1...>>> PASS <<<
Read accounts
    Expected 0, received 0...>>> PASS <<<
3 accounts
    Expected 3, received 3...>>> PASS <<<
Account 0:
    Expected 0, received 0...>>> PASS <<<
    Name = Google
   Login = johnm
     URL = http://google.com/
    Expected 0, received 0...>>> PASS <<<
Password = addition pack solar ring
Account 1:
    Expected 0, received 0...>>> PASS <<<
    Name = Intel
   Login = mechalas
     URL = http://intel.com/
    Expected 0, received 0...>>> PASS <<<
Password = GLV2TE@#P+FvMC?B9wYBz5rj
Account 2:
    Expected 0, received 0...>>> PASS <<<
    Name = Microsoft
   Login = nobody
     URL = http://miscrosoft.com/
    Expected 0, received 0...>>> PASS <<<
Password = O1d-Tongue 0pportunity-ball
Generating new password for account 0
    Expected 0, received 0...>>> PASS <<<
Password 0: #AnzRj4Daqerg11V
Changing account password for account 0
    Expected 0, received 0...>>> PASS <<<
Getting new password for account 0
    Expected 0, received 0...>>> PASS <<<
New password = #AnzRj4Daqerg11V
Comparing generated password #AnzRj4Daqerg11V to assigned password #AnzRj4Daqerg11V
    Expected True, received True...>>> PASS <<<
Generating new password for account 1
    Expected 0, received 0...>>> PASS <<<
Password 1: WfW7@q7o8x@@zQ*x
Changing account password for account 1
    Expected 0, received 0...>>> PASS <<<
Getting new password for account 1
    Expected 0, received 0...>>> PASS <<<
New password = WfW7@q7o8x@@zQ*x
Comparing generated password WfW7@q7o8x@@zQ*x to assigned password WfW7@q7o8x@@zQ*x
    Expected True, received True...>>> PASS <<<
Generating new password for account 2
    Expected 0, received 0...>>> PASS <<<
Password 2: cqYyZc7rWC$FV!yd
Changing account password for account 2
    Expected 0, received 0...>>> PASS <<<
Getting new password for account 2
    Expected 0, received 0...>>> PASS <<<
New password = cqYyZc7rWC$FV!yd
Comparing generated password cqYyZc7rWC$FV!yd to assigned password cqYyZc7rWC$FV!yd
    Expected True, received True...>>> PASS <<<
Fetching undefined account 5:
    Expected 0, received 0...>>> PASS <<<
    Name =
   Login =
     URL =
Fetch password for account 5 to clipboard
    Expected 0, received 0...>>> PASS <<<
Password =
Comparing empty password to undefined account password
    Expected True, received True...>>> PASS <<<
Copy password for account 5 to clipboard
    Expected 0, received 0...>>> PASS <<<
Copy password for account 105 to clipboard
    Expected 4, received 4...>>> PASS <<<
Copy password for account 1 to clipboard
    Expected 0, received 0...>>> PASS <<<
Copy password for account 2 to clipboard
    Expected 0, received 0...>>> PASS <<<
Write new account information for account 2
    Expected 0, received 0...>>> PASS <<<
Get new account information for account 2:
    Expected 0, received 0...>>> PASS <<<
    Name = AOL (should be AOL)
    Expected True, received True...>>> PASS <<<
   Login = aoluser (should be aoluser)
    Expected True, received True...>>> PASS <<<
     URL = http://aol.com/ (should be http://aol.com/)
    Expected True, received True...>>> PASS <<<
Write new account information for account 6
    Expected 0, received 0...>>> PASS <<<
Get new account information for account 6:
    Expected 0, received 0...>>> PASS <<<
    Name = ≪ταБЬ?σ≫ (should be ≪ταБЬ?σ≫)
    Expected True, received True...>>> PASS <<<
   Login =  (should be )
    Expected True, received True...>>> PASS <<<
     URL = http://unicodetest.net/ (should be http://unicodetest.net/)
    Expected True, received True...>>> PASS <<<
Change master password
    Expected 0, received 0...>>> PASS <<<
Lock the vault
    Expected 0, received 0...>>> PASS <<<
Read accounts  after locking
    Expected 6, received 6...>>> PASS <<<
Read account password after locking
    Expected 6, received 6...>>> PASS <<<
Unlock vault with old passphrase
    Expected 13, received 13...>>> PASS <<<
Unlock vault with new passphrase
    Expected 0, received 0...>>> PASS <<<
Read accounts
    Expected 0, received 0...>>> PASS <<<
4 accounts
    Expected 4, received 4...>>> PASS <<<
Account 0:
    Expected 0, received 0...>>> PASS <<<
    Name = Google
   Login = johnm
     URL = http://google.com/
    Expected 0, received 0...>>> PASS <<<
Password = #AnzRj4Daqerg11V
Account 1:
    Expected 0, received 0...>>> PASS <<<
    Name = Intel
   Login = mechalas
     URL = http://intel.com/
    Expected 0, received 0...>>> PASS <<<
Password = WfW7@q7o8x@@zQ*x
Account 2:
    Expected 0, received 0...>>> PASS <<<
    Name = AOL
   Login = aoluser
     URL = http://aol.com/
    Expected 0, received 0...>>> PASS <<<
Password = cqYyZc7rWC$FV!yd
Account 3:
    Expected 0, received 0...>>> PASS <<<
    Name = ≪ταБЬ?σ≫
   Login =
     URL = http://unicodetest.net/
    Expected 0, received 0...>>> PASS <<<
Password =
Copying modified vault to C:\Users\msyksphinz\Documents\reference_modified.vlt
Restoring original vault file from C:\Users\msyksphinz\Documents\reference_orig.vlt to C:\Users\msyksphinz\Documents\reference.vlt
Sleeping for 15 seconds to let clipboard timers finish...
Hit ENTER to exit...

vaultはどこに?

vaultクラスは結局PasswordManagerCoreNativeクラス内のvaultメンバとして定義されているのだが、

  • PasswordManagerCoreaNative.h
 Vault vault;

結局はキーを格納しているクラスにすぎない。

  • PasswordManagerCore.h
class PASSWORDMANAGERCORE_API Vault
{
    Crypto crypto;
    char m_pw_salt[8];
    char db_key_nonce[12];
    char db_key_tag[16];
    char db_key_enc[16];
    char db_key_obs[16];
    char db_key_xor[16];
    UINT16 db_version;
    UINT32 db_size; // Use get_db_size() to fetch this value so it gets updated as needed
    char db_data_nonce[12];
    char db_data_tag[16];
    char *db_data;
...

lock()unlock()はCryptoクラスにより制御されている。

int Vault::unlock(const char *password)
{
    crypto_status_t rv;
    char db_key[16];

    if (!this->is_valid()) return NL_STATUS_INVALID;
    if (!this->is_locked()) return NL_STATUS_OK;

    // Validate the passphrase by attempting to decrypt the database key

    rv = crypto.unlock_vault((PBYTE)password, (ULONG) strlen(password), (PBYTE)m_pw_salt, (PBYTE)db_key_enc, (PBYTE) db_key_nonce, 
        (PBYTE) db_key_tag, (PBYTE) db_key);

    if (rv == CRYPTO_ERR_DECRYPT_AUTH) return NL_STATUS_PASSWORD;
...

Freedom-U-SDKで生成したLinuxバイナリを自作RISC-Vシミュレータで実行 (3. シェル実行)

HiFive Unleashedは高くて買えないのだけれども、RISC-V SDKであるFreedom-U-SDKを使ってみることにした。

自作RISC-Vシミュレータを使ってRISC-VのLinuxをブートさせたいのだけれども、なかなか動作しないので四苦八苦していた。 #自作RISC-Vシミュレータは、そのうちGitHubに公開できるように調整中。

ログイン画面までは動いていたのだが、キー入力がうまくいかなくてそこから先に進んでいなかったのだった。 ターミナルののモジュールとか、Spikeとの動作の違いを見ながら進めていった結果、ログインしてからシェルが動くようになったぞ!

f:id:msyksphinz:20180829230543g:plain
図. 自作RISC-VシミュレータでLinuxを立ち上げログイン・シェルを動かす。

肝となったのは、デバイスにコマンドを渡すときにデバイスIDも一緒に渡すのだが、デバイスの処理が完了した際にFromHostにデータを戻すとき、同様にどのデバイスからのデータであるかを示さなければならないということだ。これを間違えていたため、いつまでたってもキーボードの入力値を受け付けなかった。

  void StdInRead () {
    int read_data;
    // DWord_t fromhost;
    // GetPeThread()->LoadFrom_FromHost (&fromhost);
    // if (fromhost == 0) {
    if ((m_req_count > 0) &&
        ((read_data = read()) != -1)) {
      read_data = 0x100 | read_data;

      // デバイスIDも一緒にFromHostに返す。
      uint64_t resp_data = static_cast<uint64_t>(GetDevId()) << 56 | read_data;
      GetPeThread()->StoreTo_FromHost (resp_data);
      GetPeThread()->DebugPrint("<Info: Tick read enable() = %x, resp_data = %016lx>\n", read_data, resp_data);
      m_req_count--;
      //}
    }
  }

とりあえずシミュレータのメンテナンスはここまでで終了かな。もう少し高速化しなければ... そのためには、今一番足かせになっているメモリアクセスのルーチンを高速化しなければ。

Google Perftoolsで解析すると、以下のようになっている。やはり、メモリアクセスが遅いんだな。

(pprof) top
Total: 19415 samples
    3750  19.3%  19.3%     5083  26.2% Memory::LoadMemory
    3088  15.9%  35.2%     6920  35.6% RiscvPeThread::WalkPageTable
    1457   7.5%  42.7%    19198  98.9% RiscvPeThread::StepExec
    1384   7.1%  49.9%     9034  46.5% RiscvPeThread::FetchMemory
    1185   6.1%  56.0%     1185   6.1% CsrEnv::Riscv_Read_CSR
    1173   6.0%  62.0%     1173   6.0% RiscvDec::DecodeInst
     872   4.5%  66.5%      872   4.5% __memmove_avx_unaligned_erms
     768   4.0%  70.4%      768   4.0% TraceInfo::RecordTrace
     543   2.8%  73.2%     2056  10.6% RiscvPeThread::CheckInterrupt
     397   2.0%  75.3%     3561  18.3% EnvBase::LoadMemoryDebug