FC2カウンター FPGAの部屋 Vitis
FC2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

Vivado HLS 2019.2 で krnl_dma_read を作成する2(IP 化)

Vivado HLS 2019.2 で krnl_dma_read を作成する1(ソースコードの表示)”の続き。

前回は、Vitis のカーネル間ストーミング接続をテストするために DMA Read カーネル ー ラプラシアン・フィルタ・カーネル ー DMA Write カーネルをストーミング接続してみようということで、最初にDMA Read を作ることにした。そして、ソースコードを貼った。今回は、それらのソースコードを使用して、Vivado HLS 2019.2 で C シミュレーション、C コードの合成、C/RTL 協調シミュレーション、Export RTL を行った。

Vivado HLS 2019.2 で Krnl_dma_read プロジェクトを作成した。その際に New Vivado HLS Project のダイアログで Vitis Bottom Up Flow にチェックを入れた。
streaming_kernel_34_200122.png

Source に krnl_dma_read.cpp を入れて、Test Bench に bmp_header.h, krnl_dma_read_tb.cpp, test.bmp を入れた。
streaming_kernel_35_200122.png

最初に C シミュレーションを行った。
streaming_kernel_27_200121.png

dma_read.bmp が生成されていた。成功だ。
streaming_kernel_30_200121.png

C コードの合成を行った。
streaming_kernel_28_200121.png

Latency の min の 3153 クロック / 3072 ピクセル ≒ 1.03 クロック/ピクセルだった。

C/RTL 協調シミュレーションを行った。Latency は 3488 クロックだった。
streaming_kernel_29_200121.png

C/RTL 協調シミュレーションの波形を示す。
streaming_kernel_31_200121.png

outs_TVALID の波形が途中で切れ気味だが、だいたいOKだろう。

ここで、krnl_dma_read.cpp の extern "C" { } のコメントアウトを外して C コードの合成を行った。
引き続きExport RTL を行った。結果を示す。
streaming_kernel_32_200121.png

dma_read.xo もできた。
streaming_kernel_36_200122.png

とりあえず、krnl_dma_read.cpp を使用して、Vitis のカーネルのストーミング接続をテストするが、その後で、 xo ファイルを使用して、Vitis のカーネルのストーミング接続をテストしてみよう。
  1. 2020年01月22日 05:01 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2019.2 で krnl_dma_read を作成する1(ソースコードの表示)

Vitis のカーネル間ストーミング接続をテストするために DMA Read カーネル ー ラプラシアン・フィルタ・カーネル ー DMA Write カーネルをストーミング接続してみよう。
今回は、DMA Read カーネルを作成しよう。

ストーミング接続用のhls::stream の定義は、hls::stream<ap_axiu<32,0,0,0> > にする必要がある。こうするとサイドチャネルの信号は last と keep , strb だけになる。この内の last を使用して、フレームの最後をマークしよう。
ストーミング接続用 DMA Read カーネルの krnl_dma_read.cpp を示す。
なお、C シミュレーションできないので、とりあえず extern "C" { } は外してある。

// krnl_dma_read.cpp
// 2020/01/21 by marsee

#include <stdint.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

//extern "C" {
void dma_read(volatile int32_t *inm, hls::stream<ap_axiu<32,0,0,0> >& outs, int32_t x_size, int32_t y_size){
#pragma HLS INTERFACE s_axilite port=return bundle=control
#pragma HLS INTERFACE s_axilite port=y_size bundle=control
#pragma HLS INTERFACE s_axilite port=x_size bundle=control
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE m_axi depth=3072 port=inm offset=slave bundle=gmem

    ap_axiu<32,0,0,0> pix;

    LOOP_DRY: for(int y=0; y<y_size; y++){
#pragma HLS LOOP_TRIPCOUNT min=48 max=600
        LOOP_DRX: for(int x=0; x<x_size; x++){
#pragma HLS LOOP_TRIPCOUNT min=64 max=800
#pragma HLS PIPELINE II=1
            pix.data = inm[x_size*y+x];

            if(x==(x_size-1) && y==(y_size-1))
                pix.last = 1;
            else
                pix.last = 0;
            outs << pix;
        }
    }
}
//}


テストベンチの krnl_dma_read_tb.cpp を示す。

// krnl_dma_read_tb.cpp
// 2020/01/21 by marsee

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <iostream>
#include <fstream>
#include <ap_axi_sdata.h>
#include "bmp_header.h"

void dma_read(volatile int32_t *inm, hls::stream<ap_axiu<32,0,0,0> >& outs, int32_t x_size, int32_t y_size);

int main(){
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int32_t *rd_bmp, *dmar;
    int32_t blue, green, red;
    hls::stream<ap_axiu<32,0,0,0> > outs;
    ap_axiu<32,0,0,0> vals;

    if ((fbmpr = fopen("test.bmp", "rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open test.bmp by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int32_t *)malloc(sizeof(int32_t) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((dmar =(int32_t *)malloc(sizeof(int32_t) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate hw_lapd memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for(int y=0; y<bmpihr.biHeight; y++){
        for(int x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (int32_t)((blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16));
        }
    }
    fclose(fbmpr);

    dma_read(rd_bmp, outs, bmpihr.biWidth, bmpihr.biHeight);

    // DMAされた値のチェック
    for(int y=0; y<bmpihr.biHeight; y++){
        for(int x=0; x<bmpihr.biWidth; x++){
            outs >> vals;
            dmar[(y*bmpihr.biWidth)+x] = (int32_t)vals.data;
            if ((int32_t)vals.data != rd_bmp[(y*bmpihr.biWidth)+x]){
                printf("ERROR HW and SW results mismatch x = %ld, y = %ld, DMAR = %d, ORG = %d\n", x, y, (int)vals.data, (int)rd_bmp[(y*bmpihr.biWidth)+x]);
                return(1);
            }
        }
    }
    std::cout << "Success DMA READ results match" << std::endl;
    std::cout << std::endl;


    // DMA_Read の結果を dma_read.bmp へ出力する
    if ((fbmpw=fopen("dma_read.bmp", "wb")) == NULL){
        fprintf(stderr, "Can't open temp_lap.bmp by binary write mode\n");
        exit(1);
    }
    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    // RGB データの書き込み、逆順にする
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            blue = dmar[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (dmar[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (dmar[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);
    free(rd_bmp);
    free(dmar);

    return(0);
}


bmp_header.h を示す。

// bmp_header.h
// BMP ファイルフォーマットから引用させて頂きました
// http://www.kk.iij4u.or.jp/~kondo/bmp/
//
// 2017/05/04 : takseiさんのご指摘によりintX_tを使った宣言に変更。takseiさんありがとうございました
//              変数の型のサイズの違いによってLinuxの64ビット版では動作しなかったためです
//              http://marsee101.blog19.fc2.com/blog-entry-3354.html#comment2808
//

#include <stdio.h>
#include <stdint.h>

// BITMAPFILEHEADER 14bytes
typedef struct tagBITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
} BITMAPFILEHEADER;

// BITMAPINFOHEADER 40bytes
typedef struct tagBITMAPINFOHEADER{
    uint32_t biSize;
    int32_t biWidth;
    int32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t biXPixPerMeter;
    int32_t biYPixPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImporant;
} BITMAPINFOHEADER;

typedef struct BMP24bitsFORMAT {
    uint8_t blue;
    uint8_t green;
    uint8_t red;
} BMP24FORMAT;

  1. 2020年01月21日 05:39 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた3

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた2”の続き。

前回は、カーネル間のストーミング接続の設定ファイルを確認して、自分で新しいプロジェクトを作成して、やってみたが、カーネル間のストーミング接続ファイルがVitis に見つけられなくてエラーになってしまった。今回は、エラーの解消を図った。

まずは、”Vitis のカーネル間のストリーミング接続について”を見ると、ストーミング接続ファイルの指定方法は、v++ のリンク時に --config オプションでファイルを指定する。よって、Vitis のGUI でリンク時のオプションを追加する方法でやってみよう。

Vitis のGUI でリンク時のオプションを追加する。
Assistant ウインドウの streaming_k2k_mm2_system -> streaming_k2k_mm2 を右クリックし右クリックメニューから Settings... を選択する。
streaming_kernel_19_200118.png

Project Settings ダイアログが開く。
V++ linker options に

--config ../src/krnl_stream_vadd_vmult.ini

を入力した。
streaming_kernel_20_200118.png

これでビルドしたところ、xcl2.hpp が無いというエラーが発生した。
streaming_kernel_21_200118.png

streaming_k2k_mm/libs から xcl2.cpp と xcl2.hpp をインポートした。
streaming_kernel_22_200118.png

これでもう一度ビルドしたところ、ビルドが成功したようだ。
streaming_kernel_23_200118.png

もう一度、ビルドすると、 streaming_k2k_mm2_system -> streaming_k2k_mm2 -> Hardware にみどりのチェックマークがついた。
streaming_kernel_24_200118.png

Ultra96-V2 の電源をON して、Linux をブートする。
BOOT.BIN の Ultra96-V2 の MicroSD カードの第 1 パーティションへのSFTP は前回と同じカーネルだから良いだろうということでやっていない。

Ultra96-V2 を reboot した。
Ultra96-V2 の Linux が起動したら、root でログインした。
zocl ドライバをロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

Vitis で Run Configuration を作成した。
RUN ボタンをクリックすると、TEST PASSED が表示された。成功だ
streaming_kernel_25_200118.png

シリアル・ターミナルの表示を示す。
streaming_kernel_26_200118.png

これで、自分書いたカーネルでカーネル間のストーミング接続ができるようになったと言えるだろう。
  1. 2020年01月20日 04:31 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた2

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた1”の続き。

前回は、Vitis のサンプル・プロジェクトに入っている streaming_k2k_mm をビルドして、実機確認し、成功した。今回は、カーネル間のストーミング接続の設定ファイルを確認して、自分で新しいプロジェクトを作成して、やってみようと思う。
カーネル間のストーミング記述ファイルは、何も指定しないとVitis ビルド時に見つからなくてエラーになってしまった。

まずは、カーネル間のストーミング接続を記述したファイルを見ていく。
カーネル間のストーミング接続を記述したファイルは streaming_k2k_mm/krnl_stream_vadd_vmult.ini のようだ。
streaming_kernel_12_200118.png

krnl_stream_vadd_vmult.ini を見ると、”Vitis のカーネル間のストリーミング接続について”で調べたように記述されていた。
streaming_kernel_13_200118.png

このファイルを使用すれば、カーネル間のストーミング接続を指定することができそうだ。

さて、自分で Vitis アクセラレーション・アプリケーション・プロジェクトを作成する。名前は streaming_k2k_mm2 とした。
streaming_kernel_14_200118.png

ultra96v2_min2 プラットフォームを使用した。
Templates では Empty Application を指定した。
streaming_kernel_15_200118.png

streaming_k2k_mm2 プロジェクトが作成された。
streaming_kernel_16_200118.png

streaming_k2k_mm2/src ディレクトリにホスト・アプリケーションとカーネル・アプリケーション 2 個、カーネル間のストーミング接続記述ファイルをインポートした。
streaming_kernel_17_200118.png

これで Hardware でビルドしたところエラーになった。
streaming_kernel_18_200118.png

ERROR: [CFGEN 83-2284] No stream resources found that can accomodate compute unit "krnl_stream_vadd_1.out"
ERROR: [SYSTEM_LINK 82-36] [20:11:30] cfgen failed
Time (s): cpu = 00:00:00.28 ; elapsed = 00:00:00.29 . Memory (MB): peak = 296.441 ; gain = 0.000 ; free physical = 14175 ; free virtual = 39277
ERROR: [SYSTEM_LINK 82-62] Error generating design file for /home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm2/Hardware/krnl_stream_vadd_vmult.build/link/sys_link/cfgraph/cfgen_cfgraph.xml, command: /media/masaaki/Ubuntu_Disk/tools/Xilinx/Vitis/2019.2/bin/cfgen -nk krnl_stream_vadd:1 -nk krnl_stream_vmult:1 -dmclkid 0 -r /home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm2/Hardware/krnl_stream_vadd_vmult.build/link/sys_link/_sysl/.cdb/xd_ip_db.xml -o /home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm2/Hardware/krnl_stream_vadd_vmult.build/link/sys_link/cfgraph/cfgen_cfgraph.xml
ERROR: [SYSTEM_LINK 82-96] Error applying explicit connections to the system connectivity graph
ERROR: [SYSTEM_LINK 82-79] Unable to create system connectivity graph
INFO: [v++ 60-1442] [20:11:30] Run run_link: Step system_link: Failed
Time (s): cpu = 00:00:05 ; elapsed = 00:00:05 . Memory (MB): peak = 677.906 ; gain = 0.000 ; free physical = 14193 ; free virtual = 39295
ERROR: [v++ 60-661] v++ link run 'run_link' failed
ERROR: [v++ 60-626] Kernel link failed to complete
ERROR: [v++ 60-703] Failed to finish linking
makefile:94: recipe for target 'krnl_stream_vadd_vmult.xclbin' failed
make: *** [krnl_stream_vadd_vmult.xclbin] Error 1


結局、カーネル間のストーミング接続記述ファイルが認識されていないようだ。
  1. 2020年01月19日 04:54 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のカーネル間のストリーミング接続サンプル streaming_k2k_mm をやってみた1

昨日、新横浜から家に帰ってきました。

Vitis でメモリベースのIP を並べているとメモリ帯域の逼迫が心配なので、どうしてもカーネル間をストリーミング接続したいということで、”Vitis のストリーミング接続について”で調べたが、その内の、”Streaming Data Transfers Between Kernels (K2K)”のサンプルの streaming_k2k_mm についてやってみよう。

streaming_k2k_mm は、私のパソコンのVitis 上にサンプルとして読み込まれているので、Vitis GUI 上からサンプルとして実行することができる。
さて、Vitis のプロジェクトを作成して行こう。
Vitis GUI の File メニューから New -> Application Project... を選択する。
Create a New Application Project 画面で、Project Name に streaming_k2k_mm を入力した。
streaming_kernel_1_200118.png

Platform 画面では、ultra96v2_min2 プラットフォームを選択した。
streaming_kernel_2_200118.png

Domain 画面はそのまま。
streaming_kernel_3_200118.png

Template では、Stream Kernel to Kernel Memory Mapped を選択し、Finish ボタンをクリックした。
streaming_kernel_4_200118.png

krnl_steram_vadd カーネルと krnl_stream_vmult カーネル間のストーミング接続について図を書いてみた。下に示す。
streaming_kernel_5_200118.png

krnl_steram_vadd カーネルで in1 と in2 のメモリの値を足し算して、その値をストーミング接続で krnl_steram_vmult カーネルに送って、その値と krnl_steram_vmult カーネルの in1 のメモリの値を乗算して out のメモリにWrite する。

一度、Hardware をビルドしたが、ワーニングが出ている。
streaming_kernel_6_200118.png

もう一度、Hardware をビルドすると今度は成功。
streaming_kernel_7_200118.png

Ultra96-V2 の電源をON して、Linux をブートする。
/home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm/Hardware/sd_card ディレクトリに行って、BOOT.BIN を Ultra96-V2 の /run/media/mmcblk0p1/ に SFTP した。
cd /home/masaaki/Vitis_Work/2019.2/streaming_k2k_mm/Hardware/sd_card
scp BOOT.BIN 192.168.3.23:/run/media/mmcblk0p1


Ultra96-V2 を reboot した。
Ultra96-V2 の Linux が起動したら、root でログインした。
zocl ドライバをロードした。
insmod /lib/modules/4.19.0-xilinx-v2019.2/extra/zocl.ko

Vitis で Run Configuration を作成した。
streaming_kernel_8_200118.png

streaming_kernel_9_200118.png

RUN ボタンをクリックすると、TEST PASSED が表示された。成功だ。
streaming_kernel_10_200118.png

シリアル・ターミナルの表示を示す。
streaming_kernel_11_200118.png
  1. 2020年01月18日 14:59 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のカーネル間のストリーミング接続について

現在まだ新横浜プリンスホテルに宿泊しているが、Vitis のカーネル間のストリーミング接続について、覚書を書いておく。

Streaming Data Transfers Between Kernels (K2K)
Vitis のカーネル間のストリーミング接続については、まずは、Vitis Unified Software Development Platform DocumentationStreaming Data Transfers Between Kernels (K2K) に書かれている。
ストリームの形式は、hls::stream with the ap_axiu data type ということだ。hls::stream<ap_axiu<32,0,0,0> > aaa; という感じになるだろう。

Specify Streaming Connections Between Compute Units
Specify Streaming Connections Between Compute Units はコンピューティングユニット間のストリーミング接続を指定する方法について書かれている。
ストリーム間のストリーミング接続については、v++ のリンクの処理の中で、connectivity.stream_connect オプションを使用して、指定するらしい。
ファイルの書き方の例(Specify Streaming Connections Between Compute Units から引用する)

[connectivity]
#stream_connect=<cu_name>.<output_port>:<cu_name>.<input_port>
stream_connect=vadd_1.stream_out:vadd_2.stream_in


ファイルは、v++ の --config オプションにで指定する。(Specify Streaming Connections Between Compute Units から引用する)

v++ -l --config vadd_config.txt ...



Vitis Compiler General Options
--config オプションを見ている。
やはり --config オプションを使用して、カーネル間のストリーミング接続ファイルをリンカーに教えるようだ。

後は、v++ のオプションを GUI 上で指定できる方法を見つければストリーミング接続できるんじゃないだろうか?
とりあえず出先で Vitis の環境が無いので、家に帰ったら確かめてみよう。
  1. 2020年01月17日 05:12 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0

Vitis のストリーミング接続について

今日は新横浜プリンスホテルに宿泊しているので、大したことは書けないが Vitis のストリーミング接続について書いてみよう。

今まで実装してきたAXI4 Master アクセスによるカーネルは基本的にメモリを介して他のカーネルと通信するので、メモリ帯域を消費する。1 個や 2 個のカーネルだったら良いかもしれないが、3 , 4 個と接続するとメモリ帯域が足りなくなってしまうだろう。そこで、カーネルをストリーミング接続するとメモリ帯域を消費しないで何個でもカーネルを接続できるはずだ。

Vitis Unified Software Development Platform DocumentationStreaming Connections を見るとカーネルのストリーミング接続について書いてある。カーネルのストリーミング接続については 3 つあるようだ。

1. Streaming Data Between the Host and Kernel (H2K)
ホストからカーネルへのストリーミング接続で、残念ながら PCIe ベースの Alveo しか使用できないようだ。Ultra96V2 のプラットフォームでは使用できない。ここは、Ultra96V2 でやる場合には、カーネルにDMA を組み込んでストリーミングにすれば良いだろう。

2. Streaming Data Transfers Between Kernels (K2K)
カーネル間のストリーミング接続。
Alveo Data Centerアクセラレータカード用のQDMAプラットフォームなど、特定のターゲットプラットフォームでのみ使用できると書いてあるが、サンプルをやってみたところ Ultra96V2 でも動作するようだ。

3. Free-running Kernel
フリー・ランニング・カーネルなので、制御信号が無く、動作しっぱなしということらしい。これも Ultra96V2 でサンプルを動かしてみたが動作した。

今のところ、サンプルを動作させているだけで、自分で作成したカーネルをストリーミング接続することができていない。
それは、カーネル間のストリーミング接続が Vitis に認識されないためである。
カーネル間のストリーミング接続は、カーネル名.ini ファイルで Vitis に知らされるようなのだが、それを作って src に入れてもストリーミング接続が今のところ認識されずに困っている。
Vitis の GUI でのカーネルのストリーミング接続を有効にする方法を知っている方がいらしたら、教えてください。
  1. 2020年01月16日 04:34 |
  2. Vitis
  3. | トラックバック:0
  4. | コメント:0
»