150 lines
3.5 KiB
Python
150 lines
3.5 KiB
Python
|
#!/usr/bin/env python3
|
||
|
|
||
|
import sys
|
||
|
import textwrap
|
||
|
|
||
|
# Very basic bitstream to SVF converter, tested with the ULX3S WiFi interface
|
||
|
|
||
|
flash_page_size = 256
|
||
|
erase_block_size = 64*1024
|
||
|
|
||
|
|
||
|
def bitreverse(x):
|
||
|
y = 0
|
||
|
for i in range(8):
|
||
|
if (x >> (7 - i)) & 1 == 1:
|
||
|
y |= (1 << i)
|
||
|
return y
|
||
|
|
||
|
with open(sys.argv[1], 'rb') as bitf:
|
||
|
bs = bitf.read()
|
||
|
# Autodetect IDCODE from bitstream
|
||
|
idcode_cmd = bytes([0xE2, 0x00, 0x00, 0x00])
|
||
|
idcode = None
|
||
|
for i in range(len(bs) - 4):
|
||
|
if bs[i:i+4] == idcode_cmd:
|
||
|
idcode = bs[i+4] << 24
|
||
|
idcode |= bs[i+5] << 16
|
||
|
idcode |= bs[i+6] << 8
|
||
|
idcode |= bs[i+7]
|
||
|
break
|
||
|
if idcode is None:
|
||
|
print("Failed to find IDCODE in bitstream, check bitstream is valid")
|
||
|
sys.exit(1)
|
||
|
bitf.seek(0)
|
||
|
|
||
|
address = 0
|
||
|
last_page = -1
|
||
|
|
||
|
with open(sys.argv[2], 'w') as svf:
|
||
|
print("""
|
||
|
STATE RESET;
|
||
|
HDR 0;
|
||
|
HIR 0;
|
||
|
TDR 0;
|
||
|
TIR 0;
|
||
|
ENDDR DRPAUSE;
|
||
|
ENDIR IRPAUSE;
|
||
|
STATE IDLE;
|
||
|
""", file=svf)
|
||
|
print("""
|
||
|
SIR 8 TDI (E0);
|
||
|
SDR 32 TDI (00000000)
|
||
|
TDO ({:08X})
|
||
|
MASK (FFFFFFFF);
|
||
|
""".format(idcode), file=svf)
|
||
|
print("""
|
||
|
SIR 8 TDI (1C);
|
||
|
SDR 510 TDI (3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||
|
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
||
|
|
||
|
// Enter Programming mode
|
||
|
SIR 8 TDI (C6);
|
||
|
SDR 8 TDI (00);
|
||
|
RUNTEST IDLE 2 TCK 1.00E-02 SEC;
|
||
|
|
||
|
// Erase
|
||
|
SIR 8 TDI (0E);
|
||
|
SDR 8 TDI (01);
|
||
|
RUNTEST IDLE 2 TCK 2.0E-1 SEC;
|
||
|
|
||
|
// Read STATUS
|
||
|
SIR 8 TDI (3C);
|
||
|
SDR 32 TDI (00000000)
|
||
|
TDO (00000000)
|
||
|
MASK (0000B000);
|
||
|
|
||
|
// Exit Programming mode
|
||
|
SIR 8 TDI (26);
|
||
|
RUNTEST IDLE 2 TCK 1.00E-02 SEC;
|
||
|
|
||
|
// BYPASS
|
||
|
SIR 8 TDI (FF);
|
||
|
STATE IDLE;
|
||
|
RUNTEST 32 TCK;
|
||
|
RUNTEST 2.00E-2 SEC;
|
||
|
|
||
|
// Enter SPI mode
|
||
|
|
||
|
SIR 8 TDI (3A);
|
||
|
SDR 16 TDI (68FE);
|
||
|
STATE IDLE;
|
||
|
RUNTEST 32 TCK;
|
||
|
RUNTEST 2.00E-2 SEC;
|
||
|
|
||
|
// SPI IO
|
||
|
SDR 8 TDI (D5);
|
||
|
|
||
|
RUNTEST 2.00E-0 SEC;
|
||
|
|
||
|
// CONFIRM FLASH ID
|
||
|
SDR 32 TDI (000000F9)
|
||
|
TDO (68FFFFFF)
|
||
|
MASK (FF000000);
|
||
|
|
||
|
SDR 8 TDI(60);
|
||
|
SDR 16 TDI(0080);
|
||
|
RUNTEST 1.00E-0 SEC;
|
||
|
|
||
|
|
||
|
""", file=svf)
|
||
|
while True:
|
||
|
if((address // 0x10000) != last_page):
|
||
|
last_page = (address // 0x10000)
|
||
|
print("""SDR 8 TDI (60);
|
||
|
""", file=svf)
|
||
|
address_flipped = [bitreverse(x) for x in [0xd8,int(address // 0x10000),0x00,0x00]]
|
||
|
hex_address= ["{:02X}".format(x) for x in reversed(address_flipped)]
|
||
|
print("\n".join(textwrap.wrap("SDR {} TDI ({});".format(8*len(hex_address), "".join(hex_address)), 100)), file=svf)
|
||
|
print("""RUNTEST 3.00 SEC;
|
||
|
""", file=svf)
|
||
|
|
||
|
chunk = bitf.read(flash_page_size)
|
||
|
if not chunk:
|
||
|
break
|
||
|
# Convert chunk to bit-reversed hex
|
||
|
br_chunk = [bitreverse(x) for x in bytes([0x02, int(address / 0x10000 % 0x100),int(address / 0x100 % 0x100),int(address % 0x100)]) + chunk]
|
||
|
address += len(chunk)
|
||
|
hex_chunk = ["{:02X}".format(x) for x in reversed(br_chunk)]
|
||
|
print("""
|
||
|
SDR 8 TDI (60);
|
||
|
""", file=svf)
|
||
|
print("\n".join(textwrap.wrap("SDR {} TDI ({});".format(8*len(br_chunk), "".join(hex_chunk)), 100)), file=svf)
|
||
|
print("""
|
||
|
RUNTEST 2.50E-2 SEC;
|
||
|
""", file=svf)
|
||
|
|
||
|
print("""
|
||
|
// BYPASS
|
||
|
SIR 8 TDI (FF);
|
||
|
|
||
|
//REFRESH
|
||
|
SIR 8 TDI(79);
|
||
|
SDR 24 TDI(000000);
|
||
|
|
||
|
STATE IDLE;
|
||
|
RUNTEST 32 TCK;
|
||
|
RUNTEST 2.00E-2 SEC;
|
||
|
STATE RESET;
|
||
|
""", file=svf)
|