├── .gitignore ├── README.md ├── iCEburn ├── __init__.py ├── __main__.py ├── libiceblink.py └── regtool.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lattice iCEBlink40 Programming Tool 2 | 3 | The Lattice iCEBlink40 is a low-cost FPGA board (currently $50 cad) supporting the iCE40 LP/HX families. While the iCEcube programming software on linux supports the chips, it doesn't ship with a port of iceutil.exe to program the board itself. This is an open-source tool, licensed under GPL2, to allow for programming of the FPGA board from linux. 4 | 5 | This script has only been tested by the author with the iCEblink40-LP1K board, with the M25P10VP flash. Other users have reported that it works with the HX1K flavour boards. 6 | 7 | This tool was created from black-box inspection of the USB datastream between a VM running iceutil and the iCEblink40-LP1K evaluation board. No binary analysis, binary reverse engineering or disassembly was used during the creation of this tool; all information in this file was derived from black-box analysis. 8 | 9 | The iCEBlink boards only support programming of the flash memory used to boot the FPGA. Direct SPI programming of the FPGA does not appear to be possible. 10 | 11 | # Requirements: 12 | 13 | - python 3 or newer 14 | - pyusb 1.0 or newer 15 | 16 | # Usage 17 | 18 | ## iCEburn.py 19 | 20 | The main flash programming tool is iCEburn.py. It supports two operation flags, `-e` for erase, and `-w` for write. The most common usage is: 21 | 22 | 23 | `./iCEburn.py -ew path\_to\_build.bin` 24 | 25 | 26 | ## regtool.py 27 | 28 | Regtool is an example test script to poke at registers via the FPGA data link. The argument 29 | 30 | -r 0xAB 31 | 32 | will do a read of register 0xAB and print the result to the console. The argument 33 | 34 | -w 0xAB:0xCD 35 | 36 | will write register 0xAB with value and print the result to the console. With the stock FPGA firmware 37 | 38 | ./regtool.py -w 5:0x48 39 | 40 | will stop the scrolling pattern and fix LED3 on. 41 | 42 | Regtool is primarily intended as an example of API usage, and not as a production grade tool. 43 | 44 | 45 | # Bugs 46 | 47 | - If using old versions of pyUSB, you may get an error: 48 | 49 | AttributeError: "'NoneType' object has no attribute 'libusb_exit'" 50 | 51 | This is a bug in pyusb. 52 | 53 | 54 | # TODO 55 | 56 | - Add additional flash devices 57 | 58 | -------------------------------------------------------------------------------- /iCEburn/__init__.py: -------------------------------------------------------------------------------- 1 | name = "iCEburn" 2 | -------------------------------------------------------------------------------- /iCEburn/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import argparse 3 | from iCEburn.libiceblink import ICE40Board, M25P10 4 | 5 | def main(): 6 | ap = argparse.ArgumentParser() 7 | ap.add_argument("-e", "--erase", action="store_true") 8 | ap.add_argument("-v", "--verbose", action="store_true") 9 | ap.add_argument("-w", "--write", type=argparse.FileType("rb")) 10 | args = ap.parse_args() 11 | 12 | board = ICE40Board() 13 | 14 | if args.verbose: 15 | print("Found iCE40 board serial: %s" % board.get_serial()) 16 | 17 | 18 | sp = board.get_spi_port(0) 19 | 20 | with board.get_gpio() as gpio: 21 | # Force the FPGA into reset so we may drive the IOs 22 | gpio.ice40SetReset(True) 23 | 24 | with board.get_spi_port(0) as sp: 25 | sp.setSpeed(50000000) 26 | sp.setMode() 27 | 28 | flash = M25P10(sp.io) 29 | 30 | flash.wakeup() 31 | 32 | # Verify that we're talking to the part we think we are 33 | assert flash.getID() == b'\x20\x20\x11' 34 | 35 | # Now, do the actions 36 | if args.erase: 37 | if args.verbose: 38 | print("Erasing flash...") 39 | flash.chipErase() 40 | if args.verbose: 41 | print("") 42 | 43 | if args.write: 44 | data = args.write.read() 45 | 46 | if args.verbose: 47 | print("Writing image...") 48 | 49 | for addr in range(0, len(data), 256): 50 | buf = data[addr:addr+256] 51 | flash.pageProgram(addr, buf) 52 | 53 | if args.verbose: 54 | print("Verifying written image...") 55 | # Now verify 56 | buf = flash.read(0, len(data)) 57 | assert len(buf) == len(data) 58 | 59 | nvfailures = 0 60 | for i,(a,b) in enumerate(zip(buf, data)): 61 | if a!=b: 62 | print ("verification failure at %06x: %02x != %02x" % 63 | (i,a,b)) 64 | nvfailures += 1 65 | 66 | if nvfailures == 5: 67 | print("Too many verification failures, bailing") 68 | break 69 | 70 | # Release the FPGA reset 71 | gpio.ice40SetReset(False) 72 | 73 | 74 | if __name__ == "__main__": 75 | main() 76 | -------------------------------------------------------------------------------- /iCEburn/libiceblink.py: -------------------------------------------------------------------------------- 1 | import usb.core 2 | import usb.util 3 | import array 4 | import struct 5 | import binascii 6 | import time 7 | 8 | class ProtocolError(Exception): 9 | pass 10 | 11 | class SPIProtocolError(Exception): 12 | def __init__(self, cmd, rescode): 13 | lut = { 14 | 3: "Resource in use", 15 | 4: "Resource not opened", 16 | 12: "Invalid enum" 17 | } 18 | if rescode in lut: 19 | err = lut[rescode] 20 | else: 21 | err = "error code %d" % rescode 22 | 23 | ProtocolError.__init__(self, "Command %s failed with error: %s" %( cmd, 24 | err)) 25 | 26 | class M25P10(object): 27 | STAT_BUSY = 0x1 28 | STAT_WEL = 0x2 29 | 30 | CMD_GET_STATUS = 0x05 31 | CMD_WRITE_ENABLE = 0x6 32 | CMD_READ_ID = 0x9F 33 | CMD_WAKEUP = 0xAB 34 | CMD_CHIP_ERASE = 0xC7 35 | CMD_PAGE_PROGRAM = 0x02 36 | CMD_FAST_READ = 0xB 37 | 38 | def __init__(self, iofn): 39 | self.io = iofn 40 | 41 | def wakeup(self): 42 | self.io([self.CMD_WAKEUP]) 43 | 44 | def setWritable(self): 45 | self.io([self.CMD_WRITE_ENABLE]) 46 | 47 | def chipErase(self): 48 | self.setWritable() 49 | self.io([self.CMD_CHIP_ERASE]) 50 | self.waitDone() 51 | 52 | def read(self, addr, size): 53 | return self.io([self.CMD_FAST_READ, (addr>>16) & 0xFF, (addr>>8)&0xFF, addr & 54 | 0xFF, 0x00], size+5)[5:] 55 | def pageProgram(self, addr, buf): 56 | self.setWritable() 57 | assert len(buf) <= 256 58 | assert addr & 0xFF == 0 59 | 60 | self.io([self.CMD_PAGE_PROGRAM, (addr>>16) & 0xFF, (addr>>8)&0xFF, addr & 61 | 0xFF] + list(buf)) 62 | self.waitDone() 63 | 64 | def waitDone(self): 65 | while self.getStatus() & self.STAT_BUSY: 66 | pass 67 | 68 | def getStatus(self): 69 | return self.io([self.CMD_GET_STATUS],2)[1] 70 | 71 | def getID(self): 72 | return self.io([self.CMD_READ_ID],4)[1:] 73 | 74 | 75 | class ICE40Board(object): 76 | 77 | CMD_GET_BOARD_TYPE = 0xE2 78 | CMD_GET_BOARD_SERIAL = 0xE4 79 | 80 | class __ICE40BoardComm(object): 81 | def __init__(self, dev): 82 | self.__is_open = False 83 | self.dev = dev 84 | 85 | def __enter__(self): 86 | self.open() 87 | return self 88 | 89 | def __exit__(self, type, err, traceback): 90 | self.__cleanup() 91 | 92 | def __del__(self): 93 | self.__cleanup() 94 | 95 | def __cleanup(self): 96 | if self.__is_open: 97 | self.close() 98 | 99 | def open(self): 100 | assert not self.__is_open 101 | self.dev.checked_cmd(0x04, 0x00, "bcommopen", [0x00], noret=True) 102 | self.__is_open = True 103 | 104 | def close(self): 105 | assert self.__is_open 106 | self.dev.checked_cmd(0x04, 0x01, "bcommclose", [0x00], noret=True, 107 | ) 108 | self.__is_open = False 109 | 110 | def __check_counts(self, status, resb, wr, rd): 111 | if status & 0x80: 112 | wb = struct.unpack('