231 lines
6.7 KiB
Python
Executable File
231 lines
6.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import struct
|
|
from typing import NamedTuple, Union
|
|
from enum import IntEnum
|
|
import sys
|
|
|
|
WORD_BE = struct.Struct(">I")
|
|
WORD_LE = struct.Struct("<I")
|
|
|
|
WORD = WORD_BE
|
|
WORD_SWAPPED = False
|
|
|
|
def _pack_word(word: int) -> bytes:
|
|
return WORD.pack((word,))
|
|
def _unpack_word(word: bytes) -> int:
|
|
return WORD.unpack(word)[0]
|
|
|
|
def swap_endian() -> None:
|
|
global WORD_SWAPPED
|
|
global WORD
|
|
if WORD_SWAPPED:
|
|
raise Exception("Swapping enddianness twice oops")
|
|
WORD_SWAPPED = True
|
|
WORD = WORD_LE
|
|
|
|
def _hex_pad(word: int) -> str:
|
|
return "{0:#0{1}x}".format(word, 10)
|
|
|
|
|
|
DUMMY_PAD = 0xFFFFFFFF
|
|
BUS_WIDTH_DET_1 = 0x000000BB
|
|
BUS_WIDTH_DET_1_EXCEPT_WRONG_WAY = 0xBB000000
|
|
BUS_WIDTH_DET_2 = 0x11220044
|
|
SYNC_WORD = 0xAA995566
|
|
|
|
PACKET_TYPE_1 = 1
|
|
PACKET_TYPE_2 = 2
|
|
|
|
CFG_FRAME_WORD_LENGTH = 101
|
|
|
|
class MiscWord(IntEnum):
|
|
DUMMY_PAD = 1
|
|
BUS_WIDTH_DET_1 = 2
|
|
BUS_WIDTH_DET_2 = 3
|
|
SYNC_WORD = 4
|
|
|
|
class PacketOp(IntEnum):
|
|
NOP = 0
|
|
READ = 1
|
|
WRITE = 2
|
|
RESERVED = 3
|
|
|
|
class Register(IntEnum):
|
|
CRC = 0b00000
|
|
FAR = 0b00001
|
|
FDRI = 0b00010
|
|
FDRO = 0b00011
|
|
CMD = 0b00100
|
|
CTL0 = 0b00101
|
|
MASK = 0b00110
|
|
STAT = 0b00111
|
|
LOUT = 0b01000
|
|
COR0 = 0b01001
|
|
MFWR = 0b01010
|
|
CBC = 0b01011
|
|
IDCODE = 0b01100
|
|
AXSS = 0b01101
|
|
COR1 = 0b01110
|
|
WBSTAR = 0b10000
|
|
TIMER = 0b10001
|
|
BOOTSTS= 0b10110
|
|
CTL1 = 0b11000
|
|
BSPI = 0b11111
|
|
|
|
UNDOC_1= 0b10011 # Documentation? what's that lol ~ Xilinx, probably
|
|
|
|
class FarType(IntEnum):
|
|
FABRIC = 0
|
|
BRAM = 1
|
|
CFG_CLB = 2
|
|
UNDOC_1 = 7 # And again
|
|
|
|
class FarValue(NamedTuple):
|
|
blocktype: FarType
|
|
bottom_top: int
|
|
row_addr: int
|
|
col_addr: int
|
|
minor_addr: int
|
|
|
|
class CmdValue(IntEnum):
|
|
NULL = 0b00000
|
|
WCFG = 0b00001
|
|
MFW = 0b00010
|
|
DGHIGH_LFRM = 0b00011
|
|
RCFG = 0b00100
|
|
START = 0b00101
|
|
AGHIGH = 0b01000
|
|
RCRC = 0b00111
|
|
SWITCH = 0b01001
|
|
GRESTORE = 0b01010
|
|
SHUTDOWN = 0b01011
|
|
DESYNC = 0b01101
|
|
|
|
class Type1(NamedTuple):
|
|
op: PacketOp
|
|
address: Register
|
|
word_count: int
|
|
|
|
class Type2(NamedTuple):
|
|
op: PacketOp
|
|
word_count: int
|
|
|
|
|
|
def _skip_xil_header(data: bytes) -> bytes:
|
|
# I really don't know what in the nondeterministic fuck is going on with this
|
|
# there's a size field at the end that might be important
|
|
# see http://www.pldtool.com/pdf/fmt_xilinxbit.pdf
|
|
for i in range(len(data) - 4):
|
|
word = _unpack_word(data[i:i+4])
|
|
if word == DUMMY_PAD:
|
|
if i >= 4:
|
|
# we found a header
|
|
print(".......... xilinx header")
|
|
data_len = _unpack_word(data[i-4:i])
|
|
# safety check the size field
|
|
assert data_len == len(data) - i
|
|
else:
|
|
# no header
|
|
assert i == 0
|
|
# return start of bitstream data
|
|
return i
|
|
raise Exception("Didn't find pad word!")
|
|
|
|
def _parse_far_word(word: int) -> FarValue:
|
|
try:
|
|
blk_type = (word >> 23) & 0b111
|
|
bottom_top = (word >> 22) & 0b1
|
|
row_addr = (word >> 17) & 0b11111
|
|
col_addr = (word >> 7) & 0b1111111111
|
|
minor_addr = word & 0b1111111
|
|
return FarValue(FarType(blk_type), bottom_top, row_addr, col_addr, minor_addr)
|
|
except:
|
|
raise Exception("Invalid FAR value: " + _hex_pad(word))
|
|
|
|
def _parse_cmd_word(word: int) -> CmdValue:
|
|
try:
|
|
return CmdValue(word)
|
|
except:
|
|
raise Exception("Invalid command value: " + _hex_pad(word))
|
|
|
|
def _parse_next_word(word: int) -> Union[MiscWord, Type1, Type2]:
|
|
try:
|
|
if word == DUMMY_PAD:
|
|
return MiscWord.DUMMY_PAD
|
|
elif word == BUS_WIDTH_DET_1_EXCEPT_WRONG_WAY:
|
|
swap_endian()
|
|
return MiscWord.BUS_WIDTH_DET_1
|
|
elif word == BUS_WIDTH_DET_1:
|
|
return MiscWord.BUS_WIDTH_DET_1
|
|
elif word == BUS_WIDTH_DET_2:
|
|
return MiscWord.BUS_WIDTH_DET_2
|
|
elif word == SYNC_WORD:
|
|
return MiscWord.SYNC_WORD
|
|
else:
|
|
header_bits = (word >> 29) & 0b111
|
|
opcode = (word >> 27) & 0b11
|
|
if header_bits == PACKET_TYPE_1:
|
|
addr = (word >> 13) & 0b11111
|
|
count = word & 0b1111111111
|
|
return Type1(PacketOp(opcode), Register(addr), count)
|
|
elif header_bits == PACKET_TYPE_2:
|
|
count = word & 0b11111111111111111111111111
|
|
return Type2(PacketOp(opcode), count)
|
|
raise Exception()
|
|
except:
|
|
raise Exception("Unknown word: " + _hex_pad(word))
|
|
|
|
def parse_bitstream(data: bytes) -> None:
|
|
i = _skip_xil_header(data)
|
|
data_counter = 0
|
|
last_far = None
|
|
bram_addrs = set()
|
|
while i < len(data):
|
|
word = _unpack_word(data[i:i+4])
|
|
obj = _parse_next_word(word)
|
|
print(_hex_pad(word), obj)
|
|
|
|
i += 4
|
|
|
|
if isinstance(obj, Type1) and obj.address == Register.FAR:
|
|
assert obj.word_count == 1
|
|
dat_word = _unpack_word(data[i:i+4])
|
|
last_far = _parse_far_word(dat_word)
|
|
if last_far.blocktype == FarType.BRAM:
|
|
bram_addrs.add(dat_word)
|
|
print(_hex_pad(dat_word), last_far)
|
|
i += 4
|
|
elif isinstance(obj, Type1) and obj.address == Register.CMD:
|
|
assert obj.word_count == 1
|
|
dat_word = _unpack_word(data[i:i+4])
|
|
print(_hex_pad(dat_word), _parse_cmd_word(dat_word))
|
|
i += 4
|
|
elif isinstance(obj, Type1) or isinstance(obj, Type2):
|
|
if obj.word_count > 0:
|
|
if obj.word_count < 4:
|
|
for j in range(obj.word_count):
|
|
dat_word = _unpack_word(data[i+j*4:i+j*4+4])
|
|
print(_hex_pad(dat_word), "data")
|
|
else:
|
|
l = f"{data_counter}"
|
|
if last_far is not None and last_far.blocktype == FarType.BRAM:
|
|
l = f"_bram_{last_far.bottom_top}_{last_far.row_addr}_{last_far.col_addr}_{last_far.minor_addr}"
|
|
out_file = f"{sys.argv[1]}-frames-{l}.dat"
|
|
data_counter += 1
|
|
print("..........", obj.word_count, "words (written to ", out_file, ")")
|
|
with open(out_file, "wb") as f:
|
|
f.write(data[i:i+obj.word_count*4])
|
|
i += obj.word_count * 4
|
|
|
|
if i != len(data):
|
|
raise Exception("Desync??")
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
print("Usage: ./xilbitstream.py <bitstream-file>")
|
|
sys.exit()
|
|
with open(sys.argv[1], "rb") as f:
|
|
data = f.read()
|
|
parse_bitstream(data)
|