Posted at

Linux の FPGA Manager で Xilinx のビットストリームファイルを扱う方法

More than 1 year has passed since last update.


問題点

Linux Kernel 4.10 になって FPGA Region が追加されました。これは FPGA のコンフィギュレーションをユーザーから出きるようにした FPGA Manager の Higher Level Interface です。ところが、この FPGA Region に Vivado が生成したビットストリームファイルを指定すると、フォーマットが合わない旨のログが出て、コンフィギュレーションに失敗します。


原因

Zynq 用の FPGA Manager の Low level Interface である devcfg が、Vivado のビットストリームファイルを扱えないのが原因です。

devcfg には FPGA にダウンロードする Raw Data を渡さなければなりませんが、ビットストリームファイルにはヘッダに各種情報が含まれています。まずは、このヘッダを除去しなければなりません。

また、(何故か)、devcfg は Raw Data の1ワード(32bit,4Byte)単位でエンディアン変換(バイトスワップ)しなければなりません。


解決方法

この手の問題は、多分誰かが解決しているだろうと思ったらありました。

Zynq: Loading bitfile into FPGA from Linux (xdevcfg)

に、 https://github.com/milosoftware/meta-zynq/raw/master/recipes-bsp/fpga/fpga-image/fpga-bit-to-bin.py というツールが紹介されています。残念ながらここはリンクが切れていましが、検索したところ https://github.com/topic-embedded-products/meta-topic/blob/master/recipes-bsp/fpga/fpga-bit-to-bin/fpga-bit-to-bin.py がみつかりました。

このオプションに --flip というのがあって、これでエンディアンを変換することができます。

shell$ fpga-bit-to-bin.py --flip input.bit output.bin


fpga-bit-to-bin.py

もしかしたら https://github.com/topic-embedded-products/meta-topic/blob/master/recipes-bsp/fpga/fpga-bit-to-bin/fpga-bit-to-bin.py がリンク切れになるかもしれませんので、こちらにも貼っておきます(ライセンスが GPL2.0なので問題ないはず)。作者に感謝。


fpga-bit-to-bin.py

#!/usr/bin/python

import sys
import os
import struct

def flip32(data):
sl = struct.Struct('<I')
sb = struct.Struct('>I')
try:
b = buffer(data)
except NameError:
# Python 3 does not have 'buffer'
b = data
d = bytearray(len(data))
for offset in range(0, len(data), 4):
sb.pack_into(d, offset, sl.unpack_from(b, offset)[0])
return d

import argparse
parser = argparse.ArgumentParser(description='Convert FPGA bit files to raw bin format suitable for flashing')
parser.add_argument('-f', '--flip', dest='flip', action='store_true', default=False, help='Flip 32-bit endianess (needed for Zynq)')
parser.add_argument("bitfile", help="Input bit file name")
parser.add_argument("binfile", help="Output bin file name")
args = parser.parse_args()

short = struct.Struct('>H')
ulong = struct.Struct('>I')

bitfile = open(args.bitfile, 'rb')

l = short.unpack(bitfile.read(2))[0]
if l != 9:
raise Exception("Missing <0009> header (0x%x), not a bit file" % l)
bitfile.read(l)
l = short.unpack(bitfile.read(2))[0]
d = bitfile.read(l)
if d != b'a':
raise Exception("Missing <a> header, not a bit file")

l = short.unpack(bitfile.read(2))[0]
d = bitfile.read(l)
print("Design name: %s" % d)

# If bitstream is a partial bitstream, get some information from filename and header
if b"PARTIAL=TRUE" in d:
print("Partial bitstream")
partial = True;

# Get node_nr from filename (last (group of) digits)
for i in range (len(args.bitfile) - 1, 0, -1):
if args.bitfile[i].isdigit():
pos_end = i + 1
for j in range (i - 1, 0, -1):
if not args.bitfile[j].isdigit():
pos_start = j + 1
break
break
if pos_end != 0 and pos_end != 0:
node_nr = int(args.bitfile[pos_start:pos_end])
else:
node_nr = 0
print("NodeID: %s" % node_nr)

# Get 16 least significant bits of UserID in design name
pos_start = d.find(b"UserID=")
if pos_start != -1:
pos_end = d.find(b";", pos_start)
pos_start = pos_end - 4
userid = int(d[pos_start:pos_end], 16)
print("UserID: 0x%x" % userid)

else:
print("Full bitstream")
partial = False
node_nr = 0

KEYNAMES = {b'b': "Partname", b'c': "Date", b'd': "Time"}

while 1:
k = bitfile.read(1)
if not k:
bitfile.close()
raise Exception("unexpected EOF")
elif k == b'e':
l = ulong.unpack(bitfile.read(4))[0]
print("Found binary data: %s" % l)
d = bitfile.read(l)
if args.flip:
print("Flipping data...")
d = flip32(d)
# Open bin file
binfile = open(args.binfile, 'wb')
# Write header if it is a partial
if partial:
binfile.write(struct.pack("B", 0))
binfile.write(struct.pack("B", node_nr))
binfile.write(struct.pack(">H", userid))
# Write the converted bit-2-bin data
print("Writing data...")
binfile.write(d)
binfile.close()
break
elif k in KEYNAMES:
l = short.unpack(bitfile.read(2))[0]
d = bitfile.read(l)
print(KEYNAMES[k], d)
else:
print("Unexpected key: %s" % k)
l = short.unpack(bitfile.read(2))[0]
d = bitfile.read(l)

bitfile.close()