はじめに
初歩の説明ということでまずはこのテーマで。
前提
以下の経験がある前提で話を進めます。
- FPGAを触ったことがある。
具体的なレベル感としては組み合わせ回路や順序回路とかがわかれば大丈夫です。 - C/C++を少し触ったことがある。
書いてあることがなんとなくわかるレベルであれば大丈夫です。
動作環境
PC:M4 Macbook Pro(Sequoia 15.1)
IDE:VitisHLS 2023.1
※皆さんはWindows PCでやってくださいね
Vitis Unified IDEという最新のIDEがありますが、今回はそれを使用しません。
割と最近でてきたIDEなのでバグがあるのかなと思ってまだほとんど使用したことないです。
2024Verなら大分安定しているのかな?
ソースコード
全体のソースコードは以下です。
Vitis HLSの使い方
最低限の説明だけします。
プロジェクト生成方法
- Vitis HLSを起動する。コマンド
vitis_hls
を入力。 - 「Create Project」を押下する。
- 「Project name」と「Location」を入力する。
- 「New Vitis HLS Project」画面は「Next」を押下する。(2回出てくるので両方とも)
- 「...」で対象のデバイスを選択できるため設定する。他は必要に応じて設定する。
ソースコードの登録方法
- ソースコード(テストベンチも)の用意をする。
- 「Source」を右クリックし、「Add Source File...」を押下し、ソースコードを登録する。
- 「Test Bench」を右クリックし、「Add Source File...」を押下し、ソースコードを登録する。
- プロジェクトを選択して、右クリックを押下し、「Project Settings...」を押下する。
- 「Synthesis」を選択し、Top Function欄にTop階層となる関数を設定する。
合成/シミュレーションのやり方
やり方を説明する前に工程の説明をする。
工程 | 説明 |
---|---|
C Simulation | C言語レベルでのシミュレーションを行う |
C Synthesis | 合成を行う(この時点でRTLが生成される) |
CoSimulation | FPGAレベルでのシミュレーションを行う |
- 「緑の再生」的なボタンの右の下三角形を押下する。
- 「C Simulation」を押下する。下の画面が表示されるので、「OK」を押下する。
「Options」は任意でチェックする。
デバッグをしたい場合は「Launch Debugger」をチェックする。
「Clean Build」は差分ビルドをせずに、全ビルドを行う。
- 「C Synthesis」を押下する。下の画面が表示されるので、「OK」を押下する。
- 「CoSimulation」を押下する。下の画面が表示されるので、「OK」を押下する。
「Vivado XSIM」と書いてあるのは、Vivadoを使ってシミュレーションするという意味です。
横の三角形を押下すると他のシミュレーションが出てきます。
「Dump Trace」に「all」を入れることにより、シミュレーション結果をダンプできます。
以上。では次から本題に。
組み合わせ回路
以下の組み合わせ回路を例とします。
ソースコード
void CombinationalCircuit(bool a, bool b, bool c, bool *d)
{
#pragma HLS INTERFACE ap_none port = a
#pragma HLS INTERFACE ap_none port = b
#pragma HLS INTERFACE ap_none port = c
#pragma HLS INTERFACE ap_none port = d
#pragma HLS INTERFACE ap_ctrl_none port = return
*d = a && b && c;
}
解説
- 入出力について
関数の引数が入出力になります。型についてはC言語の型をそのまま記載できます。
C/C++の値渡しをしたら入力になり、参照渡し(ポインタ)にしたら出力となります。
そしてTOP階層の関数には #pramga の定義を記載します。
HLS INTERFACE
プラグマはIFのプロトコルなどを設定するものになります。
今回のap_none/ap_ctrl_none
はプロトコルなしの純粋なIFという意味になります。 - 処理について
基本的にはC/C++と同じです。
今回の場合だとAND回路を作りたいので、&&
演算子を記載するだけです。
合成結果
組み合わせ回路のみなので、FFが使用されず、LUTのみ使用されています。
入出力も意図した通りになっています。
シミュレーション
シミュレーションのソースコードは制限なしでフルのC/C++機能を使用できます。
void GoldenModel(bool a, bool b, bool c, bool *d)
{
*d = a && b && c;
}
int main()
{
std::vector<bool> a_values = {true, false};
std::vector<bool> b_values = {true, false};
std::vector<bool> c_values = {true, false};
bool d, d_golden;
for (bool a : a_values)
{
for (bool b : b_values)
{
for (bool c : c_values)
{
CombinationalCircuit(a, b, c, &d);
GoldenModel(a, b, c, &d_golden);
if (d == d_golden)
{
std::cout << "Test passed!" << std::endl;
}
else
{
std::cout << "Test failed!" << std::endl;
}
}
}
}
return 0;
}
期待通りに最初だけTrueが出力され、その後はFalseが出力されていますね。
順序回路
以下の順序回路を例とします。
ソースコード
#include "sequential_circuit.h"
void SequentialCircuit(bool a, bool *c1, bool *c2)
{
#pragma HLS INTERFACE ap_none port = a
#pragma HLS INTERFACE ap_none port = c1
#pragma HLS INTERFACE ap_none port = c2
#pragma HLS INTERFACE ap_ctrl_none port = return
static bool ff1 = false;
static bool ff2 = false;
ff2 = ff1;
ff1 = a;
*c1 = ff1;
*c2 = ff2;
}
解説
- 入出力について
順序回路なので、クロック&リセット信号がいるのでは?と思いますが、これは意識しなくても大丈夫です。
ツール側が上手いことやってくれます。 - 処理について
まず、static宣言ですがこれを付与することによりその変数がFFとして定義されます。RTLで言えばregになります。
付与しなくてもFFになることはありますが、それはツール任せという結果になります。
また、staticの初期化ですがこの書き方をするとPORになります。
合成結果
FFが使用されていますね。
入出力も意図した通りになっています。
シミュレーション
#include <iostream>
#include <vector>
#include "../src/sequential_circuit.h"
void GoldenModel(bool a, bool *c1, bool *c2)
{
static bool ff1 = false;
static bool ff2 = false;
ff2 = ff1;
ff1 = a;
*c1 = ff1;
*c2 = ff2;
}
int main()
{
std::vector<bool> a_values = {true, false};
// テストベンチ
for (bool a : a_values)
{
bool c1, c2;
SequentialCircuit(a, &c1, &c2);
bool golden_c1, golden_c2;
GoldenModel(a, &golden_c1, &golden_c2);
std::cout << "Input: " << a << ", Output: (" << c1 << ", " << c2 << "), Golden: (" << golden_c1 << ", " << golden_c2 << ")" << std::endl;
if (c1 != golden_c1 || c2 != golden_c2)
{
std::cout << "Mismatch!" << std::endl;
return 1;
}
}
std::cout << "All tests passed!" << std::endl;
return 0;
}
これも問題ないですね。
おまけ
先ほどの順序回路でff2
から代入していましたよね。
なぜこの順番なのか、ff1 = a
じゃダメなんですか?と思いますが、結論言うとダメです。
↓これじゃダメ?
ff1 = a;
ff2 = ff1;
では試しにこれで作ってみましょう。
合成結果は上記です。あれ?FFがなくなっている…
どうなってるんだ、という感じですよね。
とりあえず次にシミュレーションしてみましょうか。
あれれ?これ組み合わせ回路になってない??と思いましたよね。
そうなんです、これは組み合わせ回路ができあがります。
なぜあの書き方をすると組み合わせ回路になってしまうのかですが、理由は簡単です。
ツール側で最適化が働いているからです。
ff1 = a;
ff2 = ff1;
これだとff2
に関しては、ff2 = a
とも読み取れますよね。
だからツール側が、「ff1
いらなくね?てかff2
もいらなくね?じゃあ入力をそのまま出力でよくね?」となる。
そして組み合わせ回路ができあがります。
では実際に生成されたverilogを見てみましょう。
やっぱり入力がそのまま出力として出ていますね。ということです。
おわり
これにてお終い。