FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

オープンソースの波形ビューワSurferを試す

少し前に話題になっていたけれども、Rustで記述されたオープンソースの波形ビューワのSurferについていろいろ試行していた。

surfer-project / surfer · GitLab

これまでは基本的に波形ビューワとしてGTKwaveを使っていたのだけれども、どうにもGTKwaveも使いにくいので、こちらも試行してみようと思う。

とりあえずリリースビルドのダウンロードしてきたものは、wellenというfst読み込み用のライブラリが古くて、自作CPUの出力したfstが読み込めなかったので、wellenだけをバージョンアップしてローカルでWindows用に再ビルドした。

とりあえず、手元のfstを読み込むことができることを確認した。

ちなみに、URLを指定して波形を読み込むことができる。ただしこれはやってみると波形ファイルをダウンロードするようだ。 ダウンロードに必要な時間が生じるのは変わらない。

なるほど、波形ビューワの分割とかもできるんだな。

もうちょっとほしい機能としては、波形の複数選択とかなな。

LiteXのUART書き込みのフローについて

LiteXにおけるUARTの書き込みについて、そのフローを確認しておく。

  • litex/litex/soc/software/libc/stdio.c
static int
litex_putc(char c, FILE *file)
{
    (void) file; /* Not used in this function */
#ifdef CSR_UART_BASE
    uart_write(c);
    if (c == '\n')
        litex_putc('\r', NULL);
#endif
    return c;
}

uart_write()を呼び出す。

  • litex/litex/soc/software/libbase/uart.c

ポイントは、IRQにおけるUART_INTERRUPTをDisableにして、tx_bufに文字を書き込んでいく。 書き込んだ後に、irqmaskを書き込み直して、割り込みを発生させる。 書き込みバッファにすでに値が入ってれば、バッファを更新する。そうでなければ、uart_rxtx_write(c)を直接書き込む。

void uart_write(char c)
{
    unsigned int oldmask;
    unsigned int tx_produce_next = (tx_produce + 1) & UART_RINGBUFFER_MASK_TX;

    if(irq_getie()) {
        while(tx_produce_next == tx_consume);
    } else if(tx_produce_next == tx_consume) {
        return;
    }

    oldmask = irq_getmask();
    irq_setmask(oldmask & ~(1 << UART_INTERRUPT));
    if((tx_consume != tx_produce) || uart_txfull_read()) {
        tx_buf[tx_produce] = c;
        tx_produce = tx_produce_next;
    } else {
        uart_rxtx_write(c);
    }
    irq_setmask(oldmask);
}

このuart_rxtx_write()が実際にUARTのモジュールに書き込みを行う。

static inline void uart_rxtx_write(uint32_t v) {
    csr_write_simple(v, (CSR_BASE + 0x3000L));
}

LiteXによるSoC環境構築を試行する (20. ILAを使ったデザインのデバッグ)

LiteXのLiteScopeではどうにも細かい信号を取得することができないので、Xilinxの純正の波形のデバッグツールであるILA(Integrated Logic Analyzer)を使って内部の信号を観察する方法を見てみる。

一応、一歩ずつ問題を特定しているが、まだまだ動いていない。 分岐命令のオペランドがReadyにならずに、命令がデッドロックしている。 一応リネーム・マップを確認すると、Readyになっているのでこれがうまく伝搬していない。

Freelistに32が2回Popされている。

0x154がDEAD状態になって物理ID=32をフリーリストに返却する。 さらに、新たなるフェッチ(ID=23)が物理ID=58を取得するが、その時に古い物理ID=32が通知されている。 これは、Rename-Mapにまだ物理ID=32が残っており、どうもそれを取得しているっぽい?

LiteXによるSoC環境構築を試行する (20. ILAを使ったデザインのデバッグ)

LiteXのLiteScopeではどうにも細かい信号を取得することができないので、Xilinxの純正の波形のデバッグツールであるILA(Integrated Logic Analyzer)を使って内部の信号を観察する方法を見てみる。

一応、一歩ずつ問題を特定しているが、まだまだ動いていない。 分岐命令のオペランドがReadyにならずに、命令がデッドロックしている。 一応リネーム・マップを確認すると、Readyになっているのでこれがうまく伝搬していない。

これも要解析だな。

LiteXによるSoC環境構築を試行する (19. ILAを使ったデザインのデバッグ)

LiteXのLiteScopeではどうにも細かい信号を取得することができないので、Xilinxの純正の波形のデバッグツールであるILA(Integrated Logic Analyzer)を使って内部の信号を観察する方法を見てみる。

もうちょっと細かい信号を取得するために、tclでILAを挿入する場所を細かく変えながらいろいろ動作を取得している。

以下のようなtclファイルを用意して、取得したい信号をILAに挿入する。

# BRU dispatch
set bru_net_lists {
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_issue_unit/w_entry_valid.*"
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_issue_unit/w_entry_ready.*"
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_issue_unit/entry_loop.0..u_issue_entry/r_state.*"
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_issue_unit/entry_loop.1..u_issue_entry/r_state.*"
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_issue_unit/entry_loop.0..u_issue_entry/r_entry.*"
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_issue_unit/entry_loop.1..u_issue_entry/r_entry.*"
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_bru_pipe/w_ex0_issue.*"
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_bru_pipe/r_ex1_issue.*"
    "mycpu_subsystem_axi_wrapper/u_mycpu_subsystem/u_tile/u_bru/u_bru_pipe/r_ex2_issue.*"
    }
foreach net $bru_net_lists {
    set debug_nets [lsort -dictionary [get_nets -hierarchical -regexp $net]]
    puts $debug_nets
    set new_port [create_debug_port u_ila_0 probe]
    set_property port_width [llength $debug_nets] [get_debug_ports $new_port]
    set_property PROBE_TYPE DATA_AND_TRIGGER $new_port
    connect_debug_port $new_port $debug_nets
}

必要な信号部分は、Vivadoでの最適化を抑制するためのアトリビュートを挿入する必要がある。

(* mark_debug="true" *)(* dont_touch="yes" *) scariv_bru_pkg::issue_entry_t            w_ex0_issue;

論理合成を最初からやり直しているのでそれぞれのイタレーションは30分~1時間くらいかかってしまうが、仕方がない。 これで、少しずつ命令がデッドロックしてしまう原因を特定している。時間はかかるが、もう少しだ。

LiteXによるSoC環境構築を試行する (18. ILAを使ったデザインのデバッグ)

LiteXのLiteScopeではどうにも細かい信号を取得することができないので、Xilinxの純正の波形のデバッグツールであるILA(Integrated Logic Analyzer)を使って内部の信号を観察する方法を見てみる。

ある程度信号波形を取れてきたので、必要な個所にデバッグ信号を入れて動かない場所を特定してみた。

問題になったのは、Vivadoで論理合成されるかどうかを確認せずにTemporaryで入れてしまった$countones()で論理が消えてしまっていたこと(これはVivadoにちゃんとエラーとして出力してほしい...)。 これを解決してとりあえずIssue Queueの問題は消えたが、まだLSUでメモリアクセスを行うとハングしてしまう。引き続き解析をしていこうと思う。

LiteXによるSoC環境構築を試行する (17. LiteXデザインにILAを挿入する方法の調査)

LiteXのLiteScopeではどうにも細かい信号を取得することができないので、Xilinxの純正の波形のデバッグツールであるILA(Integrated Logic Analyzer)を使って内部の信号を観察する方法を見てみる。

まず、ILA無しでLiteXでFPGAをビット・ストリームファイルを作成するのは、以下のコマンドを実行すればよいだろう。

python3 -m litex_boards.targets.digilent_nexys_video --cpu-type=vexriscv --build --output-dir vexriscv_build

この時、vexriscv_build/gatewareにVivadoを実行したtclファイルなどのログが残る。これを改造して、ILAを挿入する方法を試行していきたいと思う。

  • digilent_nexys_video.tcl
# Create Project

create_project -force -name digilent_nexys_video -part xc7a200t-sbg484-1
set_msg_config -id {Common 17-55} -new_severity {Warning}

# Add project commands


# Add Sources
...

基本的にこのコマンドを使えば、LiteXのFPGAビット・ファイルを再現できる。

vivado -source ./digilent_nexys_video.tcl -mode tcl

したがって、このdigilent_nexys_video.tclを改造して、ILAを挿入する。

簡単に言えば、以下のようなコマンドをファイル内に挿入する。

# ILA
create_debug_core u_ila_0 ila
set_property C_DATA_DEPTH 1024       [get_debug_cores u_ila_0]
set_property C_TRIGIN_EN false       [get_debug_cores u_ila_0]
set_property C_TRIGOUT_EN false      [get_debug_cores u_ila_0]
set_property C_ADV_TRIGGER false     [get_debug_cores u_ila_0]
set_property C_INPUT_PIPE_STAGES 0   [get_debug_cores u_ila_0]
set_property C_EN_STRG_QUAL false    [get_debug_cores u_ila_0]
set_property ALL_PROBE_SAME_MU true  [get_debug_cores u_ila_0]
set_property ALL_PROBE_SAME_MU_CNT 1 [get_debug_cores u_ila_0]
connect_debug_port u_ila_0/clk [get_nets [list sys_clk]]
set_property port_width 1 [get_debug_ports u_ila_0/probe0]
set_property PROBE_TYPE DATA_AND_TRIGGER [get_debug_ports u_ila_0/probe0]
connect_debug_port u_ila_0/probe0 [get_nets main_basesoc_dbus_ack]
set debug_nets [lsort -dictionary [get_nets -regexp main_basesoc_dbus_cyc.*]]
set new_port [create_debug_port u_ila_0 probe]
set_property port_width [llength $debug_nets] $new_port
set_property PROBE_TYPE DATA_AND_TRIGGER $new_port
connect_debug_port $new_port $debug_nets

ポイントは、get_netsによってプローブしたい信号を取得し、それを新たに作成したILAのデバッグポートにひたすらつないでいくことだ。 ある程度テンプレート化して、debug_netsで信号を取得したうえで数字順にソーティングするようにしている。

最後に、ltxファイルを出力してデバッグ用のファイルを書き出す。

# Bitstream generation

write_bitstream -force digilent_nexys_video.bit
write_cfgmem -force -format bin -interface spix4 -size 16 -loadbit "up 0x0 digilent_nexys_video.bit" -file digilent_nexys_video.bin

write_debug_probes -force digilent_nexys_video.ltx

Vivado LabでFPGAに書き込みを行うと、ILAが立ち上がって、トリガによる実行が可能になる。 うーん、やっぱりLiteScopeより便利だ。