7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

インテル® FPGA Advent Calendar 2020

Day 20

Cyclone® V GT FPGA Development Kitを用いて、FPGAに実装したHLSコンポーネントをPCIe Gen2x4を介してアクセス

Last updated at Posted at 2020-12-19

はじめに

 今回、PCIeのリファレンスデザインに利用して、FPGA上のコンポーネントやメモリをホストPCからPCIeバス経由でアクセスしてみます。基本的にはPCIeを触ったことがない人が対象です。そのため、データ転送はホストPCがFPGAボード上のメモリに対して行います。FPGAがバスマスターとなるアクセスは含まれません。最初はLinuxでのPCIeでよく使うコマンドを使ってアクセスします。次に、Intel® High Level Synthesis Compiler(以下HLS)により作成したコンポーネントを、インテル®︎のシステム・インテグレーション・ツールであるPlatform Designerに追加し、アクセスします。この時、FPGAを再コンフィグレーションするので、PCで再認識させる手順も併せて説明します。最後に、C言語で作成した単純なプログラムでアクセスします。この方法であれば、ドライバーを作成する必要は無いので、FPGAの初期デバッグ時には有効かとは思います。ターゲットFPGAボードはCyclone® V GT FPGA Development Kitを使用します。

もくじ

テスト環境

PCとOSは下記を使用しました。

PC Spec

OS

  • CentOS 7.8.2003

Design Software

FPGAをデザインするツールの環境としてIntel® Quartus® PrimeソフトウェアとIntel® HLSコンパイラーは19.1.0 Build 670 Linuxバージョンを使用しました。

インストール方法に関しては、下記を参照してください。

また、Platform Designerで生成エラーが発生する場合、

export PERL5LIB=[Quartus 19.1 install path]/quartus/linux64/perl/lib/5.28.1

ModelSim*で起動時にエラーが発生する場合は、下記に該当する可能性がありますので、参考にしてください。

PCI関連ツールのインストール

lspcisetpciなどの基本コマンドをインストールします。

sudo yum install -y pciutils

ソースを参照したい方は、https://github.com/pciutils/pciutilsを参照してください。

次に、PCIデバイスをメモリのように直接アクセスツールとしてpcimemを準備します。こちらは、https://github.com/billfarrow/pcimemからソースをダウンロードし、下記に従ってコンパイルしインストールします。このツールはPCIリソースへのアクセスする良いサンプルので、一度ソースを確認してみてください。

git clone https://github.com/billfarrow/pcimem.git
cd pcimem
make
sudo cp pcimem /usr/sbin

これでPCIツールのインストールは完了です。後ほど具体的な使用方法を説明します。

Cyclone® V GT FPGA Development Kit

インストールキットのダウンロード

Cyclone®V GT FPGA Development Kitのサイトに行き、"Table 2. Collateral for the Cyclone V GT FPGA Development Kit"の "Kit Installation (zip) (Linux)"よりインストールキットをダウンロードし、任意のフォルダーに解凍してください。このキットには回路図、サンプルデザイン、ドキュメント、工場出荷復元ファイルなどが含まれます。

工場出荷状態へ戻す

Cyclone®V GT FPGA Development Kitの状態を確実にするために、一回工場出状態に戻します。まず、PCにはインストールせず、単体で動作させます。復元手順に関しては、Cyclone®V GT FPGA Development Kit User GuideのFactory Default Switch and Jumper Settingsより、DIPスイッチをディフォルト状態に設定し、Restoring the MAX V CPLD to the Factory SettingsによりCPLD, Flash ROMを工場出荷状態に復元させます。


工場出荷状態戻し、電源を入れると、LCDディスプレイに文字が表示され、PCIeコネクタそばのオレンジLEDが交互に点滅します。もし、この状態にならない場合はDIPスイッチを再度確認してください。特に、左下のDIP-SW4.3がONであることを確認してください
cvgt_devkit_factory.png

リファレンスデザイン

ダウンロード

PCI Express ハード IP を使用した DMA 転送の実現にある、ELS1362_c5gt_gen2x4_mSGMDA__2.zipを使用するので、ダウンロードして解凍してください。解凍するとELS1362_c5gt_gen2x4_mSGMDA.qarが生成さます。

再コンパイル

ダウンロードしたリファレンスデザインは古いIntel® Quartus® Primeソフトウェアでコンパイルされているため、19.1.0で再コンパイルします。

  • Intel® Quartus® Primeソフトウェア 19.1.0を起動
  • File->Open ProjectメニューよりELS1362_c5gt_gen2x4_mSGMDA.qarを開く
  • Project Navigatorより、q_sys:u0をダブルクリックし、Platform Designerを起動
  • Generaete HDLをクリックし、デザインを再生成し、Platform Designerを閉じる
  • Processing->Start Compilationメニューよりコンパイルを実行

ユーザーイメージ領域への書き込み

再生成したリファレンスデザインを下記に従ってDevelopment Kitのユーザー領域に書き込みます。

具体的な手順は、ELS1362_c5gt_gen2x4_mSGMDAのoutput_filesフォルダーにSOFがあるとすると、Terminalを起動し、下記のようにnios2_command_shell.shを実行します。

[Quartus Install Path]/nios2eds/nios2_command_shell.sh
-----------------------------------------
Altera Nios2 Command Shell

Version 19.1, Build 670
-----------------------------------------

そして、下記コマンドでSOFをFlash形式に変換し、書き込みを実行します。

sof2flash --input=output_files/c5gt_gen2x4_mSGMDA.sof --output=output_files/c5gt_gen2x4_mSGMDA.flash --offset=0xC80000 --pfl --optionbit=0x00018000 --programmingmode=FPP
nios2-flash-programmer --base=0 output_files/c5gt_gen2x4_mSGMDA.flash

書き込み完了後、一回電源を切り、左下のDIP-SW4.3をOFFにして、再度電源を入れてください。LCD画面に何も表示されず、PCIeコネクタそばのLEDが点滅していなければ、正常にユーザーイメージの書き込みは完了していいます。


cvgt_devkit_user.png

これで、Development Kitの初期設定は完了ですので、PCにインストールしてください。このとき、LANコネクタの横にあるIntel® FPGA USB Cable II (昔の名前ではUSB-Blaster II)コネクターをPC本体のUSBポートに接続します。これを忘れると、あとでFPGAを再コンフィグレーションしたり、SignalTapIIを使ってFPGA内部信号をモニターしたりすることができません。接続が完了したら、PCの電源を入れて、OSを起動させてください。

PCIeツールでデバイスを確認

lspciで確認

OSが起動したら、Terminalを開きます。そして、下記コマンドを入力してください。

$ lspci | grep Altera
65:00.0 Unassigned class [ff00]: Altera Corporation Device e001 (rev 09)

上記のよう表示されれば、PCがFPGAを認識しています。もし表示されないようであれば、ボードの接続を確認したり、インストールするスロットを変更してみてください。

BDF

上記結果の65:00.0はPCIボードを識別すための数字です。BDFと呼ばれ、Bus,Device, Functionの略です。PCIはこの数字で管理されます。

lspci -nで確認

下記コマンドを入力します。

$ lspci -n | grep -i 1172:e001
65:00.0 ff00: 1172:e001 (rev 09)

上記のように表示されると思います。1172:e001はManufacturer ID:Device IDと呼ばれ、メーカーとデバイスを識別するための数字です。この番号はPlatfrom Designer上のPCIe Hard IPに設定されている数字です。

デバイス名の変更

/usr/share/hwdata/pci.idsにこの数字と表示の対応表があります。これを編集すれば、表示を変更できます。例えば、'-s'オプションでバスをBDFで指定すると、

$ lspci -s 65:00.0
65:00.0 Unassigned class [ff00]: Altera Corporation Device e001 (rev 09)

と表示されますが、pci.idsを編集して下記のように、最後に'e001 Cyclone V GT'を追加して、

1172  Altera Corporation
	00a7  Stratix V
	0530  Stratix IV
	e001  Cyclone V GT

再度コマンド入力すると

$ lspci -s 65:00.0
65:00.0 Unassigned class [ff00]: Altera Corporation Cyclone V GT (rev 09)

'e001'の部分が'Cyclone V GT'へ変更されます。

lspciの詳しい表示

PCIの情報をもう少し詳細を知りたい場合は'-v'を付けます。この時sudoを付けないと、表示がPermission deniedの途中で止まります。

$ sudo lspci -v -s 65:00.0
65:00.0 Unassigned class [ff00]: Altera Corporation Cyclone V GT (rev 09)
	Subsystem: Device a106:2484
	Flags: bus master, fast devsel, latency 0, IRQ 11, NUMA node 0
	Memory at 3800b0000000 (64-bit, prefetchable) [size=256M]
	Memory at c8000000 (32-bit, non-prefetchable) [size=128M]
	Capabilities: [50] MSI: Enable- Count=1/4 Maskable- 64bit+
	Capabilities: [78] Power Management version 3
	Capabilities: [80] Express Endpoint, MSI 00
	Capabilities: [100] Virtual Channel
	Capabilities: [200] Vendor Specific Information: ID=a000 Rev=0 Len=044 <?>

lspciのもっと詳しい表示

もっともっと詳細が知りたい場合は'-vv'を付けます。

$ sudo lspci -vv -s 65:00.0
65:00.0 Unassigned class [ff00]: Altera Corporation Device e001 (rev 09)
	Subsystem: Device a106:2484
	Control: I/O- Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx-
	Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
	Latency: 0, Cache Line Size: 32 bytes
	Interrupt: pin A routed to IRQ 11
	NUMA node: 0
	Region 0: Memory at 3800b0000000 (64-bit, prefetchable) [size=256M]
	Region 2: Memory at c8000000 (32-bit, non-prefetchable) [size=128M]
	Capabilities: [50] MSI: Enable- Count=1/4 Maskable- 64bit+
		Address: 0000000000000000  Data: 0000
	Capabilities: [78] Power Management version 3
		Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0-,D1-,D2-,D3hot-,D3cold-)
		Status: D0 NoSoftRst- PME-Enable- DSel=0 DScale=0 PME-
	Capabilities: [80] Express (v2) Endpoint, MSI 00
		DevCap:	MaxPayload 256 bytes, PhantFunc 0, Latency L0s <64ns, L1 <1us
			ExtTag- AttnBtn- AttnInd- PwrInd- RBE+ FLReset- SlotPowerLimit 0.000W
		DevCtl:	Report errors: Correctable- Non-Fatal- Fatal- Unsupported-
			RlxdOrd+ ExtTag- PhantFunc- AuxPwr- NoSnoop+
			MaxPayload 256 bytes, MaxReadReq 512 bytes
		DevSta:	CorrErr- UncorrErr- FatalErr- UnsuppReq- AuxPwr- TransPend-
		LnkCap:	Port #1, Speed 5GT/s, Width x4, ASPM L0s, Exit Latency L0s <4us, L1 <1us
			ClockPM- Surprise- LLActRep- BwNot- ASPMOptComp+
		LnkCtl:	ASPM Disabled; RCB 64 bytes Disabled- CommClk-
			ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt-
		LnkSta:	Speed 5GT/s, Width x4, TrErr- Train- SlotClk+ DLActive- BWMgmt- ABWMgmt-
		DevCap2: Completion Timeout: Range ABCD, TimeoutDis+, LTR-, OBFF Not Supported
		DevCtl2: Completion Timeout: 50us to 50ms, TimeoutDis-, LTR-, OBFF Disabled
		LnkCtl2: Target Link Speed: 5GT/s, EnterCompliance- SpeedDis-
			 Transmit Margin: Normal Operating Range, EnterModifiedCompliance- ComplianceSOS-
			 Compliance De-emphasis: -6dB
		LnkSta2: Current De-emphasis Level: -3.5dB, EqualizationComplete-, EqualizationPhase1-
			 EqualizationPhase2-, EqualizationPhase3-, LinkEqualizationRequest-
	Capabilities: [100 v1] Virtual Channel
		Caps:	LPEVC=0 RefClk=100ns PATEntryBits=1
		Arb:	Fixed- WRR32- WRR64- WRR128-
		Ctrl:	ArbSelect=Fixed
		Status:	InProgress-
		VC0:	Caps:	PATOffset=00 MaxTimeSlots=1 RejSnoopTrans-
			Arb:	Fixed- WRR32- WRR64- WRR128- TWRR128- WRR256-
			Ctrl:	Enable+ ID=0 ArbSelect=Fixed TC/VC=ff
			Status:	NegoPending- InProgress-
	Capabilities: [200 v1] Vendor Specific Information: ID=a000 Rev=0 Len=044 <?>  

このオプションを付けると、PCIeのリンクアップ状態やエラー状態を確認できます。'LnkSta'を見てください。'Speed 5GT/s, Width x4'になっているので、FPGAがPCIe Gen2 x 4でリンクアップしていることが確認できます。

sysfsの検索

PCIのリソースにアクセスするには/sys/以下のsysfsを指定する必要があります。そのパスを検索するスクリプトを下記に示します。

pci_path.sh
#!/bin/bash
PCIE_DEVID=1172:e001
export BDF=$(lspci -n | grep -i $PCIE_DEVID | awk '{print $1}')
export PCI_BDF=$(find /sys/devices -name "*$BDF")

これをsource ./pci_path.shと実行すれば、PCI_BDFにパスが設定されます。実際に実行してみると、

$ source ./pci_path.sh
$ echo $PCI_BDF
/sys/devices/pci0000:64/0000:64:00.0/0000:65:00.0

となります。ここで、BDFの前に0000が付いていますが、これはdomainと呼ばれる番号で通常のPCであれば、ほぼ0000です。サーバーなどの周辺機器が多いマシンでは異なる場合があります。

setpciによるデバイスIDの取得

setpciコマンドを使用して、Manufacturer IDとDevice IDを読み出してみましょう。コマンドは

$ sudo setpci -s $PCI_BDF VENDOR_ID DEVICE_ID
1172
e001

となり、期待値と一致しています。

sysfsの名用

sysfsに含まれる内容を見てみます。

$ ls $PCI_BDF
broken_parity_status      d3cold_allowed   irq             msi_bus    resource          subsystem_vendor
class                     device           local_cpulist   numa_node  resource0         uevent
config                    dma_mask_bits    local_cpus      power      resource0_wc      vendor
consistent_dma_mask_bits  driver_override  max_link_speed  remove     resource2
current_link_speed        enable           max_link_width  rescan     subsystem
current_link_width        firmware_node    modalias        reset      subsystem_device

このように、色々なsysfsがあることが確認できます。この中で、FPGAのリソースアクセスに必要なsysfsはresource0とresource2です。

pcimemによるアクセス

pcimemコマンドを利用して、DDR3メモリの読み書きを行います。FPGAのメモリーマップは下記のようになっています。

Resource Symbol Address Size
Resouce0 OCRAM_MEM 0x07000000 0x40000
DDR3_MEM 0x08000000 0x2000000
Resource2 PCIe CSR 0x00000000 0x4000
MSGDMA 0x06000000 0x60

pcimemの1番目の引数はsysfsのパス、2番目はDDR3のアドレスで0x8000000、3番目はアクセス単位(ワードxn)、4番目は書き込む値です(読み出し時不要)。下記は32ビット値をDDR3に2ワード書き込み、2ワード読み出すコマンドです。

sudo pcimem $PCI_RC0 0x08000000 w*1 0x00020000
sudo pcimem $PCI_RC0 0x08000004 w*1 0x00020001
sudo pcimem $PCI_RC0 0x08000000 w*2

pcimemの実装

このコマンドの実装はsysfsをオープンし、mmap関数でメモリマップし、そのポインターをベースとして読み書きを行うものです。ポインターを使って、FPGA上のメモリーにアクセスできます。下記に簡略化コードを示します。

fd = open( sysfs_path , O_RDWR | O_SYNC)) == -1);
map_base = mmap(0, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
virt_addr = map_base + OFFSET;
    *((uint64_t *)virt_addr) = data;
munmap(map_base, map_size);
close(fd);

実際の実行ログがこちらになります。

$ sudo pcimem $PCI_RC0 0x08000000 w*1 0x00020000
/sys/devices/pci0000:64/0000:64:00.0/0000:65:00.0/resource0 opened.
Target offset is 0x8000000, page size is 4096
mmap(0, 4096, 0x3, 0x1, 3, 0x8000000)
PCI Memory mapped to address 0x7f0911af2000.
0x8000000: 0x00020000
Written 0x20000; readback 0x20000

$ sudo pcimem $PCI_RC0 0x08000004 w*1 0x00020001
/sys/devices/pci0000:64/0000:64:00.0/0000:65:00.0/resource0 opened.
Target offset is 0x8000004, page size is 4096
mmap(0, 4096, 0x3, 0x1, 3, 0x8000004)
PCI Memory mapped to address 0x7f9faf3ea000.
0x8000004: 0x00020001
Written 0x20001; readback 0x20001

$ sudo pcimem $PCI_RC0 0x08000000 w*2
/sys/devices/pci0000:64/0000:64:00.0/0000:65:00.0/resource0 opened.
Target offset is 0x8000000, page size is 4096
mmap(0, 4096, 0x3, 0x1, 3, 0x8000000)
PCI Memory mapped to address 0x7f4eadfde000.
0x8000000: 0x00020000
0x8000004: 0x00020001

これで、PCIe経由でのFPGAへのアクセスが確認できました。

HLSデザインの実装

概要

今回リファレンスデザインにHLSで作成したコンポーネントを追加します。そのコンポーネントの入出力はAvalon Stream(以下Avalon-ST)で構成され、制御レジスタ(Control Status Register又はCSR)はAvalon-Memory Mapped(以下Avalon-MM)で構成されます。Avalon-STは単方向のデータインターフェースで、Avalon-MMは一般的なメモリアクセスバスです。Avalon-ST, Avalon-MMの詳細に関しては、Avalon Interface Specificationsを参照してください。下記にブロック図を示します。


block_diag.png

青が元々あるコンポーネントで、紫が今回追加したコンポーネントです。

  • System ID Peripheral Core(以下System ID)

    ユーザ定義32ビットIDとPlatform Designerで再生成した時の32ビットタイムスタンプを返すIPです。バージョン確認などに使用されます。
  • Modular Scatter-Gather DMA Core(以下MSGDMA)

    Avalon-MM<>Avalon-MM、Avalon-MM->Avalon-ST, Avalon-ST->Avalon-MM間の転送を行うDMA IPです。複数の転送記述により、一つの転送を構成します。連続したメモリ空間でなく、散らばったメモリを一か所にまとめることができるので、Scatter-Gather DMAと呼ばれています。
  • HLS Adder

    HLSで作成するIPです。
  • Avalon-MM Cyclone V Hard IP for PCIe(以下PCIe Hard IP)

    PCIeのエンドポイントを構成するIPです。BAR0,BAR2を有効にしており、これが先ほど説明したsysfsのresource0, resource2に対応します。テストではresource0をアクセスしていたので、対象がオンチップメモリとDDR3メモリであることがわかります。

HLS Adderの実装

下記が今回使用するHLSのソースコードです。

HLS Adderソースコード

main.cpp
のちほど更新します

簡単にソースの説明を行います。

TS_DAT定義

Table 1-2: Feature Comparison for 128- and 256-Bit Avalon-MM with DMA Interface to the Application Layerより、PCIe Gen2 x 4では、Avalon Interfaceバス幅は128ビットです。そのため、TS_DATは32ビットDWORDを4つ束ねた構造体として定義されます。

AVST_IN, AVST_OUT

HLSにおける、Avalon-STを表す、stream_out, stream_inを使って、AVST_OUT、AVST_INを定義します。この中の、usePacketsとはAvalon-STのStart Of Packet(以下SOP), End Of Packet(EOP)を有効にするための設定です。詳しは4.2. Avalon Streaming Interfacesを参照してください。

adder_avst

HLS Adderの実装です。単純にAVST_INからデータをread() Function APIで読み出し、加算を行い、そのデータをwrite() Function APIで書き込みます。次に、この実装で使用したHLS固有のキーワードについて説明します。

  • component

    ハード化される関数を示す属性です。
  • #pragma unroll

    ループ文を展開するオプションです。詳しくは4.2.2. Unroll Loopsを参照してください。
  • hls_avalon_slave_component

    ハード化されるコンポーネントの制御をレジスタアクセス行う場合に指定します。未指定の場合は通常の入出力信号で制御されます。詳しくはhls_avalon_slave_component Argumentを参照してください。
  • hls_avalon_slave_register_argument

    この指定を含む変数はレジスタアクセスにより設定されます。未指定の場合は通常の入出力信号で制御されます。詳しくは4.4.1. Control and Status Register (CSR) Slaveを参照してください。

main

テストベンチを記述するブロックです。ここで、テスト用入出力データを作成し、それをadder_avstに引数として与え、実行し計算します。

  • Preparing test vector

    テスト用入力データを作成します。
  • Executing components

    adder_avstを呼び出します。
  • Verifing results

    演算結果を検証します。

HLS用Makefile

Makefileはmain.cppを同一ディレクトリーに保存します。CXX_FLAGSのオプションについて、

  • --clock

    ターゲットクロックを設定するオプションで、PCIe Gen2x4の場合コアクロックは125MHzなので、この値をターゲットとします。
  • -v -v

    もう少し詳しいビルドログを表示させたい場合はもう一つ'-v'を追加して'-v -v'としてください。
Makefile
SOURCE_FILES=main.cpp
CXX_FLAGS=-v -g --clock 125MHz
CXX := i++

# Run the testbench and the component as a regular program
.PHONY: test_x86-64
test_x86-64: CXX_FLAGS += -march=x86-64 -o test_x86-64
test_x86-64: $(SOURCE_FILES)
	$(CXX) $(CXX_FLAGS) $(SOURCE_FILES)
	./test_x86-64

.PHONY: test_fpga
test_fpga: CXX_FLAGS += -march="Cyclone V" -o test_fpga
test_fpga: $(SOURCE_FILES)
	$(CXX) $(CXX_FLAGS) -ghdl $(SOURCE_FILES)
	./test_fpga

# Show waveform by ModelSim
wave:
	vsim -v test_fpga.prj/verification/vsim.wlf -do wave.do 

# Show a report
report:
	firefox test_fpga.prj/reports/report.html 

# Clean up temprary and delivered files
CLEAN_FILES := test_fpga \
               test_fpga.prj \
               test_x86-64 \
               test_x86-64.prj
clean:
	rm -rfv $(CLEAN_FILES)

Makefileコマンド

下記がサポートしているコマンドです。

Command Description
make x86_64でエミュレーション
make test_fpga FPGA用でRTL生成し、シミュレーション
make clean 生成ファイルを削除
make report レポートをfirefoxで表示
make wave ModelSimでシミュレーション波形を表示

HLSビルドログ

下記がビルドログです。エミュレーション、シミュレーション共にエラー無く完了しています。ここで、make時のTarget FPGA part nameは無視してください。make test_fpgaの場合は目的のパーツ番号と異なっていると思いますが、これは-marchをCyclone Vとしているためのディフォルト値です。型番が確定している場合はその型番を指定してください。

$ make
i++ -v -g --clock 125MHz -march=x86-64 -o test_x86-64 main.cpp
Target FPGA part name:   10AX115U1F45I1SG
Target FPGA family name: Arria 10
Target FPGA speed grade: -1
Analyzing main.cpp
Linking x86 objects
./test_x86-64
Preparing test vector
Executing components
Verifing results
No error

$ make_test_fpga
[ysaito@localhost hls]$ make test_fpga
i++ -v -g --clock 125MHz -march="Cyclone V" -o test_fpga -ghdl main.cpp
Target FPGA part name:   5CEFA9F23I7
Target FPGA family name: Cyclone V
Target FPGA speed grade: -7
Analyzing main.cpp for testbench generation
Creating x86-64 testbench 
Analyzing main.cpp for hardware generation
Optimizing component(s) and generating Verilog files
Generating cosimulation support
Generating simulation files for components: adder_avst
HLS simulation directory: [your worksapce]/test_fpga.prj/verification.
Linking x86 objects
./test_fpga
Preparing test vector
Executing components
Verifing results
No error

HLSレポート画面

make reportを実行してレポートの表示結果を下記に示します。詳細はA. Reviewing the High Level Design Reportsを参照してください。
qiita_hls_report_summary.png
qiita_hls_report_component.png
qiita_hls_report_verfication.png

ModelSim シミュレーション波形

make waveを実行して、シミュレーション結果波形を表示させます。このとき、下記のwave.doをMakefileと同じディレクトリーに保存してください。
qiita_hls_wave.png

ここで、Avalon-STのSOP,EOPはValid信号がHIGHの場合有効です。シミュレーション波形でも、リセット解除後、inp_startofpacketはHIGHで始まっていますが、inp_validはLOWであるため、その区間は無効です。

wave.do(クリックすると展開)
wave.do
onerror {resume}
quietly WaveActivateNextPane {} 0
add wave -noupdate /tb/adder_avst_inst/clock
add wave -noupdate /tb/adder_avst_inst/resetn
add wave -noupdate -radix hexadecimal /tb/adder_avst_inst/avs_cra_address
add wave -noupdate -radix hexadecimal /tb/adder_avst_inst/avs_cra_byteenable
add wave -noupdate /tb/adder_avst_inst/avs_cra_read
add wave -noupdate -radix unsigned /tb/adder_avst_inst/avs_cra_readdata
add wave -noupdate /tb/adder_avst_inst/avs_cra_write
add wave -noupdate -radix unsigned /tb/adder_avst_inst/avs_cra_writedata
add wave -noupdate /tb/adder_avst_inst/inp_valid
add wave -noupdate /tb/adder_avst_inst/inp_ready
add wave -noupdate /tb/adder_avst_inst/inp_startofpacket
add wave -noupdate /tb/adder_avst_inst/inp_endofpacket
add wave -noupdate -radix hexadecimal /tb/adder_avst_inst/inp_data
add wave -noupdate /tb/adder_avst_inst/outp_valid
add wave -noupdate /tb/adder_avst_inst/outp_ready
add wave -noupdate /tb/adder_avst_inst/outp_startofpacket
add wave -noupdate /tb/adder_avst_inst/outp_endofpacket
add wave -noupdate -radix hexadecimal /tb/adder_avst_inst/outp_data
add wave -noupdate /tb/adder_avst_inst/done_irq
TreeUpdate [SetDefaultTree]
WaveRestoreCursors {{Cursor 1} {56013 ps} 0}
quietly wave cursor active 1
configure wave -namecolwidth 321
configure wave -valuecolwidth 185
configure wave -justifyvalue left
configure wave -signalnamewidth 0
configure wave -snapdistance 10
configure wave -datasetprefix 0
configure wave -rowmargin 4
configure wave -childrowmargin 2
configure wave -gridoffset 0
configure wave -gridperiod 1
configure wave -griddelta 40
configure wave -timeline 0
configure wave -timelineunits ns
update
WaveRestoreZoom {18155 ps} {49278 ps}

HLSデザインをリファレンスデザインへ組み込む

Platform Designerでの組み込み

IP Search Pathの設定

Intel® Quartus® PrimeソフトウェアからPlatform Designer起動し、Tools->Optionsメニューを開きます。IP Search PathにHLSで作成したコンポーネントパスを下記のように追加し、Finishを押します。
qiita_qsys_option_p.png

Avalon-ST Bus Swap IPの追加

HLS AdderとMSGDMAのAvalon-STポートのバイトオーダーが逆になっており、これを解決するために、下記の3ファイルをリファレンスデザインと同じディレクトリーにコピーしてください。

avst_byte_swap.v(クリックすると展開)
avst_byte_swap.v
// Byte swap for Avalon-ST data

module avst_byte_swap
#(parameter WIDTH=128)
(
	input 	clock,
	input 	resetn,
	
	input 	in_valid,
	input 	in_sop,
	input 	in_eop,
	input 	[WIDTH-1:0] in_data,
	output 	in_ready,
	
	output 	out_valid,
	output 	out_sop,
	output 	out_eop,
	output 	[WIDTH-1:0] out_data,
	input 	out_ready
);
	assign out_valid 	= in_valid ;
	assign out_sop 	= in_sop ;
	assign out_eop 	= in_eop ;
	assign in_ready 	= out_ready ;

	generate 
		genvar i ;
		for(i = 0; i < WIDTH; i = i + 8) begin: SWAP
			assign out_data[WIDTH-i-1:WIDTH-i-8] = in_data[i+8-1:i] ;
		end
	endgenerate
	
endmodule
avst_byte_swap_snk2src_128_hw.tcl(クリックすると展開)
avst_byte_swap_snk2src_128_hw.tcl
# TCL File Generated by Component Editor 19.1
# Sun Dec 13 17:50:45 JST 2020
# DO NOT MODIFY


# 
# avst_byte_swap "avst_byte_swap" v1.0
#  2020.12.13.17:50:45
# 
# 

# 
# request TCL package from ACDS 16.1
# 
package require -exact qsys 16.1


# 
# module avst_byte_swap
# 
set_module_property DESCRIPTION ""
set_module_property NAME avst_byte_swap_snk2src_128
set_module_property VERSION 1.0
set_module_property INTERNAL false
set_module_property OPAQUE_ADDRESS_MAP true
set_module_property AUTHOR ""
set_module_property DISPLAY_NAME avst_byte_swap_snk2src_128
set_module_property INSTANTIATE_IN_SYSTEM_MODULE true
set_module_property EDITABLE true
set_module_property REPORT_TO_TALKBACK false
set_module_property ALLOW_GREYBOX_GENERATION false
set_module_property REPORT_HIERARCHY false


# 
# file sets
# 
add_fileset QUARTUS_SYNTH QUARTUS_SYNTH "" ""
set_fileset_property QUARTUS_SYNTH TOP_LEVEL avst_byte_swap
set_fileset_property QUARTUS_SYNTH ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property QUARTUS_SYNTH ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file avst_byte_swap.v VERILOG PATH avst_byte_swap.v TOP_LEVEL_FILE

add_fileset SIM_VERILOG SIM_VERILOG "" ""
set_fileset_property SIM_VERILOG TOP_LEVEL avst_byte_swap
set_fileset_property SIM_VERILOG ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property SIM_VERILOG ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file avst_byte_swap.v VERILOG PATH avst_byte_swap.v


# 
# display items
# 


# 
# connection point avst_source
# 
add_interface avst_source avalon_streaming start
set_interface_property avst_source associatedClock clock
set_interface_property avst_source associatedReset reset_n
set_interface_property avst_source dataBitsPerSymbol 128
set_interface_property avst_source errorDescriptor ""
set_interface_property avst_source firstSymbolInHighOrderBits true
set_interface_property avst_source maxChannel 0
set_interface_property avst_source readyLatency 0
set_interface_property avst_source ENABLED true
set_interface_property avst_source EXPORT_OF ""
set_interface_property avst_source PORT_NAME_MAP ""
set_interface_property avst_source CMSIS_SVD_VARIABLES ""
set_interface_property avst_source SVD_ADDRESS_GROUP ""

add_interface_port avst_source out_data data Output 128
add_interface_port avst_source out_eop endofpacket Output 1
add_interface_port avst_source out_ready ready Input 1
add_interface_port avst_source out_sop startofpacket Output 1
add_interface_port avst_source out_valid valid Output 1


# 
# connection point avst_sink
# 
add_interface avst_sink avalon_streaming end
set_interface_property avst_sink associatedClock clock
set_interface_property avst_sink associatedReset reset_n
set_interface_property avst_sink dataBitsPerSymbol 8
set_interface_property avst_sink errorDescriptor ""
set_interface_property avst_sink firstSymbolInHighOrderBits true
set_interface_property avst_sink maxChannel 0
set_interface_property avst_sink readyLatency 0
set_interface_property avst_sink ENABLED true
set_interface_property avst_sink EXPORT_OF ""
set_interface_property avst_sink PORT_NAME_MAP ""
set_interface_property avst_sink CMSIS_SVD_VARIABLES ""
set_interface_property avst_sink SVD_ADDRESS_GROUP ""

add_interface_port avst_sink in_data data Input 128
add_interface_port avst_sink in_eop endofpacket Input 1
add_interface_port avst_sink in_ready ready Output 1
add_interface_port avst_sink in_sop startofpacket Input 1
add_interface_port avst_sink in_valid valid Input 1


# 
# connection point clock
# 
add_interface clock clock end
set_interface_property clock clockRate 0
set_interface_property clock ENABLED true
set_interface_property clock EXPORT_OF ""
set_interface_property clock PORT_NAME_MAP ""
set_interface_property clock CMSIS_SVD_VARIABLES ""
set_interface_property clock SVD_ADDRESS_GROUP ""


# 
# connection point reset_n
# 
add_interface reset_n reset end
set_interface_property reset_n associatedClock clock
set_interface_property reset_n synchronousEdges DEASSERT
set_interface_property reset_n ENABLED true
set_interface_property reset_n EXPORT_OF ""
set_interface_property reset_n PORT_NAME_MAP ""
set_interface_property reset_n CMSIS_SVD_VARIABLES ""
set_interface_property reset_n SVD_ADDRESS_GROUP ""

add_interface_port reset_n resetn reset_n Input 1
avst_byte_swap_src2snk_128_hw.tcl(クリックすると展開)
avst_byte_swap_src2snk_128_hw.tcl
# TCL File Generated by Component Editor 19.1
# Sun Dec 13 17:50:45 JST 2020
# DO NOT MODIFY


# 
# avst_byte_swap "avst_byte_swap" v1.0
#  2020.12.13.17:50:45
# 
# 

# 
# request TCL package from ACDS 16.1
# 
package require -exact qsys 16.1


# 
# module avst_byte_swap
# 
set_module_property DESCRIPTION ""
set_module_property NAME avst_byte_swap_src2snk_128
set_module_property VERSION 1.0
set_module_property INTERNAL false
set_module_property OPAQUE_ADDRESS_MAP true
set_module_property AUTHOR ""
set_module_property DISPLAY_NAME avst_byte_swap_src2snk_128
set_module_property INSTANTIATE_IN_SYSTEM_MODULE true
set_module_property EDITABLE true
set_module_property REPORT_TO_TALKBACK false
set_module_property ALLOW_GREYBOX_GENERATION false
set_module_property REPORT_HIERARCHY false

# 
# file sets
# 
add_fileset QUARTUS_SYNTH QUARTUS_SYNTH "" ""
set_fileset_property QUARTUS_SYNTH TOP_LEVEL avst_byte_swap
set_fileset_property QUARTUS_SYNTH ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property QUARTUS_SYNTH ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file avst_byte_swap.v VERILOG PATH avst_byte_swap.v TOP_LEVEL_FILE

add_fileset SIM_VERILOG SIM_VERILOG "" ""
set_fileset_property SIM_VERILOG TOP_LEVEL avst_byte_swap
set_fileset_property SIM_VERILOG ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property SIM_VERILOG ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file avst_byte_swap.v VERILOG PATH avst_byte_swap.v


# 
# display items
# 


# 
# connection point avst_source
# 
add_interface avst_source avalon_streaming start
set_interface_property avst_source associatedClock clock
set_interface_property avst_source associatedReset reset_n
set_interface_property avst_source dataBitsPerSymbol 8
set_interface_property avst_source errorDescriptor ""
set_interface_property avst_source firstSymbolInHighOrderBits true
set_interface_property avst_source maxChannel 0
set_interface_property avst_source readyLatency 0
set_interface_property avst_source ENABLED true
set_interface_property avst_source EXPORT_OF ""
set_interface_property avst_source PORT_NAME_MAP ""
set_interface_property avst_source CMSIS_SVD_VARIABLES ""
set_interface_property avst_source SVD_ADDRESS_GROUP ""

add_interface_port avst_source out_data data Output 128
add_interface_port avst_source out_eop endofpacket Output 1
add_interface_port avst_source out_ready ready Input 1
add_interface_port avst_source out_sop startofpacket Output 1
add_interface_port avst_source out_valid valid Output 1


# 
# connection point avst_sink
# 
add_interface avst_sink avalon_streaming end
set_interface_property avst_sink associatedClock clock
set_interface_property avst_sink associatedReset reset_n
set_interface_property avst_sink dataBitsPerSymbol 128
set_interface_property avst_sink errorDescriptor ""
set_interface_property avst_sink firstSymbolInHighOrderBits true
set_interface_property avst_sink maxChannel 0
set_interface_property avst_sink readyLatency 0
set_interface_property avst_sink ENABLED true
set_interface_property avst_sink EXPORT_OF ""
set_interface_property avst_sink PORT_NAME_MAP ""
set_interface_property avst_sink CMSIS_SVD_VARIABLES ""
set_interface_property avst_sink SVD_ADDRESS_GROUP ""

add_interface_port avst_sink in_data data Input 128
add_interface_port avst_sink in_eop endofpacket Input 1
add_interface_port avst_sink in_ready ready Output 1
add_interface_port avst_sink in_sop startofpacket Input 1
add_interface_port avst_sink in_valid valid Input 1


# 
# connection point clock
# 
add_interface clock clock end
set_interface_property clock clockRate 0
set_interface_property clock ENABLED true
set_interface_property clock EXPORT_OF ""
set_interface_property clock PORT_NAME_MAP ""
set_interface_property clock CMSIS_SVD_VARIABLES ""
set_interface_property clock SVD_ADDRESS_GROUP ""


# 
# connection point reset_n
# 
add_interface reset_n reset end
set_interface_property reset_n associatedClock clock
set_interface_property reset_n synchronousEdges DEASSERT
set_interface_property reset_n ENABLED true
set_interface_property reset_n EXPORT_OF ""
set_interface_property reset_n PORT_NAME_MAP ""
set_interface_property reset_n CMSIS_SVD_VARIABLES ""
set_interface_property reset_n SVD_ADDRESS_GROUP ""

add_interface_port reset_n resetn reset_n Input 1

IP Catalogの確認

メニューより、File->Refresh Systemをクリックすると、システムが更新され、赤枠で囲まれた、Avalon-ST Bus SwapとHLS->adder_avstが現れます。
qiita_qsys_ip_catalog.png

IPの追加

下記IPを追加します。

  • System ID Peripheral Intel FPGA IP x 1
  • avst_byte_swap_snk2src_128 x 1
  • avst_byte_swap_src2snk_128 x 1
  • Modular Scatter-Gather DMA Intel FPGA IP x 2

クロックとリセットは全て下記の通りです。

  • pcie_cv_hip_avmm_0:coreclkout
  • pcie_cv_hip_avmm_0:nreset_status

名前、アドレス、割り込み番号は下記の通りです。

|Resource|Name|Address|Size|IRQ|
|:---|:---|:---|:---|:---|:---|
|Rxm_BAR0|OCRAM_MEM|0x07000000|0x40000||
||DDR3_MEM|0x08000000|0x2000000||
|Rxm_BAR2|PCIe CSR|0x00000000|0x4000|1|
||MSGDMA_0|0x06010000|0x40|0|
||SYSID|0x06000000|0x8||
||MSGDMA_1|0x06020000|0x40|4|
||adder_avst|0x06020080|0x40|2|
||MSGDMA_2|0x06020040|0x60|3|

Avalon-MM, Avalon-ST接続ポートは下記の通りです。各ポートはConnectionsのポートと全て接続されます。

Name Port Connections
MSGDMA_0 mm_read mem_if_ddr3_emif_0.avl_0
onchip_memory2_0.s1
pcie_cv_hip_avmm_0.Txs
mm_write mem_if_ddr3_emif_0.avl_0
onchip_memory2_0.s1
pcie_cv_hip_avmm_0.Txs
csr pcie_cv_hip_avmm_0.Rxm_BAR2
descriptor_slave pcie_cv_hip_avmm_0.Rxm_BAR2
sysid_qsys control_slave pcie_cv_hip_avmm_0.Rxm_BAR2
MSGDMA_1 mm_read mem_if_ddr3_emif_0.avl_0
onchip_memory2_0.s1
pcie_cv_hip_avmm_0.Txs
st_source avst_byte_swap_snk2src_128.avst_sink
csr pcie_cv_hip_avmm_0.Rxm_BAR2
descriptor_slave pcie_cv_hip_avmm_0.Rxm_BAR2
avst_byte_swap_snk2src_128 avst_source adder_avst.inp
avst_sink msgdma_1.st_source
adder_avst inp avst_byte_swap_snk2src_128.avst_source
outp avst_byte_swap_src2snk_128.avst_sink
avs_cra pcie_cv_hip_avmm_0.Rxm_BAR2
avst_byte_swap_src2snk_128 avst_sink adder_avst.outp
avst_source msgdma_2.st_sink
MSGDMA_2 mm_write mem_if_ddr3_emif_0.avl_0
onchip_memory2_0.s1
pcie_cv_hip_avmm_0.Txs
st_sink avst_byte_swap_src2snk_128.avst_source
csr pcie_cv_hip_avmm_0.Rxm_BAR2
descriptor_slave pcie_cv_hip_avmm_0.Rxm_BAR2
response pcie_cv_hip_avmm_0.Rxm_BAR2

接続画面

qiita_qsys_pcie.png
qiita_qsys_all.png

各IPの設定

  • System ID
    qiita_qsys_sysid.png

  • MSGDMA_0
    qiita_qsys_msgdma_0.png

  • MSGDMA_1
    qiita_qsys_msgdma_1.png

  • MSGDMA_2
    qiita_qsys_msgdma_2.png

  • PCIe 設定 BAR0
    qiita_qsys_pcie_setting_0.png

  • PCIe 設定 BAR2
    qiita_qsys_pcie_setting_1.png

  • PCIe 設定
    qiita_qsys_pcie_setting_2.png

再生成

IP設定が完了したら、Genereate HDLをクリックし、RTLを生成してください。生成後、FinishをクリックしてPlatform Designerを終了させてください。

SignalTapIIの追加

SignalTapIIの追加を行います。Platform Designerでコンポーネントを追加したので、それを反映されるために、Intel® Quartus® PrimeソフトウェアのProcessing->Start->Start Analysis & Syhthesisをクリックして、論理合成を行います。

次に、Tools->Signal Tap Logic Analyzerをクリックして、Signal Tap logic analyzerを起動させます。
クロックはPCIeのcoreclkoutを、データはadder_avstの全信号です。設定すると、下記のようなります。トリガーはinp_validの立ち上げりエッジです。
qiita_stp_setup.png

再コンパイル

Intel® Quartus® Primeソフトウェアに戻り、Processing->Start Compilationをクリックし、全体をコンパイルします。

コンフィグレーション

最初に、FPGAをコンフィグレーションするための設定ファイルを作成します。設定ファイルを作成すれば、コマンドラインより、簡単にコンフィグレーションできます。

  • Intel® Quartus® PrimeソフトウェアのTools->Programmerメニューをクリックして、Programmerを起動
  • Programmer起動後、左の'Auto Detect'ボタンをクリック
  • Select Deviceダイアログが表示されますが、OKクリック
  • 警告ダイアログが開きますが、そのままYesをクリック
  • 5CGTFD9A5を選択し、右クリックして、Change Fileを選択
  • ファイル選択ダイアログでoutput_files/c5gt_gen2x4_mSGDMA.sofを選択
  • 5CGTFD9E5F35のProgram/Configureチェックボックスをチェック
  • File->Save Asメニューをクリックし、ファイル名をconf.cdfとして、c5gt_gen2x4_mSGDMA.qpfファイルのあるディレクトリーに保存
  • Startをクリック

正常にコンフィグレーションが完了すると下記のようになります。

qiita_programmer.png

  • 注意 PCIの設定で下記のAdvanced error reporting(AER)をチェックした場合はコンフィグレーションした瞬間にPCがリブートします。これを回避するにはAERをマスクし、無効にしてからコンフィグレーションする必要があります。
    qiita_qsys_pcie_aer.png

以上より、今後は、コマンドラインから下記コマンドでコンフィグレーションできます。

quartus_pgm -c 1 conf.cdf

-c オプションの数字はIntel® FPGA USB Cable IIのインデックス番号で、jtagconfigコマンドで確認できます。この場合は、1のみ有効です。

$ jtagconfig
1) USB-BlasterII [1-9.3.3]
  02B040DD   5CGTFD9(A5|C5|D5|E5)/..
  020A40DD   5M(1270ZF324|2210Z)/EPM2210

PCIeの再構成

コンフィグレーションは完了しましたが、この状態ではPCからは正しくアクセスできません。そのため、PCIeを再構成する必要があります。それを実行するのが下記スクリプトです。

reconfig.sh
#!/bin/bash
PCIE_DEVID=1172:e001
BDF=$(lspci -n | grep -i $PCIE_DEVID | awk '{print $1}')
PCI_BDF=$(find /sys/devices -name "*$BDF")
if [ ! -f myConfig ] ; then
	echo "Copy PCIe config"
	sudo chmod 666 $PCI_BDF/config
	sudo \cp -r $PCI_BDF/config myConfig
fi
echo "Conguring FPGA"
quartus_pgm -c 1 config.cdf
echo "Rescanning PCIe"
sudo chmod 666 $PCI_BDF/config
sudo \cp -r myConfig $PCI_BDF/config

このスクリプトは

  • デバイスIDからPCIeのBDFを検索
  • sysfsにあるconfigデータをmyConfigとして保存、既にある場合は何もしない
  • FPGAの再コンフィグレーション
  • myConfigをconfigに書き込みPCIeを再構成

これで、今回追加したFPGA上のコンポーネントへアクセスできます。

PCIe AERのマスク

PCIeの設定で、Advanced error reporting(AER)が有効になっている場合、コンフィグレーション前後でAERをマスクしないとPCがリブートすると、説明しました。これはFPGAを再コンフィグレーションするとき、PCIeバスがリンクダウンし、それをエラーとして、ホストPCに通知され、リブートが実行されるためです。マスクレジスタは

  • Uncorrectable Error Mask Register (Offset 08h)
  • Correctable Error Mask Register (Offset 14h)

2つあり、この32ビット値にすべて1を書き込めば、レポートはマスクされ、0を書き込めば、有効になります。実際に実行するにはsetpciコマンドを使用します。下記にサンプルスクリプトを示します。これはテストはされていないので、確認の上ご使用ください。

UER_MASK=$(setpci -s $PCI_BDF ECAP_AER+0x08.l)
CER_MASK=$(setpci -s $PCI_BDF ECAP_AER+0x14.l)
setpci -s $PCI_BDF ECAP_AER+0x08.l=0xffffffff
setpci -s $PCI_BDF ECAP_AER+0x14.l=0xffffffff

FPGA reconfig

setpci -s $PCI_BDF ECAP_AER+0x08.l=$UER_MASK
setpci -s $PCI_BDF ECAP_AER+0x14.l=$CER_MASK

System IDへのアクセス

試しにSystem IDをアクセスしてみます。

get_sysid.sh
#!/bin/bash
PCIE_DEVID=1172:e001
BDF=$(lspci -n | grep -i $PCIE_DEVID | awk '{print $1}')
PCI_BDF=$(find /sys/devices -name "*$BDF")
PCI_RC2=$PCI_BDF/resource2
SYSID=0x06010000
sudo pcimem $PCI_RC2 $SYSID w*2

下記が実行ログです。0x601000には設定したSystem ID(0x12345678)が表示されているのが確認できます。

$ source ./get_sysid.sh
/sys/devices/pci0000:64/0000:64:00.0/0000:65:00.0/resource2 opened.
Target offset is 0x6010000, page size is 4096
mmap(0, 4096, 0x3, 0x1, 3, 0x6010000)
PCI Memory mapped to address 0x7ffb9127f000.
0x6010000: 0x12345678
0x6010004: 0x5FD5E7D6

アドレス0x6010004の0x5FD5E7D6はタイムスタンプを示します。q_sys.qsysファイルのあるディレクトリーにRTL生成後に、q_sys.sopcinfoファイルが生成されます。その中に生成時のタイムスタンプが記録されています。下記コマンドを入力してください。

$ cat q_sys.sopcinfo | grep -A 1 TIMESTAMP
   <name>embeddedsw.CMacro.TIMESTAMP</name>
   <value>1607854038</value>

1607854038 がタイムスタンプで16進数に変換すると0x5FD5E7D6となり、一致していることがわかります。アプリケーションにて、これらの値を最初に確認すれば、間違ったFPGAデザインを使用して発生するエラーを防ぐことができます。

CサンプルでFPGAをアクセス

今までは主にコマンドを使用してFPGAのデバイスにアクセスしてきましたが、ここでは簡単なCプログラムを用いてアクセスしてみます。

拡張方法

基本はopen, mmapを組み合わせて拡張します。メモリをアクセスするにはRxm_BAR0に対応するresource0を使用し、制御レジスタをアクセスするにはRxm_BAR2に対応するresource2を使用すれば、FPAのすべてのリソースにアクセスできます。下記が基本コードです。

fd_mem = open( sysfs_path/resource0 , O_RDWR | O_SYNC)) == -1);
fd_csr = open( sysfs_path/resource2 , O_RDWR | O_SYNC)) == -1);
map_mem_base = mmap(0, map_mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_mem, 0);
map_csr_base = mmap(0, map_csr_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_csr, 0);
virt_mem_addr = map_mem_base + OFFSET_MEM ;
virt_csr_addr = map_csr_base + OFFSET_CSR ;

*((uint64_t *)virt_mem_addr) = data1;
*((uint32_t *)virt_csr_addr) = data2;

munmap(map_csr_base, map_csr_size);
munmap(map_mem_base, map_mem_size);
close(fd_csr);
close(fd_mem);

このvirt_mem_addrとvirt_csr_addrに対して、Platform Designerで定義した、各IPのオフセットアドレスを加算してアクセスします。例えば、

sysid     = *((uint32_t *)(virt_csr_addr+0x601000));
timestamp = *((uint32_t *)(virt_csr_addr+0x601004));

とすれば、System IDにアクセスできます。

HLS adder_avstの制御

HLSで作成したコンポーネントはhls_avalon_slave_componentとhls_avalon_slave_register_argument属性を追加したため、動作させるには制御レジスタを操作する必要があります。

include file

定義レジスタ詳細はレジスタマップファイル(test_fpga.prj/components/adder_avst/adder_avst_csr.h)に記述されています。ファイルには、

  • Memory Map Summary
  • Register Address Macros

が含まれます。下記にadder_avstのレジスタマップ概要を示します。

Register Address Access Register bit(s) Name Description
0x0 R 0 Busy Read the busy status of the component
0 - the component is ready to accept a new start
1 - the component cannot accept a new start
0x8 W 0 Start Write 1 to signal start to the component
0x10 R/W 0 interrupt_enable 0 - Disable interrupt
1 - Enable interrupt
0x18 R 1 done Signals component completion
Wclr 0 interrupt_status Write 1 to clear
0x20 R 31-0 returndata Return data
0x28 R/W 31-0 count Argument count

起動方法

今回の場合、コンポーネントがBusyで無いことを確認し、countに転送数をセットし、最後に、Startに1を書き込むことで、adder_avstが起動し、データ入力待ちになります。データはMSGDMAにより供給されますので、次にMSGDMAについて説明します。

MSGDMAの制御

転送種類

MSGDMAは次の転送をサポートしています。

  • Memory-Mapped to Memory-Mapped (Avalon-MM to Avalon-MM)
  • Memory-Mapped to Streaming (Avalon-MM to Avalon-ST)
  • Streaming to Memory-Mapped (Avalon-ST to Avalon-MM)

オリジナルのリファレンスデザインでは、Avalon-MM間の転送を用いていましたが、HLS adder_avstの入出力がAvalon-STであるため、MSGDMAを二つ使用することでメモリへの転送を可能としています。

制御ポート

MSDMAには下記3ポートがあります。

  • Descriptor Slave Port

    DMA転送情報を書き込みFIFOポート。標準、拡張フォーマットがあり、主な違いは64ビットアドレスサポートの有無です。今回は標準フォーマットを使用します。
  • Control and Status Register Slave Port

    MSGDMAを制御するCSRポート
  • Response Port
    MSGDMA転送完了時のバイト数、エラー状態を取得するポートです。今回は使用しません。

Descriptorポート詳細

標準フォーマットには32ビットのリード、ライトアドレス、転送バイト数、制御情報を設定します。Avalon-MM->Avalon-STの場合、ライトアドレスには書き込みを行いません。同様に、Avalon-ST->Avalon-MMの場合にはリードアドレスは書き込みを行いません。今回、制御情報ではGO,Generate SOP,Generate EOPビットのみ使用します。

Table 287. Standard Descriptor Format

Offset Access Description
0x0 W Read Address[31:0]
0x4 W Write Address[31:0]
0x8 W Length[31:0]
0xC W Control[31:0]

Table 289. Descriptor Control Field Bit Definition

bit Access Description
31 Go Commits all the descriptor information into the descriptor FIFO
9 Generate EOP Emits an end of packet on last beat of a Avalon-MM to Avlaon-ST transfer
8 Generate SOP Emits a start of packet on the first beat of a Avalon-MM to Avalon-ST transfer

制御ポート

リセット、エラーステータス、状態取得などをおこなうポートです。詳細は下記を参照してください。

Table 290. CSR Registers Map

Avalon-MM->Avalon-MMの動作フロー

  • MSGDMAをリセットし、リセット完了を待つ
  • ディスクリプター書き込み
  • BUSYフラグが完了するまで待つ

となります。

HLS Adder_avst, MSGDMA動作フロー

  • adder_avstがBUSYでないことを確認
  • MSGDMA_1,MSGDMA_2をリセット
  • MSGDMA_1,MSGDMA_2のリセット完了を待つ
  • adder_avstに送受信カウンター値を設定
  • MSGDMA_2のディスクリプター書き込み
  • adder_avstを開始
  • MSGDMA_1のディスクリプター書き込み
  • MSGDMA_1,MSGDMA_2のBUSYフラグが完了するまで待つ

ここで、設定の順番がAvalon-STの受信側から順番に行っていることに注意してください。Avalon-STにはReady信号があり、これがHIGHの場合に転送可能となります。そのため、複数IPでパイプラインを構成している場合は、受信側から順番に実行を許可しReady信号をHIGHにします。

ソースコード

msgdma.h(クリックすると展開)
msgdma.h
/*************************************************************
 *
 *      Modular Scatter-Gather DMA Include Header
 *
 ************************************************************/
/*
 *  See 30. Modular Scatter-Gather DMA Core
 *  URL:https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_embedded_ip.pdf
 *
 */

#ifndef __MSGDMA_DISPATCHER_H__
#define __MSGDMA_DISPATCHER_H__

#include <sys/types.h>

#define STANDARD_DESCRIPTOR_SIZE 16
#define EXTENDED_DESCRIPTOR_SIZE 32
#define RESPONSE_SIZE 8

// bits making up the "errors" register
#define RESPONSE_ERROR_MASK                      (0xFF)
#define RESPONSE_ERROR_OFFSET                    (0)
#define RESPONSE_EARLY_TERMINATION_MASK          (1<<8)
#define RESPONSE_EARLY_TERMINATION_OFFSET        (8)

// masks for the status register bits
#define CSR_BUSY_MASK                           (1)
#define CSR_BUSY_OFFSET                         (0)
#define CSR_DESCRIPTOR_BUFFER_EMPTY_MASK        (1<<1)
#define CSR_DESCRIPTOR_BUFFER_EMPTY_OFFSET      (1)
#define CSR_DESCRIPTOR_BUFFER_FULL_MASK         (1<<2)
#define CSR_DESCRIPTOR_BUFFER_FULL_OFFSET       (2)
#define CSR_RESPONSE_BUFFER_EMPTY_MASK          (1<<3)
#define CSR_RESPONSE_BUFFER_EMPTY_OFFSET        (3)
#define CSR_RESPONSE_BUFFER_FULL_MASK           (1<<4)
#define CSR_RESPONSE_BUFFER_FULL_OFFSET         (4)
#define CSR_STOP_STATE_MASK                     (1<<5)
#define CSR_STOP_STATE_OFFSET                   (5)
#define CSR_RESET_STATE_MASK                    (1<<6)
#define CSR_RESET_STATE_OFFSET                  (6)
#define CSR_STOPPED_ON_ERROR_MASK               (1<<7)
#define CSR_STOPPED_ON_ERROR_OFFSET             (7)
#define CSR_STOPPED_ON_EARLY_TERMINATION_MASK   (1<<8)
#define CSR_STOPPED_ON_EARLY_TERMINATION_OFFSET (8)
#define CSR_IRQ_SET_MASK                        (1<<9)
#define CSR_IRQ_SET_OFFSET                      (9)

// masks for the control register bits
#define CSR_STOP_MASK                           (1)
#define CSR_STOP_OFFSET                         (0)
#define CSR_RESET_MASK                          (1<<1)
#define CSR_RESET_OFFSET                        (1)
#define CSR_STOP_ON_ERROR_MASK                  (1<<2)
#define CSR_STOP_ON_ERROR_OFFSET                (2)
#define CSR_STOP_ON_EARLY_TERMINATION_MASK      (1<<3)
#define CSR_STOP_ON_EARLY_TERMINATION_OFFSET    (3)
#define CSR_GLOBAL_INTERRUPT_MASK               (1<<4)
#define CSR_GLOBAL_INTERRUPT_OFFSET             (4)
#define CSR_STOP_DESCRIPTORS_MASK               (1<<5)
#define CSR_STOP_DESCRIPTORS_OFFSET             (5)

// masks for the FIFO fill levels and sequence number
#define CSR_READ_FILL_LEVEL_MASK                (0xFFFF)
#define CSR_READ_FILL_LEVEL_OFFSET              (0)
#define CSR_WRITE_FILL_LEVEL_MASK               (0xFFFF0000)
#define CSR_WRITE_FILL_LEVEL_OFFSET             (16)
#define CSR_RESPONSE_FILL_LEVEL_MASK            (0xFFFF)
#define CSR_RESPONSE_FILL_LEVEL_OFFSET          (0)
#define CSR_READ_SEQUENCE_NUMBER_MASK           (0xFFFF)
#define CSR_READ_SEQUENCE_NUMBER_OFFSET         (0)
#define CSR_WRITE_SEQUENCE_NUMBER_MASK          (0xFFFF0000)
#define CSR_WRITE_SEQUENCE_NUMBER_OFFSET        (16)

// masks and offsets for the bits in the descriptor control field
#define DESCRIPTOR_CONTROL_TRANSMIT_CHANNEL_MASK         (0xFF)
#define DESCRIPTOR_CONTROL_TRANSMIT_CHANNEL_OFFSET       (0)
#define DESCRIPTOR_CONTROL_GENERATE_SOP_MASK             (1<<8)
#define DESCRIPTOR_CONTROL_GENERATE_SOP_OFFSET           (8)
#define DESCRIPTOR_CONTROL_GENERATE_EOP_MASK             (1<<9)
#define DESCRIPTOR_CONTROL_GENERATE_EOP_OFFSET           (9)
#define DESCRIPTOR_CONTROL_PARK_READS_MASK               (1<<10)
#define DESCRIPTOR_CONTROL_PARK_READS_OFFSET             (10)
#define DESCRIPTOR_CONTROL_PARK_WRITES_MASK              (1<<11)
#define DESCRIPTOR_CONTROL_PARK_WRITES_OFFSET            (11)
#define DESCRIPTOR_CONTROL_END_ON_EOP_MASK               (1<<12)
#define DESCRIPTOR_CONTROL_END_ON_EOP_OFFSET             (12)
#define DESCRIPTOR_CONTROL_TRANSFER_COMPLETE_IRQ_MASK    (1<<14)
#define DESCRIPTOR_CONTROL_TRANSFER_COMPLETE_IRQ_OFFSET  (14)
#define DESCRIPTOR_CONTROL_EARLY_TERMINATION_IRQ_MASK    (1<<15)
#define DESCRIPTOR_CONTROL_EARLY_TERMINATION_IRQ_OFFSET  (15)
#define DESCRIPTOR_CONTROL_ERROR_IRQ_MASK                (0xFF<<16)  // the read master will use this as the transmit error, the dispatcher will use this to generate an interrupt if any of the error bits are asserted by the write master
#define DESCRIPTOR_CONTROL_ERROR_IRQ_OFFSET              (16)
#define DESCRIPTOR_CONTROL_EARLY_DONE_ENABLE_MASK        (1<<24)
#define DESCRIPTOR_CONTROL_EARLY_DONE_ENABLE_OFFSET      (24)
#define DESCRIPTOR_CONTROL_GO_MASK                       (1<<31)  // at a minimum you always have to write '1' to this bit as it commits the descriptor to the dispatcher
#define DESCRIPTOR_CONTROL_GO_OFFSET                     (31)

#define CSR_OFF                 0x00
#define DESC_OFF                0x20

#define DESC_STD_RD_OFF         (DESC_OFF+0x00)
#define DESC_STD_WR_OFF         (DESC_OFF+0x04)
#define DESC_STD_LEN_OFF        (DESC_OFF+0x08)
#define DESC_STD_CTRL_OFF       (DESC_OFF+0x0C)

#define DESC_EXT_RD_OFF         (DESC_OFF+0x00)
#define DESC_EXT_WR_OFF         (DESC_OFF+0x04)
#define DESC_EXT_LEN_OFF        (DESC_OFF+0x08)
#define DESC_EXT_SEQ_OFF        (DESC_OFF+0x0C)
#define DESC_EXT_RBURST_OFF     (DESC_OFF+0x0E)
#define DESC_EXT_WBURST_OFF     (DESC_OFF+0x0F)
#define DESC_EXT_RSTRIDE_OFF    (DESC_OFF+0x10)
#define DESC_EXT_WSTRIDE_OFF    (DESC_OFF+0x12)
#define DESC_EXT_RDH_OFF        (DESC_OFF+0x14)
#define DESC_EXT_WRH_OFF        (DESC_OFF+0x18)
#define DESC_EXT_CTRL_OFF       (DESC_OFF+0x1C)

#define CSR_STATUS_REG_OFF                    (CSR_OFF+0x0)
#define CSR_CONTROL_REG_OFF                   (CSR_OFF+0x4)
#define CSR_DESCRIPTOR_FILL_LEVEL_REG_OFF     (CSR_OFF+0x8)
#define CSR_RESPONSE_FILL_LEVEL_REG_OFF       (CSR_OFF+0xC)
#define CSR_SEQUENCE_NUMBER_REG_OFF           (CSR_OFF+0x10)

static inline void msgdma_setup_std_desc(void* base, uint32_t dst,uint32_t src, uint32_t len, uint32_t ctrl )
{
    *((uint32_t*)(base+DESC_STD_RD_OFF)) = src ;
    *((uint32_t*)(base+DESC_STD_WR_OFF)) = dst ;
    *((uint32_t*)(base+DESC_STD_LEN_OFF)) = len ;
    *((uint32_t*)(base+DESC_STD_CTRL_OFF)) = ctrl | DESCRIPTOR_CONTROL_GO_MASK ;
}

static inline void msgdma_setup_std_desc_to_st(void* base, uint32_t src, uint32_t len, uint32_t ctrl )
{
    *((uint32_t*)(base+DESC_STD_RD_OFF)) = src ;
    *((uint32_t*)(base+DESC_STD_LEN_OFF)) = len ;
    *((uint32_t*)(base+DESC_STD_CTRL_OFF)) = ctrl | DESCRIPTOR_CONTROL_GO_MASK ;
}

static inline void msgdma_setup_std_desc_from_st(void* base, uint32_t dst, uint32_t len, uint32_t ctrl )
{
    *((uint32_t*)(base+DESC_STD_WR_OFF)) = dst ;
    *((uint32_t*)(base+DESC_STD_LEN_OFF)) = len ;
    *((uint32_t*)(base+DESC_STD_CTRL_OFF)) = ctrl | DESCRIPTOR_CONTROL_GO_MASK ;
}

static inline uint32_t msgdma_get_csr_status(void* base)
{
    return *((uint32_t*)(base+CSR_STATUS_REG_OFF)) ;
}

static inline void msgdma_clr_csr_status(void* base,uint32_t val)
{
    *((uint32_t*)(base+CSR_STATUS_REG_OFF)) = val;
}

static inline uint32_t msgdma_get_csr_ctrl(void* base)
{
    return *((uint32_t*)(base+CSR_CONTROL_REG_OFF)) ;
}

static inline void msgdma_set_csr_ctrl(void* base,uint32_t ctrl)
{
    *((uint32_t*)(base+CSR_CONTROL_REG_OFF)) = ctrl;
}

#endif /*__MSGDMA_DISPATCHER_H__*/
test.c(クリックすると展開)
test.c
/******************************************

    Test program for PCIe reference design
        with HLS Adder component

******************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "msgdma.h"
#include "adder_avst_csr.h"

/***************************************
 *
 *      Memory Map
 *
 ***************************************/

/* Resouirce0   */
#define OCRAM_MEM_ADDR          0x07000000
#define OCRAM_MEM_SIZE          0x40000
#define DDR3_MEM_ADDR           0x08000000
#define DDR3_MEM_SIZE           0x02000000

/* Resouirce2   */
#define PCIE_CSR_ADDR           0x00000000
#define PCIE_CSR_SIZE           0x4000
#define MSGDMA_ADDR             0x06000000
#define MSGDMA_CSR_SIZE         0x40
#define SYSID_ADDR              0x06010000
#define SYSID_CSR_SIZE          0x8
#define MSGDMA_SRC_ADDR         0x06020000
#define MSGDMA_SRC_CSR_SIZE     0x40
#define ADDER_AVST_ADDR         0x06020080
#define ADDER_AVST_CSR_SIUZE    0x40
#define MSGDMA_SNK_ADDR         0x06020040
#define MSGDMA_SNK_CSR_SIZE     0x40

#define PCIE_MEM_WIDTH          16          /* byte unit */

/* Error macro */
#define PRINT_ERROR \
    do { \
        fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
        __LINE__, __FILE__, errno, strerror(errno)); exit(1); \
    } while(0)

/* Timout values */
#define STATUS_MSGDMA_RESET_TMO 5       /* 5 us */
#define STATUS_MSGDMA_RUN_TMO   5       /* 5 us * sizeof data */
#define STATUS_ADDER_BUSY_TMO   5       /* 5 us */

/****************************************
 *
 *      Testing memory
 *
 ***************************************/

static int test_mem_64( char* msg, void* base, int off, int size )
{
    void* virt_addr ;
    int i , err ;
    uint64_t dat ;
    printf("**** Testing on %s at 0x%08X, virtual_base = %p ****\n", msg, off , base ) ;
    // Write
    virt_addr = base + off ;
    printf("Writing %s.. virtual address = %p\n", msg, virt_addr );
    for( i = 0 ; i < size ; i+= sizeof(uint64_t) ) {
        *((uint64_t *)virt_addr) = (uint64_t)virt_addr;
        virt_addr += sizeof(uint64_t);
    }
    // Read
    virt_addr = base + off ;
    printf("Reading %s.. virtual address = %p\n", msg, virt_addr );
    err = 0 ;
    for( i = 0 ; i < size ; i+= sizeof(uint64_t) ) {
        dat = *((uint64_t *)virt_addr) ;
        if( dat != (uint64_t)virt_addr ) err++ ;
        virt_addr += sizeof(uint64_t);
    }
    printf( "Completed test on %s, Error = %d\n\n", msg, err ) ;
    return err ;
}

/****************************************
 *
 *      Testing MSGDMA
 *
 ***************************************/

static int test_msgdma( void* base, void* mem_base, uint32_t dst, uint32_t src, uint32_t size )
{
    int         i , err, tmo ;
    uint32_t    temp ;
    uint32_t*   ps ;
    uint32_t*   pd ;

    printf("**** Testing Modular SGDMA ****\n" ) ;

    /* Initialize buffer */
    printf( "Initializing buffer\n");
    ps = (uint32_t*)(mem_base+src) ;
    pd = (uint32_t*)(mem_base+dst) ;
    for( i = 0 ; i < size ; i += sizeof(uint32_t) ) {
        *ps++ = ( ~(i+1) << 16 ) | ( (i+1) << 0 ) ;
        *pd++ = 0xCCDDCCDD ;
    }
    ps = (uint32_t*)(mem_base+src) ;
    pd = (uint32_t*)(mem_base+dst) ;
    printf( "Source 0x%08X at 0x%08X\n", *ps, src ) ;
    printf( "Destination 0x%08X at 0x%08X\n", *pd, dst ) ;

    printf( "Resetting MSGDMA\n");

    /* Reset MSGDMA */
    msgdma_set_csr_ctrl(base,CSR_RESET_MASK);

    /* Check Status */
    tmo = STATUS_MSGDMA_RESET_TMO ;
    do {
        temp = msgdma_get_csr_status(base);
        if( ( temp & CSR_RESET_STATE_MASK ) == 0 ) break ;
        if( --tmo == 0 ) return 1 ;
        usleep(1);
    } while(1);

    /* Set descriptor */
    printf( "Submit a descriptor to transfer from OCRAM to DDR3\n");
    msgdma_setup_std_desc(base,
                        dst,
                        src,
                        size,
                        DESCRIPTOR_CONTROL_GO_MASK) ;

    /* Check Status */
    printf( "Waiting for complete MSGDMA\n");
    tmo = STATUS_MSGDMA_RUN_TMO * (size/PCIE_MEM_WIDTH) ;
    if( tmo == 0 ) tmo = STATUS_MSGDMA_RUN_TMO ;
    do {
        temp = msgdma_get_csr_status(base);
        if( ( temp & CSR_BUSY_MASK ) == 0 ) break ;
        if( --tmo == 0 ) return 1 ;
        usleep(1);
    } while(1);

    /* Verify data */
    printf( "Verifing memory\n");
    ps = (uint32_t*)(mem_base+src) ;
    pd = (uint32_t*)(mem_base+dst) ;
    printf( "Source 0x%08X at 0x%08X\n", *ps, src ) ;
    printf( "Destination 0x%08X at 0x%08X\n", *pd, dst ) ;
    err = 0 ;
    for( i = 0 ; i < size ; i += sizeof(uint32_t) ) {
        if( *ps != *pd ) {
            printf( "error src %08X -> dst %08X\n", (unsigned int)*ps, (unsigned int)*pd );
            err++;
        }
        ps++ ;
        pd++ ;
    }
    if( err ) {
        printf( "Found %d verify error(s)\n", err) ;
    } else {
        printf( "No error\n") ;
    }

    temp = msgdma_get_csr_status(base);
    printf( "CSR Status(RES) = 0x%08X\n\n", (unsigned int)temp ) ;

    return 0;
}

/****************************************
 *
 *      Testing Adder AVST
 *
 ***************************************/

static int test_adder( void* base, void* mem_base, uint32_t dst, uint32_t src, uint32_t size )
{
    int         i , j , tmo , err ;
    uint32_t    temp , temp0, sum , sum0 ;
    uint32_t*   ps ;
    uint32_t*   pd ;

    printf("**** Testing Adder by AVST ****\n" ) ;

    /* Initialize buffer */
    printf( "Initializing buffer\n");
    ps = (uint32_t*)(mem_base+src) ;
    pd = (uint32_t*)(mem_base+dst) ;
    j = 1 ;
    sum0 = 0 ;
    for( i = 0 ; i < size ; i += sizeof(uint32_t) ) {
        temp = j++ ;
        *ps++ = temp ;
        *pd++ = 0xCCDDCCDD ;
        sum0 += temp ;
    }
    ps = (uint32_t*)(mem_base+src) ;
    pd = (uint32_t*)(mem_base+dst) ;
    printf( "Source 0x%08X at 0x%08X\n", *ps, src ) ;
    printf( "Destination 0x%08X at 0x%08X\n", *pd, dst ) ;

    /* Stop Adder AVST */
    *((uint32_t*)(base+ADDER_AVST_ADDR+ADDER_AVST_CSR_START_REG)) = 0 ;

    /* Check busy */
    temp = *((uint32_t*)(base+ADDER_AVST_ADDR+ADDER_AVST_CSR_BUSY_REG));
    printf( "Check busy 0x%08X\n", temp );
    temp = *((uint32_t*)(base+ADDER_AVST_ADDR+ADDER_AVST_CSR_INTERRUPT_STATUS_REG));
    printf( "Check done 0x%08X\n", temp );
    tmo = STATUS_ADDER_BUSY_TMO ;
    do {
        temp = *((uint32_t*)(base+ADDER_AVST_ADDR+ADDER_AVST_CSR_BUSY_REG));
        if( ( temp & ADDER_AVST_CSR_BUSY_MASK ) == 0 ) break ;
        if( --tmo == 0 ) return 1 ;
        usleep(1);
    } while(1);

    /* Reset MSGDMA */
    printf( "Resetting MSGDMA\n");
    msgdma_set_csr_ctrl(base+MSGDMA_SRC_ADDR,CSR_RESET_MASK);
    msgdma_set_csr_ctrl(base+MSGDMA_SNK_ADDR,CSR_RESET_MASK);

    /* Check Status */
    printf( "Waiting for reset of MSGDMA\n");
    tmo = STATUS_MSGDMA_RESET_TMO ;
    do {
        temp = msgdma_get_csr_status(base+MSGDMA_SRC_ADDR);
        if( ( temp & CSR_RESET_STATE_MASK ) == 0 ) break ;
        if( --tmo == 0 ) return 1 ;
        usleep(1);
    } while(1);
    tmo = STATUS_MSGDMA_RESET_TMO ;
    do {
        temp = msgdma_get_csr_status(base+MSGDMA_SNK_ADDR);
        if( ( temp & CSR_RESET_STATE_MASK ) == 0 ) break ;
        if( --tmo == 0 ) return 1 ;
        usleep(1);
    } while(1);

    /* Set counter and start */
    *((uint32_t*)(base+ADDER_AVST_ADDR+ADDER_AVST_CSR_ARG_COUNT_REG))   = size / 16 ;

    /* Set descriptor */
    printf( "Submit a descriptor to transfer from memory to Adder AVST\n");
    msgdma_setup_std_desc_to_st(base+MSGDMA_SNK_ADDR,
                        dst,
                        size,
                        DESCRIPTOR_CONTROL_GO_MASK);

    *((uint32_t*)(base+ADDER_AVST_ADDR+ADDER_AVST_CSR_START_REG)) = ADDER_AVST_CSR_START_MASK ;

    msgdma_setup_std_desc_from_st(base+MSGDMA_SRC_ADDR,
                        src,
                        size,
                        DESCRIPTOR_CONTROL_GO_MASK|
                        DESCRIPTOR_CONTROL_GENERATE_SOP_MASK|
                        DESCRIPTOR_CONTROL_GENERATE_EOP_MASK) ;

    /* Check Status */
    printf( "Waiting for complete MSGDMA\n");
    tmo = STATUS_MSGDMA_RUN_TMO * (size/PCIE_MEM_WIDTH) ;
    if( tmo == 0 ) tmo = STATUS_MSGDMA_RUN_TMO ;
    do {
        temp = msgdma_get_csr_status(base+MSGDMA_SNK_ADDR);
        if( ( temp & CSR_BUSY_MASK ) == 0 ) break ;
        if( --tmo == 0 ) return 1 ;
        usleep(1);
    } while(1);
    tmo = STATUS_MSGDMA_RUN_TMO ;
    do {
        temp = msgdma_get_csr_status(base+MSGDMA_SRC_ADDR);
        if( ( temp & CSR_BUSY_MASK ) == 0 ) break ;
        if( --tmo == 0 ) return 1 ;
        usleep(1);
    } while(1);
    temp = *((uint32_t*)(base+ADDER_AVST_ADDR+ADDER_AVST_CSR_INTERRUPT_STATUS_REG));
    printf( "Check done 0x%08X\n", temp );

    /* Verify data */
    printf( "Veryfing data\n") ;
    printf( "Index:source/destnation\n" ) ;
    err = 0 ;
    ps = (uint32_t*)(mem_base+src) ;
    pd = (uint32_t*)(mem_base+dst) ;
    sum = 0 ;
    for( i = 0 ; i < size ; i += sizeof(uint32_t) ) {
        temp  = *ps++ ;
        temp0 = *pd++ ;
        sum += temp ;
        if( sum != temp0 ) err++ ;
        printf( "%03d:%u/%u\n", (int)(i/sizeof(uint32_t)), temp, temp0 ) ;
    }
    
    temp = *((uint32_t*)(base+ADDER_AVST_ADDR+ADDER_AVST_CSR_RETURNDATA_REG));
    if( sum != temp ) err++ ;
    printf( "Sum = %u, %u\n", temp, sum0 );
    if( err ) {
        printf("Found %d verify error(s)\n", err ) ;
        return 1 ;
    } else {
        printf("No error\n") ;
    }
    return 0;
}

/****************************************
 *
 *      Read SYSID
 *
 ***************************************/

static void read_sysid( void* base )
{
    printf("**** Showing System ID ****\n" ) ;
    printf("ID = 0x%08X, Timestamp = %d\n\n", 
        *((uint32_t*)(base+SYSID_ADDR)),
        *((uint32_t*)(base+SYSID_ADDR+4)) );
}

/****************************************
 *
 *      main
 *
 ***************************************/

#define FN_RC0  argv[1]
#define FN_RC2  argv[2]

int main(int argc, char **argv) {
    int fd_mem, fd_csr;
    void *map_mem_base, *map_csr_base ;
    int map_mem_size = 0x10000000UL;
    int map_csr_size = 0x08000000UL;

    printf("*******************************************************\n");
    printf("  Simple test program for Cyclone V GT FPGA Development Kit\n");
    printf("*******************************************************\n");
    if(argc < 2) {
        // test /sys/bus/pci/devices/0001\:00\:07.0/resource0
        fprintf(stderr, "Usage:\t%s sys_mem sys_csr\n", argv[0]);
        exit(1);
    }
    printf( "Resource0 for memory = %s\n", FN_RC0);
    printf( "Resource2 for CSR    = %s\n", FN_RC2);
    printf("\n");

    if((fd_mem = open(FN_RC0, O_RDWR | O_SYNC)) == -1) {
        PRINT_ERROR;
    }
    if((fd_csr = open(FN_RC2, O_RDWR | O_SYNC)) == -1) {
        PRINT_ERROR;
    }
    map_mem_base = mmap(0, map_mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_mem, 0);
    if(map_mem_base == (void *) -1) PRINT_ERROR;
    map_csr_base = mmap(0, map_csr_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_csr, 0);
    if(map_csr_base == (void *) -1) PRINT_ERROR;

    /* Showing system id */
    read_sysid(map_csr_base);
    /* Testing OCRAM */
    test_mem_64("OCRAM", map_mem_base , OCRAM_MEM_ADDR , OCRAM_MEM_SIZE ) ;
    /* Testing DDR3 */
    test_mem_64("DDR3", map_mem_base , DDR3_MEM_ADDR , DDR3_MEM_SIZE ) ;
    /* Testing MSGDMA */
    test_msgdma(map_csr_base+MSGDMA_ADDR,map_mem_base, DDR3_MEM_ADDR,OCRAM_MEM_ADDR,OCRAM_MEM_SIZE);
    /* Testing Adder AVST */
    test_adder(map_csr_base,map_mem_base, DDR3_MEM_ADDR,OCRAM_MEM_ADDR,128);

    if(munmap(map_csr_base, map_csr_size) == -1) PRINT_ERROR;
    if(munmap(map_mem_base, map_mem_size) == -1) PRINT_ERROR;
    close(fd_csr);
    close(fd_mem);
    return 0;
}

コンパイル方法

test.c, msgdma.hを全て同じディレクトリーにコピーし、HLSディレクトリーよりadder_avst_csr.hをコピーし

gcc -g test.c -o test

を実行すると、testが生成されます。そして下記スクリプトを実行するとテストを開始します。

run_test.sh
#!/bin/bash
PCIE_DEVID=1172:e001
BDF=$(lspci -n | grep -i $PCIE_DEVID | awk '{print $1}')
PCI_BDF=$(find /sys/devices -name "*$BDF")
PCI_RC0=$PCI_BDF/resource0
PCI_RC2=$PCI_BDF/resource2
sudo ./test $PCI_RC0 $PCI_RC2

テスト内容&結果

  • System IDの表示
  • プログラム転送によるOnChipメモリのテスト
  • プログラム転送によるDDR3メモリのテスト
  • MSGDMA_0を使用してOnChipからDDR3メモリのテスト
  • MSGDMA_1,MSGDMA_2,adder_avstを使用して、HLSモジュールの動作確認
実行ログ(クリックすると展開)
$ ./run_test.sh
*******************************************************
  Simple test program for Cyclone V GT FPGA Development Kit
*******************************************************
Resource0 for memory = /sys/bus/pci/devices/0000:65:00.0/resource0
Resource2 for CSR    = /sys/bus/pci/devices/0000:65:00.0/resource2

**** Showing System ID ****
ID = 0x12345678, Timestamp = 1607854038

**** Testing on OCRAM at 0x07000000, virtual_base = 0x7f34d1757000 ****
Writing OCRAM.. virtual address = 0x7f34d8757000
Reading OCRAM.. virtual address = 0x7f34d8757000
Completed test on OCRAM, Error = 0

**** Testing on DDR3 at 0x08000000, virtual_base = 0x7f34d1757000 ****
Writing DDR3.. virtual address = 0x7f34d9757000
Reading DDR3.. virtual address = 0x7f34d9757000
Completed test on DDR3, Error = 0

**** Testing Modular SGDMA ****
Initializing buffer
Source 0xFFFE0001 at 0x07000000
Destination 0xCCDDCCDD at 0x08000000
Resetting MSGDMA
Submit a descriptor to transfer from OCRAM to DDR3
Waiting for complete MSGDMA
Verifing memory
Source 0xFFFE0001 at 0x07000000
Destination 0xFFFE0001 at 0x08000000
No error
CSR Status(RES) = 0x00000002

**** Testing Adder by AVST ****
Initializing buffer
Source 0x00000001 at 0x07000000
Destination 0xCCDDCCDD at 0x08000000
Check busy 0x00000000
Resetting MSGDMA
Waiting for reset of MSGDMA
Submit a descriptor to transfer from memory to Adder AVST
Waiting for complete MSGDMA
Veryfing data
Index:source/destnation
000:1/1
001:2/3
002:3/6
003:4/10
004:5/15
005:6/21
006:7/28
007:8/36
008:9/45
009:10/55
010:11/66
011:12/78
012:13/91
013:14/105
014:15/120
015:16/136
016:17/153
017:18/171
018:19/190
019:20/210
020:21/231
021:22/253
022:23/276
023:24/300
024:25/325
025:26/351
026:27/378
027:28/406
028:29/435
029:30/465
030:31/496
031:32/528
Sum = 528, 528
No error

Signal Tap logic analyzer

最後にSignal Tap logic analyzerの波形を示します。Run Analysis(F5)を押して、Signal Tap logic analyzerを待機状態させてから、testを実行すると、adder_avstのAvlon-ST信号をキャプチャーできます。

  • 全体波形
    qiita_stp_data.png
  • 先頭拡大波形
    qiita_stp_zoomin_0.png

まとめ

今回、Cycone® V GT Development Kitを用いて、FPGA上のリソースをFPGAのコンフィグレーションを含めて、PCIe経由でアクセスしてみました。今回はホストPCからのアクセスのみで、割り込みやFPGAバスマスターなどは用いませんでした。時間を作って、このあたりも投降したと思います。どうもありがとうございました。

参考資料

Cyclone V GT FPGA Development Kit

Kit Installation (zip) (Linux)

V-Series Avalon-MM DMA Interface for PCIe Solutions User Guide

Embedded Peripherals IP User Guide

Avalon Interface Specifications

Intel® High Level Synthesis Compiler Standard Edition Version 19.1 Release Notes

Intel® High Level Synthesis Compiler Standard Edition Getting Started Guide

Intel® High Level Synthesis Compiler Standard Edition User Guide

Intel® High Level Synthesis Compiler Standard Edition Reference Manual

Intel® High Level Synthesis Compiler Standard Edition Best Practices Guide

Notices & Disclaimers

Intel technologies may require enabled hardware, software or service activation.
No product or component can be absolutely secure.
Your costs and results may vary.

© Intel Corporation. Intel, the Intel logo, and other Intel marks are trademarks of Intel Corporation or its subsidiaries. Other names and brands may be claimed as the property of others.

The products described may contain design defects or errors known as errata which may cause the product to deviate from published specifications. Current characterized errata are available on request.​

Intel disclaims all express and implied warranties, including without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement, as well as any warranty arising from course of performance, course of dealing, or usage in trade.

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?