Posts Tagged with "FPGA"

既に発行済みのブログであっても適宜修正・追加することがあります。
We may make changes and additions to blogs already published.

Pongの開発 (5)

posted by sakurai on November 10, 2023 #694

パドルコントロール

さて、Pongの実装で欲しくなるのがパドルコントロールのためのツマミです。一般には可変抵抗器で実装しているようです。幸いCmodA7にはアナログ入力があるので、可変抵抗器を接続すればよさそうです。

FPGAボード内のアナログ入力は以下のようになっています。外部の0~3.3Vの電圧を抵抗で分圧し、FPGAのADC入力は0~1Vの入力としています。入力インピーダンスが高いとは言えないので、外部回路の出力インピーダンスが高い場合問題になります。

図%%.1
図694.1 CmodA7 ADC入力回路

可変抵抗器は出力インピーダンスが変化するので、設計が案外面倒です。最大と最小のみの2点だけを考えれば良いのかもしれませんが、ここではアナログ回路シミュレータであるLTSpiceを使用してみます。

まず、可変抵抗器のシミュレーションをする前に、出力インピーダンスが高い場合にどうなるかを見てみます。出力インピーダンスが3.3Kとした場合の回路とシミュレーション波形です。浮遊容量を少し付加しています。

図%%.2
図694.2 出力インピーダンス3.3Kと波形

サイン波形を入れていますが、DC特性を見る目的です。

波形から明らかなように、基準電圧$V_\text{in}$(ブルー)は3.3Vまで上昇しているにも関わらず、出力インピーダンスが後段の入力インピーダンスと同程度であるため、CmodA7ボードの入力電圧$V_\text{1}$(レッド)は期待の3.3Vまで上昇せず1.6V Maxとなり、分圧したADCの入力電圧$V_\text{out1}$(グリーン)は0.5V Maxと半分しか上がりません。


左矢前のブログ 次のブログ右矢

Pongの開発 (4)

posted by sakurai on November 9, 2023 #693

疑似乱数生成器

ChatGPTにLFSRのアルゴリズムを持つ疑似乱数生成器を記述してもらいました。以下の完成したモジュールはそれを手直ししたものです。

interface Randomizer_ifc;
  method ActionValue#(Bit#(1)) random_01();
endinterface

//(* synthesize *) モジュールをインライン化するためコメントアウト
module mkRandomizer(Randomizer_ifc);
    Reg#(Bit#(16)) lfsr <- mkReg(16'hACE1); // 適当な非ゼロの初期値

  method ActionValue#(Bit#(1)) random_01();
       Bit#(1) newBit = lfsr[15] ^ lfsr[13] ^ lfsr[12] ^ lfsr[10];
       lfsr <= {lfsr[14:0], newBit};
       return lfsr[15];
  endmethod

endmodule

ActionValueメソッドの呼び出し方

作成した疑似乱数生成器の呼び出しが少々難しかったのでまとめておきます。BSVにおいてはモジュールインタフェース内に記述されるメソッドの型は

  • Value Method
  • ActionValue Method
  • Action Method

の3種類があります。それぞれ入力、入出力、出力ポートに対応しますが、ActionValueの呼び出し方に少々困難がありました。単純にメソッドを変数に入れることができないためです。

特にFSMを構成するseqブロック内で、あるレジスタwにValueメソッドの戻り値を代入するだけなら、

seq
   :
   w <= random_01();
   :
endseq

等とすれば良いのですが、この場合のrandom関数は内部状態を持ち、それが呼び出しにより更新されるという副作用を持つため、ActionValueメソッドとして呼び出します。この呼び出し法が少々難しく、"<-"を用いてインスタンスした上で、かつ単純にseqの中で呼ぶことはできず、actionブロックを構成してその中でのみ有効な値となります。

実例を挙げると、

import Randomizer::*;
Randomizer_ifc randomizer <- mkRandomizer;
   :
    seq
        action
   :
           Bit#(1) w <- randomizer.random_01();
   :
        endaction
    endseq

のように、actionブロックの中で"<-"を用いて関数を呼び出します。特にactionブロックを構成することになかなか気づきませんでした。

追記:
この場合の変数wはaction - endactionのスコープ内でしか有効でないことが後日判明したので、それを回避する方法を追記します。action - endactionのスコープ内でコピー変数にコピーするだけです。

    Reg#(Bit#(1)) ww <- mkRegU; // wをactionスコープの外で使用するコピー変数
   :
    seq
        action
   :
           let w <- randomizer.random_01();  // Bit#(1)宣言でなくてletでも良い
           ww <= w;
   :
        endaction
    endseq

なお、関数がマルチサイクルの場合はこれでうまく行かない場合もあり、その場合はruleの中に入れます。


左矢前のブログ 次のブログ右矢

Pongの開発 (3)

posted by sakurai on November 8, 2023 #692

ブロック図

以下にPongのデモ画面が動作するステートマシンを組み込んだブロック図を示します。

図%%.1
図692.1 Pongブロック図

これらのモジュールのうち、クロック系、デュアルポートRAMを含むグラフィクス系、1チャネルのサウンド系はほぼ流用です。新規設計はGameFSMのみであり、GameFSMとSoundFSMを連結するコマンドバッファもそのまま流用しています。 

完成画面

システムが動作している画面を示します。

図%%.2
図692.2 Pong動作画面

方向制御

パドルの縦位置はボールと同じにしてあるため、必ずボールは打ち返します。パドルでの反射は表692.1のとおり。

表692.1 2種類の乱数とボールの方向
乱数1 bcount 乱数2 dy
0 0 (45°) 0 (dx=1に対して)+1
1 (dx=1に対して)-1
1 3 (18.4°) 0 (dx=3に対して)+1
1 (dx=3に対して)-1

乱数1で傾きの逆数であるbcountを0または3とします。乱数2で方向がプラス+1かマイナスかを決定します。合わせると、bcount=0は右方向の場合速度ベクトルが(+1, +1)または(+1, -1)です。bcount=3の場合速度ベクトルが(+1, +1/3)または(+1, -1/3)です。

オリジナルゲームにはまっすぐ反射する反射もあったのですが、まっすぐ反射してもあまり面白くないので、カットしました。

上下の壁にボールが衝突するとy方向の速度dyの符号を反転させます。一方、左右のパドルに衝突するとx方向の速度であるdxの符号を反転させ、かつ上記の表により方向をランダムに変化させます。


左矢前のブログ 次のブログ右矢

Pongの開発 (2)

posted by sakurai on November 7, 2023 #691

サウンドROMデータの作成

以下のコマンドにより、Vivadoの読めるCOEファイルを作成します。

echo 'memory_initialization_radix=16;' > srom.coe
echo -n 'memory_initialization_vector=' >> srom.coe
cat s?o.wav | \od -An -t x1 -v >> srom.coe
echo ';' >> srom.coe

サウンドステートマシン

サウンドステートマシンは以前作成したものを流用します。Space Invadersの開発の際には4多重音のため、4個のサウンドステートマシンを使用しましたが、Pongは単純なので1個で十分です。そのため自分と他人のサウンドコードを見分ける必要がないため、キューへの書き込みを示す!emptyで起動します。またミキサーも無くなるため、従来後段のミキサーに入れていた符号拡張と桁調整を本モジュールに組み込みました。

以下にソースコードを示します。コメントは一部ChatGPTにより作成してもらいました。

// 波形ファイルを読み込み、オーディオDACにサウンドデータを出力するFSMの定義

import StmtFSM.*;  // FSMを生成するためのユーティリティモジュールのインポート

// サウンドイベントと無音を表すマクロ定義
`define SOUND1    1    // 発射音
`define SOUND2    2    // パドルとの衝突音
`define SOUND3    3    // 壁との衝突音
`define SOUND4    4    // アウトの際の音
`define NULL      'h80 // 無音を表す値(8ビットPCMで中間値)

// 必要な型定義
typedef UInt#(13) Addr_t;  // メモリアドレス用の13ビット符号なし整数
typedef UInt#(8) Data_t;   // 8ビットデータ用の符号なし整数
typedef Bit#(16) Sound_t;  // 16ビット符号付PCMサウンドデータ
typedef Bit#(3) Code_t;    // サウンドコード

// FSMのインターフェース定義。外部からアクセスするためのメソッドが定義されています。
interface FSM_ifc;
   method Action sound(Code_t code);              // 音声コードを示す入力メソッド
   method Action rom_data(Data_t indata);         // ROMからのデータ入力メソッド
   method Action sync(Bool lrclk);               // 同期信号を処理するための入力メソッド
   method Action empty(Bool flag);               // FIFOが空を表す入力メソッド
   method Addr_t rom_address();                  // 現在のROMアドレスの出力メソッド
   method Sound_t sdout();                        // 音声出力データの出力メソッド
   method Bool soundon();                         // 音声が再生中かどうかを示す出力メソッド
   method Bool fifo_ren();                        // FIFOの読み出し要求の出力メソッド
endinterface

(* synthesize,always_ready,always_enabled *)
module mkSoundFSM(FSM_ifc);

// 内部ワイヤとレジスタの定義
   
Wire#(Code_t) code <- mkWire, // コードを格納するワイヤと現在のコードを保持するレジスタ
              current <- mkRegU;
Wire#(Bool) lrclk <- mkWire;  // 左右のクロック同期用のワイヤ
Reg#(Data_t) romdata <- mkRegU; // ROMから読み込まれたデータを保持するレジスタ
Reg#(Data_t) dout <- mkReg(`NULL); // データ出力用のレジスタ(初期値は無音)
Reg#(UInt#(32)) workd <- mkRegU;  // 32ビット作業用データレジスタ
Reg#(UInt#(13)) dcount <- mkRegU; // 再生カウント用の13ビットレジスタ
Reg#(Addr_t) worka <- mkRegU, // アドレス計算用の作業用アドレスレジスタ
                         romaddr <- mkRegU, // ROMのアドレスレジスタ
                         addr <- mkRegU; // 出力用アドレスレジスタ
Reg#(UInt#(8)) ii <- mkReg(0); // ループカウンタ用の8ビットレジスタ
Reg#(Bool) son <- mkReg(False), // サウンド再生中フラグ用のレジスタ
                     sonEarly <- mkReg(False), // 早期サウンド開始フラグ用のレジスタ
                     ren <- mkReg(False),  // FIFO読み込み要求フラグ用のレジスタ
                     emptyf <- mkReg(True); // FIFOが空かどうかを示すフラグ用のレジスタ

   // subfunctions
   //   READ MEM  サブ関数:メモリからの読み出し
   //     input:  worka
   //     output: romdata;
   //
   function Stmt readmem;
      return (seq
         addr <= worka;
         delay(2);
      endseq);
   endfunction

   //   READ COUNT    サブ関数:カウント読み出し
   //     input:  romaddr
   //     output: (romaddr,...,romaddr+3) => dcount;
   //             romaddr + 4 => romaddr;
   //
   function Stmt readcount;
      return (seq
         workd <= 0;
         for (ii <= 0; ii <= 3; ii <= ii + 1) seq
            worka <= romaddr + extend(3-ii);
            readmem;
            if (ii == 3) dcount <= truncate(workd<<8) | extend(romdata);
            else workd <= workd<<8 | extend(romdata);
         endseq
         romaddr <= romaddr + 4;
      endseq);
   endfunction
      
   //  Mainloop    メインループの定義
   //
   Stmt main = seq
      while(True) seq
         action
            dout <= `NULL;
            sonEarly <= False;
            son <= False;
            ren <= False;
         endaction
         await(!emptyf);
         action
            ren <= True; // consume 1 entry of Q
            current <= code;
         endaction
         await(emptyf);
         ren <= False;
         // Sync to LRCLK 
         //
         await(lrclk);
         await(!lrclk);
         delay(4);

         // Format decoding
         //
         action    
            case (current)
              `SOUND1:  romaddr <=   0 + 16;
              `SOUND2:  romaddr <=  1610 + 16;
              `SOUND3:  romaddr <=  (1610 + 900) + 16;
              `SOUND4:  romaddr <=  (1610 + 900 + 872) +16;
            endcase
         endaction
         readcount;
         romaddr <= romaddr + extend(dcount) + 4;
      
         readcount;
         romaddr <= romaddr - 1;

         // play loop
         while (dcount != 0) seq
            // Play 0
            if (sonEarly == False) seq
            // 1cycle目
               readmem;
               action
                  sonEarly <= True;
                  son <= False;
                  dout <= `NULL;
               endaction

            endseq else seq
            // 2cycle目以降
               readmem;
               action
                  son <= True;
                  dout <= romdata;
               endaction
            endseq // if

            delay(11);
            action
               romaddr <= romaddr + 1;
               worka <= romaddr + 1;
               dcount <= dcount - 1;
            endaction
         endseq // while(!終了条件)
      endseq // while(True)
   endseq; // Stmt

   mkAutoFSM(main);     // FSMを生成し実行
   
   method Action sound(Code_t incode);
      code <= incode;
   endmethod
   method Action rom_data(Data_t indata);
      romdata <= indata;
   endmethod
   method Addr_t rom_address();
      return addr;
   endmethod
   method Sound_t sdout(); 
      let bdout = pack(dout);     // 現在のオーディオデータ(dout)をパックし、16ビットのデータ(bdout)に変換します。
      let s =  ~bdout[7]; // 8ビット目(MSB)を反転させてサインビット(s)を生成します。
      return {{s,s},bdout[6:0],{7'h0}}; // オーディオデータをsignedに変換します。
   endmethod
   method Bool soundon();
      return son;
   endmethod
   method Action sync(Bool inlrclk);
      lrclk <= inlrclk;
   endmethod
   method Bool fifo_ren();
      return ren;
   endmethod
   method Action empty(Bool flag);
      emptyf <= flag;
   endmethod

endmodule: mkSoundFSM

左矢前のブログ 次のブログ右矢

Pongの開発

posted by sakurai on November 6, 2023 #690

Pong Game

強化学習のトライアルとして、ビデオゲームを題材に取り上げます。本来はインベーダーゲームを対象としたいのですが複雑であるため、比較的単純なPongとします。

Youtubeで動作画像を探すとこのような動画が見つかりました。

図%%.1
図690.1 CmodA7toPMODボード回路図

これを参考にしつつ、bsvによりプログラミングを行います。

Sound

強化学習本来の目的ではサウンドは不要ですが、ゲームとしての完成度のためにサウンドも実装します。サウンドコントローラであるSoundFSMやその周りの回路は開発済みなので、サウンド実装の工数はサウンド収集加工以外にはほとんどかかりません。そこでサウンドの収集から始めます。

サウンドフォーマット等の参考にする過去記事はこれです。

まずWindows+Gによりゲームバーを呼び出し上記動画の動画をキャプチャします。次にffmpegによりwaveに変換します。変換コマンドは次のとおりです。audacityはmp4を読み込めないため、ffmpegを用います。

$ ffmpeg -i input.mp4 output.wav

これをaudacityにより編集し以下の4つのサウンドを取得します。

表690.1 4種のサウンド
コード 種類
1 発射音
2 パドル
3
4 アウト

ffmpegを用いるとINFO等の余分な情報が削除できずハードウエアが読み込めません。よってaudacityでサウンドデータを開き、

  • 音量をノーマライズ。イフェクト⇒音量⇒ノーマライズとします。
  • メタデータを全て削除
  • 形式はwav (microsoft)
  • チャンネルはモノラル
  • サンプリングは11025 Hz
  • エンコーディングはUnsigned 8-bit PCM

としてエクスポートします。

ここで各ファイルサイズを見ると、

$ ls -l s?o.wav
-rwxrwx--- 1 root vboxsf  1610 10月 29 21:10 s1o.wav
-rwxrwx--- 1 root vboxsf   900 10月 29 21:10 s2o.wav
-rwxrwx--- 1 root vboxsf   872 10月 29 21:13 s3o.wav
-rwxrwx--- 1 root vboxsf  4388 10月 29 21:11 s4o.wav

以上からROMの構成表を作成すれば、

表690.2 ROM構成表
Code Sound Start Size [bytes] Entry=Start+16
1 発射音 0 1,610 0+16
2 パドル 1,610 900 1,610+16
3 1,610+900 872 (1,610+900)+16
4 アウト 1,610+900+872 4,388 (1,610+900+872)+16
合計 [bytes] (16KB ROM使用率) 7,770 (95%)

となります。各サウンドのエントリは表の黄色で示したアドレスであり、これを次のようにbsvでFSM内に記述します(次ページにFSMの全リストを掲載)。
     // Format decoding
     //
     action
        case (current)
           `SOUND1:  romaddr <=   0 + 16;
           `SOUND2:  romaddr <=  1610 + 16;
           `SOUND3:  romaddr <=  (1610 + 900) + 16;
           `SOUND4:  romaddr <=  (1610 + 900 + 872) +16;
        endcase
     endaction

左矢前のブログ 次のブログ右矢

Cmod A7の利用 (3)

posted by sakurai on October 26, 2023 #686

完成した回路図を図686.1に、レイアウト図を686.2に示します。ボードは仕様的に2層、80cm^2未満であるため、Eagleの無料版で設計することができました。

図%%.1
図686.1 CmodA7toPMODボード回路図

図%%.1
図686.2 CmodA7toPMODボードガーバー図

基板業者JLCPCBにおいて基板製造及び部品実装(PCBA)をオーダーしようと思いました。ところがSMT部品でないとアセンブリできないらしく、今回SMT部品が無いことから基板のみのサービスを利用しました。

表686.1 JLCPCB費用まとめ
内容 費用[USD]
基板製造費10枚 5.00
配送費(OCS) 1.98
合計 6.98

という結果でした。


左矢前のブログ 次のブログ右矢

Cmod A7の利用 (2)

posted by sakurai on October 25, 2023 #685

Eagleライブラリの作成

同様にDC Jackを登録します。以下にその基盤図及び実体図を示します。基盤穴をグレーで塗っています。

図%%.1
図685.1 Cmod A7ボード

EAGLEでこれを登録するには基盤図をfootprintに入力する必要があり、ライブラリを次のように作成します。

(1) symbolの編集画面において、

  • 部品外形をLayer=94 Symbolsで記述。
  • 適宜論理ピンを設定。
  • textでLayer=95 Namesとして">NAME"を追加。
  • textでLayer=96 Valuesとして">VALUE"を追加。

(2) footprintの編集画面において、

  • textでLayer=25 tNamesとして">NAME"を追加。
  • textでLayer=27 tValuesとして">VALUE"を追加。
  • Layer=20 Dimensionとする。
  • Widthを0とする。
  • Gridをmmにする。
  • ラインコマンドをクリックする。
  • 入力窓に、"(x, y)"と点を打つ。

以下"(x, y)"の繰り返しで図形を描きます。"20 Dimension"は外形線ですが、外形線の囲む内部に外形線があれば、囲まれた領域が穴になるという仕様です。ただし、上図は背面から見た図なので、上面から見た図はその裏返しとなります。

さらに、表面から背面に貫通する端子を背面で+5Vと接続させるため、背面のソルダーレジストを禁止とします。そのためには

  • 禁止したい領域をLayer=30 bStopとして矩形で囲む。

(3) deviceの編集画面において、

  • Edit ⇒ Addで作成したsymbolの読み込み
  • Edit ⇒ Packageで作成したfootprintの読み込み
  • 論理ピンと物理ピン(pad)の接続
  • prefixをクリックしてJと入力 (J1, J2, としたい場合)

特にここで挙げたsymbol画面とfootprint画面からのdevice画面への読み込みが直感的ではないので注意してください。


左矢前のブログ 次のブログ右矢

Cmod A7の利用

posted by sakurai on October 24, 2023 #684

Cmod A7

Cmod A7とはDigilentから販売されているArtix 7シリーズのFPGAの評価ボードで、その特長は小さくて安いことにあります。最近の世界的なインフレにより各種FPGAボードが値上がりしている中で、USD 99と最安の部類に入ります。

図%%.1
図684.1 Cmod A7ボード

小さくて安いFPGAボードですが、Arty A7-35ボードと同じFPGAを採用しているため、Space Invadersが動作するはずです。

CmodA7toPMOD

しかしながらArty A7ボードがPMODインタフェースを4個搭載しておりそのままPMOD-VGAやPMOD-Audioボードを接続できるのに対し、このボードはPMODが1個しかないため、PMODインタフェースを設計する必要があります。ただし、Ultra96と異なり端子電圧が3.3VであるためPMODと直接インタフェースでき、レベル変換ICが不要です。

そのためには変換ボード上に

  • Cmod A7ボード
  • PMODピンソケットコネクタ x 4
  • ACアダプタコネクタ(MJ-179PH)
  • 5V-3.3V電圧降下コンバータ(NJU7223F33)

等の部品を搭載する必要があります。

Eagleライブラリの作成

EAGLEでこれらを使用するにはシンボル図や基盤図をライブラリに登録する必要があります。例として電圧降下コンバータNJU7223F33の外形図を図684.2に示します。

図%%.2
図684.2 NJU7223F33外形図

このシンボルとフットプリントを作成した図を以下に示します。

図%%.3
図684.3 NJU7223F33 symbol図

図%%.4
図684.4 NJU7223F33 footprint図

左矢前のブログ 次のブログ右矢

Ultra96toPMODの追加製造費用

posted by sakurai on May 29, 2023 #608

Ulta96toPMOD基板製造再見積り

JLCPCBがPCBA(PCB製造及び部品実装)のセールをやっているようなので, 以前掲載したUltra96toPMODボードのオーダー費を再度計算してみました。

前回と同様パープルとグリーン基板を10枚製造し、さらにICを2個/枚×10枚実装した場合の費用の内訳を示します。前回パープル基板はEconomyの有利な価格で基板が製造できず、Standardとなっていました。今回はパープルもグリーンと同じEconomy扱いに変更されています。

表608.1 Ultra96toPMOD Jlcpcbの費用構成
10枚製造時費用内訳 基板色[USD]
グリーン パープル
PCB Special Offer Price 5.00
Components(TXS0108EPWR --- 20個) 9.39
Extended Components 2.96
SMT Assembly 0.65
Setup fee 7.88
Stencil 1.48
合計 27.36
送料(OCS Express:6~8日) 11.68
総計 39.04

前回と比較してみると、部品(レベコンIC)代が若干安くなり、セットアップフィー、SMTアセンブリ、ステンシルがほんの少し安くなりました。一方、送料が上がりクーポンが使えないので、40.2%の値上げ(パープルは28.6%の値下げ)という結果になりました。

今回、再度BokTech及びSeeedFusion PCBの見積もりを取りましたが、それぞれこの2倍、4倍ほどの費用となりました。JLCPCBの安さが光ります。

PMODピンソケットコネクタの再オーダー

PMOD仕様の12ピンソケットの型格はSamtec製のSSW-106-02-T-D-RAです。

図%%.1
図608.1 SSW-106-02-T-D-RA

以前オーダーしたときはMouser から購入しました。この時は単価が141円と安く送料が無料なこともあり、総額が安かったのですが、今回見積もったら単価が258円とかなり値上がりしていたため、Arrowから購入しました。

再度調べたところ、以下のように最安はChip 1stopでした。単価的にはArrowと同じですが、送料が無料となることから、今後はChip 1stopで購入しようと思います。

  • Chip1stop 単価156.2 x 40(送料650)= 6,899円 (税込み) 最安
  • Arrow 単価156.2 x 40(送料3,080)= 9,330円 (税込み)
  • RSオンライン 単価254.9x 40 (送料無料) =10,199円 (税込み)
  • Mouser 単価263.6 x 40 (送料無料) =10,542円 (税込み)

図%%.2
図608.2 送付部品(SSW-106-02-T-D-RA)

本ピンソケット互換品でA2541HWR-2x6Pという製品があるようです。


左矢前のブログ 次のブログ右矢

Ultra96toPMODのBOM

posted by sakurai on May 26, 2023 #607

Ultra96toPMODのBOM

弊社作成のUltra96toPMODボードですが、BoMを掲載していなかったので、掲載します。

表607.1 Ultra96toPMODボードBoM
Degignator Value Qty MPN
C1, C2, C4, ..., C11 0.1u 10 Multilayer Ceramic Capacitor
C3 10u 1 47 µF 50 V Aluminum Electrolytic Capacitors Radial
J1, ..., J4 SSW-106-02-T-D-RA 4 SSW-106-02-T-D-RA
J100 MTMM-120-03-T-D-155 1 MTMM-120-03-T-D-155
LED1 TLLR4400 1 Red LED 3mm Through Hole
R1 470 1 Axial Carbon Film Resistor
R2, ..., R4, R6 3.3k 4 Axial Carbon Film Resistor
R5 10k 1 Axial Carbon Film Resistor
SW1 COUNT 1 4bit DIP SW
SW2 RESET 1 Tactile Push SW
TP1, ..., TP10 Test Pin 10 Test Pin
U1, U3 TXS0108EPWR-TSSOP20 2 Level Converter IC (SMD)
U2 DC DC Converter IC 1 PQ3RD23

Online-shopの開設

併せてオンラインショップを開設しました。

図%%.1
図607.1 オンラインショップ画面

左矢前のブログ 次のブログ右矢


ページ: