FPGA開発日記

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

Rocket-Chipにおけるコンフィグレーション別の面積比較 (BlockRAMを推論させる方法調査)

前回のRocket Chipの標準論理合成では、FPGA向けにVivadoで合成してもBlockRAMが正常に推論されず、Dcacheが面積のほとんどを消費してしまう構成になってしまっていた。

msyksphinz.hatenablog.com

どうにかしてBlockRAMを使うように変更することはできないだろうか。 そもそも、なぜSRAMの部分がLUTで作られてしまうかというと、Rocket Chipの標準フローで合成すると、ASIC用にSRAMは外部定義に変更される。 rocket-chip/emulator/freechips.rocketchip.system.DefaultConfig.behav_srams.v というのがそれだ。 ASICを作る場合は、このモジュールを各プロセスに対応するSRAMに置き換える。 ただしこれは標準的なSRAMのように、WMEMのようなビット単位のWrite Maskが付いているSRAMを前提としており、BlockRAMを推論するのには不向きな構成になっている。

module data_arrays_0_ext(
  input RW0_clk,
  input [8:0] RW0_addr,
  input RW0_en,
  input RW0_wmode,
  input [31:0] RW0_wmask,
  input [255:0] RW0_wdata,
  output [255:0] RW0_rdata
);

  reg reg_RW0_ren;
  reg [8:0] reg_RW0_addr;
  reg [255:0] ram [511:0];
  `ifdef RANDOMIZE_MEM_INIT
    integer initvar;
    initial begin
...
  always @(posedge RW0_clk)
    if (RW0_en && RW0_wmode) begin
      if (RW0_wmask[0]) ram[RW0_addr][7:0] <= RW0_wdata[7:0];
      if (RW0_wmask[1]) ram[RW0_addr][15:8] <= RW0_wdata[15:8];
      if (RW0_wmask[2]) ram[RW0_addr][23:16] <= RW0_wdata[23:16];
      if (RW0_wmask[3]) ram[RW0_addr][31:24] <= RW0_wdata[31:24];
      if (RW0_wmask[4]) ram[RW0_addr][39:32] <= RW0_wdata[39:32];
      if (RW0_wmask[5]) ram[RW0_addr][47:40] <= RW0_wdata[47:40];
      if (RW0_wmask[6]) ram[RW0_addr][55:48] <= RW0_wdata[55:48];

そこでどうにかしてこの外部SRAM向けのWrapperが生成されるのを防がなければならないのだが、SiFive社の提供するFreedom環境ではSRAM Wrapperは生成されていない。どうやっているんだろう。

いろいろ調査した結果、SRAM Wrapperを作らない方法としては、FIRRTLでのVerilogファイルを生成する際、--repl-seq-mem を付加しないことがポイントなのではないかと思えてきた。

  • rocket-chip/emulator/Makefrag-verilator の抜粋
%.v: %.fir $(FIRRTL_JAR)
    echo $(FIRRTL)
    mkdir -p $(dir $@)
    $(FIRRTL) $(patsubst %,-i %,$(filter %.fir,$^)) -o $*.v -X verilog --infer-rw $(MODEL) --repl-seq-mem -c:$(MODEL):-o:$*.conf -faf $*.anno -ffaaf
  • freedom/common.mk の抜粋
$(verilog): $(firrtl) $(FIRRTL_JAR)
    $(FIRRTL) -i $(firrtl) -o $@ -X verilog

したがって、Rocket-Chipのリポジトリ内の Makefrag-verilatorを以下のように改造した。これでfreechips.rocketchip.system.DefaultConfig.vを再生成すると、外部SRAM Wrapperを生成しなくなっていた。成功だ!

diff --git a/emulator/Makefrag-verilator b/emulator/Makefrag-verilator
index 021e131..86fdea6 100644
--- a/emulator/Makefrag-verilator
+++ b/emulator/Makefrag-verilator
@@ -3,8 +3,9 @@
 #--------------------------------------------------------------------
 firrtl = $(generated_dir)/$(long_name).fir
 verilog = \
-  $(generated_dir)/$(long_name).v \
-  $(generated_dir)/$(long_name).behav_srams.v \
+  $(generated_dir)/$(long_name).v
+
+# $(generated_dir)/$(long_name).behav_srams.v \

 .SECONDARY: $(firrtl) $(verilog)

@@ -12,9 +13,12 @@ $(generated_dir)/%.fir $(generated_dir)/%.d: $(FIRRTL_JAR) $(chisel_srcs) $(boot
        mkdir -p $(dir $@)
        cd $(base_dir) && $(SBT) "run-main $(PROJECT).Generator $(generated_dir) $(PROJECT) $(MODEL) $(CFG_PROJECT) $(CONFIG)"

-%.v %.conf: %.fir $(FIRRTL_JAR)
+%.v: %.fir $(FIRRTL_JAR)
+       echo $(FIRRTL)
        mkdir -p $(dir $@)
-       $(FIRRTL) $(patsubst %,-i %,$(filter %.fir,$^)) -o $*.v -X verilog --infer-rw $(MODEL) --repl-seq-mem -c:$(MODEL):-o:$*.conf -faf $*.anno -ffaaf
+       $(FIRRTL) $(patsubst %,-i %,$(filter %.fir,$^)) -o $*.v -X verilog
+
+#   $(FIRRTL) $(patsubst %,-i %,$(filter %.fir,$^)) -o $*.v -X verilog --infer-rw $(MODEL) --repl-seq-mem -c:$(MODEL):-o:$*.conf -faf $*.anno -ffaaf

freechips.rocketchip.system.DefaultConfig.v をVivadoで合成し、Utilizationを出力させてみた。よっしゃ!しっかりBlockRAMが使われている!

+---------------------------------------+----------------------------+------------+------------+---------+------+-------+--------+--------+--------------+
|                Instance               |           Module           | Total LUTs | Logic LUTs | LUTRAMs | SRLs |  FFs  | RAMB36 | RAMB18 | DSP48 Blocks |
+---------------------------------------+----------------------------+------------+------------+---------+------+-------+--------+--------+--------------+
|     dcache                            |              DCache_dcache |       2585 |       2584 |       0 |    1 |  2577 |      0 |     36 |            0 |
|       (dcache)                        |              DCache_dcache |        898 |        897 |       0 |    1 |   536 |      0 |      4 |            0 |
|       data                            |            DCacheDataArray |        324 |        324 |       0 |    0 |     0 |      0 |     32 |            0 |
|       tlb                             |                        TLB |       1363 |       1363 |       0 |    0 |  2041 |      0 |      0 |            0 |

合成結果の効果は見ての通りだ。BlockRAMを活用したことで、しっかり面積が削減できている。

これで、公平な評価が取れそうだな。

f:id:msyksphinz:20171116023808p:plain

「GPUを支える技術」を読了

Hisa Ando氏の「GPUを支える技術」を、とりあえず読了した。最後の方はだいぶ適当に省略してしまったが、メインの部分はかなりちゃんと読み込んだと思う。

GPUの基礎を分かっていない私にとって、一から解説してくれる良い本だった。詳細までは触れることはないが、全般的な技術の概要をイラストや写真を使って分かりやすく解説してくれる。

これ以降、GPUのニュース記事やネット上の解説記事を読んで、「???この用語の意味が分からない」となっても、この本を読み直せばきちんと理解できる気がする。 そんな感じの、GPUの辞典として使うことが出来るのではないか。とりあえず、良本として保管。

次は何の本を読もうかな。

GPUを支える技術 ――超並列ハードウェアの快進撃[技術基礎] (WEB+DB PRESS plus)

GPUを支える技術 ――超並列ハードウェアの快進撃[技術基礎] (WEB+DB PRESS plus)

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

msyksphinz.hatenablog.com

Verilog-HDLで記述されたオープンソースのハードウェアライブラリ"BaseJump"

HotChips29 で発表された、学生が開発したRISC-V SoC ”Celerity”について資料を読んだ。

news.mynavi.jp

Celerityは学生の作ったRISC-Vのメニーコアチップで、4大学が共同して9か月で作成したチップとなっている。

9か月という短期間で作るための秘訣として、

  • 再利用
  • モジュール化設計
    • 部品レベルで設計を最適化し、ツールの処理時間を短くするため階層化設計を行った。
    • 詳細な中身を知らなくても利用できるブラックボックス設計を行った。
  • 自動化設計
    • 実装とテストのフローを抽象化して、色々な設計に適用できるようにした。
    • 確認済みのIPコンポーネントを利用し、インテグレーションのテストだけで済むようにした。
    • ハイレベル合成ツールを使用した。
    • アナログ設計が行われていた部分についてもディジタル設計のフローを使うようにした。

ということらしい。

BaseJump: Components for Open Source Hardware

この中で注目したのは、”BaseJump”とよばれるオープンソースのハードウェア部品を使ったということ。

BaseJumpは、オープンソースなハードウェアコンポーネントで誰でも使用することが出来る。 また、ホームページを見てみるとBGAパッケージも開発されており、これらもコンポーネント化されているのだろうか。

System Verilog のハードウェアコンポーネントは、BaseJump STLとして以下のBitbucketのページにまとめられている。

bitbucket.org

中身を見てみると、NoCのようなネットワークルータの部品から、標準的なMuxや非同期FIFOなど、一通りの部品はそろっている。かなり有用そうだ。

オープンソースのハードウェアコンポーネントというと、ParallelaのOH!が有名だが、こちらよりも部品点数は多いのではないだろうか?

github.com

「GPUを支える技術」を読む(第6章 GPUの周辺技術)

Hisa Ando氏の著書「GPUを支える技術」を買っていたのだが、ずいぶんと積ん読にしているのだった。

なので、一応最後まで読んでいきたい。こういうのは、きちんと宣言しないと途中で辞めちゃうので宣言する。頑張って最後まで読んでいこう。

今回は第6章。PCI ExpressやNVLink, さらにはCAPIのようなコヒーレンシプロトコルの話。

https://zxhzdq.bn1304.livefilestore.com/y4m8XR4_mlTlpRFEy37jWwiwEyyhLJq-neFv64D8kjepGTOfWQPf-c5FcFtHeGpH_zEfKXlfvOjsArRfTgBdufc1epH6SjsU6fZabZAWCQXwdfE-1BouEHsk5kiTGF_OnPW84P8G87EuNxgprNNenOutkbvRBWubmLi2cNRQYxCpItWHBbPWoryBdi08VYmENlbxcUwpJs4Yk_HAtTFMDvgTw?width=1440&height=3611&cropmode=none
f:id:msyksphinz:20171112233525p:plain:w200

「GPUを支える技術」を読む(第5章 GPUプログラミングの基本)

Hisa Ando氏の著書「GPUを支える技術」を買っていたのだが、ずいぶんと積ん読にしているのだった。

なので、一応最後まで読んでいきたい。こういうのは、きちんと宣言しないと途中で辞めちゃうので宣言する。頑張って最後まで読んでいこう。

今回は第5章。NVIDIAのCUDAおよびOpenACC、OpenMP4の話。

https://zxhqdq.bn1304.livefilestore.com/y4mXy1kmhgJbtVTyEjO8GZc2wWmXDdjUpFx99NBlBMdJPX_eqy5hHyEYslu_gGOIl00FR6KsGNf47ZRQYQbVZsQJikn6ZFX4UjYu4AlKW2VtMSC-ODlwOmkLKRIEV3gOb7X0Hw6WibJrFMhaygHXbMGdAz_mOqwUoyXNDHigsBMhUPISlFmmbvl9N02XyFMalTuvgpkW_oe3sd9ns4jBkfojA?width=1920&height=10108&cropmode=none
f:id:msyksphinz:20171112003820p:plain

Rocket-Chipにおけるコンフィグレーション別の面積比較 (BlockRAMへのマッピングの問題)

Rocket Chipは構成によってどのようなコンポーネントが含まれているのかが変わっているのだが、それ以外にもリビジョンによっても結構構成が変わって、FPGAへのインプリメントに使用する面積が変わったりもする。

比較対象として、以下のリビジョンを使用した。

35d377d Merge pull request #1100 from freechipsproject/disable-local-amos (日記を執筆時点の最新のリビジョン)

  • DefaultConfig

FreeChipProjectのRocket Chipは、DefaultConfigとDefaultFPGAConfigというものが存在しており、それぞれの構成の違いは以下のようになっている。

  • src/main/scala/system/Config.scala
class DefaultConfig extends Config(new WithNBigCores(1) ++ new BaseConfig)
class DefaultFPGAConfig extends Config(new WithNSmallCores(1) ++ new BaseFPGAConfig)
class DefaultFPGASmallConfig extends Config(new DefaultFPGAConfig)

WithNBigCores()WithNSmallCores()の違いは、パラメータの違いとして表現されているのだが、

  • src/main/scala/coreplex/Config.scala
class WithNBigCores(n: Int) extends Config((site, here, up) => {
  case RocketTilesKey => {
    val big = RocketTileParams(
      core   = RocketCoreParams(mulDiv = Some(MulDivParams(
        mulUnroll = 8,
        mulEarlyOut = true,
        divEarlyOut = true))),
      dcache = Some(DCacheParams(
        rowBits = site(SystemBusKey).beatBits,
        nMSHRs = 0,
        blockBytes = site(CacheBlockBytes))),
      icache = Some(ICacheParams(
        rowBits = site(SystemBusKey).beatBits,
        blockBytes = site(CacheBlockBytes))))
    List.tabulate(n)(i => big.copy(hartid = i))
  }
})

class WithNSmallCores(n: Int) extends Config((site, here, up) => {
  case RocketTilesKey => {
    val small = RocketTileParams(
      core = RocketCoreParams(useVM = false, fpu = None),
      btb = None,
      dcache = Some(DCacheParams(
        rowBits = site(SystemBusKey).beatBits,
        nSets = 64,
        nWays = 1,
        nTLBEntries = 4,
        nMSHRs = 0,
        blockBytes = site(CacheBlockBytes))),
      icache = Some(ICacheParams(
        rowBits = site(SystemBusKey).beatBits,
        nSets = 64,
        nWays = 1,
        nTLBEntries = 4,
        blockBytes = site(CacheBlockBytes))))
    List.tabulate(n)(i => small.copy(hartid = i))
  }
})

これを見ると、だいたい分かってくるのは以下のような構成になっているということだ。主に命令キャッシュ、データキャッシュの構成を変更している。

WithNBigCores WithNSmallCores
コア機能 VM 指定なし(あり) なし
演算器 mulUnroll 8 指定なし(なし)
mulEarlyOut あり 指定なし(なし)
divEarlyOut あり 指定なし(なし)
FPU 指定なし(あり) なし
Dキャッシュ rowBits システムバスと同じサイズ システムバスと同じサイズ
セット数 指定なし(64) 64
ウェイ数 4 1
TLBエントリ数 32 4
MSHR数 0 0 MSHR: Miss Status Handling Register
ブロックバイト数 CacheBlockBytes CacheBlockBytes *
命令キャッシュ rowBits システムバスと同じサイズ システムバスと同じサイズ
セット数 64 64
ウェイ数 4 1
TLBエントリ数 32 4
ブロックバイト数 CacheBlockBytes CacheBlockBytes *

それぞれの構成において、Vivadoで論理合成して比較してみた。

なお、合成のためには以下のモジュールをRocket Chip以外に追加する必要がある。

  • plusarg_reader.v // Argument Reader シミュレーション用。ただしFPGA合成用はからのモジュールが定義される。
  • freechips.rocketchip.system.DefaultFPGAConfig.behav_srams.v // SRAMのビヘイビアモデル。一応FPGAで合成することは出来るが、あまり良い構成ではない。

というわけで、Rocket-ChipのVerilogのモジュールのうち、RocketTile_rocket から下の1階層のモジュールの面積比較を行ってみた。

f:id:msyksphinz:20171111141629p:plain

DCacheが大きすぎる。ちゃんとブロックRAMに変換できていないんじゃないか?BigCoresとSmallCoresでキャッシュの構成が違いすぎている気がする。

基本的にDCacheの大きさがダイレクトに聞いているような気がしていて、data_arrays_0_extを正しく作らなければこのようになってしまう。 これは外部のVerilogで定義されているのだが、この実装がそのまんまなのでBlockRAMに手起業させることが出来ない。 っていうかRAMBを全く使ってくれない。

  • WithNBigCoresの場合
+---------------------------------------+----------------------------+------------+------------+---------+------+--------+--------+--------+--------------+
|                Instance               |           Module           | Total LUTs | Logic LUTs | LUTRAMs | SRLs |   FFs  | RAMB36 | RAMB18 | DSP48 Blocks |
+---------------------------------------+----------------------------+------------+------------+---------+------+--------+--------+--------+--------------+
|     dcache                            |              DCache_dcache |      56796 |      56795 |       0 |    1 | 133722 |      0 |      4 |            0 |
|       (dcache)                        |              DCache_dcache |        788 |        787 |       0 |    1 |    536 |      0 |      0 |            0 |
|       data                            |            DCacheDataArray |      54494 |      54494 |       0 |    0 | 131145 |      0 |      0 |            0 |
|         data_arrays_0                 |              data_arrays_0 |      54494 |      54494 |       0 |    0 | 131145 |      0 |      0 |            0 |
|           data_arrays_0_ext           |          data_arrays_0_ext |      54494 |      54494 |       0 |    0 | 131145 |      0 |      0 |            0 |
|       tag_array                       |                  tag_array |        147 |        147 |       0 |    0 |      0 |      0 |      4 |            0 |
|         tag_array_ext                 |              tag_array_ext |        147 |        147 |       0 |    0 |      0 |      0 |      4 |            0 |
|       tlb                             |                        TLB |       1367 |       1367 |       0 |    0 |   2041 |      0 |      0 |            0 |
  • WithNSmallCoresの場合
+--------------------------------+----------------------------+------------+------------+---------+------+------+--------+--------+--------------+
|            Instance            |           Module           | Total LUTs | Logic LUTs | LUTRAMs | SRLs |  FFs | RAMB36 | RAMB18 | DSP48 Blocks |
+--------------------------------+----------------------------+------------+------------+---------+------+------+--------+--------+--------------+
|     dcache                     |              DCache_dcache |       1119 |       1119 |       0 |    0 |  482 |      1 |      1 |            0 |
|       (dcache)                 |              DCache_dcache |        880 |        880 |       0 |    0 |  482 |      0 |      0 |            0 |
|       data                     |            DCacheDataArray |        139 |        139 |       0 |    0 |    0 |      1 |      0 |            0 |
|         data_arrays_0          |              data_arrays_0 |        139 |        139 |       0 |    0 |    0 |      1 |      0 |            0 |
|           data_arrays_0_ext    |          data_arrays_0_ext |        139 |        139 |       0 |    0 |    0 |      1 |      0 |            0 |
|       tag_array                |                  tag_array |         91 |         91 |       0 |    0 |    0 |      0 |      1 |            0 |
|         tag_array_ext          |              tag_array_ext |         91 |         91 |       0 |    0 |    0 |      0 |      1 |            0 |
|       tlb                      |                        TLB |          9 |          9 |       0 |    0 |    0 |      0 |      0 |            0 |
|         pmp                    |                 PMPChecker |          9 |          9 |       0 |    0 |    0 |      0 |      0 |            0 |

以下がそのVerilogの実装だ。ちょっとひどい。ちゃんとBlockRAMに置き換えないと。

  • WithNBigCores の場合、 freechips.rocketchip.system.DefaultConfig.behav_srams.v の構成
  reg [255:0] ram [511:0];
...
  always @(posedge RW0_clk)
    if (RW0_en && RW0_wmode) begin
      if (RW0_wmask[0]) ram[RW0_addr][7:0] <= RW0_wdata[7:0];
      if (RW0_wmask[1]) ram[RW0_addr][15:8] <= RW0_wdata[15:8];
      if (RW0_wmask[2]) ram[RW0_addr][23:16] <= RW0_wdata[23:16];
      if (RW0_wmask[3]) ram[RW0_addr][31:24] <= RW0_wdata[31:24];
      if (RW0_wmask[4]) ram[RW0_addr][39:32] <= RW0_wdata[39:32];
      if (RW0_wmask[5]) ram[RW0_addr][47:40] <= RW0_wdata[47:40];
      if (RW0_wmask[6]) ram[RW0_addr][55:48] <= RW0_wdata[55:48];
      if (RW0_wmask[7]) ram[RW0_addr][63:56] <= RW0_wdata[63:56];
      if (RW0_wmask[8]) ram[RW0_addr][71:64] <= RW0_wdata[71:64];
      if (RW0_wmask[9]) ram[RW0_addr][79:72] <= RW0_wdata[79:72];
      if (RW0_wmask[10]) ram[RW0_addr][87:80] <= RW0_wdata[87:80];
      if (RW0_wmask[11]) ram[RW0_addr][95:88] <= RW0_wdata[95:88];
      if (RW0_wmask[12]) ram[RW0_addr][103:96] <= RW0_wdata[103:96];
      if (RW0_wmask[13]) ram[RW0_addr][111:104] <= RW0_wdata[111:104];
      if (RW0_wmask[14]) ram[RW0_addr][119:112] <= RW0_wdata[119:112];
      if (RW0_wmask[15]) ram[RW0_addr][127:120] <= RW0_wdata[127:120];
      if (RW0_wmask[16]) ram[RW0_addr][135:128] <= RW0_wdata[135:128];
      if (RW0_wmask[17]) ram[RW0_addr][143:136] <= RW0_wdata[143:136];
  • WithNSmallCores の場合、 freechips.rocketchip.system.DefaultFPGAConfig.behav_srams.v の構成
  reg [63:0] ram [511:0];
  always @(posedge RW0_clk)
    if (RW0_en && RW0_wmode) begin
      if (RW0_wmask[0]) ram[RW0_addr][7:0] <= RW0_wdata[7:0];
      if (RW0_wmask[1]) ram[RW0_addr][15:8] <= RW0_wdata[15:8];
      if (RW0_wmask[2]) ram[RW0_addr][23:16] <= RW0_wdata[23:16];
      if (RW0_wmask[3]) ram[RW0_addr][31:24] <= RW0_wdata[31:24];
      if (RW0_wmask[4]) ram[RW0_addr][39:32] <= RW0_wdata[39:32];
      if (RW0_wmask[5]) ram[RW0_addr][47:40] <= RW0_wdata[47:40];
      if (RW0_wmask[6]) ram[RW0_addr][55:48] <= RW0_wdata[55:48];
      if (RW0_wmask[7]) ram[RW0_addr][63:56] <= RW0_wdata[63:56];
    end

追記: fpga-zynqの構成で実行すると、BlockRAMが正しく推論されているようだ。

+-----------------------------------------+------------------------------------------+------------+------------+---------+------+-------+--------+--------+--------------+
|                 Instance                |                  Module                  | Total LUTs | Logic LUTs | LUTRAMs | SRLs |  FFs  | RAMB36 | RAMB18 | DSP48 Blocks |
+-----------------------------------------+------------------------------------------+------------+------------+---------+------+-------+--------+--------+--------------+
|     DCache                              |                                   DCache |       1659 |       1658 |       0 |    1 |   986 |      0 |     36 |            0 |
|       (DCache)                          |                                   DCache |        504 |        503 |       0 |    1 |   502 |      0 |      0 |            0 |
|       MetadataArray                     |                            MetadataArray |        283 |        283 |       0 |    0 |     7 |      0 |      4 |            0 |
|       data                              |                          DCacheDataArray |        535 |        535 |       0 |    0 |     0 |      0 |     32 |            0 |
|       fq                                |                              FinishQueue |          8 |          8 |       0 |    0 |     5 |      0 |      0 |            0 |
|       tlb                               |                                    TLB_2 |        329 |        329 |       0 |    0 |   472 |      0 |      0 |            0 |

「GPUを支える技術」を読む(第4章 後半)

Hisa Ando氏の著書「GPUを支える技術」を買っていたのだが、ずいぶんと積ん読にしているのだった。

なので、一応最後まで読んでいきたい。こういうのは、きちんと宣言しないと途中で辞めちゃうので宣言する。頑張って最後まで読んでいこう。

今回は第4章後半。NVIDIA以外のGPUの話と、最新のGPUの新機能の話。

https://0rhsdq.bn1304.livefilestore.com/y4m3qSdO-84x6JVFksVxXo1KS1k0x5mw3GBXZN904WPBKRSOGONH-68fDXXEpj29HIDA2PwYBwz1Pc1B4yJh13_uohtSg7kdeWTr0f9EbvGI5Z8iA9HOAKg10fsY8ye-AyALgr2__O7A7l1SVoZ43jOIDfaNFfiMFpVvamFiEOthD5IboyL0h3mt0IaSMlDBDNs7e0dskBNQZ6JfQEkkJy64g?width=1414&height=3856&cropmode=none
f:id:msyksphinz:20171110003212p:plain