My RISC-V debug feature part2

Saturday, April 17, 2021

今回はIntel FPGAのVJTAGのIPを試してみる。 FPGAボードはDE10 Liteを使用する。

JTAG(Joint Test Action Group)

JTAGについては以下のサイトがわかりやすい。

JTAGを用いると、ICの端子を操作したり、ICの内部と通信ができるようになる。 もともと、JTAGはICの端子を操作するバウンダリースキャンのために作られたようである。 IEEE1149.1は4本の信号線でIC内部と通信するための、プロトコルを定めた規格である。 今回は、バウンダリースキャンは行わない。プロセッサの内部の状態をriscv-debug-specに沿って操作することを目標とする。 なお、riscv-debug-specでは、Debug Transport(情報のやり取り)には規定はないが、JTAGの例が乗っている。 例に乗っ取り、今回は、JTAGを用いる。

JTAGはIRレジスタとDRレジスタがある。 これらはシフトレジスタであり、Shift-IR, Shift-DRにてシフトする。 IRは主にDRの命令(アドレス)を保持する。 IRは最低2ビット必要である。

実装の必須な命令は以下である。

  • BYPASS
  • SAMPLE
  • PRELOAD
  • EXTEST

オプションな命令

  • IDCODE

今回使用する命令

  • BYPASS
  • IDCODE なお、バウンダリースキャンはサポートしない。

なお、IRレジスタを読み出すときは、前回の値でなくステータスが帰る。 ステータスは下位2ビットが01であること以外は実装定義である。 DRはIRによって選択する。 DRの長さは実装定義で、制御用のソフトウエアはシフト回数を適切に行う必要がある。 なお、仕様で規定されている必須のDRが存在する。

  • IDCODE(IR: 任意)
    • JTAGのIDコードを保持する(32 bit)
  • BYPASS(IR: すべてのビットが1)
    • 1 bitのレジスタ
    • 実装していない命令などはこちらへ書き込むことが推奨されている

JTAG信号線

  • TCK (Test Clock)
    • クロック信号
  • TMS (Test Mode Select)
    • 次のステートを決める
    • 5回以上1を入力するとリセットすることができる
    • TCKの立ち上がりエッジ
  • TDI (Test Data-In)
    • データ入力
    • TCKの立ち上がりエッジ
  • TDO (Test Data-Out)
    • データ出力
    • TCKの立ち下がりエッジ

State machine of JTAG TAP

JTAGの状態はTAP(Test Access Port)が管理する。 16のステートを持ち、現在の状態とTMSにより次のステートが決まる。 ステートマシン図を以下に示す。

Select

Select_IRの場合は、IRをオペレーション対象とする。 Select_DRの場合は、DR Chainを選択し、IRにて、各DRを選択する。

Shift

Shift_DR, Shift_IRにてシフトレジスタをシフトする。 Shift_DRの場合は、IRの値に応じて、DRが選択され、シフトされる。 TDIはMSBへ、LSBはTDOへ接続される。 Shift_IRはIRをシフトする。 TDIはMSBへ、ステータス(IRの値ではないことに注意)のLSBはTDOへ接続される。

Capture

Capture_DRでは、IRで選択する、DRにパラレルで値を代入する。 Capture_IRでは、IRへパラレルで値を代入する。 処理の結果やステート、初期値などをこのステートでDR/IRへ転送する。

Update

Updateでは、並列でDRもしくはIRの値を内部ロジックへコピーする。 なにか、処理をする場合はこのステートで実行する。(RISC-V DebugのDTMにおけるDMI Write・Readなど)

重要なステートはUpdate, Capture, Shift、Selectである。 なお、アプリケーションはDRの長さや現在の値、ステートについてしっかり把握して制御する必要がある。 ステートに関して、どのステートからでも、TMSを5回以上1にしておくことで初期状態へ戻ることができる。

Virtual JTAG

今回はVJTAGのIPを使用する。 VJTAGを用いると、Download Cable(USB-Blaster)を用いて、FPGAとPC間のJTAG通信ができる。 VJTAGとJTAGの違いは、あまりなく、実際はJTAGと同じ制御をしている。 しかし、VJTAGのインスタンスは最大255個作成できるため、これらを識別する機能が必要である。 各インスタンスにはユニークなインデックス(アドレス)が割り当てられ(ユーザが指定可能、指定しない場合はQuartusが合成時に自動割当) これを用いて、識別する。 FPGAのデザイン内で、通信のルーティングを行う仕組みとして、sld_hubがぞんざいする。 各VJTAGのインスタンスはsld_nodeとなり、sld_hubに接続される。 以下にブロック図を示す。 ここより 引用 よって、アプリケーションはsld_hubを介して、ユーザロジックのVJTAGインスタンス(sld_node)へアクセスする。 なお、1つしかインスタンスが存在しない場合でも、sld_hubは生成される。 また、sld_node, sld_hubはLEs(Logic Element)を消費する。 JTAG TAPは物理ハードウェアとして実装されている。 以下に、sld_hubおよびsld_nodeの内部ブロック図を図示する。

IPのドキュメントは以下のリンクより閲覧できる。

Virtual JTAGのインスタンスの作成

Quartus Megafunctionを使って、Virtual JTAGのインスタンスファイル?を作る。

// vjtag.v

// Generated using ACDS version 20.1 711

`timescale 1 ps / 1 ps
module vjtag (
		output wire       tdi,                // jtag.tdi
		input  wire       tdo,                //     .tdo
		output wire [4:0] ir_in,              //     .ir_in
		input  wire [4:0] ir_out,             //     .ir_out
		output wire       virtual_state_cdr,  //     .virtual_state_cdr
		output wire       virtual_state_sdr,  //     .virtual_state_sdr
		output wire       virtual_state_e1dr, //     .virtual_state_e1dr
		output wire       virtual_state_pdr,  //     .virtual_state_pdr
		output wire       virtual_state_e2dr, //     .virtual_state_e2dr
		output wire       virtual_state_udr,  //     .virtual_state_udr
		output wire       virtual_state_cir,  //     .virtual_state_cir
		output wire       virtual_state_uir,  //     .virtual_state_uir
		output wire       tms,                //     .tms
		output wire       jtag_state_tlr,     //     .jtag_state_tlr
		output wire       jtag_state_rti,     //     .jtag_state_rti
		output wire       jtag_state_sdrs,    //     .jtag_state_sdrs
		output wire       jtag_state_cdr,     //     .jtag_state_cdr
		output wire       jtag_state_sdr,     //     .jtag_state_sdr
		output wire       jtag_state_e1dr,    //     .jtag_state_e1dr
		output wire       jtag_state_pdr,     //     .jtag_state_pdr
		output wire       jtag_state_e2dr,    //     .jtag_state_e2dr
		output wire       jtag_state_udr,     //     .jtag_state_udr
		output wire       jtag_state_sirs,    //     .jtag_state_sirs
		output wire       jtag_state_cir,     //     .jtag_state_cir
		output wire       jtag_state_sir,     //     .jtag_state_sir
		output wire       jtag_state_e1ir,    //     .jtag_state_e1ir
		output wire       jtag_state_pir,     //     .jtag_state_pir
		output wire       jtag_state_e2ir,    //     .jtag_state_e2ir
		output wire       jtag_state_uir,     //     .jtag_state_uir
		output wire       tck                 //  tck.clk
	);

	sld_virtual_jtag #(
		.sld_auto_instance_index ("YES"),
		.sld_instance_index      (0),
		.sld_ir_width            (5)
	) virtual_jtag_0 (
		.tdi                (tdi),                // jtag.tdi
		.tdo                (tdo),                //     .tdo
		.ir_in              (ir_in),              //     .ir_in
		.ir_out             (ir_out),             //     .ir_out
		.virtual_state_cdr  (virtual_state_cdr),  //     .virtual_state_cdr
		.virtual_state_sdr  (virtual_state_sdr),  //     .virtual_state_sdr
		.virtual_state_e1dr (virtual_state_e1dr), //     .virtual_state_e1dr
		.virtual_state_pdr  (virtual_state_pdr),  //     .virtual_state_pdr
		.virtual_state_e2dr (virtual_state_e2dr), //     .virtual_state_e2dr
		.virtual_state_udr  (virtual_state_udr),  //     .virtual_state_udr
		.virtual_state_cir  (virtual_state_cir),  //     .virtual_state_cir
		.virtual_state_uir  (virtual_state_uir),  //     .virtual_state_uir
		.tms                (tms),                //     .tms
		.jtag_state_tlr     (jtag_state_tlr),     //     .jtag_state_tlr
		.jtag_state_rti     (jtag_state_rti),     //     .jtag_state_rti
		.jtag_state_sdrs    (jtag_state_sdrs),    //     .jtag_state_sdrs
		.jtag_state_cdr     (jtag_state_cdr),     //     .jtag_state_cdr
		.jtag_state_sdr     (jtag_state_sdr),     //     .jtag_state_sdr
		.jtag_state_e1dr    (jtag_state_e1dr),    //     .jtag_state_e1dr
		.jtag_state_pdr     (jtag_state_pdr),     //     .jtag_state_pdr
		.jtag_state_e2dr    (jtag_state_e2dr),    //     .jtag_state_e2dr
		.jtag_state_udr     (jtag_state_udr),     //     .jtag_state_udr
		.jtag_state_sirs    (jtag_state_sirs),    //     .jtag_state_sirs
		.jtag_state_cir     (jtag_state_cir),     //     .jtag_state_cir
		.jtag_state_sir     (jtag_state_sir),     //     .jtag_state_sir
		.jtag_state_e1ir    (jtag_state_e1ir),    //     .jtag_state_e1ir
		.jtag_state_pir     (jtag_state_pir),     //     .jtag_state_pir
		.jtag_state_e2ir    (jtag_state_e2ir),    //     .jtag_state_e2ir
		.jtag_state_uir     (jtag_state_uir),     //     .jtag_state_uir
		.tck                (tck)                 //  tck.clk
	);
endmodule

NSLから使えるようにヘッダーファイルを書く。 なお、クロック供給などは不要で、合成時にうまくやってくれる。 ちなみに、JTAGのコントローラーは別に実装されているらしく、クロックやtdi, tdoなどの信号は sld_hubなどに供給されるようである。 しかし、VJTAGのインスタンス(sld_node)や(sld_hub)はロジックエレメントを消費する。 jtag_*の信号はデバック用途であり、使用不可である。 この信号はJTAGのコントローラーから来ている。 virtual_*の信号はVJTAGのステートマシンの状態である。

declare vjtag interface {
	output		tdi;
	input		tdo;
	output		ir_in[5];
	input		ir_out[5];
	func_out	virtual_state_cdr;
	func_out	virtual_state_sdr;
	func_out	virtual_state_e1dr;
	func_out	virtual_state_pdr;
	func_out	virtual_state_e2dr;
	func_out	virtual_state_udr;
	func_out	virtual_state_cir;
	func_out	virtual_state_uir;
	output		tms;
	func_out	jtag_state_tlr;
	func_out	jtag_state_rti;
	func_out	jtag_state_sdrs;
	func_out	jtag_state_cdr;
	func_out	jtag_state_sdr;
	func_out	jtag_state_e1dr;
	func_out	jtag_state_pdr;
	func_out	jtag_state_e2dr;
	func_out	jtag_state_udr;
	func_out	jtag_state_sirs;
	func_out	jtag_state_cir;
	func_out	jtag_state_sir;
	func_out	jtag_state_e1ir;
	func_out	jtag_state_pir;
	func_out	jtag_state_e2ir;
	func_out	jtag_state_uir;
	output		tck;
}

VJTAGとの通信

VJTAGのインスタンスと通信するには、まずsld_hubと通信をする必要がある。 sld_hubには2つのオペレーションが定義されている。 なお、sld_hubのIRはインスタンスによらず、10bitである。 sld_hubに接続されているインスタンスの情報を取り出すことができるが、今回は省略する。 ドキュメントおよびriscv-openocd/src/target/riscv/riscv_tap_vjtag.cを見てほしい。 なお、VJTAGのインスタンスのIR/DRはsld_hubのサブセットという形な模様。 そのため、それぞれVIR(Virtual IR)およびVDR(Virtual DR)と呼ばれている。 ブロック図を以下に示す(仕様から書き起こしたので、もしかしたら間違いがあるかもしれない)。

なお、sld_hubには、2つの命令がある。 これは、Shift_IRを用いてsld_hubのIR(10 bit)へ書き込むことで命令を発行できる。

  • USER0 VDRのパスを選択する。
  • USER1 VIRのパスを選択する。

OpenOCDでは、or1kのみVJTAGに対応している。 次からは、OpenOCDのRISC-VターゲットにVJTAGサポートを行う。 その後、実際にOpenOCDからVJTAG on DE10-Liteに通信ができるかテストをする。

RISC-VRISC-VJTAGFPGA

My RISC-V debug feature part3

My RISC-V debug feature part1