Skip to content

Instantly share code, notes, and snippets.

@gregdavill
Created January 16, 2019 11:38
Show Gist options
  • Save gregdavill/4f9f536757966171ef974f98348bbacb to your computer and use it in GitHub Desktop.
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)
@janrinze
Copy link

the flash chip is the MX25L12833FMI-10G
I'm running the whole toolchain on ARM Linux. Diamond won't run on that unfortunately.

@daveshah1
Copy link

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)

@janrinze
Copy link

janrinze commented Aug 31, 2019

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!!

@DurandA
Copy link

DurandA commented May 10, 2020

Is this tool still actual or is it possible to enable SPI flash programming directly from Project Trellis tools now?

@gregdavill
Copy link
Author

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

@DurandA
Copy link

DurandA commented May 10, 2020

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. 🤦‍♂️

@gregdavill
Copy link
Author

That's great! I don't own a ecp5_evn, so I've not tested on that yet. Good to know it works! :D

@pepijndevos
Copy link

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...
image

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.
image

But yea, didn't write anything to flash as far as I can tell.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment