eisuke::log()

ゆる〜く備忘録を書いたりするところ

Zynq MPSoCの複数DPUコアで並列に画像認識処理する

この備忘録の概要 Zynq MPSoCの中でもハードウェア容量の大きなFPGAでは、DPU (Deep-Learning Processor Unit)の最大規模のアーキテクチャであるDPUCZDX8GのB4096を複数実装できる。今回は、DPUCZDX8G (B4096)を2コア用いて、画像認識処理を2並列で処理することを目指す。本備忘録では、事前学習済みのDenseBox(顔検出)とResNet-50(クラス分類)モデルを2並列で処理してみることにした。Vitis AI Libraryからの使い方はそれぞれ、UG1354 vitis-ai-FaceDetectとUG1354 vitis-ai-Classificationを参照すること。 動作確認済み環境 Zynq UltraScale+ MPSoC カスタムボードでの動作を確認した。詳細は以下の通りである。 Device Part: xczu19eg-ffvc1760-2-i OS: Petalinux 2022.2 CPU: Cortex-A53 Petalinux SDK: 2022.2 Vitis-AI: 3.0 DPU: DPUCZDX8G v4.1 (B4096) x 2コア カスタムボード向けへのDPU構築にはPG338 DPU-TRDのVitisフローを用いた。構築の解説は割愛するが、Vitisフローが完了すると、以下の画像のようにVivadoのブロックデザインや使用したハードウェア量を見ることができる。具体的な構築方法は、Zynq UltraScale+ MPSoC DPU TRD V4.1 Vitis 2022.2を追っていくと良い。また、Vitisフローで登場するDPUの構成を変更できる設定ファイルdpu_conf.vhは、デフォルトのまま変更せずに構築した。 一番使用率の高いDSPでも27%の空きがあるので、B1024のようなハードウェア使用率を抑えたアーキテクチャであれば、実装可能かもしれない。 と思ったのだが、PG338 featuresには、 Software and IP core support for up to a maximum of four homogeneous DPU instances in a single AMD Xilinx® SoC. ...

September 8, 2023 · Eisuke Okazaki

AlveoとCorundumで100GbE NIC作成

NICのオープンソースプロジェクトCorundumを使用してAlveo U200とU50をNIC化し、Alveo間で通信ができたのでその備忘録です。 この記事のゴール 以下の画像のようにAlveo U200とAlveo U50をNIC化したもの使って通信する。 環境 基本的に前に投稿した記事Alveo U200とU50のセットアップのセットアップ環境を用いた。ただし、Vivadoは別マシンにインストールしているものを使ったためバージョンがズレている(2021.1でなく2022.1)。 Corundum: 記事執筆時点での最新コミット(c708bc4) AlveoホストマシンOS: Ubuntu 20.04 Vivado: 2022.1 FPGA: Alveo U200/U50 DACケーブル: 1m (3ft) Cisco QSFP-100G-CU1M互換 100G QSFP28パッシブダイレクトアタッチ銅製Twinaxケーブル(DAC) NIC化の手順 (U200で説明) Corundumでは、10G/25G/100GのEthernetに対応している。今回は、U200とU50を100GbEへNIC化した。以下はCorundumのドキュメントのGetting Startedを参考に進めていったときの手順である。また、以下の説明では、U200を対象とするが、同様の手順でU50もNIC化できる。 1. UltraScale+ Integrated 100G Ethernet Subsystemのライセンス取得 まず、XilinxのUltraScale+ Integrated 100G Ethernet Subsystemから100G Ethernet Subsystemのライセンスを取得する(無償)。また、ライセンスファイルが取得できたら、Vivadoのライセンスマネージャで読み込む。 読み込むと以下の画像のようにcmac_usplusという名前で追加された。 2. Corundumのダウンロード GitHubからCloneする $ git clone https://github.com/corundum/corundum.git 3. Vivadoのセットアップ これはいつもどおりVivadoのsettings64.shを実行する。 ...

February 21, 2023 · Eisuke Okazaki

高位合成ベースのIPコアを最大256個含むブロックデザインを自動で作る(ネタ)

2022年の5月くらいに作ってたTclスクリプトの紹介 実用的な回路を256個も含んで並列処理、というのはハードウェアリソースの面で現実的ではなさそうなのでネタっぽさはあるが、複数のIPコアを含むブロックデザインを作る際に、作成したTclスクリプトで作業効率が上がった。 「IPコア」という表記をする場合は高位合成ベースのIPコアを指す。 背景 VivadoのブロックデザインにはIPコア同士を接続できる機能Run Connection Automationがある。この機能を使って、Zynq SoC向けにVitis HLSで作成したIPコアを複数個使ってブロックデザインを作成したいが、AXI4-Liteインターフェイスを含むIPコアを64個より多く含んで自動接続することができない。その原因は、AXI Interconnectのマスタ側は最大64接続?であるが、その数を超えたIPコアがあっても新たにAXI Interconnectを追加してくれないためである。実際に128個の加算回路IPコアを追加しRun Connection Automationした際のエラーを画像に示す。65個目の加算回路IPコアの接続でエラーが出ている。 また、Zynq SoC向けにAXI4-StreamインターフェイスのIPコアをAXI DMA IPを用いてデータ入出力することがしばしば発生する。IPコアを複数個追加しAXI DMA IPと1対1に接続するとき、追加した数だけAXI DMA IPの設定(チャネルの設定)する必要があり、手間である(AXI Interconnectの件よりも、これを解決するのが本命だった)。 何をしたか UG835を参考に、Zynq SoC向けの高位合成ベースのIPコアを最大256個含むブロックデザインを自動で追加・接続のできるTclスクリプトを作成した。スクリプトは vivado-auto-bd-playgroud - GitHub にてMITライセンスで公開している。 どんなブロックデザインが生成できるか 同一のIPコアを複数個 作成したスクリプトを使うことで画像のようなブロックデザインを自動で作成することができる。以下の画像は入出力がAXI4-Liteの加算回路のIPコアを自動で256個追加・接続したときのブロックデザインである(画像では、もやは何がなんだかわからない)。 異種のIPコアを複数個 また、複数種類のIPコアを複数個、追加・接続することもできる。以下の画像は、入出力にAXI4-Streamを使うIPコアを4つ、入出力にAXI4-Liteを使うIPコアを8個追加した例である。AXI4-Streamの場合はDMA回路の設定を含めて自動で行われる。 スクリプトの使い方 後述の環境と制限をよく読み、Vitis HLSでC Synthesis、export RTL vivado_block_design.tclを必要なパラメータと一緒にvivadoのコマンドライン引数に渡す vivado -nolog -nojournal -mode batch \ -source ./vivado_block_design.tcl \ -tclargs -project_name vivado \ -device_part <デバイスパーツ> \ -ips_directory <IPコア(.zip)のディレクトリ> \ -start_gui 1 \ -auto_connect 1 \ -write_bitstream 0 \ -ip <IPコア1のトップ関数名(IPコア名)> <その個数> \ -ip <IPコア2のトップ関数名(IPコア名)> <その個数> \ -ip <IPコア3のトップ関数名(IPコア名)> <その個数> ... スクリプトの簡単な説明 簡単にスクリプトの特徴を挙げる ...

January 11, 2023 · Eisuke Okazaki

Alveo U200とU50のセットアップ

2022年最終日!今年はARMプロセッサと一体のFPGAボード(SoC FPGA)を中心に使っていたが、卒業論文や学会発表が終わり次第、Alveoカードを中心に使うことになりそうなので開発環境をセットアップした。そのときのメモを残しておく。基本的にUG1301やUG1370を参考にセットアップした。 セットアップ環境 Xilinx Alveoはデータセンター向けのFPGAで、PCI Express経由でホストマシン(x86)と通信することができる。今回はAlveoのU200とU50のセットアップをした。U200もU50も同じような手順でセットアップすることができる。セットアップアップするホストマシンの環境は以下の通りである。 ホストマシン #1 CPU: 12th Gen Intel(R) Core(TM) i5-12400 OS: 20.04.5 LTS (Focal Fossa) Vitis 2021.1 PCI Expressスロット ← Alveo U200を装着 ホストマシン #2 CPU: 12th Gen Intel(R) Core(TM) i5-12400 OS: 20.04.5 LTS (Focal Fossa) Vitis 2021.1 PCI Expressスロット ← Alveo U50を装着 Vitisのインストール Vitis 2021.1を各ホストマシンにインストールした。インストール方法はいつもどおりなので割愛する。 ...

December 31, 2022 · Eisuke Okazaki

2022年の振り返り

いつの間にか学部4年生になっていて、今取り組んでいる卒業研究の単位が認定されれば学士号取得です(あっという間の4年間)。今年は全然記事かけませんでしたが、たまには書こうということで、今年買ってよかったものと、学生生活に分けて振り返りました。 今年買ってよかったもの 生活の木 アロモアミニ ポータブル ウッド あまりにも睡眠の質がひどく悩まされていたので、池袋の「生活の木」でアロマディフューザーを買ってみたところ最高でした。私は、ラベンダーばかり買っています。ホットアイマスクと併用して寝ると、なお良いです。睡眠以外にも作業中に使用するとリフレッシュでき、卒業研究の助けになりました。 書籍 今年はあまり書籍は買っていませんが、2冊ほど… カンデル神経科学 amazon.co.jp 情報系の学部に所属しているので脳神経のことは素人ですが、その素人にも理解しやすいように図解がかなり丁寧です。定価¥14,000ですが奮発して買いました。かなりオススメ。今年の11月に原著第6版の邦訳である第2版が出ていることに驚きました。今から買うんだったら第2版がよさそう。 詳説 イーサネット 第2版 amazon.co.jp これは最近買った本です。学部の通信関係の授業では、せいぜいアプリケーション層からネットワーク層のように高レイヤを中心とした講義でしたが、最近になって通信分野の低レイヤの知識が必要となったので買いました。100GbEまでしっかり解説されている(400GbEの章も一応ある…)。 ...

December 26, 2022 · Eisuke Okazaki

I/Oの多重化(epoll API)を使いやすくしたい

はじめに epoll APIは有能だなあと思う今日この頃ですが、epoll_wait()してイベントを取り出す処理をべた書きするとコードが汚くなりがちです。少し前の記事ですが、epoll APIを使ったチャットサーバでは、epoll_wait()後のfor(){}が長くなり、読みにくいものになっていました。そこでイベント監視を少し一般化して裏でepollを呼ぶような仕組みを作りたくなりました。話がややこしいので結論だけ書いておくと、 epoll_data_t dataのユーザ定義のポインタに関数ポインタが混じった構造体を代入すると簡潔にアプリケーション側が書けそう インタレストリストへ追加/削除するような基本的な関数を作ってアプリケーション側から呼ぶと良さそう だねっていう話です。 後述するこれらの良くないであろう点でも書いていますが、あまりいいものではないです。 この記事内で使うコードの一式をGitHubに置いておきます。ヘッダファイルはこの記事内ですべてを載せてるわけではなので使ってみたい場合は以下のリポジトリからどうぞ。 いい感じにするための方針 方向性 いい感じにするには、struct epoll_eventのメンバであるepoll_data_t dataカギになりそうです。epoll_data_tは共用体なので実際に(同時に)使えるメンバは1つだけです。前回のチャットサーバと同様に、void *ptrを使うことを考えていきます。void *ですので任意の型で代入することができます。 typedef union epoll_data { void *ptr; // ユーザ定義のデータへのポインタ int fd; // ファイルディスクリプタ uint32_t u32; // 32ビット整数 uint64_t u64; // 64ビット整数 } epoll_data_t; 前回のチャットサーバで、for(){}が長くなっている原因はIO可能になって処理する内容を関数として作られていないからというのがありますが、何よりもそれらの処理が連結リストのポインタの操作が混じっていることが原因だと思います。そのため、ポインタ操作など面倒くさいものはできるだけ裏でやるようにすると良さそうです。 void*ptrに入れる構造体 void *ptrに代入するものを以下のstruct io_eventのようにしました。 /** * epollのstruct epoll_eventのユーザ定義のフィールドで使いたい構造体 */ struct io_event { //! 監視対象のファイルディスクリプタ int fd; //! そのイベントがIO可能になった時に呼び出される関数(ポインタ) void (*handler)(struct io_event*); //! 呼び出される関数内で使いたい変数など(任意) void* arg; //! 前のio_event(連結リスト) struct io_event* prev; //! 後のio_event(連結リスト) struct io_event* next; //! 最後に発生したイベント日時 struct tm* timestamp; //! 発生したイベントの種類 observe_type type; }; int fd struct epoll_eventのメンバであるepoll_data_t dataは共用体なので、void *ptrを使うとファイルディスクリプタを代入する変数int fdは使えなくなり、そのままだと何かと不便なので以下のstruct io_eventにファイルディスクリプタの変数を作りました。 void (*handler)(struct io_event*) IO可能となった時に呼び出される関数を関数ポインタvoid (*handler)(struct io_event*);としました。その引数をstruct io_event*とし、そのIO可能となったファイルディスクリプタのイベント情報を渡せるようにしました。 void* arg void (*handler)(struct io_event*);内で使いたい変数などを入れておくための変数。例えば、クライアント-サーバモデルを考えたときにファイルディスクリプタにユーザ名結び付けた場合は、この変数にユーザ名を入れておくことでユーザ名の登録後の発言で、発言内容と発言元のユーザ名が取れるようになります。 struct io_event* prev、struct io_event* next; 連結(双方向)リストのためのポインタ、説明不要 struct tm* timestamp IOが可能となった時点のタイムスタンプ observe_type type 監視するイベントの種類(以下の列挙体) /** * @enum observe_type * 監視するイベントの種類が定義された列挙体 */ typedef enum { //! EPOLLINに相当 OBS_IN = 1u, //! EPOLLOUTに相当 OBS_OUT = 4u, //! EPOLLONESHOTに相当 OBS_ONESHOT = 1u << 30, } observe_type; /** * event_io全体で使われる変数一式が定義された構造体 */ struct event_config { //! epollのファイルディスクリプタ int epfd; //! struct io_eventの先頭ポインタ struct io_event* head; //! struct io_eventの末尾ポインタ struct io_event* tail; }; これらを使って基本的なイベント監視の登録/解除の操作を作っていきます。 ...

July 3, 2021 · Eisuke Okazaki

I/O多重化でチャットサーバを作る

チャットサーバの簡単な説明 1プロセス1スレッドで動作するチャットサーバを考えていきます。 echoサーバとほぼ同じです。 複数のクライアントが接続できるサーバを考えます。 クライアントからの1回目のwrite()でユーザ名の登録をします。 ユーザ名の登録以外(2回目以降)のwrite()はサーバはそれを受け取った後に、メッセージを他の全クライアントに対して送信します。 サーバはメッセージを受け取った後に以下のフォーマットに従って編集します。 [メッセージの通し番号 発言元のユーザ名 日時] メッセージ プログラムの実行例(というか最終的に出来上がったものの実行例) ※ 今回はサーバがメインです。クライアント側はtelnetをいくつか開いて、それぞれをクライアントと想定しています。 発言元はログイン時、ログアウト時は"server"とし、それ以外の発言はユーザ名を表示しています。 epoll APIについて I/Oの多重化を扱うにはいくつかの方法があるようですが、select()やpoll()よりも扱いやすそうだったため、今回はepoll()を使用しました。 epoll APIはLinux固有の機能であるためLinux上での実行を想定しています。 int epoll_create(int size); epollインスタンスの作成 インタレストリストを空にする 引数 説明 備考 int size 監視対象のファイルディスクリプタ数 sizeは初期化処理のヒントのためであって、sizeを超えた数のファイルディスクリプタの処理も可能(だから適当な値でいいっぽい?) 戻り値の型 説明 int 成功時にファイルディスクリプタ、エラー時に-1 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev); インタレストリストの変更(追加、編集、削除) 引数 説明 int epfd epoll_create();で作成したfd int op EPOLL_CTL_ADD: epfd上のインタレストリストにfdを追加する EPOLL_CTL_MOD: epfd上のインタレストリストに追加済みのfdを指定されたイベントに変更する EPOLL_CTL_DEL : epfd上のインタレストリストからfdを削除する(監視を外す) int fd 監視対象のファイルディスクリプタ struct epoll_event* ev epollイベント.eventsとユーザデータ.dataが格納されたstruct epoll_event 戻り値の型 説明 int 成功時に0、エラー時に-1 int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout); イベントを待つ ...

June 4, 2021 · Eisuke Okazaki

Mouser Electronicsで電子機器を買ってみた

とある電子機器を買おうと思って、日本の正規代理店を調べていたのですが、最終的に海外のMouser Electronicsにしました(¥2,000ほど安かった)。それよりも配送そのものが少し不安だったのでまとめておきます。Mouser Electronicsでは配送業者をいくつからか選ぶことができたと思いますが、そもそも個人輸入とをしたことがなかったので冒険をせず、デフォルトで設定されていたUPSで届けてもらうことにしました。 Mouser発注 Mouserで発注手続きをしたのが1/26の22時頃。Mouser日本を経由してMouser本社に注文が行くようなので、発注手続き直後は何も起こらず、次の日(1/27)にMouser日本から以下のメールが届きました。 そして、1/28の夜に届いた発送通知がこれです。UPSのトラッキングができるページのURLが添付されていました。 ロケーション 日付 現地時刻 説明 TOKYO, JP 2021/02/02 04:00 PM 配達済み Chuo-Ku, Japan 2021/02/01 09:00 AM お客様の荷物は弊社施設で処理されました / 荷物は配達先都市のUPS施設へ転送されました。 Tokyo, Japan 2021/01/31 10:10 PM 施設を出発 Tokyo, Japan 2021/01/31 08:29 PM 空港上屋スキャン(輸入) Tokyo, Japan 2021/01/31 06:00 PM 荷物は地元エージェントに引き渡され、配達が手配されました。 Tokyo, Japan 2021/01/30 10:51 PM この出荷に関連付けられているすべての荷物が到着するまで留め置かれます。 Tokyo, Japan 2021/01/30 10:15 PM この出荷に関連付けられているすべての荷物が到着するまで留め置かれます。 Tokyo, Japan 2021/01/30 12:58 PM ウエアハウススキャン(在庫スキャン) Tokyo, Japan 2021/01/30 12:57 PM 空港上屋スキャン(輸入) Narita, Japan 2021/01/30 11:30 AM 施設を出発 Narita, Japan 2021/01/30 11:12 AM 施設に到着 Anchorage, AK, United States 2021/01/29 09:24 AM 施設を出発 Anchorage, AK, United States 2021/01/29 07:17 AM 施設に到着 Louisville, KY, United States 2021/01/29 04:32 AM 施設を出発 Louisville, KY, United States 2021/01/28 11:54 PM 施設に到着 DFW Airport, TX, United States 2021/01/28 08:58 PM 施設を出発 DFW Airport, TX, United States 2021/01/28 07:01 PM 発地国センタースキャン United States 2021/01/28 07:24 PM 荷送人がラベルを作成しましたが、UPSはまだ荷物を受領していません。 ヤマト運輸 通関後はヤマト運輸に引き渡されたみたいです。UPSの配送状況がずっと「運送中」だったので気になり、カスタマーサポートに電話してみてようやくわかりました。ほぼ1週間かかったとはいえ、無事に届いてよかったです。 ...

February 2, 2021 · Eisuke Okazaki

Pygameのテキスト入力

授業でPygameを触り、グループワーク時にタイピングゲームを作ったので、その時に面倒だったテキスト入力の備忘録。 Pygame 1.9.5(SDL2.0.0)以降のテキスト入力 Pygame1.9.5以降、新たなイベントが加わり、テキスト入力が比較的楽になったようです(多分)。とはいえ、Pygameの日本語のドキュメントにそれがまだ載ってない上に、公式のテキスト入力のサンプル(この記事の執筆時の最新コミット)も分かりづらかったので、改めて書くことにしました。テキスト入力系で追加されたのは、TEXTINPUTとTEXTEDITINGの2つです。TEXTINPUTは直接入力モード時に受け取るイベント、TEXTEDITINGがIMEを使っている時に受け取るイベントらしいです。また、SDLのバグの関係で、IMEの予測変換は使えないので今回はそれを扱っていません。 プログラム ライブラリ化したものを上げておきました。ご活用ください。 以下がテキストの処理をするロジック(text.py) from typing import List class Text: """ PygameのINPUT、EDITINGイベントで使うクラス カーソル操作や文字列処理に使う """ def __init__(self) -> None: self.text = ["|"] # 入力されたテキストを格納していく変数 self.editing: List[str] = [] # 全角の文字編集中(変換前)の文字を格納するための変数 self.is_editing = False # 編集中文字列の有無(全角入力時に使用) self.cursor_pos = 0 # 文字入力のカーソル(パイプ|)の位置 def __str__(self) -> str: """self.textリストを文字列にして返す""" return "".join(self.text) def edit(self, text: str, editing_cursor_pos: int) -> str: """ edit(編集中)であるときに呼ばれるメソッド 全角かつ漢字変換前の確定していないときに呼ばれる """ if text: # テキストがあるなら self.is_editing = True for x in text: self.editing.append(x) # 編集中の文字列をリストに格納していく self.editing.insert(editing_cursor_pos, "|") # カーソル位置にカーソルを追加 disp = "[" + "".join(self.editing) + "]" else: self.is_editing = False # テキストが空の時はFalse disp = "|" self.editing = [] # 次のeditで使うために空にする # self.cursorを読み飛ばして結合する return ( format(self)[0 : self.cursor_pos] + disp + format(self)[self.cursor_pos + 1 :] ) def input(self, text: str) -> str: """半角文字が打たれたとき、もしくは全角で変換が確定したときに呼ばれるメソッド""" self.is_editing = False # 編集中ではなくなったのでFalseにする for x in text: self.text.insert(self.cursor_pos, x) # カーソル位置にテキストを追加 # 現在のカーソル位置にテキストを追加したので、カーソル位置を後ろにずらす self.cursor_pos += 1 return format(self) def delete_left_of_cursor(self) -> str: """カーソルの左の文字を削除するためのメソッド""" # カーソル位置が0であるとき if self.cursor_pos == 0: return format(self) self.text.pop(self.cursor_pos - 1) # カーソル位置の一個前(左)を消す self.cursor_pos -= 1 # カーソル位置を前にずらす return format(self) def delete_right_of_cursor(self) -> str: """カーソルの右の文字を削除するためのメソッド""" # カーソル位置より後ろに文字がないとき if len(self.text[self.cursor_pos+1:]) == 0: return format(self) self.text.pop(self.cursor_pos + 1) # カーソル位置の一個後(右)を消す return format(self) def enter(self) -> str: """入力文字が確定したときに呼ばれるメソッド""" # カーソルを読み飛ばす entered = ( format(self)[0 : self.cursor_pos] + format(self)[self.cursor_pos + 1 :] ) self.text = ["|"] # 次回の入力で使うためにself.textを空にする self.cursor_pos = 0 # self.text[0] == "|"となる return entered def move_cursor_left(self) -> str: """inputされた文字のカーソル(パイプ|)の位置を左に動かすメソッド""" if self.cursor_pos > 0: # カーソル位置をカーソル位置の前の文字と交換する self.text[self.cursor_pos], self.text[self.cursor_pos - 1] = ( self.text[self.cursor_pos - 1], self.text[self.cursor_pos], ) self.cursor_pos -= 1 # カーソルが1つ前に行ったのでデクリメント return format(self) def move_cursor_right(self) -> str: """inputされた文字のカーソル(パイプ|)の位置を右に動かすメソッド""" if len(self.text) - 1 > self.cursor_pos: # カーソル位置をカーソル位置の後ろの文字と交換する self.text[self.cursor_pos], self.text[self.cursor_pos + 1] = ( self.text[self.cursor_pos + 1], self.text[self.cursor_pos], ) self.cursor_pos += 1 # カーソルが1つ後ろに行ったのでインクリメント return format(self) 次に、プログラム本体(text_input.py)ですが、日本語が扱えるフォントを用意しておく必要があります(Windowsならコピペでも動くかもしれません)。追記をご参照ください。 ...

January 30, 2021 · Eisuke Okazaki