#!/usr/bin/env python3
import sys
import os
def parse_lattice_bitstream(input_path, output_path):
if not os.path.exists(input_path):
print(f"Error: File {input_path} not found.")
return False
with open(input_path, 'rb') as f:
raw_data = f.read()
file_size = len(raw_data)
print(f"Opened file: {input_path} ({file_size} bytes)")
curr_pos = 0
# 1. Проверяем сигнатуру "LSCC" в самом начале
if raw_data[0:4] == b'LSCC':
print("Found header signature: LSCC")
curr_pos += 4
else:
print("Error: File does not start with LSCC signature")
return False
# 2. Проверяем маркер комментариев 0xFF 0x00
if raw_data[curr_pos:curr_pos+2] != b'\xff\x00':
print(f"Error: Missing comment area marker (Expected FF 00, got {raw_data[curr_pos:curr_pos+2].hex()})")
return False
curr_pos += 2
# 3. Динамический поиск преамбулы Nexus (0xb3bdffff, 0xb3bfffff или 0xb3beffff)
# openFPGALoader ищет байт 0xB3, начиная от позиции после маркера комментариев
pos_b3 = -1
for i in range(curr_pos, file_size - 3):
# Проверяем возможные варианты преамбулы с учетом порядка байт в Nexus
# Преамбула лежит как: [3 фиктивных FF] + [ключ BD/BE/BF] + [B3]
if raw_data[i] == 0xb3:
enc_key = raw_data[i - 1]
if enc_key in (0xbd, 0xbe, 0xbf):
pos_b3 = i
break
if pos_b3 == -1:
print("Error: Preamble key (0xB3 + BD/BE/BF) not found!")
return False
# Вычисляем точную позицию начала заголовка (выравнивание на 3 Dummy байта + ключ перед 0xB3)
# То есть отступаем на 4 байта назад от найденного 0xB3
end_header = pos_b3 - 4
print(f"Header end calculated at offset: {end_header} bytes")
# 4. Парсим текстовые метаданные (для информативности, как в openFPGALoader)
header_bytes = raw_data[curr_pos:end_header]
print("\n--- Bitstream Header Infos ---")
lines = header_bytes.split(b'\x00')
for line in lines:
if b':' in line:
try:
decoded_line = line.decode('utf-8', errors='ignore').strip()
if decoded_line:
print(decoded_line)
except Exception:
pass
print("------------------------------\n")
# 5. Отрезаем всё, что было ДО end_header, сохраняя чистый поток
clean_bitstream = raw_data[end_header:]
clean_size = len(clean_bitstream)
print(f"Stripped {end_header} bytes of metadata.")
print(f"Clean bitstream size: {clean_size} bytes (Matches openFPGALoader output!)")
# 6. Сохраняем в выходной файл
with open(output_path, 'wb') as f:
f.write(clean_bitstream)
print(f"Successfully saved clean binary to: {output_path}")
return True
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 parse_bit.py <input_file.bit> [output_file.bin]")
sys.exit(1)
infile = sys.argv[1]
outfile = sys.argv[2] if len(sys.argv) > 2 else infile.replace('.bit', '_clean.bin')
if outfile == infile:
outfile = "out_clean.bin"
success = parse_lattice_bitstream(infile, outfile)
sys.exit(0 if success else 1)