マイPlatform Designerコンポーネントを作ろう

# はじめに

こんにちは。
J-7SYSTEM WORKSの長船です。
今年はデバイスの入手難が続いており、出来合のターゲットボードでやりくりする事が多くなって大変です。

今回のアドベントカレンダーではPlatform Designerのコンポーネントについての説明と、使いやすくするTips集を紹介したいと思います。
本記事ではPlatform Designerで何度かカスタムコンポーネントを作ったことがある中級以降向けの内容となります。

また今回の記事内容は、QuartusPrime 16.1以降であれば共通に使える内容となっています。
文献などは記事の最後に紹介していますので、そちらも参照していただればと思います。

![pd_splash.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701335/d41e8d49-10df-12a8-34f5-ee555d680fc3.png)

# Platform Designerコンポーネントの構成

まずはPlatform Designerでコンポーネントとして識別されるファイルについておおまかに説明しましょう。
Platform Designerで使われるコンポーネントはHDLとTclファイルのセットで構成されています。
Tclファイルはコンポーネント一式があるフォルダの下に`<コンポーネント名>_hw.tcl`という名称で存在します。決まった作法はありませんが、次のようなフォルダ構成にするのが一般的です。

```:フォルダ構成
<コンポーネントフォルダ>
  ├─ foo_hw.tcl   ← コンポーネント定義用tclファイル
  └─ <hdl>        ← 構成HDLファイルを格納するフォルダ
      ├─ foo_top.vhd
      ├─ foo_bar.vhd
      :
      :
```

HDLファイルはQuartusのプロジェクトに追加されるごく一般的な形式のものです。ただしPlatform Designerでファイルの自動生成やパラメーターの編集を行う都合上、運用や記法に独特の制限があります。
Platform Designerではコンポーネントのファイル名、同一性の識別を単純にファイル名で行っています。生成する際にコンポーネント構成ファイルをすべて一つのIPコアとしてまとめてしまうので、別々のコンポーネントに同じ名前のファイルがあったときにそれを解決できません。さらにHDLのエンティティ名（モジュール名）も同様で、そもそもHDLではローカルの名前空間を持てないのでこのような場合は人力でユニーク名を付ける必要があります。
これらの名前が衝突していた場合、Platform Designerの生成でエラーが出たり、Quartusでのコンパイル途中でエラーが出たりと、かなり後になって問題が発覚することになります。
このような手戻りを少なくするため、ある程度長いコンポーネント名、例えばメーカーや上位ライブラリ名をプリフィクスにしたものを全てのファイルに適応するという運用がよく行われています。
ツールのエラーメッセージや挙動からは原因が推測しにくいために、仕組みを知らないと問題解決に時間がかかってしまいます。

さて、Platform Designerコンポーネント中での属性や設定記述は都度オブジェクトを指定して属性を設定していく記法を採っています。
このような場合はブロック構造を使ってまとめるのが一般的な記法なのですが、これはTclの都合に引っ張られている部分もあり文法上はどこにでも書けてしまいます。
実際、コンポーネントのなかには上記の構造を無視して記述しているのも多数あり、これがわかり難くしている一因でもあります。

そこで、コンポーネントのTclファイルは先頭から

* **モジュールプロパティ部分**
* **ファイルセット部分**
* **パラメーター部分**
* **インターフェース部分**
* **プロシージャ部分**（ない場合もある）

といったルールでまとめるようにします。各ブロックは便宜上名付けたもので、正式になにか決まった名称があるわけではありません。
同様に構造も何かで決められたものではありませんが、Component Editorで生成したTclファイルは上記の構造で生成されているのでここではこのような記法を基準としましょう。
リファレンス中ではこの構造のうちプロシージャより前の部分、つまり関数でない部分を"Main Program"と呼びます。


### モジュールプロパティ部分

Tclファイルの最初に記述される、Platform Designer上で管理される情報や動作を指示する部分です。
`set_module_property`でコンポーネントそのものに対するプロパティを設定します。Component Editorで生成するとデフォルトの設定値も含めていくつか記述されます。
例として下記のようなコンポーネントで、キーポイントになる部分を説明していきましょう。

```:モジュールプロパティ部分
package require -exact qsys 16.1
set_module_property NAME peridot_sdif
set_module_property GROUP "PERIDOT Peripherals"
set_module_property DISPLAY_NAME "PERIDOT SD Filesystem interface"
set_module_property DESCRIPTION "PERIDOT SD Filesystem interface"
set_module_property AUTHOR "J-7SYSTEM WORKS LIMITED"
set_module_property VERSION 17.1
set_module_property VALIDATION_CALLBACK validate
set_module_property INTERNAL false
set_module_property OPAQUE_ADDRESS_MAP true
set_module_property INSTANTIATE_IN_SYSTEM_MODULE true
set_module_property HIDE_FROM_SOPC true
set_module_property HIDE_FROM_QUARTUS true
set_module_property EDITABLE false
```

最初の行はPlatform Designerのパッケージ依存に関する宣言です。過去に何度かインターフェース部分の改訂があり、後方互換や識別のために使われます。
古いバージョン用のコンポーネントをメンテナンスする場合でもないかぎり最新のものを指定します。現在は16.1が最新です。

次の`NAME`プロパティではコンポーネントの名称を設定します。この名称が同じコンポーネントは同一のものとして扱われるため、ユニークな名前になるようある程度長い名称が望ましいです。
この名称はPlatform Designerでペリフェラル名やインスタンス名のデフォルト文字列としても使われます。そのためスペースやTclで機能を持つ記号、多バイト文字などは使えません。
Platform Designerではペリフェラル名の大文字・小文字は原則として区別しません。ただこれはツール運用上そうしているだけで、個別のファイル（例えばVerilogソースなど）では区別されます。
そのためか名称の文字列は小文字で記述するのが暗黙の了解のようです。
またここに限らず、Tclソース中では多バイト文字はコメントであっても使うことができません。これはUTF-8エンコードであってもソース中に多バイト文字が入っているとエラーになるので注意しましょう。

続く`GROUP`プロパティはIP Catalog上で所属するグループ名を記述します。同じ名称の`GROUP`プロパティを持つコンポーネントはIP Catalog上でディレクトリツリー状にまとめられて表示されます。
`DISPLAY_NAME`プロパティはIP Catalog上で表示される名称です。`DESCRIPTION`や`AUTHOR`はコンポーネントの付加情報として利用されます。
これらの名称には""で囲ってスペースを入れることができます。

`VERSION`プロパティでは対応するPlatfform Designer（QuartusPrime）のバージョンを設定します。ここの設定値はQuartusPrimeのIPバージョン情報やNiosII EDSのペリフェラルバージョン情報として利用されます。
アップデートで使う識別以外の意味はないのでユーザーが自由に使っても問題ありませんが、QuartusのIP Upgradeでコンポーネントの自動アップデートをするときはこのプロパティが参照されます。

`VALIDATION_CALLBACK`プロパティは検証イベント（バリデーションイベント）が発生したときに呼び出すプロシージャ名を記述します。
GUI上で設定値が変更されるとここで指定したプロシージャが呼ばれます。パラメーターの計算をしたり、範囲チェックをして警告あるいはエラーを発生させたり、EDS側へ追加情報を引き渡したりと、インテリジェンスなコンポーネントを作るのに出番の多いプロパティです。
ここでは使っていませんが、似た機能を持つコールパックに`ELABORATION_CALLBACK`プロパティがあります。こちらはインターフェースの変更があった場合に呼び出されます。

以降は内部的なプロパティが続きます。Component Editorで生成したままであれば特に触る必要はありませんが、カスタマイズしておくと便利なものもいくつかあります。
`HIDE_FROM_QUARTUS`プロパティはQuartus側のIP Catalogリストから隠すかどうかを設定できます。Avalon-MMペリフェラルなどは単体で使えないので、これに`true`を指定しておくとPlatform Designerのみで表示されるようになります。
`EDITABLE`プロパティはPlatform DesignerのGUI上でコンポーネント編集の許可するかどうかを指定します。
Component Editorでのみ編集操作する場合はそのままでも構いませんが、Tclファイルを直接記述していくようになると不用意にComponent Editorで編集されては困る状況も多いため、通常は`false`を指定しておきます。


### ファイルセット部分

コンポーネントのファイル構成を設定する部分です。例は代表的なファイルセット部分の構成で、`add_fileset`,`set_fileset_property`,`add_fileset_file`の３つからなります。
これもComponent Editorで生成したもののうちよく使う部分を説明していきます。

```:ファイルセット部分
add_fileset QUARTUS_SYNTH QUARTUS_SYNTH "" ""
set_fileset_property QUARTUS_SYNTH TOP_LEVEL peridot_sdif
set_fileset_property QUARTUS_SYNTH ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property QUARTUS_SYNTH ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file peridot_sdif.vhd VHDL PATH hdl/peridot_sdif.vhd TOP_LEVEL_FILE
```

`add_fileset QUARTUS_SYNTH QUARTUS_SYNTH`は生成時に呼び出されるファイルセット専用のプロシージャを指定します。HDLを動的に生成したりパラメーターによって組み込むファイルを切り替えたい場合に使いますが、ほとんどの場合は例のように呼び出し無しの設定です。このときは後続（Main Program中）でHDLファイルを指示します。

`set_fileset_property QUARTUS_SYNTH TOP_LEVEL`はコンポーネントのトップレベルのエンティティ名を指示します。HDL側のトップレベルの名称と一致していなければなりません。

`add_fileset_file`でコンポーネントで使うHDLファイルを指示します。形式は次の通りです。

`add_fileset_file <モジュール内でのファイル名> <ファイル種別> PATH <ファイルパス> [TOP_LEVEL_FILE]`

Platform DesignerでGenerateした時に、モジュールの構成ファイルは原則として一つのフォルダにコピーされてIPパッケージになります。このときに使われるファイル名が<モジュール内でのファイル名>です。普通はファイルパス先のファイル名と同名を指示します。
ファイル種別はソースに合わせて`VHDL`,`VERILOG`,`SYSTEM_VERILOG`を指定します。HDL以外にも`HEX`,`MIF`でメモリ初期化ファイルを、`SDC`でタイミング制約ファイルを指定することもできます。
`PATH`に続いてファイルパスを指定します。パスのフォルダ区切りは`/`(スラッシュ)で指定します。Component Editorで生成するとここがプロジェクトフォルダ直下からのパスになるため、まず最初に修正する部分になります。
コンポーネントを構成するファイルが複数になる場合は必要数だけ`add_fileset_file`で指定していきます。また`TOP_LEVEL_FILE`プロパティはコンポーネントのトップレベルになるファイルのみにつけます。


### パラメーター部分

パラメーター部分はPlatform Designer上に表示されるGUIを設定する部分で、ある意味でもっとも「IPコンポーネントらしさ」に直結するものです。ユーザーコンポーネントを作る場愛は一番手を加える部分の多くなる場所でしょう。
構成としては`add_parameter`でパラメーターのオブジェクトを追加して`set_parameter_property`でそのプロパティを設定するという単純なものなのですが、ブロック構造を取れないのと数が多いのとで見た目がかなり冗長になっているのが正直面倒なところです。
パラメーターのプロパティは多くのパターンがありますが、比較的単純な例を挙げてみましょう。

```:パラメーター部分
add_parameter DEFAULT_REG_CLKDIV INTEGER 255
set_parameter_property DEFAULT_REG_CLKDIV DISPLAY_NAME "Default value of clock divider(CLKDIV)"
set_parameter_property DEFAULT_REG_CLKDIV ALLOWED_RANGES 0:255
set_parameter_property DEFAULT_REG_CLKDIV HDL_PARAMETER true

add_parameter HOSTUART_BAUDRATE integer 115200
set_parameter_property HOSTUART_BAUDRATE DISPLAY_NAME "UART baudrate"
set_parameter_property HOSTUART_BAUDRATE UNITS bitspersecond
set_parameter_property HOSTUART_BAUDRATE ALLOWED_RANGES {38400 57600 115200 230400 460800 921600}
set_parameter_property HOSTUART_BAUDRATE HDL_PARAMETER true
```

`add_parameter <パラメーター名> [<型> [<初期値>]]`でパラメーターを追加します。型でサポートされているは多くありますが、よく使われるものは`integer`,`boolean`,`string`あたりです。型を指定した場合はさらに初期値を与えることができます。

生成したパラメーターには`set_parameter_property <パラメーター名> <プロパティ> <値>`でさらに細かいプロパティを設定できます。よく使うものを紹介しましょう。
`DISPLAY_NAME`プロパティはGUI上に表示するテキストを指定します。省略した場合はパラメーター名が表示されます。
`UNITS`プロパティはGUI上のパラメーターの後ろ側に表示する単位を指定します。`hertz`(Hz),`kilohertz`(kHz),`megahertz`(MHz),`bitspersecond`(bps),`microseconds`(us)など、いろいろな単位表示をサポートしています。
`ALLOWED_RANGES`プロパティはパラメーターに入力できる値の範囲を設定します。数値範囲を設定する場合は`0:255`のようにセミコロンを挟んで下限値と上限値を記述します。例の下のように複数の数値を`{}`でくくるとそれらを選択するプルダウンリスト入力にすることができます。
`HDL_PARAMETER`プロパティはHDLに引き渡すパラメーターかどうかを指示します。`true`を設定した場合、パラメーターの値はGenerate時にコンポーネントのインスタンスパラメーターとして埋め込まれます。このプロパティを使う場合は、`add_parameter`のパラメーター名とHDL側のgenericラベル名（Verilogの場合はparameter名）を一致させておかなければなりません。このときGUI側ではパラメーター名は大文字で識別されるため、HDL側がVerilogの場合はparameterラベルは大文字で宣言しておく必要があります。


### インターフェース部分

Platform Designerコンポーネントがどのようなインターフェースを持つかを宣言する部分です。
基本は`add_interface`,`set_interface_property`,`add_interface_port`の３つで構成され、EDSなどに与える情報がある場合は`set_interface_assignment`も使われます。
Avalon-MMペリフェラルではインターフェースの数が多くなり、プロパティも増えるため分量が多くなる箇所です。
とはいえ、パラメーターでインターフェースの種別を切り替えるような複雑なコンポーネントでないかぎり、Component Editorで生成したものをそのまま流用して十分なので例は省略します。

```:インターフェース部分
add_interface clock clock end
set_interface_property clock clockRate 0
add_interface_port clock csi_clk clk Input 1

add_interface reset reset end
set_interface_property reset associatedClock clock
set_interface_property reset synchronousEdges DEASSERT
add_interface_port reset csi_reset reset Input 1

add_interface s1 avalon end
set_interface_property s1 addressUnits WORDS
set_interface_property s1 associatedClock clock
set_interface_property s1 associatedReset reset
set_interface_property s1 bitsPerSymbol 8
set_interface_property s1 burstOnBurstBoundariesOnly false
set_interface_property s1 burstcountUnits WORDS
set_interface_property s1 explicitAddressSpan 0
set_interface_property s1 holdTime 0
set_interface_property s1 linewrapBursts false
set_interface_property s1 maximumPendingReadTransactions 0
set_interface_property s1 maximumPendingWriteTransactions 0
set_interface_property s1 readLatency 0
set_interface_property s1 readWaitTime 1
set_interface_property s1 setupTime 0
set_interface_property s1 timingUnits Cycles
set_interface_property s1 writeWaitTime 0
add_interface_port s1 avs_address address Input 2
add_interface_port s1 avs_read read Input 1
add_interface_port s1 avs_readdata readdata Output 32
add_interface_port s1 avs_write write Input 1
add_interface_port s1 avs_writedata writedata Input 32
set_interface_assignment s1 embeddedsw.configuration.isFlash 0
set_interface_assignment s1 embeddedsw.configuration.isMemoryDevice 0
set_interface_assignment s1 embeddedsw.configuration.isNonVolatileStorage 0
set_interface_assignment s1 embeddedsw.configuration.isPrintableDevice 0
　：
　：
（省略）
```

インターフェース部分についてはさわりだけの説明にします。
`add_interface`でインターフェースのオブジェクトを生成し、`set_interface_property`で該当インターフェースのプロパティを設定し、`add_interface_port`でコンポーネントの入出力ポートを設定しています。


### プロシージャ部分

プロシージャ部分はPlatform DesignerやGUIのイベントで呼び出されるコールバック関数で、この中は一般的なTclコードになっています。
またプロシージャの中でのみ使えるTclコマンドもあり、HDLでは作り込みにくいパラメーターのエラーチェックや設定値の計算など、うまく使いこなすと楽ができる部分でもあります。
例では設定されたクロックとUARTのビットレートから、設定値が範囲外になるエラーやエラー率が規定値を超えるワーニングを出す部分を抜粋しています。

```tcl:プロシージャ部分
proc validate {} {
	set uart_clock [get_parameter_value AVM_CLOCKFREQ]
	set uart_baudrate [get_parameter_value HOSTUART_BAUDRATE]
	set uart_divcount [expr (ceil($uart_clock / $uart_baudrate)) - 1]
	set uart_maxdiv [expr (ceil(pow(2,12))) - 1]

	if {$uart_clock == 0} {
		send_message error "avmclock signal clock frequency is unknown."
	} else {
		if {$uart_divcount > $uart_maxdiv} {
			send_message error "This baudrate is not supported. avmclock signal is changed to the slow clock frequency."
		} elseif {$uart_divcount < 8} {
			send_message error "This baudrate is not supported. avmclock signal is changed to the fast clock frequency."
		} else {
			if {[expr (abs($uart_baudrate - ($uart_clock /($uart_divcount + 1)))*100)/ $uart_baudrate] > 1.0} {
				send_message warning "An error of this baudrate is more than 1%. Recommend to change a clock frequency or baudrate."
			}
		}
	}

　：
　：
```

プロシージャ内部で頻繁に使われるのが`get_parameter_value`でしょう。このコマンドは指定のパラメーターに設定された値を取得することができます。
`VALIDATION_CALLBACK`でプロシージャ呼び出しが設定されているとパラメーターに変更があるたびにプロシージャが呼び出されるため、このようなチェックを行うことが容易にできます。

`send_message <レベル> <テキスト>`でGUI側にレベル付きメッセージを送ることができます。<テキスト>の文字列がGUI下のコンソール部分に表示されます。よく使われるレベルは`info`,`warning`,`error`です。`error`レベルのメッセージを送るとGenerateが出来なくなります。
Tclでテキスト表示にはputsが使われますが、Platform Designerのコンポーネントでは標準入出力のputsはブロックされてしまい使うことができません。そこでデバッグでプロシージャ内部の変数を表示したい場合などには`send_message info <テキスト>`で代用します。



# コンポーネントカスタマイズのTips

さて、コンポーネントの定義Tclファイルの構造が大まかに掴めたところで、いいかんじのマイPlatform Designerコンポーネントを作るために役立つTipsを紹介していきたいと思います。
先ほども説明したとおり、コンポーネントTclファイルはComponent Editorを使ってHDLファイルから作成することができます。しかしそのままではIPコンポーネントとして独立していませんし、Platform Designer側で管理している情報と連携したり、ユーザーフレンドリーな設定やエラーハンドリングができていません。なにより見てくれが今ひとつです。
そこで、Tclに手を入れる部分としてそのあたりの修正からスタートしてきましょう。


### IPコンポーネントの独立性を良くする

Component Editorで作ったままの状態は「ひとまずPlatform Designerからはコンポーネントとして認識されるようになった」程度でしかありません。このままではプロジェクトローカルなコンポーネントとしてしか使えません。どうしてかというと、Component Editorで生成される定義ファイル（～_hw.tcl）は**プロジェクトフォルダ直下に置かれる**ので、そのままではパッケージ化ができません。


```:フォルダ構成の例
<プロジェクトフォルダ>
  ├─ fpga_top.qpf
  ├─ fpga_top.qsf
  ├─ fpga_top.qws
  ├─ hoge_module.qsys ← Platform Designerのモジュール
  ├─ foo_component_hw.tcl ← コンポーネント定義ファイルがここにある
  ├─ <ip>
  │   └─ <foo_component>  ← このフォルダの下に完結させたい
  │       └─ <hdl>
  │           └─ foo_top.v
  :
  :
```

とはいってもそれほど難しいことはありません。コンポーネント定義ファイルは`ip`フォルダ以下のフォルダに格納しておけば自動で検索してくれますので配置場所にそれほど制約はありません。
`add_fileset_file`の`PATH`プロパティで指定されているパスをコンポーネントトップからの相対パスに書き換えれば済みます。

```tcl:ファイルパスの修正
add_fileset_file foo_top.v VERILOG PATH ip/foo_component/hdl/foo_top.v TOP_LEVEL_FILE
  ↓
add_fileset_file foo_top.v VERILOG PATH hdl/foo_top.v TOP_LEVEL_FILE
```

これで今後他のプロジェクトで再利用したい場合でも、コンポーネントフォルダをそのプロジェクトローカルの`ip`フォルダにコピーするだけで使い回せるようになります。
いちいちコピーするのが面倒くさいという場合は、ライブラリフォルダをどこかに用意してPlatform DesignerのIPフォルダ検索にパスを追加して参照させるような使い方もできます。


### 定義Tclファイルのリロード

基本的にコンポーネントの定義TclファイルはPlatform Designer起動時に読み込まれるだけですが、コンポーネントTclファイルのデバッグを進めていく場合、書き換えたTclの内容を再度読み込ませたい状況が頻繁に発生します。
こういう場合に**いちいち再起動するのはめっちゃ面倒**なので、`F5`キーで定義ファイルのリロードを行わせます。Fileメニュー→Reflesh Systemのショートカットキーですが、Tclソース編集中は最もお世話になる機能であることに間違いありません。


### パラメーターの見た目を変更する

GUI上に入力（パラメーター）をするには`add_parameter`を使います。Component EditorでHDLから定義Tclを生成すると、HDLのgeneric（またはparameter）への入力をもつパラメーターが定義されますが、こうして作られるパラメーターに名前と型だけでのシンプルな入力ボックスです。

先ほども説明に出てきましたが`set_parameter_property`でパラメータープロパティを追加することで装飾を行う事ができます。よく使われるプロパティをいくつか紹介しましょう。

* `DISPLAY_NAME`プロパティ
パラメーター左側に表示される文字列を指定します。簡単な説明文を追加します。

* `DISPLAY_UNITS`プロパティ
パラメーター右側に表される文字列を指定します。`UNIT`プロパティとは異なり、単純な文字列の扱いです。

* `DESCRIPTION`プロパティ
パラメーターにカーソルを乗せると表示される詳細情報ボックスの文字列を指定します。

* `UNIT`プロパティ
パラメーターの単位を指定します。パラメーターボックスの右側に該当の単位が表示されます。設定できる代表的な値は次のようなものがあります。

|値|表示単位|
|-----|-----|
|nanoseconds|ns|
|microseconds|us|
|milliseconds|ms|
|seconds|s|
|hertz|Hz|
|kilohertz|kHz|
|megahertz|MHz|
|bitspersecond|bps|
|kilobitspersecond|kbps|
|megabitspersecond|Mbps|
|bits|Bits|
|bytes|Bytes|
|kilobytes|kBytes|
|megabytes|MBytes|
|percent|%|

* `ALLOWED_RANGES`プロパティ
パラメーターボックスに入力できる値範囲、または集合を指定します。値範囲を指定した場合、パラメーターボックスの型は数値型（`integer`,`natural`,`positive`など）に限られます。

`set_parameter_property FOO_PARAM ALLOWED_RANGES 0:100`
入力範囲を0～100に限定

値に`{}`で括られた集合を指定した場合はパラメーターボックスはプルダウンリストになります。

`set_parameter_property FOO_PARAM ALLOWED_RANGES {0 1 2 4 8 16}`
0,1,2,4,8,16の要素のプルダウンリストで選択

`set_parameter_property FOO_PARAM ALLOWED_RANGES {"Haruka" "Chihaya" "Miki"}`
文字列要素のプルダウンリストで選択

また`"入力値:文字列"`を要素にすることで、入力値と表示名を別にする事もできます。入力値は数値以外にもとることができます。

`set_parameter_property FOO_PARAM ALLOWED_RANGES {"2005:Arcade" "2006:X360" "2008:L4U" "2009:SP"}`
選択に応じた数値（GUI上には表示されない）が選択した値として入る

* `DISPLAY_HINT`プロパティ
デフォルトでは入力ボックスかプルダウンリストになるパラメーターボックスの形状を変更することができます。

`boolean`を設定した場合チェックボックスになります。パラメーターの型には`integer`か`boolean`のどちらかを指定することができ、`integer`の場合☑（チェック）=1、□（非チェック）=0が代入されます。

`radio`を設定した場合はプルダウンリストの代わりにラジオボタンになります。このときは`ALLOWED_RANGES`プロパティに選択肢として表示する集合を設定しなければなりません。

```tcl:パラメーターの表示の例
add_parameter DEVSELECT_NUMBER integer 1
set_parameter_property DEVSELECT_NUMBER DISPLAY_NAME "Number of devices(ss_n)"
set_parameter_property DEVSELECT_NUMBER ALLOWED_RANGES {1 2 3 4 5 6 7 8}
set_parameter_property DEVSELECT_NUMBER HDL_PARAMETER true

add_parameter DEFAULT_REG_MODE integer 0
set_parameter_property DEFAULT_REG_MODE DISPLAY_NAME "Default value of mode register(MODE)"
set_parameter_property DEFAULT_REG_MODE DISPLAY_HINT radio
set_parameter_property DEFAULT_REG_MODE ALLOWED_RANGES {"0:MODE0" "1:MODE1" "2:MODE2" "3:MODE3"}
set_parameter_property DEFAULT_REG_MODE HDL_PARAMETER true

add_parameter DEFAULT_REG_BITRVS integer 0
set_parameter_property DEFAULT_REG_BITRVS DISPLAY_NAME "Enable bit reverse register(BITRVS)"
set_parameter_property DEFAULT_REG_BITRVS DISPLAY_HINT boolean
set_parameter_property DEFAULT_REG_BITRVS HDL_PARAMETER true

add_parameter DEFAULT_REG_CLKDIV integer 255
set_parameter_property DEFAULT_REG_CLKDIV DISPLAY_NAME "Default value of clock divider(CLKDIV)"
set_parameter_property DEFAULT_REG_CLKDIV ALLOWED_RANGES 0:255
set_parameter_property DEFAULT_REG_CLKDIV HDL_PARAMETER true
```


### 接続されたクロックの周波数を取得する

パラメーターの`SYSTEM_INFO`プロパティを使うと、Platform Designer側のいろいろな情報を取得することができます。
その中でも利用頻度が高いのは、コンポーネントのクロックインターフェースに接続されたクロック周波数を取得する`SYSTEM_INFO {CLOCK_RATE <クロックインターフェース名>}`でしょう。
このプロパティを指定すると、GUI上からユーザー操作で入力ができなくなります。内部情報として使うことが多いため`HDL_PARAMETER true`と`VISIBLE false`がセットで指定されることが多いです。

HDLのパラメーターに引き渡してHDL側で物理的なタイミングを調整させたり、プロシージャで分周比を計算したり、あるいはEDSへ駆動クロック周波数情報を通知してタイムティックの計算をさせたりとHDL以外でも利用頻度の高いプロパティです。

```tcl:クロック周波数を取得する例
add_parameter CLOCKFREQ integer
set_parameter_property CLOCKFREQ SYSTEM_INFO {CLOCK_RATE clock}
set_parameter_property CLOCKFREQ UNITS hertz
	:
	:
add_interface clock clock end
	:
```


### プロジェクトのデバイス情報を取得する

`SYSTEM_INFO`プロパティでもう一つ利用頻度の高いものが、プロジェクト（またはPlatform Designerで指定された）のデバイスファミリ名の取得です。
パラメーターのプロパティに`SYSTEM_INFO {DEVICE_FAMILY}`を指定すると、指定されたパラメータに現在選択しているデバイスのファミリ名（特定デバイスの型番ではない）を割り当てます。
また、あまり使う頻度はないですが`SYSTEM_INFO {DEVICE}`を指定すれば現在選択しているデバイスの型番を取得することができます。

この機能はHDL側にデバイスファミリ依存のIPコア（あるいはデバイスファミリ名をヒントに与えるようなIPコア）をインスタンスしている場合に重宝します。
コンポーネントTclでも、デバイスファミリによってコンポーネントに加えるHDLソースを切り替えたい場合に、この情報を元にファイルセット部分のコールバックで該当HDLソースファイルを切り替えるといった使い方ができます。

```tcl:デバイスファミリと型番を取得する例
add_parameter DEVICE_FAMILY string
set_parameter_property DEVICE_FAMILY SYSTEM_INFO {DEVICE_FAMILY}
set_parameter_property DEVICE_FAMILY HDL_PARAMETER true
set_parameter_property DEVICE_FAMILY VISIBLE true

add_parameter PART_NAME string
set_parameter_property PART_NAME SYSTEM_INFO {DEVICE}
set_parameter_property PART_NAME VISIBLE true
```


### サポートしているデバイスファミリだけで表示する

Platform DesignerはインテルのFPGAファミリに幅広く対応しているわけですが、作ったコンポーネントが必ずしも全てのデバイスファミリに対応できるとは限りません。
Quartusは比較的メタな記述を許してくれるツールで、デバイス間移植に苦労することはあまりありませんが、それでもデバイスに特有のハードマクロを使っていたりメモリマクロなどのアーキテクチャに依存した設計をしていたりしてデバイスファミリ間の機械的な移植ができない場合もそれなりにあります。
このような場合に（プロジェクトのデバイス情報を取得するのとは少し異なりますが）Platform Designerで選択されているデバイスがサポートしているファミリの時だけIP Catalogにコンポーネントを表示させる方法です。

モジュールプロパティ部分に`SUPPORTED_DEVICE_FAMILIES {<対応するデバイスファミリのリスト>}`を追加すると、リストに指定したデバイスファミリの場合のみにIP Catalogでコンポーネントを選択することができるようになります。
例えば下のようなリストを与えると、MAX10とCycloneIV,V,10LPのデバイスファミリでのみ使えるコンポーネントにすることができます。

```tcl:特定のデバイスファミリでのみ使える例
package require -exact qsys 16.1
set_module_property NAME peridot_hostbridge
set_module_property DISPLAY_NAME "PERIDOT Host Bridge"
set_module_property DESCRIPTION "PERIDOT Host to Avalon-MM bridge"
set_module_property GROUP "PERIDOT Peripherals"
set_module_property AUTHOR "J-7SYSTEM WORKS LIMITED"
set_module_property VERSION 20.1
set_module_property SUPPORTED_DEVICE_FAMILIES {"MAX 10" "Cyclone IV E" "Cyclone IV GX" "Cyclone 10 LP" "Cyclone V"}
	:
	:
```


### パラメーターの値を取得／設定する

プロシージャの中でパラメーターの値を取得するには`get_parameter_value`を使用します。逆に、プロシージャの中でパラメーターに値を設定するには`set_parameter_value`を使いますが、デフォルトではパラメーターはGUI上からの入力のみになっていてプロシージャ側からは変更できません。
プロシージャ側から値を代入できるようにするには該当のパラメーターの`DERIVED`プロパティを`true`を設定します。ただしこうした場合、今度はGUI上からの値変更はできなくなります。
通常はこのようなパラメーターはGUIから隠して内部的にHDLのパラメータ設定に使われることが多いため、たいていは`HDL_PARAMETER true`と`VISIBLE false`がセットで指定されます。

```tcl:プロシージャから値を取得・設定する例
	:
add_parameter SWI_USE_MESSAGE boolean true
set_parameter_property SWI_USE_MESSAGE DISPLAY_HINT boolean

add_parameter SWI_MESSAGE_FEATURE string
set_parameter_property SWI_MESSAGE_FEATURE DERIVED true
set_parameter_property SWI_MESSAGE_FEATURE HDL_PARAMETER true
set_parameter_property SWI_MESSAGE_FEATURE VISIBLE false
	:
	:
proc validate {} {
	set message_str ""

	if ([get_parameter_value SWI_USE_MESSAGE]) {
		set message_str "Hello!"
	}

	set_parameter_value SWI_MESSAGE_FEATURE $message_str
}
```


### デバッグ用に表示する

コンポーネント定義用のTclファイルでは一般的なTclのように`puts`で文字列を出力させることができません。プロシージャ中の変数や設定値を確認したい場合にはデバッグ用のパラメーターや`send_message info`を使ってテキストを表示します。

```tcl:デバッグ用に変数の値を表示する例
	:
proc validate {} {
	set parameter_value [get_parameter_value FOO_PARAM]
	send_message info "FOO_PARAM = $parameter_value"
}
```


### EDS用の情報を追加する

NiosIIを使ったシステムを作ると、NiosII EDS側ではPlatform Designerモジュール（.sopcファイル）を読み込んでBSPをビルドし、ペリフェラル名やアドレス情報などがsystem.hに書き出されます。
ソフトウェアで扱うときにペリフェラルの設定値や駆動クロックなどの追加情報が同時に欲しい場合が多くなります。値であればパラメーターでHDLに引き渡して埋め込む方法もありますが、プロシージャの中で`set_module_assignment`で追加情報を指定しsystem.hに書き出すことができます。

`set_module_assignment embeddedsw.CMacro.<ラベル> <値>`をプロシージャ中に記述しておくと、BSPをビルドした時にsystem.hの中に`#define <ペリフェラル名>_<ラベル> <値>`という形で宣言されます。ここで設定する値は文字列がそのままsystem.hにされるため、formatコマンドで表記を成形しておくこともできます。逆に文字列を引き渡したい場合には`""`を付加しなければならない点に注意してください。
またC言語ではbooleanの値をそのまま使うことができないので、boolean型のパラメーターの値を引き渡す際は0/1への変換が必要になります。

```tcl:EDSに引き渡す情報を追加する例
proc validate {} {
	set_module_assignment embeddedsw.CMacro.FREQ			[get_parameter_value SWI_CLOCKFREQ]

	set_module_assignment embeddedsw.CMacro.ID				[format 0x%08x [get_parameter_value SWI_CLASSID]]
	set_module_assignment embeddedsw.CMacro.TIMESTAMP		[format %u [get_parameter_value SWI_TIMECODE]]
	set_module_assignment embeddedsw.CMacro.CPURESET_KEY	[format 0x%04x [get_parameter_value SWI_CPURESET_KEY]]

	set message_str [get_parameter_value SWI_MESSAGE_FEATURE]
	set_module_assignment embeddedsw.CMacro.MESSAGE			"$message_str"

	set_module_assignment embeddedsw.CMacro.USE_MESSAGE		[expr [get_parameter_value SWI_USE_MESSAGE]? 1 : 0]
}
```


### インターフェースのビット幅を変更する

パラメタラブルなコンポーネントを作るときに頻繁に出てくるのは、ポートのビット幅を変更するというものでしょう。Platform Designerコンポーネントでこのようなコンポーネントを作る場合、HDL側の対応も必要になります。

まず、HDLのポート宣言でパラメーターの引数でビット幅を可変できるような記述にしておく必要があります。VHDLではそのままgeneric中にラベルを書くだけですが、Verilogではparameterをポート宣言より前に置く記法にしなければなりません（いまどきVerilog-95スタイルで書く人もいないと思いますが）。

```verilog:可変ビット幅ポート宣言の例
module foo_top #(
	parameter PORT_BITWIDTH	= 16
) (
	:
	:
	input wire [PORT_BITWIDTH-1:0]	input_port,
	:
```

Tcl側ではHDL側に引き渡すパラメーターを追加し、その後方のインターフェースでパラメーターの値を`add_interface_port`の値として設定しています。Main Program中ではexprのような計算評価構文は使えませんが、値の参照だけであればそのまま記述できます。

```tcl:可変ビット幅のインターフェースの例
	:
add_parameter PORT_BITWIDTH integer 16
set_parameter_property PORT_BITWIDTH ALLOWED_RANGES 1:32
set_parameter_property PORT_BITWIDTH HDL_PARAMETER true
	:
	:
add_interface export conduit end
add_interface_port export input_port input_port Input {PORT_BITWIDTH}
	:
```


### インターフェースを切り替える

Platform DesignerコンポーネントのTclコマンドを見ると、オブジェクト操作には追加(add)しかないことに気がつくと思います。そうです、**一度追加したオブジェクトはGUI中では消せない**（Removeできない）のです。
ではコンポーネントの入出力ポートをパラメーターで切り替えたいような場合はどのようにするかというと、予めインターフェースをすべて`add_interface`で作成しておき必要に応じてインターフェースの`ENABLED`プロパティで有効／無効を切り替える、という操作を行います。
これはGUIの操作イベントで該当プロパティを再設定することになりますから、`set_module_property ELABORATION_CALLBACK`でエラボレーションのプロシージャを設定しておく必要があります。
エラボレーションやバリデーションのコールバックはGUIロード時にも呼ばれるため、例のように初期化としていったん無効化→必要なインターフェースを有効に設定、という記法をよく使います。

インターフェースの`ENABLED`プロパティに`false`が設定されるとそのインターフェースはコンポーネントのポートから除外されます。このとき、HDL側のポートはoutputは未接続、inputはゼロ値入力（未接続ではない）となります。
入力値が悪影響を及ぼさないよう、インターフェースの切り替えを行うコンポーネントではHDL側はパラメーター設定とgeneration文で該当ポートを切り離しておくようなケアが必要です。
Verilogでは伝統的に``ifdef`でポート宣言そのものを消すような記法が未だに用いられますが、Platform Designerコンポーネント用のHDLではポート宣言にかかわらずプリプロセッサ記述は使うべきではありません。

```tcl:インターフェースを切り替える例
	:
set_module_property ELABORATION_CALLBACK elaboration_callback
	:
	:
add_parameter INTERFACE_SEL boolean false
	:
	:
add_interface interface_a conduit end
	:
	:
add_interface interface_b conduit end
	:
	:
proc elaboration_callback {} {
	set_interface_property interface_a ENABLED false
	set_interface_property interface_b ENABLED false

	if ([get_parameter_value INTERFACE_SEL]) {
		set_interface_property interface_a ENABLED true
	} else {
		set_interface_property interface_b ENABLED true
	}
}
```


### 起動しているQuartusのインストールフォルダを参照する

コンポーネントのTclに組み込まれているコマンドではありませんが、Platform Designerを起動したQuartusのインストールフォルダを取得する方法です。
Quartusは起動したときに環境変数`QUARTUS_ROOTDIR`に自分のファイルパスを設定します。Tcl側でこれを取得することで、起動したQuartusのインストールフォルダを得ることができます。

これが必要になるのはかなり突っ込んだ使い方をするときだと思います。例えばデバイスファミリ固有のハードマクロをブラックボックス機能のIPコアで直接呼び出すような場合です。
具体的にはデバイスのユニークシリアルを取得する機能をコンポーネントに埋め込んで使いたいとか、MAX10のUFMを直接インスタンスして呼び出したいというようなときに、Quartusインストールフォルダ以下にある大元のIPソースファイルの場所が必要になってきます。

```tcl:QuartusのIPフォルダから直接ソースを追加する例
	:
	:
add_fileset QUARTUS_SYNTH QUARTUS_SYNTH generate_synth
set_fileset_property QUARTUS_SYNTH TOP_LEVEL peridot_hostbridge
	:
	:
proc generate_synth {} {
	set quartus_ip "${::env(QUARTUS_ROOTDIR)}/../ip/altera"

	add_fileset_file altera_avalon_packets_to_master.v VERILOG PATH "${quartus_ip}/sopc_builder_ip/altera_avalon_packets_to_master/altera_avalon_packets_to_master.v"
	add_fileset_file altera_avalon_st_bytes_to_packets.v VERILOG PATH "${quartus_ip}/sopc_builder_ip/altera_avalon_st_bytes_to_packets/altera_avalon_st_bytes_to_packets.v"
	add_fileset_file altera_avalon_st_packets_to_bytes.v VERILOG PATH "${quartus_ip}/sopc_builder_ip/altera_avalon_st_packets_to_bytes/altera_avalon_st_packets_to_bytes.v"

	if {[get_parameter_value DEVICE_FAMILY] == "Arria 10"} {
		add_fileset_file altera_chip_id_a10.sv SYSTEM_VERILOG PATH "${quartus_ip}/pgm/altera_arria10_chip_id/altera_chip_id_a10.sv"
	} else {
		add_fileset_file altchip_id.v VERILOG PATH "${quartus_ip}/altchip_id/source/altchip_id.v"
	}
}
```



# おわりに

内容的にかなり端折り気味になりましたが、Qsys時代からこの辺りの情報がきちんと紹介されたことはあまり無かったように思います。
Platform Designerのコンポーネントを自由に扱えるようになるとコンポーネントの再利用率もグッと高くなります。
記事の都合から今回は紹介できませんでしたが、`add_display_item`コマンドではGUI画面のタブ化や画像の貼り込みを行う事もできます。使いやすいGUIは利用ハードルも下げていくので、どんどん再利用して楽に機能のビルドをしていきましょう。

それでは皆さま、よいPlatform Designerライフを！



# 参考

**Intel Quartus Prime Standard Edition User Guide: Platform Designer**
[https://www.intel.com/content/www/us/en/programmable/documentation/jrw1529444674987.html](https://www.intel.com/content/www/us/en/programmable/documentation/jrw1529444674987.html)


https://qiita.com/s_osafune/items/a31d9cfd88027ad04482

---