コスモリサーチの凄腕エンジニアたちが日々、難題と格闘した記録
Copyright Cosmo Research Corp.

Windows 10でWSLを有効時、Quartus PrimeのDDR3 Memory Controller IP生成でエラー

前置き

Windows 10でWSLを有効時、Quartus PrimeのDDR3 Memory Controller IP生成でエラーになって、IPの生成ができない現象に遭遇した。
ひとまずはWSLを無効にすれば正常に生成できるが、気になったので調査してみた。

エラー内容

Quartus Primeのバージョン16.1のエラー表示(17.1でも同様)。

Error: Error during execution of "{C:/intelfpga/16.1/quartus//../nios2eds/Nios II Command Shell.bat} make all 2>> stderr.txt": child process exited abnormally
Error: Execution of command "{C:/intelfpga/16.1/quartus//../nios2eds/Nios II Command Shell.bat} make all 2>> stderr.txt" failed

Quartusで自動生成されたMakefileの下記部分でエラーになっている。

D:/intelfpga/17.1/quartus//../nios2eds/sdk2/bin/nios2-bsp hal sequencer_bsp .. --default_sections_mapping sequencer_mem --use_bootloader DONT_CHANGE

調査内容

nios2-bspでエラーになるようだが、これの中身はシェルスクリプトであり、Quartus(Nios II)のmakeからシェルスクリプトを呼ぶとbash nios2-bspが実行される事がわかった。

このbashは、WSLが有効な環境ではC:\Windows\System32\bash.exeに展開されてしまうのでエラーになってしまう。本当はNIOS II Command Shellのbashを呼んでほしい。

対策

かなり無理矢理だが、自作のnios2-bsp.exeをオリジナルのnios2-bspと同じフォルダに置くことで解決した。

nios2-bsp.exeについては、特に理由はないが今回はgolangで作ってみた。(shebang書き換えでスマートに対応できたかもしれない)

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    cmd := exec.Command(
        os.Getenv("QUARTUS_ROOTDIR")+`\bin64\cygwin\bin\bash.exe`,
        append([]string{os.Getenv("SOPC_KIT_NIOS2") + "/sdk2/bin/nios2-bsp"}, os.Args[1:]...)...,
    )
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

複数の同一ブロックデザインを含むVivadoプロジェクトで、petalinuxからhdfファイルが読み込めないのを無理やり解決した話

現象(Vivado 2018.3)

Microblazeを含むプロジェクトでLinuxを使いたかったので、簡単に済ませるためにpetalinuxを使うことになった。が、petalinux-config --get-hw-description=<path>コマンドでなぜかhdfが読み込めない([Hsi 55-2019] XXX.hwh file is emptyとか言われる)。

該当のhdfファイルをzip解凍して見てみると、中身はこうなっていた。

合計 12M
-rw-r--r-- 1 saido saido 187K 2月  18 22:01 network_stack_bd.hwh
-rw-r--r-- 1 saido saido  18K 2月  18 22:01 network_stack_bd_bd.tcl
-rw-r--r-- 1 saido saido  12M 2月  18 22:01 RoXGE_udp.bit
-rw-r--r-- 1 saido saido 8.1K 2月  18 22:01 RoXGE_udp.mmi
-rw-r--r-- 1 saido saido 1.5K 2月  18 22:01 sysdef.xml

まぁ、Microblaze関連の諸々がないのでそりゃあエラーになるよな。
ちなみに、正常なhdfファイルの中身はこうなっているはず。

合計 14M
-rw-r--r-- 1 saido saido 676K 2月  15 18:47 microblaze_bd.hwh
-rw-r--r-- 1 saido saido 2.2M 2月  15 18:47 microblaze_bd_axi_smc_periph_0.hwh
-rw-r--r-- 1 saido saido 293K 2月  15 18:47 microblaze_bd_axi_smc_periph_0_bd.tcl
-rw-r--r-- 1 saido saido  41K 2月  15 18:47 microblaze_bd_bd.tcl
-rw-r--r-- 1 saido saido 274K 2月  15 18:47 microblaze_bd_ddr4_0_microblaze_mcs.hwh
-rw-r--r-- 1 saido saido  13K 2月  15 18:47 microblaze_bd_ddr4_0_microblaze_mcs_bd.tcl
-rw-r--r-- 1 saido saido 187K 2月  15 18:47 network_stack_bd.hwh
-rw-r--r-- 1 saido saido  18K 2月  15 18:47 network_stack_bd_bd.tcl
-rw-r--r-- 1 saido saido 9.4M 2月  15 18:47 RoXGE_udp.bit
-rw-r--r-- 1 saido saido 8.1K 2月  15 18:47 RoXGE_udp.mmi
-rw-r--r-- 1 saido saido 1.4K 2月  15 18:47 sysdef.xml

このプロジェクトでは、Microblaze用のブロックデザイン(microblaze_bd)の他に、UDP/IPプロトコルスタックのブロックデザイン(network_stack_bd)を含んでいた。ハードウェアとしては複数のEthernet用の口があるので、network_stack_bdを複数インスタンシエートしたら、hdfファイルがおかしくなった。

原因は不明だが、複数の同じブロックデザインを含むプロジェクトでは、hdfファイル(正確にはsysdefファイル)を作成するためのwrite_hwdefコマンドがエラーになるようだ。手元で適当に試すと以下のようになる。多分2回同じファイルを開こうとしてるんだと思う。

$ write_hwdef -force temp.hdf
zip_exception: zip archive temp.hwdef is already open for writingzip_exception: zip archive temp.hwdef is already open for writingzip_exception: zip archive temp.hwdef is already open for writingzip_exception: zip archive temp.hwdef is already open for writingzip_exception: zip archive temp.hwdef is already open for writingzip_exception: zip archive temp.hwdef is already open for writingzip_exception: zip archive temp.hwdef is already open for writingzip_exception: zip archive temp.hwdef is already open for writingzip_exception: zip archive temp.hwdef is already open for writingtemp.hwdef

対応

hdfファイルが単なるzip圧縮したファイルだということは知っていたので、必要なファイルを集めてzip圧縮すればいいや、と単純に考えたが、なぜかpetalinux-config --get-hw-description=<path>コマンドで弾かれる。

どうやら、改ざんされたhdfファイルかどうかをチェックしている模様。

だがしかし、試行錯誤の末、write_sysdefというコマンドを使ってhdfファイルを作成すればpetalinux-config --get-hw-description=<path>コマンドを騙せるということが分かった。なので、これを使ってhdfファイルを作れば良し。

今回のプロジェクトでは、以下のtclを実行してhdfファイルを作った。

# hdlで複数のブロックデザインを呼び出すとhwdef生成で
# バグって正常にhdfファイルを生成できなくなる。
#
# これを回避するために、hdfファイルを改ざんする。
# ただ、`petalinux-config --get-hw-description=<path>`コマンドでは
# hdfファイルの改ざんを検知するため、改ざんしたhdfファイルは使えない。
#
# 改ざんを隠蔽するためには、`write_sysdef`コマンドを使って
# hdfファイルを再生成する必要がある。

set target_dir hdf/modified
exec mkdir -p $target_dir

# 壊れたhdfファイル(sysdefファイルと同じ)の中身を吸い出す。(sysdef.xmlが欲しい)
# 事前にbitstreamを生成しておく必要がある。
exec unzip -o udp_ip_roxge/udp_ip_roxge.runs/impl_1/RoXGE_udp.sysdef -d $target_dir

# hdf作成に必要なファイルたちを集める。
set bd_base            udp_ip_roxge/udp_ip_roxge.srcs/sources_1/bd
set microblaze_bd      microblaze_bd/hw_handoff/microblaze_bd
set ddr4_mb_mcs        microblaze_bd/ip/microblaze_bd_ddr4_0/bd_0/hw_handoff/microblaze_bd_ddr4_0_microblaze_mcs
set microblaze_axi_smc microblaze_bd/ip/microblaze_bd_axi_smc_periph_0/bd_0/hw_handoff/microblaze_bd_axi_smc_periph_0
set network_stack_bd   network_stack_bd/hw_handoff/network_stack_bd

exec cp -f $bd_base/${microblaze_bd}.hwh         $target_dir/
exec cp -f $bd_base/${microblaze_bd}_bd.tcl      $target_dir/
exec cp -f $bd_base/${ddr4_mb_mcs}.hwh           $target_dir/
exec cp -f $bd_base/${ddr4_mb_mcs}_bd.tcl        $target_dir/
exec cp -f $bd_base/${microblaze_axi_smc}.hwh    $target_dir/
exec cp -f $bd_base/${microblaze_axi_smc}_bd.tcl $target_dir/
exec cp -f $bd_base/${network_stack_bd}.hwh      $target_dir/
exec cp -f $bd_base/${network_stack_bd}_bd.tcl   $target_dir/

# 改竄したhdfファイルをhwdefファイルとして生成する。(単にzip圧縮する)
exec zip -j ${target_dir}.hwdef $target_dir/*

# write_sysdefコマンドで本物のhdfファイルを作ってもらう。
write_sysdef -force \
    -hwdef   ./hdf/modified.hwdef \
    -bitfile ./hdf/modified/RoXGE_udp.bit \
    -meminfo ./hdf/modified/RoXGE_udp.mmi \
    ./modified.hdf

もう一つのバグ

Vivado 2018.3でMicroblazeを使用する場合、AR# 71948 UpdateMEM/SDK 2018.3 デザイン アドバイザリ – MicroBlaze および MicroBlaze MCS の MMI ファイルの生成が原因で発生していた機能的な問題を回避するための緊急パッチにハマるので必ずパッチを当てておく必要がある。
このパッチを当てないとMicroblazeが起動しない。

宣伝

上記の作業は、10GbE向けのUDP/IPプロトコルスタックの性能比較などを行うための実験の一貫だった(RoXGEというのは、RF over 10Gb Ethernetの略)。
コスモリサーチでは、RoF(Radio on Fiber 技術)を実現するためのボードを開発して、様々な実験を行っている。

Xilinx ZynqのSDカードイメージ(BOOT.bin)の構造

Vivado 2017.2でなぜかQSPIブートできない現象にハマった際のメモをここで供養する。
QSPIブートできないのはfsblのバグ(後述)で、おそらく2017.4では修正されているはず。

実際に参考にしたBOOT.binはxilinxが提供しているxilinx-zc702-v2017.2-final.bspというファイルのpre-buildに入っているもの。

BootROM

参考資料1(UG585)の6.3.2 BootROM ヘッダーに記載。以下、表6-5を抜粋。

ヘッダーアドレス 説明 道祖土コメント
0x000 - 0x01F Interrupt Table for Execution-in-Place
0x020 Width Detection 0xAA995566固定
0x024 Image Identification 'XLNX'固定
0x028 Encryption Status
0x02C FSBL/ユーザー定義
0x030 Source Offset
0x034 Length of Image
0x038 0
0x03C Start of Execution
0x040 Total Image Length
0x044 0 実際は1
0x048 Header Checksum
0x04C - 0x09F FSBL/ユーザー定義 (84バイト)
0x0A0 - 0x89F Register Initialization (2048バイト) ALL 0xFFFF0000
0x8A0 - 0x8BF FSBL/ユーザー定義 ALL 0xFFFFFFFF
0x8C0 - FSBLイメージまたはユーザーコード イメージヘッダー

以下でファイルの内容を見てみる。

od -tx4 -v -Ax --endian little BOOT.BIN | less

先頭からBootROMヘッダーが始まっている。

000000 eafffffe eafffffe eafffffe eafffffe
000010 eafffffe eafffffe eafffffe eafffffe
000020 aa995566 584c4e58 00000000 01010000
000030 00001700 00018008 00000000 00000000
000040 00018008 00000001 fc164530 00000000
000050 00000000 00000000 00000000 00000000
000060 00000000 00000000 00000000 00000000
000070 00000000 00000000 00000000 00000000
000080 00000000 00000000 00000000 00000000
000090 00000000 00000000 000008c0 00000c80

イメージヘッダーテーブル

参考資料2(UG821)の付録A: Bootgenの使用の表A-4を抜粋。

オフセット 名前 注記
0x0 バージョン 0x01020000
0x4 イメージヘッダー数
0x8 パーティションヘッダーのワードオフセット
0xC 最初のイメージヘッダーのワードオフセット
0x10 ヘッダー認証のワードオフセット
0x1C パディング 64バイト境界までALL 0xFF

0x000008A0からイメージヘッダーが始まると書いているが、実際のBOOT.binでは0x000008C0から始まっている。 BootROMヘッダーの説明(UG585)ではBootROMヘッダー自体のサイズが0x8C0と書かれているので誤記だと思う。

BootROMと同様に、odでBOOT.binを見てみる。

0008c0 01020000 00000003 00000320 00000240
0008d0 00000000 ffffffff ffffffff ffffffff
0008e0 ffffffff ffffffff ffffffff ffffffff
0008f0 ffffffff ffffffff ffffffff ffffffff
  • イメージヘッダー数: 3
  • パーティションヘッダーのワードオフセット: 0x320(バイトでは0xC80)
  • 最初のイメージヘッダーのワードオフセット: 0x240(バイトでは0x900)

イメージヘッダー

イメージヘッダーテーブルのあとにはBootgenの作ったイメージヘッダーが並ぶ。

参考資料2(UG821)の付録A: Bootgenの使用の表A-7を抜粋。

オフセット 名前
0x0 次のイメージヘッダーのワードオフセット
0x4 最初のパーティションヘッダーのワードオフセット
0x8 0固定
0xC 1固定(パーティション数)
0x10 ここからファイル名 + 0x00000000
0x10 + ファイル名分 + 0x4 64バイト境界までALL 0xFF

先程と同様にodの出力を見てみる。

000900 00000250 00000320 00000000 00000001
000910 7a796e71 5f667362 6c2e656c 66000000 # zynq_fsbl.elf
000920 00000000 ffffffff ffffffff ffffffff
000930 ffffffff ffffffff ffffffff ffffffff
000940 00000260 00000330 00000000 00000001
000950 646f776e 6c6f6164 2e626974 00000000 # download.bit
000960 00000000 ffffffff ffffffff ffffffff
000970 ffffffff ffffffff ffffffff ffffffff
000980 00000000 00000340 00000000 00000001
000990 752d626f 6f742e65 6c660000 00000000 # u-boot.elf

なので、このBOOT.binでは以下の位置にパーティションヘッダーがいる事がわかる。

  • zynq_fsbl.elf: 0x320(バイトで0xC80)
  • download.bit: 0x330(バイトで0xCC0)
  • u-boot.elf: 0x340(バイトで0xD00)

パーティションヘッダーテーブル

参考資料2(UG821)の付録A: Bootgenの使用の表A-5を抜粋。

オフセット 名前
0x0 Partition Data Word Length (Encrypted)
0x4 Extracted Data Word Length (Unencrypted)
0x8 Total Partition Word Length
0x0C Destination Load Address
0x10 Destination Execution Address
0x14 Data Word Offset in Image
0x18 Attribute Bits
0x1C Section Count
0x20 Checksum Word Offset
0x24 Image Header Word Offset
0x28 Authentication Certification Word Offset
0x2C unused (Must be 0x00000000)
0x30 unused (Must be 0x00000000)
0x34 unused (Must be 0x00000000)
0x38 unused (Must be 0x00000000)
0x3C Header Checksum

例によって、odでBOOT.binを見てみる。

000c80 00006002 00006002 00006002 00000000 # 0x00 (zynq_fsbl.elf)
000c90 00000000 000005c0 00000010 00000001 # 0x10 (zynq_fsbl.elf)
000ca0 00000000 00000240 00000000 00000000 # 0x20 (zynq_fsbl.elf)
000cb0 00000000 00000000 00000000 fffed7e8 # 0x30 (zynq_fsbl.elf)

000cc0 000f6ec0 000f6ec0 000f6ec0 00000000 # 0x00 (download.bit)
000cd0 00000000 000065d0 00000020 00000001 # 0x10 (download.bit)
000ce0 00000000 00000250 00000000 00000000 # 0x20 (download.bit)
000cf0 00000000 00000000 00000000 ffd14b7e # 0x30 (download.bit)

000d00 00019090 00019090 00019090 00400000 # 0x00 (u-boot.elf)
000d10 00400000 000fd490 00000010 00000001 # 0x10 (u-boot.elf)
000d20 00000000 00000260 00000000 00000000 # 0x20 (u-boot.elf)
000d30 00000000 00000000 00000000 ff6b774e # 0x30 (u-boot.elf)

特に変なところもなく、以下のことが分かる。

  • u-bootだけ0x0C/0x10のロード/実行アドレスが0x00400000
  • 0x18のAttributeは、bitファイルだけPL(0x00000020)、その他はPS(0x00000010)

(参考)QSPIブートできない現象のデバッグ

s25fl256s1というFlashメモリで起動しようとすると、以下の表示になる。(fsblでシンボルFSBL_DEBUG_INFOを有効にしてシリアル出力させる)

Xilinx First Stage Boot Loader
Release 2017.1  Jul  7 2017-16:40:46
Devcfg driver initialized
Silicon Version 3.1
Boot mode is QSPI
Single Flash Information
FlashID=0x1 0x2 0x19
SPANSION 256M Bits
QSPI is in single flash connection
QSPI Init Done
Flash Base Address: 0xFC000000
Reboot status register: 0x60400000
Multiboot Register: 0x0000C000
Image Start Address: 0x00000000
Partition Header Offset:0x00000880          <==== 0xC80のはず
Partition Count: 14
Invalid Partition Count
Partition Header Load Failed
FSBL Status = 0xA00E
...(以下Fallback bootの結果)

u-bootやlinuxでQSPIの中身を読んでみると正常に書き込めている。 fsblでは、polled transferをしているようなので、xqspips_flash_polled_example.c(サンプルデザイン)を実行してみる。 printf()爆弾を仕込んだxqspips_flash_polled_example.cで動作確認したところ、

#define FAST_READ_CMD       0x0B
#define DUAL_READ_CMD      0x3B
#define QUAD_READ_CMD      0x6B

のうち、QUAD_READ_CMDがNGであることがわかった。

fsbl内で同様の操作をしている箇所は、qspi.cのFlashRead()であるので、 リードコマンドになるであろう、WriteBuffer[COMMAND_OFFSET]をモニタしてみたところ、 0x04とかいう謎の値が入っていた。

これを以下のように、FAST_READ_CMDにハードコードすることで読み出しできるようになった。

diff -r e93b7f0ac8b0 xsdk/fsbl/src/qspi.c
--- a/xsdk/fsbl/src/qspi.c    Fri Jul 07 16:23:45 2017 +0900
+++ b/xsdk/fsbl/src/qspi.c    Mon Jul 10 09:11:51 2017 +0900
@@ -91,7 +91,9 @@
  * The following constants define the commands which may be sent to the FLASH
  * device.
  */
-#define QUAD_READ_CMD     0x6B
+#define FAST_READ_CMD      0x0B // s25fl256s1 OK
+#define DUAL_READ_CMD      0x3B // s25fl256s1 OK
+#define QUAD_READ_CMD      0x6B // s25fl256s1 NG
 #define READ_ID_CMD            0x9F
 
 #define WRITE_ENABLE_CMD   0x06
@@ -441,12 +443,8 @@
     * Setup the write command with the specified address and data for the
     * FLASH
     */
-  u32 LqspiCrReg;
-  u8  ReadCommand;
-
-  LqspiCrReg = XQspiPs_GetLqspiConfigReg(QspiInstancePtr);
-  ReadCommand = (u8) (LqspiCrReg & XQSPIPS_LQSPI_CR_INST_MASK);
-  WriteBuffer[COMMAND_OFFSET]   = ReadCommand;
+   /* XQspiPs_GetLqspiConfigReg()で得られる値がおかしい */
+   WriteBuffer[COMMAND_OFFSET]   = FAST_READ_CMD; // for s25fl256s1
    WriteBuffer[ADDRESS_1_OFFSET] = (u8)((Address & 0xFF0000) >> 16);
    WriteBuffer[ADDRESS_2_OFFSET] = (u8)((Address & 0xFF00) >> 8);
    WriteBuffer[ADDRESS_3_OFFSET] = (u8)(Address & 0xFF);

直流安定化電源(TEXIO PDS-Aシリーズ)の電源OFF/ONプログラムを作る

前提

提供されているドライバのAPI調査

  • IF-70GUのドライバのzipファイルが公開されているので中身を眺めてみるが、どうやら、GUIのサンプルしか入っておらず、自作プログラムに組み込むのが面倒くさそう。
  • 一応、上記のドライバをインストールするとC:\Windows\System32\IF_60.dllがインストールされる模様。

なので、IF_60.dllの中にあるAPIコマンドラインから呼べるようにすれば良い。
dll内のAPIpexportsを使えば、以下のように抜き出すことができる。

# `.def`ファイルを作成
pexports IF_60.dll > IF_60.def
# gcc用のライブラリファイルを作成(`dlltool`はmsys2のものを使った)
dlltool IF_60.dll -d IF_60.def -l libIF_60.a

これで、あとはgcc実行時に-L. -lif_60としてdllとリンクすれば良い。

電源ONするサンプル

IF70_on.cとして以下を作成した。(OFFしたいときはOUTPUT 0とすれば良い) コンパイルしたIF70_on.exeを実行すれば電源ONできる!捗る!

#include <windows.h>
#include <tchar.h>

#include <stdio.h>
#include "IF70.h"

#define ADDR (1)

/* msys2でのコンパイルの方法

事前に以下の2つを実行して必要なものをインストールしておく
(コンパイルするPCでは後述の.dllファイル以外は不要)
   PSA_Dr_API310\API\setup64.exe
   PSA_Dr_API310\Driver\Setup64.exe

以下の.dllファイルから、ライブラリファイルを作る
C:\Windows\System32\IF_60.dll

ここからpexportsのWindowsバイナリをダウンロード
https://sourceforge.net/projects/mingw/files/MinGW/Extension/pexports/
以下のコマンドで、.defファイルを作る
   pexports IF_60.dll >IF_60.def

以下コマンドでライブラリファイルを作る
   dlltool IF_60.dll -d IF_60.def -l libIF_60.a

以下のコマンドで実行ファイルを作る
   gcc -o IF70_on.exe IF70_on.c -L. -lif_60
*/

int main(int argc, char *argv[]) {

    int ret;

    // addrを引数に取るが、何を指定するのか不明
    ret = USB488_DeviceOpen(ADDR);
    if (ret <= 0) {
        printf("USB488_DeviceOpen() Error!\n");
        return -1;
    }

    ret = USB488_Send(ADDR, "OUTPUT 1\n");
    if (ret <= 0) {
        printf("USB488_Send() Error!\n");
        return -1;
    }

    ret = USB488_Send(ADDR, "GTL\n");
    if (ret <= 0) {
        printf("USB488_Send() Error!\n");
        return -1;
    }

    USB488_DeviceClose(ADDR);

    return 0;
}