MicroBlazeの組み込み方(セミナ受講して復習してみました)

 

本日、大崎にある新光商事さんへ行き、「MicroBlaze」のミニセミナを受講してきました。

そこで学習したものを、弊社所有のVC707で復習がてら忘れないために行ってみました。

はっきり言って手順をそのまま箇条書きのように書いているので、大量、見づらい、読みにくいですが、かなり細かく書きました。

 

  1. 流れの解説

一通りの流れを以下に示す。

流れ

ツール

アクションと目的

ISE

プロジェクト作成、全体のプロジェクトを作成する

ISE

新規EDKプロジェクトソース追加、MicroBlazeコア部分。EDKが起動する。

EDK

EDK起動後、BSBウィザードで MicroBlaze に関する各種初期設定を行う。これが MicroBlaze部分の開発。

ISE

トップレベル階層の VHDL を自動で作成し、コンパイル。

ISE

SDKの為の HW デザインをエクスポートする。

SDK

SDK Cプロジェクトを作成する。

SDK

Cソースを作成し、elf ファイルを作る。

SDK

ボード上で走らせデバッグする

ISE

MicroBlaze プログラム (elf)を含めて ISE でコンパイルし 最終ビットファイルを作成する。

IMPACT

プログラミングして実行。

 

  1. 作成するもの

ボード上の ボタンを押すと 対応するLED が点灯するというプログラムを MicroBlaze上で動かす。

clip_image001[10]

[23]のボタンで、[22] LED を点灯させる。

 

  1. 手順

    1. ISE: ISEを起動し、プロジェクトを作成する。

ISE 14.2 を起動する。

clip_image002[6]

ダブルクリック

 

clip_image003[6]

ISE 起動

 

clip_image004[6]

 

[File] [New Project]

 

clip_image005[6]

[Name] TestVC707 にした。

[Location]は適宜。

[Next] ボタン

 

clip_image006[6]

Project Setting では、デバイスなどの情報を設定する。

clip_image007[6]

今回は、もともと VC707 という「ボード」がサポートされているので、[Evaluation Development Board] [Virtex-7 VC707 Evaluation Platform] を選択。

[Next]ボタン

 

clip_image008[6]

サマリが表示されるので [Finish] ボタン

 

clip_image009[6]

Hierarchy VC707 のデバイスが表示される。

 

  1. ISE: ISEプロジェクトに、新規 EDK プロジェクトソースを追加する。

clip_image010[6]

[Project]メニュー→[New Source…]

 

clip_image011[6]

[Embedded Processor] を選び、File name には SYSTEM とつけた。

Location はそのまま。Add to project はチェックが入っている状態で

[Next]ボタン

 

clip_image012[6]

サマリが表示されるので [Finish] ボタン

 

clip_image013[6]

すると、しばらく EDK ファイルを作成し、 EDK (Xilinx Platform Studio) が起動する。

  1. EDK: EDK起動後、BSBウィザードで MicroBlaze に関する各種初期設定を行う。

 

clip_image014[6]

BSB Wizard で作るか聞いてくるので [Yes]

 

clip_image015[6]

使うバスタイプを決める。Virtex7 では AXI(アクシーと発音) しか選べない。

[OK] ボタン

 

clip_image016[6]

ボード情報があるかどうかなので、あるので、そのまま[Next]

 

clip_image017[6]

ここでは、プロセッサ、ペリフェラルの設定ができる。

上から、プロセッサの周波数。50/100/150 MHz を選択できる。

とりあえず 100MHz で。

Local Memory Instruction/ Data Cache 8kB で。

ペリフェラル関連は、コンパイル時間を短くするために、

LEDs_8Bits

Push_Buttons_5Bits

以外は Remove

 

[Finish]

 

clip_image018[6]

EDK のプロジェクトを作成中。。。

 

終わるとこんな画面。

clip_image019[6]

この時点で、この SYSTEM UCFが作成されている。

SYSTEM\data" フォルダに "SYSTEM.ucf" ができている。

 

本来、ここでバスにぶら下げる IP を追加したりするが、今回はとりあえずこれで終了処理。

EDK を閉じる。

 

 

 

  1. ISE: トップレベル階層の VHDL を自動で作成。UCF追加してコンパイル。

ISE に戻り、トップレベルの VHDL を作成する。(そのままではコンパイルできないので、ラッパーを作成するということ)

 

clip_image020[6]

Hierarchy で先程作成した SYSTEM (SYSTEM\SYSTEM.xmp) をハイライトする。

clip_image021[6]

Process: SYSTEM で、[Generate Top HDL Source] をダブルクリック。

 

clip_image022[6]

作成できると、Hierarchy には SYSTEM_top.vhd が追加され、先程の SYSTEM はその下に入る。

 

[Project]メニュー->[ADD Source] SYSTEM\data\SYSTEM.ucf を追加。

clip_image023[6]

チェック OK なので、[OK]ボタン

 

clip_image024[6]

Hierarchy に追加された。

 

ここで、SYSTEM.ucf を一部修正する。

この UCF の最後から2行目

clip_image025[6]

"CLK" "CLK_P" にする。

clip_image026[6]

なぜ修正するかは、吐き出された UCF のタイミングのネット名 "CLK" が本来の差動で定義されている "CLK_P" "CLK_N" にあっていないから。

 

全て保存して、Hierarchy SYSTEM_top を選択。

clip_image027[6]

Processes: SYSTEM_top – STRUCTURE [Generate Programming File] をダブルクリック。

 

まつこと数十分

 

clip_image028[6]

結果抜粋

Slice Logic Utilization

Used

Available

Utilization

Number of occupied Slice

750

75,900

1%

Number of RAMB36E1/FIFO 36E1s

2

1,030

1%

Number of BUFG/BUFGCTRLs

2

32

6%

Number of DSP48E1s

3

2,800

1%

Number of MMCME2_ADVs

1

14

7%

 

これで、MicroBlaze を組み込んだ FPGA 全体が完成する。

ただし、プログラムがないので、SDKで作成する。

 

  1. ISE: SDKの為の HW デザインをエクスポートする。

SDK MicroBlaze のプログラムを組むために、 ISE から HW デザインを エクスポートする。

 

clip_image029[6]

Hierarchy SYSTEM_i – SYSTEM (SYSTEM\SYSTEM.xmp) をハイライト。

 

clip_image030[6]

Processes: SYSTEM_i – SYSTEM [Export Hardware Design To SDK with Bitstream" をダブルクリック。

 

成功すると SDK が起動する。

 

  1. SDK: SDK Cプロジェクトを作成する。

起動すると以下のダイアログが表示される。

SDK のワークスペースを指定する。

clip_image031[6]

ISE のプロジェクトフォルダ内の、"SYSTEM\SDK\SDK_Export" を選ぶ。

 

clip_image032[6]

こんなような SDK の開発環境の画面が開く。

 

とりあえず何もないので、Cプロジェクトを作成するウィザードを実行する。

clip_image033[6]

[File]メニュー→[New] [Xilinx Cproject]

 

clip_image034[6]

デフォルトは「hello_world_0」プロジェクトを作る感じなのだが、スケルトンがいくつかあるので紹介。

 

スケルトン

記述

Dhrystone

ベンチマークプログラム

Empty Application

空の C プロジェクト

Hello World

Let’s say ‘Hello World’ in C. らしい。

lwIP Echo Server

lwIP (light-weight IO stack) のデモプログラム!!

Memory Tests

ハードウェア内のメモリ・リージョンのテスト

Peripheral Tests

ハードウェア内のすべてのペリフェラルの為の簡単なテストルーチン。

SREC Bootloader

non volatile memory (不揮発メモリ)から SREC イメージを読み込むシンプルなブートローダ。

Xilkernel POSIX Threads Demo

Xilkernel (ザイリンクスのカーネル?) ベースで複数の POSIX スレッドを動かすデモ。

Zynq FSBL

Zynq デバイスの為の First Stage Bootloader(FSBL)

 

とりえず今回は [Empty Application] を選んで [Next]

 

clip_image035[6]

ここもそのまま [Finish]

 

すると中央に情報が表示される。

clip_image036[6]

ターゲット情報

 

clip_image037[6]

OS情報

 

clip_image038[6]

周辺回路情報

 

clip_image039[6]

ライブラリについて

 

  1. SDK: Cソースを追加する。

やっと、Cソースを追加することができる。

Project Explorer [empty_application_0] のしたの [src] にソースを追加する。

 

clip_image040[6]

[File] [New] [Source File]

 

clip_image041[6]

 

Source Folder [Browse] をクリック

 

clip_image042[6]

src を選んで [OK]

 

Source File には "main.c"

 

Template では [Default C SOurce template]

 

を選んで [Finish]

 

空の Cソースが作成される。

 

clip_image043[6]

このファイルに記述していく。

 

とりあえず、細かい説明は抜きに以下のソースを張り付ける。

 

#include "xparameters.h" // Definition Address Space of Board

#include "xgpio_l.h"

 

int main(void)

{

unsighned int sw_in = 0;

 

while(1)

{

// Get push button bit

sw_in = XGpio_ReadReg(XPAR_PUSH_BUTTONS_5BITS_BASEADDR,1);

// Set LED bit by push button

XGpio_WriteReg(XPAR_LEDS_8BITS_BASEADDR,1,sw_in);

}

}

 

 

セーブすると、勝手にビルドして elf ファイルが作成される。

 

  1. SDK: ボード上で走らせデバッグする

SDK は起動させたまま。

VC707 JTAG (micro USB) ポートと PC USB ケーブルで接続。

VC707 の電源を投入。

 

SDK から、FPGA へプログラムを書き込む。

clip_image044[6]

[Xilinx Tools] メニュー [Program FPGA]

 

clip_image045[6]

下段の [Software Configuration] の部分を変更する。

clip_image046[6]

ドロップダウンで elf ファイルがあるので、選択。

[Program] ボタン。

 

clip_image047[6]

プログラム中。。。(JTAG 経由で デバイスははデフォルトで勝手に探してくれる模様)

 

書き込み終わったら、実行方法を設定。

clip_image048[6]

[Run] メニューの [Run Configurations..]

 

clip_image049[6]

ダイアログが開くので [Xilinx C/C++ ELF] をダブルクリック。

 

clip_image050[6]

empty_application_0_Debug というものが追加され、右のペーンで設定ができる。

今回は特になし。

[RUN] ボタン。

 

これで、実行された。

 

VC707 上の ボタンで 対応したビットのLED が点灯する。

clip_image001[11]

 

デバッグする。

一度、停止させる。停止させるには、Console のタブにある

clip_image051[6]

このボタンで停止。

 

clip_image052[6]

このマークでデバッグモードに入る。

 

clip_image053[6]

[Yes]

 

デバッグモードでは SDK の見た目が少し変わる。

 

clip_image054[6]

 

ソースにブレイクポイントがかけられる。

変数をウォッチしたり、一通りのデバッグ機能はある。

 

ここで、ソースをデバッグしたりして、完成したら次のフェーズへ。

 

  1. ISE: MicroBlaze プログラム (elf) を含めて ISE でコンパイルし 最終ビットファイルを作成する。

SDK でプログラム(elf) のデバッグも終わったら終了させる。

ISE に戻り、

clip_image055[6]

Hierarchy SYSTEM_i をハイライト。

clip_image056[6]

[Project] [Add Source] で、"SYSTEM\SDK\SDK_Export\empty_application_0\Debug" の下にある elf ファイルを選択。

clip_image057[6]

[OK]

 

clip_image058[6]

上がシミュレーションで使うか?下が実装するか?のオプション。

通常、どちらもチェックして[OK]

 

clip_image059[6]

SYSTEM_i の下に elf が追加された。

 

clip_image060[6]

 

Hierarchy SYSTEM_top をハイライト。

 

clip_image061[6]

新しく elf が追加されたので、[Generate Programming File] ? マークになっている。

[[Generate Programming File] をダブルクリックして bit ファイルを作成。

clip_image062[6]

 

  1. Impact でダウンロード

clip_image063[6]

ISE で、[Tool] メニューから [IMPACT] を選択。

clip_image064[6]

IMPACT プロジェクトがないのでなんやかんや。

といあえず[OK]しかないので [OK]

 

clip_image065[6]

Xilinx へ情報提供。1分といことだが、時間が無いのでまたの機会に。[No]で。

 

clip_image066[6]

IMPACT の画面。

 

clip_image067[6]

[Boundary Scan] をダブルクリック。

右のペーンに Right click to Add Device or Initialize TAG chain」と出ているので「右クリック」→ [Initialize chain]

clip_image068[6]

 

clip_image069[6]

コンフィグファイルをアサインしたいので [Yes]

 

clip_image070[6]

System_top.bit を選択し [Open]

 

clip_image071[6]

 

clip_image072[6]

Flash PROM サポートしてるけど、SPI or BPI PROM にアタッチするか?

よく意味がわからないけど、PROM に書き込むか聞いているらしい。

今回は、普通にコンフィグのみなので、No にしておく。

 

clip_image073[6]

Verify チェックが入らない。

[OK]

 

clip_image074[6]

 

[Program] をダブルクリック。

 

clip_image075[6]

 

電源を切ってもう一度。

Baundary Scan -> bit ファイル指定→Program

動作した。

 

 

CSV にクラスタを保存

計測したときのパラメータとその時の結果などを一緒にクラスタに格納して、ファイルに保存したいと思います。

設定を変更したパラメータに対して、対になる形で結果も保存できて、さらにエクセルなどで開けるのであれは結構いいですよね。

xml での保存、読み出しは、結構楽で、「XMLに平坦化」→「XMLファイルに書き込み」などで勝手に解釈してくれます。

でも、xml だと他の人に渡しても後で処理したいときに難しいです。

そこで、わかりやすいところで、CSV形式のファイルで保存できるか試してみました。

関数を探してみる

ということで、LabVIEWヘルプ のキーワード で「CSV」を検索すると、

clip_image001

なぜか MathScript RT モジュールのヘルプが出てきました。

検索の方で「CSV」を検索すると、

clip_image002

それなりに出てきました。

でも、パッと思いつく、ファイルIO パレットにある「スプレッドシートに書き込む」は出てきませんね。

そちらのヘルプを見ると、「デリミタとして , (カンマ)云々..」と書いてあり、CSV という記述はありません。だから検索でも見つけられないのですね。

とりあえず、今回は「スプレッドシートに書き込む」関数を使ってみたいと思います。

まずは、LabVIEW ヘルプの「スプレッドシートに書き込む」関数を読んでおきます。

インスタンス(多態性)なので、データタイプによって倍精度、文字列、整数が選べるようですが動作的には一緒のようです。

clip_image003

基本的には、2Dデータの方を使うようです。

1Dデータを使う場合には、呼び出される毎に行が追加されていきます。

ということは、今回のように次々に追加していく場合には使えるかもしれませんね。

で、サンプルを見てみようと、ヘルプの下の方にある「サンプルを開く」で開いてみました。

clip_image004

開いたサンプルは「EMail with Data VI」

clip_image005

使用されているのは「データ」が「ASCII」の時で「送信」が押された時のようです。

clip_image006

そのままデフオルトで実行するとエラーしますが。

とはいえ、ファイルパスと、波形の1D 配列が書き込めてはいました。デフォルトの「タブ区切り」で。

では実際に作ってみます。

実際に試してみる

現実になるべく則して考えたいと思います。

まず、対になるパラメータのクラスタと結果のクラスタが、1つのクラスタに格納されているとします。

それが、配列で複数あり、さらにクラスタ化されているとします。

clip_image007

このような感じ。

今回、Parameter A, C はある値に固定され、Parameter B のみ変化させた時の結果 Result A,B を保存します。

保存したい CSV形式は以下のようなイメージです。

INDEX

Parameter A

Parameter B

Parameter C

Result A

Result B

0

123

0

456

1

2

1

123

1

456

2

3

2

123

2

456

3

4

:

:

:

:

:

:

まずは、クラスタの中身を配列に変換します。と、「クラスタから配列に変換」というのがあります。

clip_image008

が、単純なクラスタじゃないとダメみたいです。

上記のようなクラスでは接続できませんでした。

clip_image009

ま、一旦自分でクラスタを平たくする必要はありそうですね。

ということで、「名前をバンドル解除」を使って一度ばらします。

clip_image010

ここで、問題が。今はすべて同じ型ですので、まとめて配列にできますが、もし異なる型が混じっている場合にはどうするか?

まず、確実なのは文字列化してしまうことでしょうか。

面倒ですが、必要なものだけでもよいかもしれません。

clip_image011

そしたら、その文字列を連結して 1D の配列にします。

そして、「スプレッドシートに書き込み」の 1D の入力に入れてあげます。

clip_image012

あとは、ファイルパスを指定して、ファイルに追加、デリミタを指定します。

clip_image013

では、先程のテーブルをもう一度確認してみます。

INDEX

Parameter A

Parameter B

Parameter C

Result A

Result B

0

123

0

456

1

2

1

123

1

456

2

3

2

123

2

456

3

4

:

:

:

:

:

:

インデックスも追加したいですよね。

あと、テーブルのヘッダも追加したいです。

インデックスだけまず追加してみます。

配列連結の先頭を伸ばして、For ループのインデックスを文字列化して追加します。

clip_image014

で、続いてファイルの最初だけヘッダを追加するようにしてみます。

べたに書くとすればフラットシーケンスストラクチャですね。

clip_image015

では、クラスタに先程のテーブルの値を入れて、パスを指定して実行してみます。

本来は、何かプログラム実行時にこのクラスタを作ってあげるのですが、テストですので初期値として手入力しておきます。

clip_image016

実行してみます。指定したパスにcsvファイルができています。

clip_image017

では、エクセルで開いてみましょう。

clip_image018

先程の想定していたテーブルはこちら。

INDEX

Parameter A

Parameter B

Parameter C

Result A

Result B

0

123

0

456

1

2

1

123

1

456

2

3

2

123

2

456

3

4

:

:

:

:

:

:

ちゃんと、思った通りにできています。

クラスタを保存するにはクラスタを「タイプ定義制御器」としておいて、

まとめ

独自のクラスタをCSV で保存するには。

①ばらす

②それぞれ文字列化し文字列の1次元配列にする

③「スプレッドシートに書き込む」で「ファイルに追加」を "T"、「デリミタ」 に "," に指定する。

という方法がべたですが良いかなと。

LabVIEW シェア変数を使ったテストの自動化

通信関連の プログラムは普通 送信側 (Tx) と受信側 (Rx) を対で作成することが多いです。

最初は、それぞれ作って、チェックして、繋いでチェックしてといった流れでチェックするのですが、だんだん完成が近づいてくると、設定すべきパラメータが増えてきて一連のチェックが大変になってきます。

そこで、設定と、結果取得をなるべくシンプルに自動化する方法を考えてみます。

LabVIEW では「シェア変数」という便利な変数があります。(テストスタンドという便利な製品もありますが、簡単に応用させるということで。。。)

http://zone.ni.com/devzone/cda/tut/p/id/9023

「シェア変数を使用することで、1つのダイアグラム中の複数ループ間で、またはネットワーク上のVI間でデータを共有することができます。UDP/TCP、キュー、リアルタイムFIFOなどの既存のLabVIEWデータ共有方法と違い、シェア変数は通常、編集時にプロパティダイアログを使用して構成します。そのため、アプリケーションに構成コードを含む必要がありません。」

とのことです。

やりたいことは、

・送信、受信のソフトが別々の PC上で動作する条件で

・パラメータを両方に変更しながら設定、制御したい(制御の自動化)

・結果を自動で取得し、ログを保存したい(結果取得の自動化)

です。

送受信が別PC で動作していても、ネットワークでつながっていれば「シェア変数」を通じて制御、結果取得ができそうです。

まずは、テスト的に簡単な、送受信のプログラムを組んでみます。

送受信のインターフェースも今回はシェア変数でデータを渡すものとします。

送信側 VI は、正弦波配列を1000要素出力します。パラメータは周波数とゲインです。

こんなような感じです。

clip_image001

clip_image002

受信側 VI は、受けた正弦波配列の周波数ビンとゲインを推定します。パラメータは FFTサイズです。

ただし、この推定はいい加減です(笑)

clip_image003

clip_image004

本来であれば、送信受信はアンテナやケーブルでつないで送受信するのですが、今回はデータの授受に「シェア変数」を利用します。

●シェア変数の作成

シェア変数はプロジェクトを作ったあと、「マイコンピュータ」で「右クリック」→「新規」→「変数」で作成します。

clip_image005

すると、ダイアログが出てくるので、シェアしたい変数のプロパティを設定します。

clip_image006

まずは何はなくとも名前ですよね。

ここでは、「正弦波データ」としておきます。

clip_image007

続いて変数タイプですが、離れた2台の PC で共有したいので「ネットワークで共有」にします。

clip_image008

データタイプは「倍精度の配列」です。

clip_image009

エイリアスについては、現時点では気にせず無効のままにしておきます。

ほかに、「ネットワーク」の設定や、「RT FIFO」の設定がありますが、まずはデフォルトのまま進めてみます。

OK を押すとプロジェクトにシェア変数が追加されています。

clip_image010

ライブラリの下にあるので、保存をするとライブラリ名を入力します。

このシェア変数は送受信のデータなので「信号データ」ライブラリとします。

clip_image011

●ローカルで送受信してみる。

シェア変数ができたら、送信は、「正弦波データ」へ書き込み、受信は「正弦波データ」から読み込んで通信してみます。

まずは送信側から。

フロントパネルの「正弦波出力」を右クリックして「プロパティ」を開きます。

データバインディングの項目を開き、以下のように設定します。

clip_image012

参照を押したとき。シェア変数が表示されるので該当するものを選択。

clip_image013

OKを押すと、閉じてフロントパネルに戻ります。

よく見ると、「正弦波出力」表示器の右に小さい三角のマークが見えます。

clip_image014

実行すると、その小さい三角が緑色に変わります。これで、送信側の出力がシェア変数に書き込まれるようになります。

clip_image015

今度は受信側です。受信側の「正弦波入力」を右クリックして「プロパティ」を開きます。

データバインディングの項目を開き、以下のように設定します。

clip_image016

送信と同じように「正弦波入力」制御器の右に小さい三角のマークが見えます。

clip_image017

実行すると、その小さい三角が緑色に変わります。これで、受信側の入力にシェア変数からデータが読み込まれるようになります。

clip_image018

では、並べて送受信並列に動かしてみます。

clip_image019

結果のゲインはいい加減として、周波数を動かしてみます。

clip_image020

おお、直ぐに受信側に伝わりました。

ということで、ローカルでは送受信の環境はできたようです。

●ネットワーク越しでは?

さて、送信、受信を別の ネットワークで接続した 2台のPC で別々に動かしてみます。

それぞれ、ビルドしてみましょう。

「ビルド仕様」を、Tx、Rx、それぞれ作成します。

その際、「シェア変数のデプロイ」という項目がありますので、チェックしておきます。

clip_image021

まず、別のノートPC (LabVIEW開発環境はなく、ランタイムのみインストール済み)に Rx のexe をコピーして実行してみました。

エラーメッセージが。

clip_image022

気にせず、とりあえず実行してみます。

続いてファイヤーウォールの警告もでました。

clip_image023

フロントパネルでは、小さい三角形が赤くなています。

clip_image024

Tx を実行してみます。

こちらも同様。

clip_image025

開発PC側で、Tx の Exe を実行してみました。

clip_image026

こちらは問題なく実行できたようです。

恐らく、シェア変数のエンジンがノートPCにはインストールされていません。

インストーラを作成して、「追加のインストーラ」から「NI 変数エンジン」をインストールします。

clip_image027

今度は起動に成功しました。

しかし、肝心の2台の PC 間ではうまく転送できていないようです。

理由は、「シェア変数のライブラリ」のデプロイと、それを参照するための「データバインディング」での設定。これが誤っていたようです。

まず、「シェア変数のライブラリ」のデプロイをすることで、デプロイした PC 上にシェア変数がおかれます。続いて、開発中の VI でバインドしたい制御器の「データバインディング」での設定で、「参照」するのはプロジェクト内のシェア変数ではなく、デプロイされた変数を参照するようにします。

まずは、作成したシェア変数のライブラリを「右クリック」→「デプロイ」をします。

clip_image028

続いて、該当制御器の「データバインディング」の参照で、「ネットワーク項目」からデプロイされたものを選択します。

clip_image029

また、先程「ビルド仕様」で設定していた「シェア変数のデプロイ」のチェックは外します。

こうすることで、2台のPC でシェア変数を文字通りシェアすることができました。

しかし、この場合シェア変数の位置が絶対パスのようなものなので、PCが変更された場合にはあまり宜しくないと考えられます。

では、プログラム的に行ってみたいと思います。

まず、Txをサブ VI化して、上位に接続の VI Tx_Wrap.vi を作ってみます。

この時、「データバインディング」解放しておきましょう。

Tx_Wrap では、シェア変数をプログラム的に取得して、Tx を制御してみます。

まず、シェア変数の取得は 「データ通信」の「シェア変数」パレットにあります。

「変数接続を開く」関数を置き、シェア変数refnum入力を右クリックして「作成」「制御器」で、パスを入力できる制御器が作られます。

clip_image030

clip_image031

この制御器では「参照」で指定したシェア変数を参照することができます。

一連の流れはこんな感じです。

clip_image032

受信側も同様に Rx_Wrap.viを作ってみます。

clip_image033

あとは、中間で、デプロイ役の Deploy.vi を作っておきましょう。

Deploy.viは、単純にシェア変数をデプロイするためのもので、どちらかの PC で実行します。

clip_image034

2台で Exe を実行してみます。

まず、Deploy.vi を実行し、シェア変数を PC上に作成します。

その後、Tx.exe では、「シェア変数refnum入力」で「参照」し、デプロイされた PC 上のシェア変数を探し、選択します。

clip_image035

Rx も同様です。

clip_image036

これで、異なる PC でもシェア変数を割り当てて、実行できるようになりました。

しかし、反応が遅い。。。

結構な時間がかかっています。

まぁ実際シェア変数を使いたいのは設定項目や結果の方なので、数秒ぐらいかかっても文句はないのですが、ちょっと気になります。

あ、ちなみに遅いと感じているのはスループットではなく、遅延です。

シェア変数のプロパティで、「ネットワーク」「RT FIFO」といった設定項目があります。

ヘルプでは、バッファリング、FIFOなどでデータを保持する方向の話なので、高速化とは無縁のようです。ただ、複数から非同期に制御する場合、データ順を保持する時などには絶対必要なオプションです。

とりあえず、RT-FIFO を使ってみることにしました。

clip_image037

うーんあまり変わりません。

レイテンシがあるものとして、自動化へ行きます。

●いよいよテストの自動化へ

まず、テストを自動化する際に、送受信のVI では設定、結果をシェア変数化してデータバインドする必要があります。

ちなみに設定は「読み込み」結果は「書き出し」にします。

また、実行されているかどうか?を判別する必要がありそうです。

さらに、両方を統括して制御、取得を行う VI を作成する必要がありそうです。

この VI がシェア変数をデプロイするべきでしょう。

基本は、先程まで行ったものと同様です。

まずは、パラメータをシェア変数として作成します。

まずは、統括して制御取得する AutoControl.vi を作成します。

信号データとは別に作成します。

clip_image038

問題は、それぞれにシェア変数refnumを用意する必要があることでしょうか。

ただ、現実にはカスタム制御器としてまとめることもできるので、固定でもいい制御器などはシェア変数化する必要なはいかと思われます。

とりあえず、送信側を作成します。必要なのは、「周波数」と「ゲイン」です。

clip_image039

受信側もシェア変数を使用し、結果をシェア変数に格納します。

clip_image040

そして、AutoControl.vi ですが、先程作成したシェア変数をプロジェクトからドラッグ&ドロップします。

適宜、設定側は制御器、結果取得側を表示器にしておきます。

clip_image041

まずは、これでやってみましょう。

まず、Deploy.exe で、正弦波データのシェア変数を有効にします。

次に、先程作成した AutoControl.exe を起動します。

clip_image042

まだ、Tx も Rx も起動していないので、デフォルトのままです。

Txを起動してみます。

起動時には シェア変数の refnumがないので、全て適切な refnum を指定します。

clip_image043

AutoControl で周波数、ゲインを設定変更すると、送信側の設定も変更され、正弦波出力も変更されました。

clip_image044

しかし、受信側がまだ起動していないので、推定ゲインや、周波数ビンは 0 のままです。

そこで、別 PC で受信側を起動します。

起動後、送信同様にシェア変数の refnumがないので、全て適切な refnum を指定します。

clip_image045

そこで、AutoControl のパラメータを変更してみます。

すると、変更された結果が返ってきました。

clip_image046

あとは、AutoControl の設定変更の方法、結果取得の順番、起動確認の方法などを確認できればよいことになります。

設定、結果に時間差が生じるので、起動を確認したらテスト番号のような変数を用意し、その変化と、変更の通知、変更受取の通知などのやり取りを考える必要がありそうです。

●まとめ

色々紆余曲折ありましたが、まとめてみます。

LabVIEW で別々のPC に分散した送信、受信のテストを自動化するために、

①「シェア変数」を利用

②全体の統括する VI で「シェア変数」を自動的にコントロール

③全体を統括する VI で「シェア変数はデプロイ」

④送受信はラッパーをつけて、デプロイされたシェア変数を選択できるようにし、「プログラム的に」シェア変数から読み出し、書き込み

注意点は、ネットワーク越しのシェア変数経由では、レイテンシが結構あるのでそこに注意してハンドシェイクのような機能を持たせる必要があるところでしょうか。

よほど高速な転送が必要なければ、スループットは悪くないと思います。

DDCのお話(2:ヒルベルト変換(1))

前回、NCO という複素正弦波を作成して、入力実信号の周波数シフト(IF周波数→ベースバンド)を行いました。

しかし、結果から言うと、それだけではきれいな変換とは言えません。

もし、入力された実信号を複素化できれば、きれいに変換ができそうです。

そこで登場するのが「ヒルベルト変換」です。

まず、実信号の複素化とはいったいどのようなことかを考えてみます。

●実信号を複素化したい

前回、入力実信号をFFTしてみると、正と負に周波数が現れます。

clip_image001

これを、正の周波数だけにしたいわけです。

前回、NCO の複素正弦波では、Cos と Sin を用意すると、複素正弦波となり、負の周波数(または正の周波数)が作れることを説明しました。

もう一歩進んだ解釈としては、Cos の信号に対して、π/2 だけ位相が遅れた信号(=Sin)を虚数部に用意すると複素正弦波になります。

これと同様のことが簡単できればよいのですが、なんといっても入力信号あらゆる周波数を取りうることができます。

どの周波数の信号も同じようにπ/2 位相を遅らせたい。

そこで登場するのが、「ヒルベルト変換」という方法です。

これは、いわゆるフィルタの一種なのですが、全周波数で振幅は一定(オールパスフィルタという)で位相を π/2 ずらすフィルタとなります。

そのようなフィルタをどのような理論で設計するかは、詳しい書籍等に譲るとして、FIR フィルタの係数を計算することで、「ヒルベルト変換」を実現することができます。

●ヒルベルト変換のための係数算出

では、「ヒルベルト変換」を実現するための FIR フィルタの係数算出方法です。

ヒルベルト変換のタップ係数を求める公式

タップ数が M の時(M は奇数とします)、m タップ目 (m = 0..M-1) の係数 Cm は

Cm = (2/(m – (M+1)/2)*PI()) * (SIN((m – (M+1)/2) * PI() /2))^2

ただし、m=(M+1)/2 の時(タップの中央)は Cm=0 とする

で求めることができます。

ちょっと、長いですね。

また、この変換では、入力信号の (M+1)/2 サンプルだけの遅延した信号にたいして π/2 だけずれた信号になるため、実部の信号を (M+1)/2 サンプルだけ遅延させる必要があります。

実際には、タップ数 M を 31 とか、63 とか決めて計算します。

ちなみに M = 31 の時の係数をエクセルで計算し、グラフにするとこんな感じになります。

clip_image002

中央のタップに対して、係数は奇対称であることが分かります。

また、この計算では正規化ができていないので、そのまま使用すると、結構大きな値が出てきてしまいます。

正規化をするには、一度、係数を2乗して、全ての和の、平方根で、係数を割ってあげます。

本来、ヒルベルト変換の係数は無限に続くことが前提となっていて、じつはタップ数を限定すると、厳密には正しい変換にはなりません。

clip_image003

赤が入力信号で、周波数 +/- 200 にピークが立ち、白がヒルベルト変換後で、負の -200Hz が減衰していますが、ある程度出ていますがかなり減衰していることが分かります。

実際の波形は以下のような感じです。白が実信号、赤がヒルベルト変換後の虚数部です。

clip_image004

時間波形でみても、π/2 ずれている様子がわかります。

無限に係数を持っているわけではないので、FFTなどで観測すると、若干負の周波数成分が残ってしまっているようですが、かなり正しい周波数を示しているといえると思います。

しかし、この係数でのインパルス応答による周波数をみてみると、フラットではないことがわかります。

clip_image005

clip_image006

しかも、周波数によっては、負の周波数があまり下がらないこともあります。

そこで、係数に窓を掛けます。

窓関数はいろいろあると思いますが、ここでは「ハン窓」を掛けてみました。

clip_image007

計算式は、

Cm = -0.5*COS(2*PI()*m/(M-1))

です。

で、これを掛けます。

これも、正規化が必要です。同じ要領で正規化します。

clip_image008

そこでできた係数グラフがこれです。

中央から奇対称のまま、滑らかに 0 になっていきます。

周波数もフラットになりました。

clip_image009

が、しかし、負の周波数の減衰が、あまり顕著ではありません。特定の周波数 200 を入れたときスペクトラムでみると、先程より負の周波数が減衰しきれていません。

clip_image010

clip_image011

こうしてみると、実信号から確実に複素信号を作り出すことは意外と難しいことが分かります。

ここまでは、一般的な方法にのっとった実装です。

正規化の方法がおそらく肝だと思いますが、さらに 0.94 を係数すべてに乗じると、もっと状況は良くなります。

clip_image012

clip_image013

clip_image014

もう一声、係数に 0.94377 を掛けた場合

clip_image015

clip_image016

clip_image017

と、周波数 200 の時には負の周波数 (-200) はかなり減衰されました。

しかし、周波数 200 から、100 に変えると、負の周波数(-100)はでてきます。

clip_image018

ここで、わかることは、微妙な係数の違いで特性が変わってくるということです。

窓のかけ方、正規化の仕方で結構特性も変わってきます。

次回は、このあたりの調整をもう少し見ていきます。

DDCのお話(1:NCO)

RF 信号や IF 信号を信号処理する場合、普通はダウンコンバートという処理を行ってベースバンド信号に落として行います。

この、ダウンコンバートをする計算で活躍するのが DDC (Digital Down Converter)です。

理論的なお話は、詳しい書籍がありますのでここでは割愛しますが、実際にどのような実装と振る舞いをするのかを見てみたいと思います。

DDC と一口に言ってもいろいろな実装方法、計算方法がありますが、今回は「NCO」「ヒルベルト変換」「LPF」を使った IF 信号をベースバンドに落とす DDC を実装しました。(ちなみにRF から IF へは、アナログ回路でダウンコンバートすることが多い)

単純に考えれば、IF 信号の中心周波数の逆の周波数(負の周波数)をもつ 正弦波を用意して IF信号に掛け算すれば、中心周波数が 0 であるベースバンドに落ちてくれます。この「負の周波数をもつ正弦波を作る部分」が NCO です。

●負の周波数をもつ正弦波を作る: NCO

でも、負の周波数をもつ正弦波とはいったいどうやって作ればよいのでしょう。

答えは、複素数で正弦波を作ることで、正、負、どちらの周波数も作ることができるのです。

普通、周波数は正だけです。負はありません。IF信号にしてもそうです。

でも、これは実数の世界での話。IF信号を複素数の世界で見ると、実は正、負、両方に周波数をもつ信号になります。

実際に LabVIEW で確かめてみます。

●まずは単純な正弦波で見てみる。

最初は、単純な 正弦波 (Sin関数) を用意。

clip_image001

y =sin(x) の単純な関数です。

入力 x は位相を入れます。単位はラジアンで、生成したい周波数、サンプルレートで生成してみましょう。

周波数をサンプル周波数で割ると、1サンプルあたりの位相の割合がでます。これをラジアンに直すので、2πを掛けたものを用意します。 sin 関数に入力します。

clip_image002

出力を、1ループごとに足し込んでいきます。そうすることで、指定した周波数に見合った位相が時間とともに動きます。

こんな感じで、For ループに入れて、シフトレジスタで前回の位相に足し込みます。

clip_image003

その位相を sin 関数に入れて、結果をグラフで表示してみましょう。

clip_image004

フロントパネルはこんな感じです。

clip_image005

まず、以下のようなパラメータで実行してみます。

パラメタ

周波数 (Hz)

10

サンプル周波数 (Hz)

1000

生成サンプル数

200

このパラメータでは100サンプルで、1周するので、200サンプルだと 2周分の正弦波が観測されます。

clip_image006

では次のパラメータではどうでしょうか。

パラメタ

周波数 (Hz)

-10

サンプル周波数 (Hz)

1000

生成サンプル数

200

clip_image007

位相が反転しました。

でも、位相が反転するだけで、周波数は 10 Hz であることには変わりがないですよね。

これを FFT で見てみます。

FFT を用意します。「信号処理パレット」の「変換」にあります。

この FFT は複素数で入力します。

clip_image008

先程のパラメータで実行してみます。

clip_image009

2つピークが見えています。

実はこのケースでは、FFT の 0~99 までが正の周波数、100~199までが負の周波数を表しています。

FFT は 出力サンプル数の前半分が正の周波数、後ろ半分が負の周波数に対応していて、通常IF信号など表示するのは前半分の正の周波数部分だけです。

と、いうことで、2つの周波数がみえています。

ピークの位置は、インデックスでいうと 2 と 198 です。

これは、負の周波数でも、正の周波数でも実信号のみでは区別がつかないことを意味しています。

(実際には、実信号では正だけで十分なので問題ない)

それでは、実信号に、この単純な正弦波を掛けるとどうなるでしょう。

●実信号に、単純な正弦波 NCO を掛ける

実信号として、以下のようなパラメータの信号を考えてみます。ちなみに実信号も簡単のため正弦波です。

実信号パラメタ

周波数 (Hz)

15

サンプル周波数 (Hz)

1000

生成サンプル数

200

 

単純な正弦波は

正弦波パラメタ

周波数 (Hz)

-10

サンプル周波数 (Hz)

1000

生成サンプル数

200

まずは、この2つの信号を重ねてグラフで見てみます。

clip_image010

clip_image011

わかりにくいので、0~10 を拡大。

clip_image012

周波数インデックス 2 (10Hz) と 3 (15Hz) にピークが見えます。

この場合、期待するのは 5Hz の正弦波ですが乗算した場合どうなるでしょうか。

clip_image013

明らかに 5Hz ではないですよね。

clip_image014

ピークが 4つ見えています。

わかりにくいので、0~10 を拡大。

clip_image015

周波数インデックスが、1(5Hz) と5(25Hz) に見えます。

負の側も、199(-5Hz) と 195(-25Hz) に見えています。

実は、 5Hz と 25Hz が混ざった信号になっているのです。

つまり、もともと実信号は正と負に対称の周波数をもっているので、それぞれが計算され 4 つの周波数が見えてしまっているわけです。

実信号

単純なNCO

変換された周波数

+15

-10

+5

+15

+10

+25

-15

-10

-25

-15

+10

-5

しかも、それぞれの周波数の振幅も半分程度に減ってしまっているのがお分かりでしょうか。

●複素正弦波を見てみる

さて、実信号はともかく、NCO側を複素数にしてみます。

NCO側の Sin 関数を、Cos と Sin 2つにすることで、複素正弦波と呼ばれるものが作れるようになります。

clip_image016

なんで、こうなると複素正弦波と呼ばれるかは、詳しい書籍や Google 先生に譲りますが、Cos側が実数、Sin側が虚数部 と覚えてください。

clip_image017

このようにして、複素数に変換してしまいます。

すると、FFT結果は

clip_image018

インデックスの 198 (-10Hz) にしか現れなくなります。

(赤の実信号は相変わらず実数のみなので2つピークが出ています)

では、乗算した結果はどうなってしまったでしょうか?

clip_image019

??

あまり変わりませんね。

FFT結果は?

clip_image020

0 から 10 を拡大

clip_image021

190から200を拡大

clip_image022

なんか変ですね。

Y軸の振幅に注目してください。

非常に小さい値になっています。これは演算誤差に起因するもので、非常に小さい値ですが出ています。実際の振幅はどこに行ってしまったのでしょうか。

実は 結果は複素数なので、虚部も表示する必要があります。虚部側にピークがきちんと現れていました。

clip_image023

clip_image024

虚部の方では、きちんと値がでて、インデックスの 1(+5Hz) と195(-25Hz) にはっきりとピークがでています。

clip_image025

clip_image026

つまり、複素正弦波を掛けると、入力実信号がそのままシフトしていることが分かります。

実信号

複素NCO

変換された周波数

+15

-10

+5

-15

-10

-25

しかし、本当にほしい信号は +5Hz のみです

その為には、実信号を複素数に変換する必要があります。

それが、ヒルベルト変換です。

次回は対策として「ヒルベルト変換」を実装してみたいと思います。

 

まとめ

・NCOを複素正弦波で生成することで周波数シフトが可能

・IF信号のような実信号は、NCOだけでは正負の周波数が出てしまう

CLIP変更後コンパイルエラー回避方法

以前、自作VHDLを LabVIEW FPGA に組み込む 記事を書きましたが、CLIP の元の VHDL を変更した際に、ビルド中にエラーが起きてしまう場合がありました。

厳密には、元のVHDLをネットリスト化してから CLIP に組み込んでいたのですが、なぜか FPGA のビルド途中でエラーしてしまいます。

以前も同様の現象が起こり、その時は再度プロジェクトを作り直すことで回避しました。

とはいえ、新規にプロジェクトを作って、全部の内容を復元することはプロジェクトが大きくなってからでは不可能になるので、何か良い回避方法はないかを模索してみました。

 

キャッシュのクリア

まず、以前の内容がキャッシュされているのではないかと思い、キャッシュを消すようなところはないかを探したところ、、、

「ツール」メニューの下の「上級」→「コンパイルされたオブジェクトキャッシュをクリア…」という項目がありました。

clip_image001

ためしにキャッシュをクリアしてみます。(キャッシュが 63.3kB だったので、FPGAっぽくないのであまり効果はないかもしれないと思いつつ。。。)

キャッシュクリア後、FPGAをビルドしてみました。

→約17分経過。

エラー。。。orz

clip_image002

エラーの内容としては「トランスレート(解釈)」でエラーしてしまっているようです。

再度、CLIP を LabVIEW プロジェクトで編集してみたものの結果は変わらず。。。

 

ビルド仕様を作り直す

では、実際のところ ビルド時には中間ファイルはどこに作られるかを調べてみました。

ビルド時には、ソースや中間ファイルなどが、一度以下のフォルダにコピーされ、そこからコンパイル処理されるらしいことが分かりました。そのフォルダは

「C:\NIFPGA\compilation」

で、その下にビルド仕様のビットファイル名のフォルダができます。

そして中を確認したところ、なぜか、その中にあるCLIP で指定したファイルの日付が古いま。??なぜ??そうなるのかは不明。

ですが、日付が古いままでではだめなので、新しく「ビルド仕様」を作成してみました。そしてビルド。

中間ファイルの出力先を見ていると、、、今度はあたらしい CLIP の元ファイル がコピーされたことが確認できました。これは期待できそう。

さて、結果はどうか?というと。。。

無事エラーせず、ビルド完了。

 

まとめ

ということでまとめますと

CLIP の元を変更し、原因不明のエラーが出てしまったときは「新しくビルド仕様を作る」ことで回避できる。

これで、原因不明であきらめて新しいプロジェクトを作り直す必要はなさそうです。

DMA転送したデータをRAIDに確実に保存する

昨日は「DMA転送を確実に行う」ということで、 FPGA から ホストへの DMA 転送を行いました。今度は、その取得データを確実にファイルに保存したいと思います。

今回試した仕様は前回の仕様に加えて

仕様1:50MHz で変化する U16 データを 4ch 分 をFPGAからホストへ転送する。DMA転送レートは 400MB/s 必要。

仕様2:ストレージに確実にデータを保存する

構成を図示するとこんな感じです。FPGA から、CPUボード(ホスト)へは、400MB/s で DMA 転送ができています。今度はそのデータを、確実にストレージへ保存することが目的です。ホストからすると、400MB/s の読み込み、400MB/s の書き込みを行う必要があります。両方で 800MB/s のデータ転送が発生します。

clip_image001

そこで参考にしたのが、ともに開発している「ドルフィンシステム技術部 福島の開発日記」のこの記事「LabVIEW で高速にストレージに書き込む (3) – 高速に書き込めた!」です。

データの保存方法はいくつも方法があると思いますが、最も単純かつ高速な方法で書き込む方法を詳しくレクチャーされています。

引用させていただくと

上級ファイルI/O関数のちょっとしたパラメータで、全然ストレージスピードが変わってしまいます。

  • ファイルを開く関数では、バッファリング無効
  • バッファリングはキューを使って VI に流す
  • バイトオーダーは OS 標準に合わせる (Windows なら little)
  • データサイズの追加書き込みはしない

この4点を守ると最大6倍高速に書き込みできることが分かりました。

ということです。さっそく実装してみましょう。

まずは、同じVI でストレージ書き込みを実装してみる

まずは、先日の DMA転送の VI を改造して、以下のようなブロックダイアグラムを作成します。

clip_image002

中央の3つのループは上からそれぞれ以下のような役割です。

・DMA FIFO からデータ取得し、キューに入れる

・キューからデータを取得し、バイナリデータでファイルに書き込む

・その他モニタ用ループ (100ms)

まず、ファイルを開く関数では、「バッファを無効(F)」を制御器で制御してみます。

clip_image003

キューからの取得データを「バイナリファイルに書き込む」関数で、「配列…サイズを先頭に追加」「バイト順序」を制御器で制御できるようにしておきます。

clip_image004

まずは、この状態で実際に試してみます。

デフォルトの「big-endian」かつ、「バッファ有効」「先頭に追加」の状態です。

この状態ではやはり書き込みが間に合わず、キューが一杯になってきます。

では、参考記事の通りに、「little-endian」、「バッファ無効」、「先頭に追加しない」で実行してみます。

あれ?あまり実行速度に変化が見られません。

キューがすぐに一杯です。

要素数の問題かと考え、増やしました。具体的には 16384 から 524288 (512k) に増やしてみました。

やはりキューが増えていき、転送レートは 391M と若干悪いです。

では 要素数を 1048576 (1M) にしてみます。やはりキューに溜まって行ってしまいます。

ということは、2番目のループで時間がかかっていることはわかっています。

clip_image005

制御器をやめて定数にしてみました。

若干、キューへのたまり方が緩和されたみたいですが、まだ、だめみたいです。

これは、参考記事と同じように、書き込みの部分は完全に別 VI にしておくべきと考えました。

別スレッドの VI でストレージに書き込む

参考記事のように「StorageWriter.vi」を以下のように作成します。

clip_image006

clip_image007

「little-endian」、「バッファ無効」、「先頭に追加しない」をデフォルトに設定しています。

また、上記「StorageWriter.vi」を呼ぶメイン側の VI は以下のようにします。

clip_image008

フロントパネルはこんな感じです。

clip_image009

また、メモリが足りなくなるといけないので、一定数キューの数がたまったら停止するようにしています。

データ要素数は 524288 でやってみました。

結果、キューはたまらずにきちんとすべてのデータが書き込まれ、レートも400MB/s で転送できているようです。

要素数を減らしながら結果を見てみます。

要素数

結果

524,288(512k)

問題なく 400MB/s で転送可能

262,144(256k)

問題なく 400MB/s で転送可能

131,072(128k)

問題なく 400MB/s で転送可能

65,536(64k)

キューの増減が出始めるが、キューが深ければ 400MB/s で転送可能

32,768(32k)

キューにデータがたまる一方で、400MB/s では転送が続けられない

 

65536 ぐらいが 400MB/s で書き込める限界のようです。

逆に、キリの悪い数字(512 の倍数ではない場合)ではどうでしょうか。

要素数

結果

100,000

キューにデータがたまる一方で、400MB/s では転送が続けられない

1,000,000

問題なく 400MB/s で転送可能

65536 以上でも、100,000 ではすぐにキューが一杯になってしまいます。

おまけ

上記の VI で、グラフを描画させたいと思い、モニタ用のループ(100ms毎)を追加しました。

clip_image010

実行してみると

clip_image011

問題なく、転送できました。

まとめ

参考記事の方法と同じく、

  • ファイルを開く関数では、バッファリング無効
  • バッファリングはキューを使って VI に流す
  • バイトオーダーは OS 標準に合わせる (Windows なら little)
  • データサイズの追加書き込みはしない
  • を守り、書き込み部は別 VI とすることで、FPGAからの 400MB/s のデータをストレージに確実に書き込めることが分かりました。

DMA転送を確実に行う

LabVIEW において、FPGA から ホストに対して(またはその逆)のデータ転送はは2つ方法があります。一つは、FPGA VI のフロントパネル制御器を介してのデータ転送。もう一つは DMA 転送を利用してのデータ転送です。

通常、まとまったデータを FPGAとホスト 間で高速に転送したい場合には DMA 転送を使います。

DMA 転送自体の技術的説明は他のソースにお任せして、今回は LabVIEW FPGA と ホスト間での DMA 転送を確実に行うための方法について考察してみます。

LabVIEW でDMA 実装の流れ(FPGA → ホストへのデータ転送)です。

① LabVIEW FPGA のプロジェクトを作成

② DMA FIFO を作成

③ FPGA VI で DMA FIFO (書き込み)を実装

④ ホスト VI で、DMA FIFO からデータ読み出し、検証

今回の実装例としては、「 DMA 転送を確実に行う」ために以下のような仕様で進めます。

仕様1:50MHz で変化する U16 データを 4ch 分 をFPGAからホストへ転送する。DMA転送レートは 400MB/s 必要。(微妙な転送レートですね)

仕様2:確実に転送ができているか確認する

実装編と、実践編(デバッグ編?)に分けて作成していきます。

実装編

① LabVIEW FPGA のプロジェクトを作成します。

新規に LabVIEW プロジェクトを作成し、「マイ コンピュータ」を「右クリック」→「新規」→「ターゲットとデバイス…」を選択します。

出てきたダイアログで、「新規ターゲットまたはデバイス」を選び、好きな FPGA ターゲットを選びます。

ここでは FLexRIO の「PXIe-7965R」を選びました。

clip_image001

② DMA FIFO を作成

DMA FIFO は、DMAをするための専用の FIFO です。

作り方は簡単です。

プロジェクトエクスプローラの「FPGAターゲット」で「右クリック」→「新規」→「FIFO」を選びます。

clip_image002

すると「FPGA FIFO プロパティ」ダイアログが現れますので、FIFOに関しての設定を行います。もちろん、後で変更することも可能です。

clip_image003

まず「一般」カテゴリを設定します。

・名前:FIFO の名前を設定します。今回はわかりやすいように「FPGA to Host FIFO」とします。

・タイプ:ここが、重要です。「ターゲットからホスト(DMA)」を選びます。これは、ターゲット、つまり FPGA からホストへ DMA 転送するための FIFO であることを示しています。

※「ホストからターゲット(DMA)」は、その逆にホストから FPGAへ DMA 転送するときに選びます。

・要求された要素数:FPGA に実装される FIFO の深さのことです。深ければ深いほどバッファされるのでよいのですが、現実的にはデフォルトの「1023」で問題ないと思います。なぜなら、DMA FIFO は、FPGA の FIFO + ホスト側の FIFO で一つの FIFO としてふるまうからです。

タイプがDMAの場合、実装のプロパティはグレーアウトして変更できません。

clip_image004

次に、「データタイプ」カテゴリを設定します。

ここでは、DMA 転送時のデータタイプを指定します。

今回は、U16 を 4つ(4ch)同時に転送したいので、まとめて 「U64」としたいと思います。U16 の FIFO を 4つ作っても良いですが、まとめてしまった方がタイミングなどの問題が起きにくいと思いまとめます。

clip_image005

つぎのカテゴリ「インターフェース」ですが、FIFO へアクセスが複数ある場合どうするかを指定します。今回はデフォルトの「複数リクエスタの場合のみアービトレート」にしておきます。これなら、リクエスタが1つなら、調停せず、複数あれば自動で調停してくれるので、通常このオプションでよいと思います。

これで、DMA FIFO の作成ができました。

③ FPGA VI で DMA FIFO (書き込み)を実装

実装の仕様1から、 50MHz の U16 のインクリメントデータを 4つ分束ねて、U64 としてホストへDMA 転送する VI を作成してみます。

DMA FIFO をプロジェクトエクスプローラから FPGA VI のブロックダイアグラムにドラッグして、SCTL 、ケースストラクチャなどで囲みます。

clip_image006

ホストから「転送スタート」制御器が「T」にされたら、U16 のデータを "0" からインクリメントしつつ、DMA FIFO へ入力します。DMA FIFO は U64 としたので、元のデータを「数値結合」で4つ結合して DMA FIFO の「要素」に入力します。

SCTL のクロックは、50MHz としました。

(この 50MHz は、プロジェクトエクスプローラの FPGA ターゲットにある 「40MHz Onboard Clock」を「右クリック」して「新規」→「派生クロック」で 50MHz を作成しています)

ということで、この FPGA VI で、転送中は 50MHz * 8Byte (U64) = 400MB/s のレートで転送が実現できます。

FPGA側は 400MB/s で確実に転送しますが、ホスト側で、読み出しが 400MB/s に間に合わなければ、データが FIFO に蓄積していきついには「タイムアウト」が点灯します。

そうなると、FIFOへの書き込むデータが欠落し、きちんとしたインクリメントデータにならなくなります。

そこで、ホスト側できちんと転送できているかチェックする機能も付けたいと思います。

④ ホスト VI で、DMA FIFO からデータ読み出し、検証

続いてはホスト側です。

ホスト側では、DMA 転送には「FPGA インボークメソッド」を使用します。この場合は DMA FIFO からデータを読み込みます。

出てくる値はインクリメントデータなのですが、まずは比較無しでとにかく配列表示器に入れるだけのものを作成します。こんな感じです。

clip_image007

フロントパネルはこんな感じにします。

clip_image008

まずはエミュレーションで確認します。エミュレーションの方法はプロジェクトエクスプローラの FPGA ターゲットを「右クリック」→「VI の実行場所」→「開発用コンピュータ」にします。

取得する「要素数」は 4096 にして実行してみます。「転送スタート」も「T」にして実行すると、タイムアウトが点灯します。つまり、読み出しが間に合っていないことになります。

では 「要素数」を倍の 8192 にしてみます。タイムアウトはつかなくなりました。間に合っていそうです。

16384 にしてみます。FIFO からデータ転送されなくなりました。

これは、デフォルトのホスト側 DMA FIFO の深さが10,000 なので、16384 個データがたまることが絶対になく、転送ができなくなるからです。

間に合わないと、タイムアウトが点灯し、データも取りこぼしが出てしまいます。

間に合っていれば、タイムアウトは点灯せず、データの取りこぼしはありません。

8192 の要素にすれば、間に合っていそうですが、現在はエミュレーションです。

実際にはどうなのでしょうか。

とりあえず、FPGAをこの状態でコンパイルしてみましょう。

実践編(デバッグともいう)

実際にFPGAをビルドしてみます。ビルドしたら、FPGA ターゲットの「VI 実行場所」を[

FPGA ターゲット」にしておきます。

また、ホスト側の VI の 「FPGA VI リファレンスを開く」関数もリソース名が指定できるように制御器などをつけておきましょう。

実行してみると、データの先頭がずれていくのが分かります。しかし、タイムアウトが点灯していません。

なぜ、エミュレーションと違ってしまったのでしょうか。

ホスト側 VI を改造して、転送レートが分かるようにしてみます。

clip_image009

転送レートの結果は、 385.236MB/s でした。400MB/s からはタイマーの誤差を入れても遅いです。間に合っていません。

一瞬、タイムアウトしても、ソフト側からは一瞬過ぎて認識ができないので、タイムアウトの LED が点灯していないように見えます。

ホスト側の DMA FIFO の深度を増やしてみます。ホストの VI に「FPGA インターフェース」パレットから、「メソッドからインボーク」を追加し、FPGA to Host FIFO.構成を選びます。このようになります。

clip_image010

深度はデフォルト 10000 なので、65536 程度まで増やしてみましょう。

若干改善しました。転送レートが 390MB/s を超えました。

さらに取得数を 16384 に増やすとさらによさそうです。

32768 にするともっと安定します。

深度を1M にしてみます。

32768 の時よりは安定して転送ができていそうです。

こんどは、取得要素数を 4096 にしてみます。結構安定しているのではないでしょうか?

4096でも、398MB/s とでました。

深度を 10M にしてみます。

取得要素数が4096でも完全に安定しています。

要素数をもっと減らしてみましょう。

要素数1024ではどうでしょうか。

崩れ始めました。転送レートも 367MB/sです。

要素数2048っではどうでしょうか。

395MB/s で安定しています。

ちょっとまとめてみましょう。取得要素数は 2048 にしました。

深度

データの安定性

実測レート(約10秒)

10000(デフォルト)

不安定

393.209MB/s

1M

安定

396.468M/s

10M

安定

395.925MB/s

DMA FIFO の深度を多くすればするほど安定していると思われます。

しかしながら、データがきちんともれずに取得できているかを確認する方法を考えないといけません。

そこで、ホスト側の VI をデータ取得側のループと、取得データを比較するループに分けてみます。

ちょっと比較が複雑な感じがしますがやっていることは以下の通りです。

①1回目の取得データ配列を保持する。

②2回目以降のデータは、1回目の要素数分だけたされたものである。

③取得データと、要素数分たしこまれた保持データを比較

④一致していればOK

ちょっと見ずらいですが、以下のような感じです。

clip_image011

データ取得ループ

clip_image012

取得したらキューに入れます。

インクリメントしているのは転送レート計測用に何回取得したか数えています。

こちらは、データの比較ループ

clip_image013

最初の1回はリファレンスとして保持して、実際の比較は2個目の取得データからです。

時間計測などもここで行っています。

配列の比較になるので、配列の要素ごとに、ブールが出てきます。

比較のブール配列の AND を取ることで、全て T なら T、一つでも不一致なら F になります。

clip_image014

動作結果です。

clip_image015

深度は 10M、要素数 8192 です。転送レートは 397.287MB/s 出ていますが、一致回数は たったの1回、これは最初のデータですので、実際には比較していないものです。

つまり、ずれてしまっている。ということです。

これは、データを目で検証したところ、1回目と2回目の間でデータずれを起こしていて、以降はずれたままですがきちんと転送できていそうでした。

なぜ、起きたか。

仮説としては、ホスト側のDMA転送が1回目はきちんと行われておらず、10Mの深度が有効になっていない、つまり、実際にFPGAから転送開始直後はFPGA FIFO のみの深さしかないのですぐにオーバーフローしてしまい、以降は10M のバッファが有効になるため転送はきちんとされるが、1回目と2回目の間にデータが一部落ちてしまう。というものです。

そこで、テータ取得のループの前に、「FPGA to Host FIFO.開始」というメソッドを置いてみました。こうすることで、データ取得前に ホスト側のDMA のバッファは用意されるはずです。

clip_image016

結果として、うまくいきました。

clip_image017

データはこぼれることなく、400MB/s で転送できている模様です。

(397MB/s というのはティックカウンタの開始時間、終了時間の誤差があるためです)

ちょっと、長い時間試してみようと思います。そうすれば、ティック誤差は小さくなり、400MB/s に近づくはずだからです。

5分20秒ほどで、399.953MB/s でした。エラー回数も 0 です。転送はできています。

clip_image018

欲を出して、比較ループにグラフ描画も追加してみました。

結果は、NG です。キューにたまる一方で、メモリが足りなくなってしまいました。

結構危険です。。。

そこで、もう一つ別の 100ms のループを作成し、グラフはモニタであると割り切ってみました。すると、グラフも描画したまま転送も大分持つようになりました。

clip_image019

clip_image020

まとめ

DMA 転送でレートを出すコツは以下の4点

① ホスト側の深度を大きくする

② 転送する要素数をそれなりに大きくする。

③ FIFO 取得前に、「FIFO.開始」 のメソッドを実行しておく

④ 取得後の処理はキューで別の非同期ループにする(ただし、重いとキューが一杯になり危険)グラフなど、全て取得する必要がないものは、モニタ用途として割り切り、全てを表示しなくても良いとして 100ms ぐらいの別非同期ループにする。

この辺りを気を付ければ、(もちろんハード的な限界などもありますが)高速なデータ転送でもDMAを使って確実にできそうです。

LabVIEW FPGA 固定小数点キャスト(その2)

前回、固定小数点の関連キャストの方法として2種類紹介しました。

●整数部分を評価するキャスト→値を保持

●ビット列を保持

前回 リンクしていなかったのですが、National Instruments 社での説明はこちらです。

LabVIEW固定小数点データタイプ ― 固定小数点の基礎 Part1

LabVIEW固定小数点データタイプ ― 固定小数点の基礎 Part2

ここも一度読まれるとよいと思います。

さて今回は、固定小数点の「任意の位置のビット」を抜き出して、整数にキャストする方法について考えてみたいと思います。

 

固定小数点の「任意の位置のビット」を抜き出して、整数にキャスト

イメージはこんな感じです。

clip_image001

上記の例では16bit 固定小数点(8bit 整数)のうち、整数 4bit, 小数 4bit のビット列を保持して、整数 I8 に変換したい例です。

こんなややこしいことするのか?と思うと思いますが、こんな場合を考えてみたいと思います。

ある関数の出力が、上記のように FXP<+/-,16,8> の固定小数点でした。

しかし、その関数の入力信号の範囲から、出力は必ず、整数 4bit 以下であることが分かっています。そして、次の関数は I8 で入力しなくてはなりません。

そういったケースは、FPGAではよくあります。

例えば、I8 を要求する関数は、単なる前の関数結果をモニタするものだったとします。

多少の誤差は許容します。しかし、モニタ回路に FPGA リソースをたくさん使ってしまうことを避けたい。そういったことはよくあることです。

前回のキャストをうまく使ってできるでしょうか。

いずれにしても、一回でキャストはできません。

以下のような VI を作ってみました。

ブロックダイアグラム

clip_image002

フロントパネルです。

clip_image003

入力の制御器は、FXP<+/-,16,8> の固定小数点です。

上の二つは、前回の方法の整数部を単なるキャストするのと、ビット保持のキャストです。

上の2つだけでは「任意のビット」を抜き出すことができないのは明白なので、以下のものと組み合わせてみることにしました。

・2 の n 乗でスケールを使ったキャスト

・数値の再解釈を使ったキャスト

・固定小数点キャストをつかったキャスト

・ブール配列に変換を使ったキャスト

与えた値をいくつか変化させてみると、ビット抜出しをしたとしても振る舞いが違うものがありました。

例えば、「2 の -4乗でスケール」(ようは、右4bit shift ですね)してから、「ビット保持のキャスト」した場合です。

以下のようになります。

与えた固定小数点

スケール後

ビット保持でI8

評価

0x0234x2^-8

0x0023x2^-8

23

正しく抜き出せた

0xFF34x2^-8

0xFFF3x2^-8

F3

正しく抜き出せた

0x1234x2^-8

0x0123x2^-8

23

一見正しそうだが値的にはおりかえる

0x8234x2^-8

0xF823x2^-8

23

元は負なのに結果は正になる。おりかえっている。

固定小数点キャスト→ビット保持、ブール配列変換についても、同じ状況です。

「ビット保持」では値がもし、範囲外の場合はおりかえってしまいます。

固定小数点の整数ビットを解釈しなおして、12bit 整数とする「数値の再解釈->FXP<+/-,16,12>」をしてから「単なるキャスト」を見てみます。数値の再解釈では16進数の値は変化せず、小数点の位置だけ変化します。

与えた固定小数点

数値の再解釈後

ビット保持でI8

評価

0x0234x2^-8

0x0234x2^-4

23

正しく抜き出せた

0xFF34x2^-8

0xFF34x2^-4

F3

正しく抜き出せた

0x1234x2^-8

0x1234x2^-4

7F

正の最大値でクリップした

0x8234x2^-8

0x8234x2^-4

80

負の最小値でクリップした

こちらの方が、正しそうです。

ということで、「数値の再解釈」→「単なるキャスト」が最も良いことが分かりました。

●もっとも正しそうな「固定小数点の任意の部分を整数にキャスト」する方法

clip_image004

「数値の再解釈」⇒「単なるキャスト」

でもこれ、ちょっと考えてみると、関数がたくさん固定小数点の出力があった場合にいちいち間に入れていたら、ブロックダイアグラムが大変なことになりそうな予感がします。

とりあえず、縮小表示というものがあったので試してみます。「数値の再解釈」関数を「右クリック」して「縮小表示」をクリックします。

clip_image005

すると、小さくなりました。

clip_image006

でも、複数あった場合に「数値の再解釈」と「単なるキャスト」の二つを並べていたら結構すごいことになりそうです。せめて一発で整数まで変換できたらいいと思うのですが、現状では仕方ないかもしれません。

まとめ

現状「固定小数点の任意の部分を整数にキャスト」をするには「数値の再解釈」と「単なるキャスト」を使う。

今後固定小数点についてもっと、簡便な方法にできないか、模索してみることにします。

LabVIEW FPGA 固定小数点キャスト

今日はデータタイプの一種、固定小数点の変換についてのお話です。

LabVIEW FPGA に限らず、LabVIEW 自身の話なのですが、データを扱う時、データタイプを気にするのは当然だと思います。

わかりやすいところでは、倍精度浮動小数点 (DBL)、符号付き16bit整数 (I16)、ブールなどがあります。

普通は大体これらで足りてしまいます。

でも、FPGAでは基本的に DBL は使いません(実際のところ使えません)。使おうと思えば使うこともできるかもしれませんが、少なくとも私は "一切" 使いません。

では、FPGAで普通に小数点を扱う場合はどうするかというと「固定小数点 (FXP)」を使います。

この、固定小数点ですが FPGAからすれば実はビット列に過ぎず、ソフトウェアが論理的にそのビット列に小数点の位置を定義しているわけです。

FPGAの場合、関数によって入力で受け付けるデータタイプが固定小数点だったり符号付き整数のタイプだったりするので、データタイプの変換が必要になります。

関数パレットにはいろいろ変換の関数があるのでちょっと紹介します。

●固定小数点に変換

clip_image001[4]

「固定小数点に変換」関数。

これは例えば整数などのデータを固定小数点に変換するタイプですね。

clip_image002[4]

こんな感じで使うようです。

固定小数点タイプを指定するのですが、「数値」が整数 I16 の場合には固定小数点側もビット数を 16bit にする必要があるようです。では、10bit にしたらどうでしょうか?

実は、入力された整数が 10bit で表現できない時には 10bit の最大値でクリップされます。

clip_image003[4]

整数 I16 から、16bit 固定小数点(16bit 整数)の場合はそのままコピーされる。

clip_image004[4]

整数 I16 から、10bit 固定小数点(10bit 整数)の場合は10bitで表現される範囲はそのまま。それ以上は最大値(もしくは最小値)にクリッピングされる。

clip_image005[4]

整数 I16 から、16bit 固定小数点(8bit 整数)の場合は8bitで表現される範囲はそのまま。それ以上は最大値(もしくは最小値)にクリッピングされる。

clip_image006[4]

整数 I16 から、10bit 固定小数点(5bit 整数)の場合は5bitで表現される範囲はそのまま。それ以上は最大値(もしくは最小値)にクリッピングされる。

このように変換されます。

この場合、固定小数点の整数部分を同じにとっていれば問題はないと思います。

では逆の場合はどうでしょうか。

●固定小数点を整数に変換する→「ワード整数に変換」

こんな感じで変換します

clip_image007[4]

clip_image008[4]

16bit 固定小数点(16bit 整数)から、整数 I16 の場合はそのままコピーされる。

これはわかりやすいですね。

clip_image009[4]

10bit 固定小数点(10bit 整数)から、整数 I16 の場合はそのままです。

これもわかります。

clip_image010[4]

16bit 固定小数点(8bit 整数)から、整数 I16 の場合は整数8bitで表現される範囲はそのまま。小数部分は丸めこまれます。これも、当たり前ですね。

10bit 固定小数点(5bit 整数)の場合の場合も同様です。

?ちょっとここで疑問です。もしある関数の出力が「固定小数点」で、もう一方の関数が「整数」で同じビット数の場合、そのままコピーしたいです。

そういった方法はないのでしょうか?

極端な話、三角関数で +/- 1.0000 の範囲を固定少数点に考えると、整数ビットはたった2ビットです。これを I16 なんかにこの関数でキャストすると、+1/0/-1 しか出てこなくなります。

そんな場合には、「固定小数点から整数にキャスト」関数を使います。

●「固定小数点から整数にキャスト」関数

clip_image011[4]

この関数では、固定小数点のビットをそのまま整数に移します。

したがって、下位ビットがかけたりすることがありません。

例えば、16bit 固定小数点(8bit 整数)から、整数 I16 の場合もそのまま移されます。

clip_image012[4]

例えば、FXP<+/-,16,8> で "1.5" という数値があるとします。通常の変換だと "2" ですが、この「固定小数点から整数にキャスト」関数では "384" になります。

数値は大きくなっていますが、ビットとしてみると何も変化していない変換です。

このキャストは、必ず固定小数点の全ビット以上のビット数の整数に変換されます。

この逆はどうなんでしょう?

●「整数から固定小数点にキャスト」関数

この逆もなんとなくわかりやすいです。

基本的には下のビットからコピーされていきます。整数ビット数(小数点位置)はコピー後に解釈されます。

いずれにしても、整数部分か、そのまま維持かという変換です。

まとめると、固定小数点と整数間でのキャストには2種類方法があります。

●整数部分を評価するキャスト→値を保持

●ビット列を保持

では、固定小数点の「任意の位置からのビット」を整数に、またその逆、をするにはどうするのが良いか、次回考察したいと思います。

例えばこんな場合です。

clip_image013[4]

では、また。