この記事の前提
- 対象は公式評価ボードではない中古/リバースエンジニアリング由来のPCIeカードです。board files(ボード定義)や配線情報はコミュニティの成果を利用しています
- 環境は Vivado / Vitis 2023.2。手順・値は執筆時点のものです
- 今回のゴールは「開発環境(Vitis+JTAG)から、カード上の周辺を自分の手で動かす」ことです。PCIeホストからのDMA転送は今回まだやっていません(=次回以降の展望)
はじめに
きっかけは単純で、技術的な興味だけでPCIe DMAの練習がしたかったからです。探してみると、1万円程度で遊べる中古カードが見つかったので購入しました。もう一つの理由は、普段の実機がCycloneシリーズばかりだったので、久しぶりにXilinx(現AMD)系を触ってみたかったという単純な動機もあります。
Kintex-7 XC7K480T を積んだPCIe x8カードを中古で入手しました。もとは特定用途の演算カードですが、コミュニティがリバースエンジニアリングした board files(tiferkingさんのリポジトリ)のおかげで、Vivadoから普通の開発ボードのように扱えます。
今回やったのは、このカードに MicroBlaze(ソフトCPU) を載せた小さなSoCを作り、開発環境から次の3つを動かすことです。
- LEDを点ける(AXI GPIO)
- DDR3を読み書きする(MIG+メモリテスト)
- 温度センサを読む(I2CのLM73)
操作は、JTAG経由でVitisのTCF Debug Terminalにつないで、UARTのキー入力で行います。地味に見えますが、これで「FPGAにCPUを載せて、DDR3・GPIO・I2Cを一通り叩く」という組み込みの基礎体力が、PCIeカードという少し変わった土俵で手に入りました。
このbring-upで大いに参考にした2本:
- YPCB-00338-1P1 FPGAボードのリバースエンジニアリング(tiferking) ― board files・配線情報
- Streaming data to DDR from PL(controlpaths) ― XDMA→DDR3のデータ経路(PCIe転送は次回の布石)
このカードは何者か
分かっている範囲の構成です。
| 項目 | 内容 |
|---|---|
| FPGA | Kintex-7 XC7K480T(-2FFG1156I) |
| ホスト I/F | PCI Express x8 |
| メモリ | DDR3-1600(複数チップ・複数チャネル。ECC対応の例あり) |
| 不揮発 | NOR Flash |
| その他 | JTAG、GPIO接続のLED×3、温度センサ(LM73)、200MHz差動リファレンス |
board filesは、リポジトリのypcb003381p1フォルダを Vivado/<version>/data/boards/board_files/ へコピーすると、新規プロジェクト作成時に「YPCB-00338-1P1 Accelerator Card」として選べるようになります。公式評価ボードではないので、MIGの設定やピン割当は実チップ・実配線に合わせて確認する前提です(EBAZ4205と同じ「正常が保証されていない」立ち位置)。
Vivadoのプロジェクトサマリー(Board Part・Project part。画面下のDesign Runsの数値は作業途中のスナップショットのため、本文中のWNS値とは対応しません):

全体像:載せたMicroBlaze SoC
作ったブロックデザインの地図です。中心にMicroBlaze、そこにAXIインターコネクト経由で周辺をぶら下げています。
Interface View(AXI接続関係を中心に整理した表示)

Sourcesタブのヒエラルキー表示(axi_gpio・axi_iic・mig・microblaze等の実体が並ぶ)

- MicroBlaze:ソフトCPU。Vitisのアプリはここで動く
- AXI GPIO → LED×3
- AXI UARTLite → 標準入出力(キー入力・print出力)
- MIG(7-series) → DDR3コントローラ(今回は133MHz系)
- AXI IIC → 温度センサLM73(I2Cアドレス0x4A)
- XDMA(PCIe):デザインには入れてあるが、ホストからのDMAは今回未使用(次回)
C-9で見たCPU+FPGA SoC(PS/PL)の話が、今回は「FPGAに自分でCPU(MicroBlaze)を立てる」形で再現されます。AXIでレジスタを叩き、MIG経由でDRAMを触る構図はそっくりです。
開発環境
-
Vivado 2023.2:ブロックデザイン作成+bitstream生成(プロジェクト名は
PCIe_0308_IP1_system) - Vitis 2023.2:MicroBlaze上のアプリ開発
- JTAG:Xilinx USBケーブル(D-2で触れたFPGA用JTAG)
-
TCF Debug Terminal:JTAG経由で
print()/xil_printf()の出力を表示し、キー入力も受ける
Step 1:LEDを点ける(AXI GPIO)
まずは生存確認、LEDです。アプリはUARTのキー入力を待ち、r/g/yで各色をトグルします。
// LED定義(memorytest.cより)
#define LED_R 0x01
#define LED_G 0x02
#define LED_Y 0x04
// キー入力でトグル
case 'r':
led ^= LED_R;
XGpio_DiscreteWrite(&GpioLed, LED_CH, led);
break;
GPIOは出力方向に初期化してから使います。
XGpio_SetDataDirection(&GpioLed, LED_CH, 0x0); // 全ビット出力
XGpio_DiscreteWrite(&GpioLed, LED_CH, 0x0); // 消灯で開始
TCF Terminalでrを押すと赤が点く。これが、このカードで最初に得られる「生きている」の実感でした。
Step 2:DDR3を読み書きする(メモリテスト)
本命のDDR3です。mキーで、Xilinxのメモリテスト(Xil_TestMem)を各DDR領域に対して走らせます。
// 各メモリ領域に 32/16/8bit のテストを実行(memorytest.cより抜粋)
status = Xil_TestMem32((u32*)range->base, 1024, 0xAAAA5555, XIL_TESTMEM_ALLMEMTESTS);
status = Xil_TestMem16((u16*)range->base, 2048, 0xAA55, XIL_TESTMEM_ALLMEMTESTS);
status = Xil_TestMem8 ((u8*) range->base, 4096, 0xA5, XIL_TESTMEM_ALLMEMTESTS);
XIL_TESTMEM_ALLMEMTESTSは、ウォーキング1/0、アドレス自己参照、反転など複数パターンを一通りかけるモードです。実行するとTCF Terminalには次のような出力が得られます。
Testing memory region: DDR @ 0x80000000
Memory Controller: mig_7series
Base Address: 0x80000000
32-bit test: PASSED!
16-bit test: PASSED!
8-bit test: PASSED!
Testing memory region: DDR @ 0xA0000000
...
Successfully ran Memory Test Application.
実際のVitis TCF Debug Terminalでの実行画面(メモリテストPASSEDと温度読み取りが同時に流れています):

2つのDDR領域(0x80000000 と 0xA0000000)でパス。MicroBlaze→AXI→MIG→DDR3という経路が、読み書きレベルで通ったことの証明です。
注意点:このテストアプリはDキャッシュを無効にして走ります(コメントにも明記)。キャッシュ有無での挙動・性能差は、今後の比較ネタにとっておきます。
おまけ:温度センサを読む(LM73 / I2C)
tキーで、I2C(AXI IIC)越しに温度センサLM73(アドレス0x4A)から2バイト読み、温度に変換します。
XIic_MasterRecv(&IicLM73, (u8 *)TemperaturePtr, 2);
// ...
Temperature = (TemperaturePtr[0] << 8) + TemperaturePtr[1];
xil_printf("Temperature: %d.%dC\r\n", Temperature/128, (Temperature*100/128)%100);
I2Cは割り込み駆動で受信完了を待つ作りです。GPIO・DDR3に続き、I2Cペリフェラルも生きていることが確認できました。
ハマりどころ(実際に踏んだもの)
UARTLiteはPCIe経由では喋れない
最初、UARTでのやり取りをPCIe経由でやろうとして混乱しました。AXI UARTLiteは物理的なTX/RXピン前提で、PCIe越しには通信できません。結局、標準出力はJTAG経由でVitisのTCF Terminalへリダイレクトし、キー入力もそのTerminalから、という構成に落ち着きました。物理UARTで使うならUSB-UARTブリッジが要ります。
タイミングが最後まで詰まらない(WNSとの戦い)
これがいちばんの山場でした。当時のVivadoプロジェクトを確認すると、Implementationではタイミング違反が残っていました。そこで、次の設定で改善を試みています。
- Strategy を
Performance_ExtraTimingOptに変更 -
phys_opt_designをAggressiveExploreで実行 -
route_designをNoTimingRelaxationで実行
保存されている最終のrouted timing reportでは、WNS -0.012ns、TNS -0.023ns、Failing Endpoints 2でした。違反箇所はXDMA/PCIe内部のuserclk1側のパスで、判定もTiming constraints are not metのままです。
phys_opt_designをAggressiveExploreに設定した画面:

最終のrouted timing reportと一致するTiming Summary:

この状態でもbitstreamは生成され、今回のLED・DDR3・I2Cの実機確認はできました。ただし、実機で動いたことと、静的タイミング解析を通過したことは別です。この記事では「タイミング収束した」とは扱わず、違反が残った状態で限定的な実機確認を行った結果として記録します。
その他
- board filesとMIG設定の実物合わせ:非公式board filesはDDR設定やピン割当が実チップと食い違うことがあります。MIGは実チップ(速度・幅・チャネル)で再確認
- PCIeトランシーバの制約:GTのロケーション制約など、board filesだけでは足りず手当てが要る場合があります(参考記事でも言及)
- bit生成後の書き込み:まずJTAG(Hardware Manager)でFPGAが生きているか、bitが載ったかを切り分けてから上位を疑う
現場コラム:中古FPGAカードは最高の教材
新品なら数万〜十数万円クラスのKintex-7が、中古なら驚くほど安く手に入ります。「正常が保証されない」「情報はコミュニティ頼み」というEBAZ4205と同じハンデはありますが、MicroBlaze・DDR3・I2C・GPIO・(この先の)PCIe/DMAという“実務ど真ん中”を、自腹の実機で端から端まで触れる教材はそう多くありません。
効いたのはやはり「まず正常系を1周」です(C-6の基準ログ、A-3のEBAZの話と同じ)。参考記事のsystest構成をそっくり動かして、LEDが点き・メモリテストが通る状態を先に作ってから、自分の改造を1つずつ足す。タイミングで詰まったときも、この“動く基準”があると切り分けが速い。
今後の展望(=このシリーズの続き)
今回の到達点(MicroBlazeからLED・DDR3・温度)を土台に、次はいよいよPCIeの本領へ進みます。手元のメモに書いた宿題がそのまま次回以降になります。
-
ILAデバッグ(
.ltx)で内部信号を覗く - DMA転送の実装とベンチマーク
- PCIe経由でホストとメモリ共有(XDMA→DDR3をホストPCから叩く。P-1で入れておいたXDMAがここで主役に)
- キャッシュ有無の性能比較
まとめ
- 中古のXC7K480T PCIeカードに、非公式board files+Vivado/Vitis 2023.2でMicroBlaze SoCを構築した
- 開発環境(JTAG+TCF Terminal)から、LED(GPIO)・DDR3(MIGメモリテスト
PASSED)・温度(LM73/I2C)を動かせた - UARTLiteはPCIe経由不可・stdoutはJTAGリダイレクト。最終のタイミング解析はWNS -0.012nsで、違反を残したまま限定的な実機確認を行った
- PCIeホストからのDMAは次回。今回の土台がそのまま次の主役になる
参考資料
- YPCB-00338-1P1 FPGAボードのリバースエンジニアリング(tiferking) ― board files・配線情報の出典
- Streaming data to DDR from PL(controlpaths) ― XDMA→DataMover→MIGのデータ経路(次回の布石)
- メモリテスト/GPIO/I2Cの雛形はXilinx提供テンプレート(
xil_testmem、xgpio、xiic)をベースに統合

