VivadoはTclスクリプトで操作できるというのは,Vivadoを普段から利用している人はよく知っていると思います.プロジェクト作成やビルドをスクリプト化している人も多いことでしょう.Tcl経由でFPGAにビットファイルを書き込んだり,ILAやVIOを叩くという使い方もできます.
そんな便利なVivadoのTclインタプリタですが,スクリプトの実行のたびにVivadoを起動すると起動時間の長さが気になってきます.というわけで,VivadoのTclインタプリタでコマンド実行用の簡単なTCPサーバーを作ってみました.
とりあえずの最終版は command_server.tcl です.
$ vivado -mode tcl -source ./command_server.tcl
として実行すると,16384番ポートでTCPサーバーが立ち上がって,Tclコマンドを受け付けます.
ちなみに,ここで紹介する方法では,TCP経由でなんでもできてしまいます.使用する場合には,自己責任で十分に注意して実行してください.
TclのTCPサーバーを使ってみる
TclにはTCPによるソケット通信を行なうライブラリが含まれています.このライブラリは,Vivadoに同梱されているTclインタプリタ(たとえば,2020.1と2023.1ではTclのバージョンは8.5だった)でも利用できます.
https://www.tcl.tk/about/netserver.html には,Tclのサンプルコードが公開されています.公開されている https://www.tcl.tk/about/netserver.txt を保存します.
VivadoをTclモードで起動して,ダウンロードしたTclスクリプトを source
コマンドで,ロードしてみましょう.
$ vivado -mode tcl
****** Vivado v2023.1.1 (64-bit)
**** SW Build 3900603 on Fri Jun 16 19:30:25 MDT 2023
**** IP Build 3900379 on Sat Jun 17 05:28:05 MDT 2023
**** SharedData Build 3899622 on Fri Jun 16 03:34:24 MDT 2023
** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
** Copyright 2022-2023 Advanced Micro Devices, Inc. All Rights Reserved.
Vivado% source ./netserver.txt
...(ロードしたスクリプトが表示される)...
Vivado%
これで準備は完了です.このロードしたスクリプトで定義してある Echo_Server
プロシージャを呼び出してTCPサーバーを起動します.
Vivado% Echo_Server 16384
これで,16384番ポートでTCPサーバーが起動します.telnetコマンドを使ってメッセージを送ってみます.以下は,telnetで接続した後,Hello
とメッセージを送ったところです.1つ目の Hello
が送信したメッセージ,2つ目はサーバーから送り返されてきたメッセージです.
$ telnet localhost 16384
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello
Hello
TclインタプリタにはTCP接続を受け付けた旨のメッセージが出力されます.
Accept sock8 from 127.0.0.1 port 56316
telnetを切断すると,Tclインタプリタには切断のメッセージが出力され,次のコネクションを待ちます.
Close 127.0.0.1 56316
簡単ですね.
Tclインタプリタは,無限ループに入っていて簡単には終了させられません.kill
などをつかって強制終了させてください.
TCPサーバーのコードを見てみる
TCPでVivadoのコマンドを受け付けられるように,TCPサーバーがどう記述されているか見てみましょう.サンプルスクリプトのTCPサーバーは,
proc Echo_Server {port} { ... }
proc EchoAccept {sock addr port} { ... }
proc Echo {sock} { ... }
の3つのプロシージャから構成されています.Echo_Server
は,EchoAccept
をソケットにバインドして無限ループに入ります.EchoAccept
が,TCP接続を待ち受けるプロシージャで接続がくると,Echo
プロシージャを呼び出します.実際にTCPでやりとりするデータのハンドリングは呼び出された,Echo
で行われます.
Echo
プロシージャは
proc Echo {sock} {
global echo
# Check end of file or abnormal connection drop,
# then echo data back to the client.
if {[eof $sock] || [catch {gets $sock line}]} {
close $sock
puts "Close $echo(addr,$sock)"
unset echo(addr,$sock)
} else {
puts $sock $line
}
}
となっています.送られてきた文字列を$line
に保存して,それを$sock
に出力するという動作をしていることが分かります.
というわけで,この$line
をうまく使えば,TCP経由でVivadoにコマンド実行させることができそうです.
やってみる,前に
さて,サンプルのままでもいいのですが,サーバーを終了させるたびにVivadoを強制終了する,というのは,若干使い勝手が悪いです.まずは,特定の文字列がきたらサーバーが終了するようにしましょう.
変更したコードは terminate_example.tcl です.
Echo
プロシージャを変更して,terminate
という文字列が送られてきたらEcho_Server
を終了するようにしています.これで,Tclインタプリタに次のコマンドを送ることができるようになります.たとえば,exit
コマンドでTclインタプリタを終了させることができます.
もちろん,vwait terminate
の次の行に,exit
コマンドを記述しておけば,サーバーを終了するとともにtclインタプリタを終了することができます.
シンプルなコマンドサーバー
送られてきた文字列をそのままコマンドとして実行してみることにしましょう.文字列をコマンドとして実行するには,eval
を使うことができます.
実装したコードは,simple_server.tclです.スクリプトの最後のEcho_Server 16384
で,コードがロードされるとすぐに16384番ポートでTCPサーバーが立ち上がります.
サーバーの起動
VivadoをTclモードで起動します.起動時の引数でTclスクリプトをロードしてしまいましょう.スクリプトのロードが終了するとサーバーを起動するプロシージャが呼び出されます.
$ vivado -mode tcl -source ./simple_server.tcl
****** Vivado v2023.1.1 (64-bit)
**** SW Build 3900603 on Fri Jun 16 19:30:25 MDT 2023
**** IP Build 3900379 on Sat Jun 17 05:28:05 MDT 2023
**** SharedData Build 3899622 on Fri Jun 16 03:34:24 MDT 2023
** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
** Copyright 2022-2023 Advanced Micro Devices, Inc. All Rights Reserved.
source ./simple_server.tcl
# proc Echo_Server {port} {
# global terminate
...(ロードしたスクリプトが表示される)...
# unset echo(addr,$sock)
# }
# Echo_Server 16384
クライアントの接続とコマンド実行
telnetを使ってVivadoのコマンドを実行してみます.たとえば,以下のように,telnet
で接続して,tclversion
の問い合わせをしてみることができます.
$ telnet localhost 16384
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
info tclversion
info tclversion
8.5
引数を持つコマンドも,Tclインタプリタでコマンドを実行する通り,空白をデリミタとした文字列をサーバーに送って実行することができます.
Vivadoの操作をしてみる
以下はArty A7-100が接続された状態でHW Manager関連のコマンドを実行してみたところです.
$ telnet localhost 16384
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
open_hw_manager
open_hw_manager
connect_hw_server -allow_non_jtag
connect_hw_server -allow_non_jtag
localhost:3121
open_hw_target
open_hw_target
get_hw_devices
get_hw_devices
xc7a100t_0
close_hw_target
close_hw_target
disconnect_hw_server localhost:3121
disconnect_hw_server localhost:3121
close_hw_manager
close_hw_manager
ここで,同じ文字列が二行並んでいるのは,一行目が入力した文字列,二行目がサーバーからエコーバックされてきた文字列です.入力文字列のエコーバックに続いてコマンドの実行結果が出力されます.たとえば,get_hw_devices
の実行結果のxc7a100t_0
が出力されています.
Vivado側には以下のように出力されます.
Vivado% Echo_Server 16384
Accept sock8 from 127.0.0.1 port 54810
open_hw_manager
connect_hw_server -allow_non_jtag
INFO: [Labtools 27-2285] Connecting to hw_server url TCP:localhost:3121
INFO: [Labtools 27-2222] Launching hw_server...
INFO: [Labtools 27-2221] Launch Output:
****** Xilinx hw_server v2023.1.1
**** Build date : Jun 16 2023 at 19:39:31
** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
INFO: [Labtools 27-3415] Connecting to cs_server url TCP:localhost:0
INFO: [Labtools 27-3417] Launching cs_server...
INFO: [Labtools 27-2221] Launch Output:
******** Xilinx cs_server v2023.1.0
****** Build date : Jun 01 2023-03:31:41
**** Build number : 2023.1.1685557901
** Copyright 2017-2022 Xilinx, Inc. All Rights Reserved.
** Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved.
open_hw_target
INFO: [Labtoolstcl 44-466] Opening hw_target localhost:3121/xilinx_tcf/Digilent/210319AFEB96A
get_hw_devices
close_hw_target
INFO: [Labtoolstcl 44-464] Closing hw_target localhost:3121/xilinx_tcf/Digilent/210319AFEB96A
disconnect_hw_server localhost:3121
close_hw_manager
エラーハンドリングを付ける
このままでも使えないことはないのですが,コマンドが受理されないなどによりエラーが発生すると,そのTCPコネクションでは次のメッセージが受け付けられない,という問題があります.
ある程度機械的に使うのであれば問題ありませんが不便は不便です.というわけで,実用化に向けては,簡易なエラーハンドリング機構を追加する必要があるでしょう.
tclでは,catch
コマンドでは,与えられたリストのコマンドをTclスクリプトで実行しエラーの有無を返すコマンドです.エラーがあれば1
を,なければ0
を返します.
というわけで,eval
でのエラーハンドリングを行なうバージョンのコードは,command_server.tcl のように実装できます.
Echo
プロシージャの,[ catch { set result [ eval $line ] } ]
の部分がコマンド実行部分です.実行時にエラーがなければ,if
が成立せずに,コマンドの結果$result
が $sock
すなわちクライアントに送り返されます.一方で,エラーがあれば,eval error
という文字列が返されます.
proc Echo {sock} {
global echo
global terminate
# Check end of file or abnormal connection drop,
# then echo data back to the client.
if {[eof $sock] || [catch {gets $sock line}]} {
CloseSocket $sock
} else {
puts $sock $line
if {$line eq "terminate"} {
CloseSocket $sock
set terminate 1
} else {
puts $line
if { [ catch { set result [ eval $line ] } ] } {
puts $sock "eval error"
} else {
puts $sock $result
}
}
}
}
たとえば,こんな感じです.
$ telnet localhost 16384
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
info tcl_version
info tcl_version
eval error
info tclversion
info tclversion
8.5
1 + 3
1 + 3
eval error
expr 1 + 3
expr 1 + 3
4
最初の info tcl_version
はエラーですが,次のinfo tclversion
では正しくTclインタプリタのバージョンが返ってきています.また,足し算も expr
がないときにはエラーで,expr
があるときには計算結果が返されます.
まとめ
というわけで,Tclのsocketを使ったVivadoのコマンドサーバーを作ってみました.Vivadoの外からVivadoを操作する,特に別の言語でVivadoを操作する場合には便利かもしれません.
ところで,このスクリプトは,eval
でなんでもかんでも実行してしまいます.そのため,大変危険なこともできてしまいます.利用する際には十分に気をつける必要があること,注意が必要です.