-
-
Save gregdavill/4f9f536757966171ef974f98348bbacb to your computer and use it in GitHub Desktop.
#!/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) | |
print("IDCODE in bitstream is 0x%08x" % idcode) | |
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 (A8FFFFFF) | |
MASK (FF000000); | |
""", 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([0x20, 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); | |
STATE IDLE; | |
RUNTEST 32 TCK; | |
RUNTEST 2.00E-2 SEC; | |
STATE RESET; | |
""", file=svf) |
Whoa! that worked.
So the changes are:
// CONFIRM FLASH ID
SDR 32 TDI (000000F9)
TDO (A8FFFFFF)
MASK (FF000000);
changing to :
// CONFIRM FLASH ID
SDR 32 TDI (000000F9)
TDO (18FFFFFF)
MASK (FF000000);
and:
# Convert chunk to bit-reversed hex
br_chunk = [bitreverse(x) for x in bytes([0x20, int(address / 0x10000 % 0x100),int(address / 0x100 % 0x100),int(address % 0x100)]) + chunk]
change to:
# 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]
Then the svf file created by the script can be used to flash the Lattice ECP5 evaluation board.
background_spi_test.py design.bit progfile.svf
openocd -f /usr/share/trellis/misc/openocd/ecp5-evn.cfg -c "transport select jtag; init; svf progfile.svf; exit"
Thanks!!
Is this tool still actual or is it possible to enable SPI flash programming directly from Project Trellis tools now?
This tool will still work.
If you're using an FT232H based programmer or equivalent you can check out this: https://github.com/gregdavill/ecpprog issues/pull requests welcome
ecpprog is awesome and works flawlessly on the ecp5_evn board. I can't believe I read your post about ecpprog on Twitter and then forgot about it. 🤦♂️
That's great! I don't own a ecp5_evn, so I've not tested on that yet. Good to know it works! :D
Hmmm didn't work for me with Winbond 25Q32JVSIQ flash on the Colorlight 5A-75B V8.0
First changed the ID check to
// CONFIRM FLASH ID
SDR 32 TDI (000000F9)
TDO (68FFFFFF)
MASK (FF000000);
No idea if 68 is correct, I can't make sense of what this table is trying to say. But it makes it past the check...
and then also used 0x02 as the page program command, which is the one I need as far as I can tell from the datasheet.
But yea, didn't write anything to flash as far as I can tell.
You might want to try with 0x20 here https://gist.github.com/gregdavill/4f9f536757966171ef974f98348bbacb#file-background_spi_test-py-L121 set to 0x02 (which seems to be the page program command for your flash)