Excel(VBA)でRGB2YUVの確認
前回の「浮動小数を使用せず演算」で記載したRGB→YUV変換(演算式②)の精度を確認するため、Excel VBAでプログラムを作りました。
Excelを使用することにより、以下のように画像確認ができます。
前回の参考記事は、以下になります。
前回の内容
以下がYUV変換(演算式②)となります。
係数は以下の通りとなり、n=16(×65536)でVBAを作成しました。
VBAを使う理由
演算精度を確認する場合、Excelが便利です。
が、一番の理由は視覚的にすぐ修正できる所です。
特に画像処理の場合は、期待値を作るのと同時作業ができます。
補足)
半導体設計では、期待値というものを作る必要があります。
これは設計した回路が正しく出力されているかシミュレーションで確認するためのものです。
回路では並列処理のためFFでバトンリレーのように処理します。
一つでもタイミングがズレていたりすると出力値が違ってくるためそれを確認します。
画像処理では、何万というデータがあります。
その一つ一つを正しく出力しているか、期待値を準備して比較アプリ(コマンド)で確認します。
※目視確認では何万とういうデータを確認する為に時間がかかるため。
(この期待値を作るために、C言語やVBAを自然と勉強してしまいます。)
プログラム構成
テストがしやすいように2階層に分かれています。
各Functionの概要と引数は以下の通りとなります。
Function CalcRGB2YUV
R,G,B成分と係数やClip処理用変数を受け、演算します。
また、上位階層で係数を負数(-???)で設定することにより、U,V成分の演算が可能になります。
記述
VBAプログラムは以下の通りです。
検証
Functionを使用しているため、Excelのユーザー定義マクロとして使用できます。
従って、Excelを使って簡単に検証(デバッグ)が可能です。
確認内容を以下に示します。
「入力値」の各RGB成分使って「通常係数」の場合と「係数×65536」で計算します。
「通常係数」が浮動小数で演算した値を示し、「係数×65536」がCalcRGB2YUVで演算した値となります。
上表より、各YUV成分の整数部の誤差は1と分かります。
Function RGB2YUV
引数Pointに指定したセルの背景色から各RGBを取得し、YUV変換の計算を行います。
記述
VBAプログラムは以下の通りです。
YUV変換で必要な各パラメータは「Const」で指定できるようにしています。
精度を変更する場合でもこのConstの値を変更するだけで良いようになっています。
Y成分は「ValueY」、U成分は「ValueU」、V成分は「ValueV」に代入し、変数Modeの設定値に応じて選択して返します。
検証
入力用のシートを準備し、Y,U,V成分の値を出力し、確認します。
入力画像
入力画像を以下に示します。
簡易的にテストするため、縦横32画素としています。
入力画像は以下の式で生成しています。
「Cells(Y + 2, X + 2).Interior.Color」の記述でセルに色を設定しています。
出力値
RGB2YUV()のPointに入力画像の各セルを指定し、Y成分、U成分、V成分の値をセルに出力し、色付けして確認します。
各結果を以下に示します。
Y成分結果
U成分結果
V成分結果
最後に
画像処理のデバッグ段階では、Excelを使った方が便利です。
画像のように2次元的に見れるのと、シミュレーション結果との違いも色をつけたり、視覚的にとらえることが可能です。
ただ、欠点もあります。
私の場合(半導体設計時)は、ブロック単位のデバッグ時はExcelで期待値を作って視覚的にデータ解析を行います。
その後、C言語を使って大きいサイズの期待値を作成し、期待値比較確認を行うようにしています。
除算回路を使用しない方法
除算回路を使用せずに乗算とビットシフトで演算する方法です。
※C言語では、普通に「/」で記述して問題ないですが、半導体設計では除算IPや自作します。
5個の8bit値の平均値をとる例で説明します。
尚、前回の記事「浮動小数を使用せず演算」とよく似ていますので、その記事も参考して頂ければと思います。
平均処理の演算式①
平均処理の演算式を以下に示します。
※式のA~Eは、8bitの値と示します。
回路構成①
上記演算式①の回路構成を以下に示します。
A値~E値の合計(最大値は1275)を「5」で割るだけですが、この「割る」という処理を自作した回路に処理するか 又は 除算IPを使用します。
また、除算の結果を得るまでに数クロックかかります。
※クロック周波数、半導体ベンダー 又は IPベンダーにより違う為、確認する必要があります。
平均処理の演算式②
除算を使用せず平均処理を行う式を以下に示します。
※「n」はビットシフト量を示します。(固定小数の小数部bit幅を示す)
例えば、
ビットシフト量を18bitと決めてしまうと、係数式「1/5×2^18」となり値は「52428.8」となります。
この「52428.8」の整数値をA値~E値の合計に掛け、その結果を18bitシフトすると除算結果が得られます。
尚、私は「52428.8」の小数点以下四捨五入し、「52429」としています。
演算精度を確認
通常計算した場合と、演算式②の方法で演算した場合との誤差を確認します。
A値~E値の合計の最大値(=255×5)は「1275」となります。
従って、
割られる値(被除数)は0~1275の範囲となるため、Excelで十分確認できます。
以下がExcelで確認した結果となります。
尚、被除数=1~1275の範囲での最大誤差は「0.00097504」となり、小数点以下3桁までは誤差は無いということになります。
回路構成②
演算式②の回路構成を以下に示します。
乗算結果は26bitとなり、上位8bitが整数値となるためFFでラッチして平均値を出力します。
※下位18bitは、1/5を掛けた時の小数部となります。
小数点以下四捨五入したい場合は、TMLT[17]を条件とすれば良いだけとなります。
RTL記述例
演算式②のRTL記述(Verilog-HDL)を以下に示します。
21~25行目は加算器4つ使用するため、CKの周波数でタイミングがMetしなかった場合は、FFを用いて加算を分割する必要がある。
乗算(29行目)は固定値と上位8bitを使用することより、タイミングはMetする可能性はあるが、もしMetしなかった場合は、乗算IPを用いてパイプライン化する必要があります。
最後に
この方法は1つの例です。
ASIC又はFPGAの規模やDSP数などにより、除算IPを使用した方が良い場合もありますので、こういう方法もあると頭の片隅に置いて頂いて、設計検討して頂ければと思います。
浮動小数を使用せず演算
小数を含む演算は、浮動小数点ユニットを使用する為、処理速度が遅くなります。
しかし、その式に乗算とビットシフトを追加することで演算速度が速くなります。
その方法を順を追って、また、ソフトウェア(C言語)とハードウェア(半導体設計)に分けて説明します。
まずは説明する上で、画像処理の1つ「RGB→YUV(YCbCr)変換」をご存じでしょうか?
この変換式を例に以下説明します。
YUVの用途 (ちょっとした用途の説明)
YUVは、人間の目の特性を利用したデータであり、変化を敏感に感じ取る輝度とそうではない色差に分かれます。
従って、
輝度さえしっかりしていれば色差は間引くことが可能とも言えます。
(詳細はネット上に説明があるので、調べてください。)
尚、
Yは、輝度
Uは、輝度信号と青色成分の差
Vは、輝度信号と赤色成分の差
を示します。
この特性を持ったYUVは、画像(静止画)や映像(動画)圧縮(JPEG,MPEG,H.264など)で使われます。
例えば、
カメラで撮った映像(動画)を圧縮して動画ファイルとして保存する場合です。
1、カメラからR(赤),G(緑),B(青)形式で撮ります。
2、Image ProcessorでRGB形式をYUV形式に変換して各圧縮に応じた処理を行います。
3、圧縮後のデータをファイルに保存します。
RGB→YUV変換式
RGBからYUVへの変換式は、以下の通りです。
上記の式では、YUV各成分の係数を足すと1.000になるのが特徴です。
例)
Y成分の係数の和 = 0.299 + 0.587 + 0.114 = 1.000
尚、変換式は圧縮規格を発表している所で変わるため、各勧告書を確認してください。
C言語の記述例①
演算式①をそのままC言語で記述すると以下のようになります。
15~17行目で変換式そのままで演算し、cy,cu,cvを算出しています。
また、係数が小数の為、演算結果もdouble型(倍精度浮動小数)で演算しています。
※double型にすると、64bit演算となります。
※float型の場合は、32bitとなります。
この記述例①のソースでは、CPUはFPU(浮動小数点ユニット)を使用します。
たかがRGB各8bitデータからYUV各8bitデータを求めるのに64bit(or 32bit)浮動小数点ユニットを使用し、
しかも、キャスト演算(unsigned char 、char)で小数部は切り捨てられるにも関わらず。
私としては、浮動小数点ユニットは使用する必要は無いと考えます。
尚、浮動小数演算を使うことにより、1つの演算に時間がかかります。
浮動小数を使わずに演算
では、小数を含む演算を浮動小数点ユニットを使用せず演算する方法は?
※注意事項となりますが、この方法は浮動小数点演算との差は発生します。
ただ誤差も小さいため、製品精度又は画質評価を行って確認してください。
(人間の目なのでわからないかもしれませんが。。。)
方法はすごく簡単です。
各係数を何倍かにして、算出した各YUV成分を倍した分で割るだけです。
尚、
割り算は処理に時間がかかるため、ビットシフトで演算できるよう、各係数を2のべき乗で掛けます。
演算式は以下のようになります。
尚、係数(小数点以下あり)に2のべき乗を掛けた結果は、小数点以下切り捨て又は四捨五入して整数とします。
演算式①の係数を256倍(n=8) 及び 65536倍(n=16)にした場合の係数を以下に示します。
(下の表では、小数点以下を四捨五入しています。)
上記係数を用いて演算した例を以下に示します。
尚、「256で割った値」と「65536で割った値」は誤差を示すため便宜上、小数点以下も表示させています。
上記演算例では、
係数を256倍すると、Y成分の整数部で誤差が発生しています。
この場合、YUV各成分結果を算出する時に小数点以下を切り捨てると、浮動小数計算と1bitの誤差が発生します。
また同様に65536倍した場合も小数部で浮動小数計算と誤差が発生しています。
これらの誤差が製品の許容範囲かどうかを画質評価などで確認する必要があります。
尚、演算式②の「n」を大きくするほど精度は向上します。
※YUV各成分は演算結果の整数部の8bitとなり、画質評価はその8bitの1~2bitの誤差をどこまで許せるかの判断になります。
C言語の記述例②
演算式②でn=16(65536倍)とした場合のC言語記述例を以下に示します。
尚、係数は表「演算式②の各係数」を参照してください。
15~17行目で演算し、cy,cu,cvを算出しています。
この演算では、係数は小数ではなく整数 且つ RGB各成分をInt型にキャスト 且つ ビットシフトで割り算を行っています。
これにより、浮動小数点ユニットを使用する要素は無くなります。
浮動小数演算を使うと演算に時間がかかる
演算式①(記述例①)と演算式②(記述例②)でなぜ演算速度が変わるのか?
です。
浮動小数演算自体、乗算及び除算に対してはスムーズですが、加算及び減算は面倒なのと、整数から浮動小数、浮動小数から整数にするため、フォーマット変換する必要があるためです。(詳細はネット上に説明があるので、調べてください。)
※浮動小数のフォーマットと演算の仕組みが分かれば納得がいくと思います。
演算式①と演算式②で、Y成分を求めるまでの処理の流れを以下に示します。
※処理イメージとなり、CPUやコンパイラにより処理順序など変わります)
大きな違いは、最初に「64bit浮動小数変換」が入ることです。
これにより、演算式②と比べて演算式①は「3つの処理」が増えるため、処理が遅くなります。
この「3つの処理」があることにより、
4Kテレビの場合では、縦横の画素数が3,840×2,160であり、計8,294,400(約800万)画素となり、演算式②に対して演算式①は24,883,200(=8,294,400×3)回数処理が余分にあることになります。そのためすごく遅く感じることとなります。
※処理クロック数でみると、整数演算と浮動小数演算とでも違いがあります。
この違いは、CPUの仕様を確認してください。
RTL設計
上記まではソフトウェアを中心とした説明でしたが、ここからは半導体設計の場合で説明します。
半導体設計では、CPUを使わずRGB→YUV変換を自作します。
浮動小数演算で設計することも可能ですが、カメラ速度と出力速度を考慮すると演算式②で論理回路を作成します。
回路構成
演算式②の演算構成は、以下の通りとなります。
尚、先の「演算式①と演算式②の処理の流れ」と分かりやすいようにするため、各演算は2入力としています。
※上図はY成分を算出する構成で記載していますが、U,Vも同じ構成 且つ Y成分構成と並列で処理したり、クロックの速度を3倍にしてシリアルで処理したりします。
verilog-HDLの記述例
演算式②のverilog-HDL記述例を以下に示します。
65行目でClip処理を行っていますが、
係数の和が1(65536)となるようにしているため、TRGBが0xFF0000以上の値になることはありませんが、例として記述しています。
尚、Y成分は負数となることがありません。
タイミング図
RTL図のタイミング図を以下に示します。
最後に
係数が小数の場合、この方法で浮動小数点ユニットを使用せず計算できます。
それにより、処理速度も速くなります。
補足)
×2^nは固定小数と同じ考え方となります。
例えば
2.12345の固定小数(小数部8bit)を求める場合、以下の式で求めます。
小数部8bitの固定小数を求める場合、2.12345×256=543.6032となり、
この整数部の下位8bitが固定小数の小数点以下8bitとなります。
(543=0x21F → 固定小数を2進数で示すと「10.00011111」となります。)
結局のところ、演算式②は固定小数点演算しているのと同じになります。
リセット回路について
ASICでも、FPGAでもリセット回路(非同期リセット)について気を付ける必要があります。
(ASICではDFTも考慮する必要があります。)
製品の使用環境と基板上に関して、入力端子リセットにノイズが乗るのかなど、予め確認してからリセット構成を検討します。
リセットラインを考慮していない回路例
今はもう入力端子クロック(CLOCK)と入力端子リセット(RESET_N)を直接FFに繋げることはないと思っています。
(1系統のクロックでも入力端子クロックと同一周波数でPLLを介している場合もあるかと思います。)
この例では、
1系統のクロック(CLOCK)を、PLLを介して2系統(C0とC1)にしている例で説明します。
尚、CLOCK、C0、C1の周波数は全て違うものとしています。
以下がリセットライン(LOCK)を考慮しない場合のRTL図となります。
上図では、クロックドメイン(C0、C1)に対して、リセットラインが1本だけです。
このような場合、LOCKがLow→High(PLLがロックする)になった時に誤動作が発生する可能性があります。
例えば、
図中のUser Logicにカウンタやステートマシンがある場合、意図しない所から開始する可能性があります。
FFのリセットにもSetup/Holdがある
FFのリセット端子にも、クロックに対してSetup/Holdの規定があります。
下図のCKはクロック、RはFFのリセット端子を示します。
特に、リセット解除は気を付ける必要があり、クロックの立ち上がり付近でR(LOCK)がLow → Highになると、メタステーブルが発生します。
FFの非同期リセット端子はクロックに同期させる
クロックドメインに対応するリセットを生成し、各FFの非同期リセット端子に接続します。
FPGA内では、何系統ものクロックラインが存在すると思います。
その分のクロックに同期したリセットを生成する必要があります。
下図が「Reset Logic」を挿入した例になります。
「Reset Logic」で非同期対策しつつ、各クロックに同期したリセット信号を生成します。
これにより、RST0_N及びRST1_NはRecovery Time / Removal Timeを考慮して生成されます。
(合成ツールにもよりますが。。。ご使用する合成ツールを確認してください。)
注意事項)
上図は、一例となります。
使用環境と基板上のクロック精度やリセットなどの条件を確認して構成を検討する必要があります。
尚、上図では、入力端子RESET_Nにノイズが乗る(一瞬だけLowになる)と、PLLクロック(C0,C1)がリセットされ、且つ LOCKがLowになり、「Reset Logic」のRST0_N及びRST1_NがLowとなり、「User Logic」もリセット状態になります。
(動作中にノイズのせいでリセットがかかる)
リセット解除直後から動作する回路は極力避ける
上記リセット回路を作成し、且つ リセット解除直後から動作する回路を設計しないように私は心がけています。
これは非同期リセットとは別にレジスタで同期リセットできるよう設計しています。
(リセット解除後、CPUがレジスタを1にしないと動作しない回路にし、2段階のリセットをしています。)
最後に
リセット回路は疎かにしがちかと思います。
特に外部(基板上)の確認は必要と思っています。
また、
参考書にはあまり載ってないように思っています。
SPI(マスタ側)対向モデルの記述例
対向モデルのSPI(マスタ側)の記述例です。
このモデルもUART同様、モデル内のACパラメータ(real)とタスクを実行する構成となっています。
タイミング
先にタイミング図を示します。
このSPIは、CSがネゲート時、CK(シリアルクロック)はLowとなり発振しません。
データもMSBファーストで通信します。
tsCS
SPIデータシートによくあるAC特性だと思います。
後述の記述例では、10nsとしています。
thCS
SPIデータシートによくあるAC特性だと思います。
後述の記述例では、10nsとしています。
tcCK
SPIデータシートのクロック周波数やHigh幅+Low幅となります。
MOSIの確定周期と同等となります。
tsMO
SPIデータシートのデータセットアップ時間に当ります。
これは、tpCKまたはtnCKと同等となります。
tpCK
CK周期の変化からCKが立ち上がり時間となります。
tnCK
CK周期の変化からCKが立ち下がり時間となります。
tlMI
CK周期の変化からMISOのデータを取り込むタイミングを時間で指定します。
考え方
データの変化点から tsMO分遅延してCKが変化する考え方になります。
記述例
以下に記述例を示します。
8行目から17行目までが上位階層で変更可能な設定値となります。
また、22行目のP_SPI_BITは1回(CS↓からCS↑)の転送ビット数となります。
CS動作
CSアサートがLowの時、16行目をHigh(現在の値)にします。
アサート論理がHighの場合は、Lowにしてください。
これは、CSの初期状態を設定するもので、33行目と50行目で反転させます。
CK動作
初期状態は17行目で設定します。これは、CSがネゲート状態時の論理となります。
動作中は、36~42行目のfork join文内で実行されます。
Low → High → Lowと動く場合の例となり、High → Low → Highとなる場合は、17行目のHighに設定し、tpCKとtnCKを逆の設定にすることで逆になります。
MOSI動作
初期値は不定値です。
動作中は、36~42行目のfork join文内で実行されます。
タスクAcSPIの引数TxDataをMSBファーストで出力します。
不定値やHi-ZなどもTxDataに代入しておくことでそのまま出力します。
MISO動作
本対向モデルの入力値になります。
tlMIのタイミングでRxDataに代入します。
タスクAcSPIを呼び出し時、引数RxDataの必要なビットを上位階層で使用できるようにタスク内では処理はしていません。
最後に
forkjoin文を使うことにより、自由にCKのエッジを変更しています。
タスクも準備しておき、上位階層(呼び出し元)で自由に使用できるようにしています。