Linux
FPGA
zynq

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()