Add type annotations, lint for pep8

This commit is contained in:
Matt Mets 2023-03-02 18:17:11 +01:00 committed by Michael Cardell Widerkrantz
parent ac174afb8f
commit 84d020e3c0
No known key found for this signature in database
GPG Key ID: D3DB3DDF57E704E5
3 changed files with 319 additions and 359 deletions

View File

@ -0,0 +1,8 @@
lint:
autopep8 --in-place --aggressive --aggressive usb_test.py
mypy --disallow-untyped-defs usb_test.py
#pycodestyle usb_test.py
autopep8 --in-place --aggressive --aggressive icenvcm.py
mypy --disallow-untyped-defs icenvcm.py
#pycodestyle icenvcm.py

357
hw/production_test/icenvcm.py Normal file → Executable file
View File

@ -20,15 +20,10 @@
# #
import usb_test import usb_test
from binascii import unhexlify, hexlify
import sys import sys
from time import sleep from time import sleep
import re
import os import os
def die(s):
print(s, file=sys.stderr)
exit(1)
class Nvcm(): class Nvcm():
# todo: add expected bitstream sizes # todo: add expected bitstream sizes
@ -48,7 +43,7 @@ class Nvcm():
0x21: "ICE40UP3K", 0x21: "ICE40UP3K",
} }
def __init__(self, pins, debug=False): def __init__(self, pins: dict, debug: bool = False) -> None:
self.pins = pins self.pins = pins
self.debug = debug self.debug = debug
@ -73,47 +68,47 @@ class Nvcm():
pins['miso'] pins['miso']
) )
def power_on(self): def power_on(self) -> None:
self.flasher.gpio_put(self.pins['5v_en'], True) self.flasher.gpio_put(self.pins['5v_en'], True)
def power_off(self): def power_off(self) -> None:
self.flasher.gpio_put(self.pins['5v_en'], False) self.flasher.gpio_put(self.pins['5v_en'], False)
def enable(self,cs,reset=1): def enable(self, cs: bool, reset: bool = True) -> None:
#gpio.write(cs << cs_pin | reset << reset_pin) # gpio.write(cs << cs_pin | reset << reset_pin)
self.flasher.gpio_put(self.pins['ss'], cs) self.flasher.gpio_put(self.pins['ss'], cs)
self.flasher.gpio_put(self.pins['crst'], reset) self.flasher.gpio_put(self.pins['crst'], reset)
def sendhex(self,s): def sendhex(self, s: str) -> int:
if self.debug and not s == "0500": # supress status check messages if self.debug and not s == "0500": # supress status check messages
print("TX", s) print("TX", s)
x = bytes.fromhex(s) x = bytes.fromhex(s)
#b = dev.exchange(x, duplex=True, readlen=len(x)) # b = dev.exchange(x, duplex=True, readlen=len(x))
b = self.flasher.spi_bitbang(x, toggle_cs=False) b = self.flasher.spi_bitbang(x, toggle_cs=False)
if self.debug and not s == "0500": if self.debug and not s == "0500":
print("RX", b.hex()) print("RX", b.hex())
return int.from_bytes(b, byteorder='big') return int.from_bytes(b, byteorder='big')
def sendhex_cs(self,s): def sendhex_cs(self, s: str) -> bytes:
if self.debug and not s == "0500": if self.debug and not s == "0500":
print("TX", s) print("TX", s)
x = bytes.fromhex(s) x = bytes.fromhex(s)
#b = dev.exchange(x, duplex=True, readlen=len(x)) # b = dev.exchange(x, duplex=True, readlen=len(x))
b = self.flasher.spi_bitbang(x) b = self.flasher.spi_bitbang(x)
if self.debug and not s == "0500": if self.debug and not s == "0500":
print("RX", b.hex()) print("RX", b.hex())
return b return b
def delay(self,count: int): def delay(self, count: int) -> None:
# run the clock with no CS asserted # run the clock with no CS asserted
#dev.exchange(b'\x00', duplex=True, readlen=count) # dev.exchange(b'\x00', duplex=True, readlen=count)
self.sendhex('00' * count) self.sendhex('00' * count)
def tck(self,count: int): def tck(self, count: int) -> None:
self.delay(count >> 3) self.delay(count >> 3)
self.delay(count >> 3) self.delay(count >> 3)
self.delay(count >> 3) self.delay(count >> 3)
@ -121,54 +116,50 @@ class Nvcm():
self.delay(count >> 3) self.delay(count >> 3)
self.delay(count >> 3) self.delay(count >> 3)
def init(self): def init(self) -> None:
if self.debug: if self.debug:
print("init") print("init")
self.enable(1, 1) self.enable(True, True)
self.enable(1, 0) # reset high self.enable(True, False) # reset high
sleep(0.15) sleep(0.15)
self.enable(0, 0) # enable and reset high self.enable(False, False) # enable and reset high
sleep(0.12) sleep(0.12)
self.enable(0, 1) # enable low, reset high self.enable(False, True) # enable low, reset high
sleep(0.12) sleep(0.12)
self.enable(1, 1) # enable and reset low self.enable(True, True) # enable and reset low
sleep(0.12) sleep(0.12)
return True
def status_wait(self,count=1000): def status_wait(self, count: int = 1000) -> None:
for i in range(0,count): for i in range(0, count):
self.tck(5000) self.tck(5000)
ret = self.sendhex_cs("0500") ret = self.sendhex_cs("0500")
x = int.from_bytes(ret, byteorder='big') x = int.from_bytes(ret, byteorder='big')
#print("x=%04x" %(x)) # print("x=%04x" %(x))
if (x & 0x00c1) == 0: if (x & 0x00c1) == 0:
return True return
print("status failed to clear", file=sys.stdout) raise Exception("status failed to clear")
return False
def command(self,cmd): def command(self, cmd: str) -> None:
self.sendhex_cs(cmd) self.sendhex_cs(cmd)
if not self.status_wait(): self.status_wait()
return False
self.tck(8) self.tck(8)
return True
def pgm_enable(self): def pgm_enable(self) -> None:
return self.command("06") self.command("06")
def pgm_disable(self): def pgm_disable(self) -> None:
return self.command("04") self.command("04")
def enable_access(self): def enable_access(self) -> None:
# ! Shift in Access-NVCM instruction; # ! Shift in Access-NVCM instruction;
# SMCInstruction[1] = 0x70807E99557E; # SMCInstruction[1] = 0x70807E99557E;
return self.command("7eaa997e010e") self.command("7eaa997e010e")
def read(self, address, length=8, cmd=0x03): def read(self, address: int, length: int = 8, cmd: int = 0x03) -> int:
"""Returns a big integer""" """Returns a big integer"""
# enable(0) # enable(0)
# sendhex("%02x%06x" % (cmd, address)) # sendhex("%02x%06x" % (cmd, address))
@ -185,138 +176,126 @@ class Nvcm():
ret = self.sendhex_cs(msg) ret = self.sendhex_cs(msg)
x = 0 x = 0
for i in range(0,length): for i in range(0, length):
x = x << 8 | ret[i + 4+9] x = x << 8 | ret[i + 4 + 9]
return x return x
def read_bytes(self, address, length=8): def read_bytes(self, address: int, length: int = 8) -> bytes:
"""Returns a byte array of the contents""" """Returns a byte array of the contents"""
return self.read(address, length).to_bytes(length, byteorder="big") return self.read(address, length).to_bytes(length, byteorder="big")
def write(self,address, data, cmd=0x02): def write(self, address: int, data: str, cmd: int = 0x02) -> None:
self.sendhex_cs("%02x%06x" % (cmd, address) + data) self.sendhex_cs("%02x%06x" % (cmd, address) + data)
if not self.status_wait(): try:
print("WRITE FAILED: cmd=%02x address=%06x data=%s" % (cmd, address, data.hex()), file=sys.stderr) self.status_wait()
return False except Exception as e:
raise Exception("WRITE FAILED: cmd=%02x address=%06x data=%s" %
(cmd, address, data))
self.tck(8) self.tck(8)
return True
def bank_select(self,bank): def bank_select(self, bank: int) -> None:
return self.write(cmd=0x83, address=0x000025, data="%02x" % (bank)) self.write(cmd=0x83, address=0x000025, data="%02x" % (bank))
def select_nvcm(self): def select_nvcm(self) -> None:
# ! Shift in Restore Access-NVCM instruction; # ! Shift in Restore Access-NVCM instruction;
# SDR 40 TDI(0x00A40000C1); # SDR 40 TDI(0x00A40000C1);
return self.bank_select(0x00) self.bank_select(0x00)
def select_trim(self): def select_trim(self) -> None:
# ! Shift in Trim setup-NVCM instruction; # ! Shift in Trim setup-NVCM instruction;
# SDR 40 TDI(0x08A40000C1); # SDR 40 TDI(0x08A40000C1);
return self.bank_select(0x10) self.bank_select(0x10)
def select_sig(self): def select_sig(self) -> None:
# ! Shift in Access Silicon Signature instruction; # ! Shift in Access Silicon Signature instruction;
# IDInstruction[1] = 0x04A40000C1; # IDInstruction[1] = 0x04A40000C1;
# SDR 40 TDI(IDInstruction[1]); # SDR 40 TDI(IDInstruction[1]);
return self.bank_select(0x20) self.bank_select(0x20)
def read_trim(self): def read_trim(self) -> int:
# ! Shift in Access-NVCM instruction; # ! Shift in Access-NVCM instruction;
# SMCInstruction[1] = 0x70807E99557E; # SMCInstruction[1] = 0x70807E99557E;
if not self.enable_access(): self.enable_access()
return
# ! Shift in READ_RF(0x84) instruction; # ! Shift in READ_RF(0x84) instruction;
# SDR 104 TDI(0x00000000000000000004000021); # SDR 104 TDI(0x00000000000000000004000021);
x = self.read(cmd=0x84, address=0x000020, length=8) x = self.read(cmd=0x84, address=0x000020, length=8)
self.tck(8) self.tck(8)
#print("FSM Trim Register %x" % (x)) # print("FSM Trim Register %x" % (x))
self.select_nvcm() self.select_nvcm()
return x return x
def write_trim(self,data): def write_trim(self, data: str) -> None:
# ! Setup Programming Parameter in Trim Registers; # ! Setup Programming Parameter in Trim Registers;
# ! Shift in Trim setup-NVCM instruction; # ! Shift in Trim setup-NVCM instruction;
# TRIMInstruction[1] = 0x000000430F4FA80004000041; # TRIMInstruction[1] = 0x000000430F4FA80004000041;
return self.write(cmd=0x82, address=0x000020, data=data) self.write(cmd=0x82, address=0x000020, data=data)
def nvcm_enable(self): def nvcm_enable(self) -> None:
if self.debug: if self.debug:
print("enable") print("enable")
# ! Shift in Access-NVCM instruction; # ! Shift in Access-NVCM instruction;
# SMCInstruction[1] = 0x70807E99557E; # SMCInstruction[1] = 0x70807E99557E;
if not self.enable_access(): self.enable_access()
return
# ! Setup Reading Parameter in Trim Registers; # ! Setup Reading Parameter in Trim Registers;
# ! Shift in Trim setup-NVCM instruction; # ! Shift in Trim setup-NVCM instruction;
# TRIMInstruction[1] = 0x000000230000000004000041; # TRIMInstruction[1] = 0x000000230000000004000041;
if self.debug: if self.debug:
print("setup_nvcm") print("setup_nvcm")
return self.write_trim("00000000c4000000") self.write_trim("00000000c4000000")
def enable_trim(self): def enable_trim(self) -> None:
# ! Setup Programming Parameter in Trim Registers; # ! Setup Programming Parameter in Trim Registers;
# ! Shift in Trim setup-NVCM instruction; # ! Shift in Trim setup-NVCM instruction;
# TRIMInstruction[1] = 0x000000430F4FA80004000041; # TRIMInstruction[1] = 0x000000430F4FA80004000041;
return self.write_trim("0015f2f0c2000000") self.write_trim("0015f2f0c2000000")
def disable(self): def trim_blank_check(self) -> bool:
if not self.select_nvcm(): print("NVCM Trim_Parameter_OTP blank check")
return
self.reset(1) self.select_trim()
self.tck(8)
self.reset(0)
self.tck(8)
def trim_blank_check(self):
print ("NVCM Trim_Parameter_OTP blank check");
if not self.select_trim():
return
x = self.read(0x000020, 1) x = self.read(0x000020, 1)
self.select_nvcm() self.select_nvcm()
if x != 0: if x != 0:
die ("NVCM Trim_Parameter_OTP Block is not blank. (%02x)" % x); print("NVCM Trim_Parameter_OTP Block is not blank. (%02x)" % x)
return False
return True return True
def blank_check(self,total_fuse): def blank_check(self, total_fuse: int) -> bool:
self.select_nvcm() self.select_nvcm()
status = True status = True
print ("NVCM main memory blank check"); print("NVCM main memory blank check")
contents = self.read_bytes(0x000000, total_fuse) contents = self.read_bytes(0x000000, total_fuse)
for i in range(0,total_fuse): for i in range(0, total_fuse):
x = contents[i] x = contents[i]
if debug: if self.debug:
print("%08x: %02x" % (i, x)) print("%08x: %02x" % (i, x))
if x != 0: if x != 0:
print ("%08x: NVCM Main Memory Block is not blank." % (i), file=sys.stderr) print(
"%08x: NVCM Main Memory Block is not blank." %
(i), file=sys.stderr)
status = False status = False
#break
self.select_nvcm() self.select_nvcm()
return status return status
def program(self,rows): def program(self, rows: list[str]) -> None:
print("NVCM Program main memory")
self.select_nvcm() self.select_nvcm()
if not self.enable_trim(): self.enable_trim()
return False
print ("NVCM Program main memory") self.pgm_enable()
if not self.pgm_enable():
return False
status = True status = True
@ -325,28 +304,21 @@ class Nvcm():
if i % 1024 == 0: if i % 1024 == 0:
print("%6d / %6d bytes" % (i, len(rows) * 8)) print("%6d / %6d bytes" % (i, len(rows) * 8))
i += 8 i += 8
if not self.command(row): try:
status = False self.command(row)
break except Exception as e:
raise Exception("programming failed, row:", row)
self.pgm_disable() self.pgm_disable()
if not status: def write_trim_pages(self, lock_bits: str) -> None:
print("PROGRAMMING FAILED", file=sys.stderr) self.select_nvcm()
return status
def write_trim_pages(self,lock_bits): self.enable_trim()
if not self.select_nvcm():
die("select trim failed")
if not self.enable_trim(): self.select_trim()
die("write trim command failed")
if not self.select_trim(): self.pgm_enable()
die("select trim failed")
if not self.pgm_enable():
die("write enable failed")
# ! Program Security Bit row 1; # ! Program Security Bit row 1;
# ! Shift in PAGEPGM instruction; # ! Shift in PAGEPGM instruction;
@ -357,14 +329,10 @@ class Nvcm():
# SDR 96 TDI(0x000000008000000C05000040); # SDR 96 TDI(0x000000008000000C05000040);
# ! Program Security Bit row 4; # ! Program Security Bit row 4;
# SDR 96 TDI(0x00000000800000C07000040); # SDR 96 TDI(0x00000000800000C07000040);
if not self.write(0x000020, lock_bits): self.write(0x000020, lock_bits)
die("trim write 0x20 failed") self.write(0x000060, lock_bits)
if not self.write(0x000060, lock_bits): self.write(0x0000a0, lock_bits)
die("trim write 0x60 failed") self.write(0x0000e0, lock_bits)
if not self.write(0x0000a0, lock_bits):
die("trim write 0xa0 failed")
if not self.write(0x0000e0, lock_bits):
die("trim write 0xe0 failed")
self.pgm_disable() self.pgm_disable()
@ -373,27 +341,27 @@ class Nvcm():
self.select_nvcm() self.select_nvcm()
lock_bits = int(lock_bits,16) lock_bits_int = int(lock_bits, 16)
if x & lock_bits != lock_bits: if x & lock_bits_int != lock_bits_int:
die("Failed to write trim lock bits: %016x != expected %016x" % (x,lock_bits)) raise Exception(
"Failed to write trim lock bits: %016x != expected %016x" %
(x, lock_bits_int))
print("New state %016x" % (x)) print("New state %016x" % (x))
return True
def trim_secure(self): def trim_secure(self) -> None:
print ("NVCM Secure") print("NVCM Secure")
trim = self.read_trim() trim = self.read_trim()
if (trim >> 60) & 0x3 != 0: if (trim >> 60) & 0x3 != 0:
print("NVCM already secure? trim=%016x" % (trim), file=sys.stderr) print("NVCM already secure? trim=%016x" % (trim), file=sys.stderr)
return self.write_trim_pages("3000000100000000") self.write_trim_pages("3000000100000000")
def trim_program(self) -> None:
print("NVCM Program Trim_Parameter_OTP")
self.write_trim_pages("0015f2f1c4000000")
def trim_program(self): def info(self) -> None:
print ("NVCM Program Trim_Parameter_OTP");
return self.write_trim_pages("0015f2f1c4000000")
def info(self):
self.select_sig() self.select_sig()
sig1 = self.read(0x000000, 8) sig1 = self.read(0x000000, 8)
@ -431,23 +399,20 @@ class Nvcm():
print("Sig 0: %016x" % (sig1)) print("Sig 0: %016x" % (sig1))
print("Sig 1: %016x" % (sig2)) print("Sig 1: %016x" % (sig2))
print("TrimRF: %016x" % (trim)) print("TrimRF: %016x" % (trim))
print("Trim 0: %016x" % (trim0)) print("Trim 0: %016x" % (trim0))
print("Trim 1: %016x" % (trim1)) print("Trim 1: %016x" % (trim1))
print("Trim 2: %016x" % (trim2)) print("Trim 2: %016x" % (trim2))
print("Trim 3: %016x" % (trim3)) print("Trim 3: %016x" % (trim3))
return True def read_file(self, filename: str) -> None:
def read_file(self,filename):
self.select_nvcm() self.select_nvcm()
total_fuse = 104090 total_fuse = 104090
contents = b'' contents = b''
for offset in range(0,total_fuse,8): for offset in range(0, total_fuse, 8):
if offset % 1024 == 0: if offset % 1024 == 0:
print("%6d / %6d bytes" % (offset, total_fuse)) print("%6d / %6d bytes" % (offset, total_fuse))
contents += self.read_bytes(offset, 8) contents += self.read_bytes(offset, 8)
@ -466,15 +431,14 @@ class Nvcm():
# bistream to NVCM command conversion is based on majbthrd's work in # bistream to NVCM command conversion is based on majbthrd's work in
# https://github.com/YosysHQ/icestorm/pull/272 # https://github.com/YosysHQ/icestorm/pull/272
# #
def bitstream2nvcm(bitstream): def bitstream2nvcm(bitstream: bytes) -> list[str]:
# ensure that the file starts with the correct bistream preamble # ensure that the file starts with the correct bistream preamble
for origin in range(0,len(bitstream)): for origin in range(0, len(bitstream)):
if bitstream[origin:origin+4] == bytes.fromhex('7EAA997E'): if bitstream[origin:origin + 4] == bytes.fromhex('7EAA997E'):
break break
if origin == len(bitstream): if origin == len(bitstream):
print("Preamble not found", file=sys.stderr) raise Exception("Preamble not found")
return False
print("Found preamable at %08x" % (origin), file=sys.stderr) print("Found preamable at %08x" % (origin), file=sys.stderr)
@ -486,7 +450,7 @@ def bitstream2nvcm(bitstream):
rows = [] rows = []
for pos in range(origin, len(bitstream), 8): for pos in range(origin, len(bitstream), 8):
row = bitstream[pos:pos+8] row = bitstream[pos:pos + 8]
# pad out to 8-bytes # pad out to 8-bytes
row += b'\0' * (8 - len(row)) row += b'\0' * (8 - len(row))
@ -498,11 +462,13 @@ def bitstream2nvcm(bitstream):
# NVCM addressing is very weird # NVCM addressing is very weird
addr = pos - origin addr = pos - origin
nvcm_addr = int(addr / 328) * 4096 + (addr % 328) nvcm_addr = int(addr / 328) * 4096 + (addr % 328)
rows += [ "02 %06x %s" % (nvcm_addr, row.hex()) ] rows += ["02 %06x %s" % (nvcm_addr, row.hex())]
return rows return rows
def sleep_flash(pins):
def sleep_flash(pins: dict) -> None:
""" Put the SPI bootloader flash in deep sleep mode"""
flasher = usb_test.ice40_flasher() flasher = usb_test.ice40_flasher()
# Disable board power # Disable board power
@ -529,31 +495,35 @@ def sleep_flash(pins):
pins['mosi'] pins['mosi']
) )
flasher.spi_bitbang([0xAB]) flasher.spi_bitbang(bytes([0xAB]))
# Confirm we can talk to flash # Confirm we can talk to flash
data = flasher.spi_bitbang([0x9f, 0,0]) data = flasher.spi_bitbang(bytes([0x9f, 0, 0]))
print('flash ID while awake:', ' '.join(['{:02x}'.format(b) for b in data])) print('flash ID while awake:', ' '.join(
assert(data == bytes([0xff, 0xef, 0x40])) ['{:02x}'.format(b) for b in data]))
assert (data == bytes([0xff, 0xef, 0x40]))
# Test that the flash will ignore a sleep command that doesn't start on the first byte # Test that the flash will ignore a sleep command that doesn't start on
flasher.spi_bitbang([0, 0xb9]) # the first byte
flasher.spi_bitbang(bytes([0, 0xb9]))
# Confirm we can talk to flash # Confirm we can talk to flash
data = flasher.spi_bitbang([0x9f, 0,0]) data = flasher.spi_bitbang(bytes([0x9f, 0, 0]))
print('flash ID while awake:', ' '.join(['{:02x}'.format(b) for b in data])) print('flash ID while awake:', ' '.join(
assert(data == bytes([0xff, 0xef, 0x40])) ['{:02x}'.format(b) for b in data]))
assert (data == bytes([0xff, 0xef, 0x40]))
# put the flash to sleep # put the flash to sleep
flasher.spi_bitbang([0xb9]) flasher.spi_bitbang(bytes([0xb9]))
# Confirm flash is asleep # Confirm flash is asleep
data = flasher.spi_bitbang(buf=[0x9f, 0,0]) data = flasher.spi_bitbang(bytes([0x9f, 0, 0]))
print('flash ID while asleep:', ' '.join(['{:02x}'.format(b) for b in data])) print('flash ID while asleep:', ' '.join(
assert(data == bytes([0xff, 0xff, 0xff])) ['{:02x}'.format(b) for b in data]))
assert (data == bytes([0xff, 0xff, 0xff]))
if __name__ == "__main__": if __name__ == "__main__":
@ -562,22 +532,26 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( '--port', parser.add_argument('--port',
type=str, type=str,
default='ftdi://::/1', default='ftdi://::/1',
help='FTDI port of the form ftdi://::/1') help='FTDI port of the form ftdi://::/1')
parser.add_argument( '-v', '--verbose', parser.add_argument('-v', '--verbose',
dest='verbose', dest='verbose',
action='store_true', action='store_true',
help='Show debug information and serial read/writes') help='Show debug information and serial read/writes')
parser.add_argument('-f', '--sleep_flash', parser.add_argument(
'-f',
'--sleep_flash',
dest='sleep_flash', dest='sleep_flash',
action='store_true', action='store_true',
help='Put an attached SPI flash chip in deep sleep before programming FPGA') help='Put an attached SPI flash chip in deep sleep before programming FPGA')
parser.add_argument('-b', '--boot', parser.add_argument(
'-b',
'--boot',
dest='do_boot', dest='do_boot',
action='store_true', action='store_true',
help='Deassert the reset line to allow the FPGA to boot') help='Deassert the reset line to allow the FPGA to boot')
@ -593,7 +567,8 @@ if __name__ == "__main__":
default=None, default=None,
help='Read contents of NVCM') help='Read contents of NVCM')
parser.add_argument('--write', parser.add_argument(
'--write',
dest='write_file', dest='write_file',
type=str, type=str,
default=None, default=None,
@ -604,41 +579,41 @@ if __name__ == "__main__":
action='store_true', action='store_true',
help='Proceed even if the chip is not blank') help='Proceed even if the chip is not blank')
parser.add_argument('--secure', parser.add_argument(
'--secure',
dest='set_secure', dest='set_secure',
action='store_true', action='store_true',
help='Set security bits to prevent modification (warning: not reversable!') help='Set security bits to prevent modification (warning: not reversable!')
parser.add_argument('--my-design-is-good-enough', parser.add_argument(
'--my-design-is-good-enough',
dest='good_enough', dest='good_enough',
action='store_true', action='store_true',
help='Enable the dangerous commands --write and --secure') help='Enable the dangerous commands --write and --secure')
args = parser.parse_args() args = parser.parse_args()
if not args.good_enough \ if not args.good_enough \
and (args.write_file or args.set_secure): and (args.write_file or args.set_secure):
print("Are you sure your design is good enough?", file=sys.stderr) print("Are you sure your design is good enough?", file=sys.stderr)
exit(1) exit(1)
# Instantiate a SPI controller, with separately managed CS line # Instantiate a SPI controller, with separately managed CS line
#spi = SpiController() # spi = SpiController()
# Configure the first interface (IF/1) of the FTDI device as a SPI controller # Configure the first interface (IF/1) of the FTDI device as a SPI controller
#spi.configure(args.port) # spi.configure(args.port)
# Get a port to a SPI device w/ /CS on A*BUS3 and SPI mode 0 @ 12MHz # Get a port to a SPI device w/ /CS on A*BUS3 and SPI mode 0 @ 12MHz
# the CS line is not used in this case # the CS line is not used in this case
#dev = spi.get_port(cs=0, freq=12E6, mode=0) # dev = spi.get_port(cs=0, freq=12E6, mode=0)
#reset_pin = 7 # reset_pin = 7
#cs_pin = 4 # cs_pin = 4
# Get GPIO port to manage the CS and RESET pins # Get GPIO port to manage the CS and RESET pins
#gpio = spi.get_gpio() # gpio = spi.get_gpio()
#gpio.set_direction(1 << reset_pin | 1 << cs_pin, 1 << reset_pin | 1 << cs_pin) # gpio.set_direction(1 << reset_pin | 1 << cs_pin, 1 << reset_pin | 1 << cs_pin)
# Enable power to the FPGA, then set both reset and CS pins high # Enable power to the FPGA, then set both reset and CS pins high
@ -647,13 +622,13 @@ if __name__ == "__main__":
# flasher.gpio_set_direction(tp1_pins[pin], False) # flasher.gpio_set_direction(tp1_pins[pin], False)
tp1_pins = { tp1_pins = {
'5v_en' : 7, '5v_en': 7,
'sck' : 10, 'sck': 10,
'mosi' : 11, 'mosi': 11,
'ss' : 12, 'ss': 12,
'miso' : 13, 'miso': 13,
'crst' : 14, 'crst': 14,
'cdne' : 15 'cdne': 15
} }
if args.sleep_flash: if args.sleep_flash:
@ -663,11 +638,11 @@ if __name__ == "__main__":
nvcm.power_on() nvcm.power_on()
# # Turn on ICE40 in CRAM boot mode # # Turn on ICE40 in CRAM boot mode
nvcm.init() or exit(1) nvcm.init()
nvcm.nvcm_enable() or exit(1) nvcm.nvcm_enable()
if args.read_info: if args.read_info:
nvcm.info() or exit(1) nvcm.info()
if args.write_file: if args.write_file:
with open(args.write_file, "rb") as f: with open(args.write_file, "rb") as f:
@ -683,20 +658,20 @@ if __name__ == "__main__":
nvcm.blank_check(0x100) or exit(1) nvcm.blank_check(0x100) or exit(1)
# this is it! # this is it!
nvcm.program(cmds) or exit(1) nvcm.program(cmds)
# update the trim to boot from nvcm # update the trim to boot from nvcm
nvcm.trim_program() or exit(1) nvcm.trim_program()
if args.read_file: if args.read_file:
# read back after writing to the NVCM # read back after writing to the NVCM
nvcm.read_file(args.read_file) or exit(1) nvcm.read_file(args.read_file)
if args.set_secure: if args.set_secure:
nvcm.trim_secure() or exit(1) nvcm.trim_secure()
if args.do_boot: if args.do_boot:
# hold reset low for half a second # hold reset low for half a second
nvcm.enable(1,0) nvcm.enable(True, False)
sleep(0.5) sleep(0.5)
nvcm.enable(1,1) nvcm.enable(True, True)

View File

@ -1,8 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
import usb.core import usb.core # type: ignore
import usb.util import usb.util # type: ignore
import struct import struct
class ice40_flasher: class ice40_flasher:
FLASHER_REQUEST_LED_SET = 0x00, FLASHER_REQUEST_LED_SET = 0x00,
FLASHER_REQUEST_PIN_DIRECTION_SET = 0x10 FLASHER_REQUEST_PIN_DIRECTION_SET = 0x10
@ -15,13 +16,7 @@ class ice40_flasher:
FLASHER_REQUEST_ADC_READ = 0x50 FLASHER_REQUEST_ADC_READ = 0x50
FLASHER_REQUEST_BOOTLOADRE = 0xFF FLASHER_REQUEST_BOOTLOADRE = 0xFF
def __init__(self): def __init__(self) -> None:
# self.dev = None
# for dict in hid.enumerate(USB_VID):
# self.dev = hid.Device(dict['vendor_id'], dict['product_id'])
# if self.dev is None:
# raise IOError("Couldn't find any hid device with vendor id 0x%x" % (USB_VID))
# See: https://github.com/pyusb/pyusb/blob/master/docs/tutorial.rst # See: https://github.com/pyusb/pyusb/blob/master/docs/tutorial.rst
self.dev = usb.core.find(idVendor=0xcafe, idProduct=0x4010) self.dev = usb.core.find(idVendor=0xcafe, idProduct=0x4010)
@ -30,27 +25,22 @@ class ice40_flasher:
self.dev.set_configuration() self.dev.set_configuration()
def close(self): def _write(self, request_id: int, data: bytes) -> None:
"""Close the HID device""" self.dev.ctrl_transfer(0x40, request_id, 0, 0, data)
#self.dev.close()
pass
def _write(self, request_id: int, data: bytes): def _write_bulk(self, request_id: int, data: bytes) -> None:
self.dev.ctrl_transfer(0x40, request_id,0,0,data)
def _write_bulk(self, request_id: int, data: bytes):
msg = bytearray() msg = bytearray()
msg.append(request_id) msg.append(request_id)
msg.extend(data) msg.extend(data)
self.dev.write(0x01, data) self.dev.write(0x01, data)
def _read(self, request_id: int, length: int) -> bytes: def _read(self, request_id: int, length: int) -> bytes:
#ctrl_transfer(self, bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength = None, timeout = None): # ctrl_transfer(self, bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength = None, timeout = None):
# Request type: # Request type:
# bit 7: direction 0:host to device (OUT), 1: device to host (IN) # bit 7: direction 0:host to device (OUT), 1: device to host (IN)
# bits 5-6: type: 0:standard 1:class 2:vendor 3:reserved # bits 5-6: type: 0:standard 1:class 2:vendor 3:reserved
# bits 0-4: recipient: 0:device 1:interface 2:endpoint 3:other # bits 0-4: recipient: 0:device 1:interface 2:endpoint 3:other
ret = self.dev.ctrl_transfer(0xC0, request_id,0,0,length) ret = self.dev.ctrl_transfer(0xC0, request_id, 0, 0, length)
return ret return ret
def gpio_set_direction(self, pin: int, direction: bool) -> None: def gpio_set_direction(self, pin: int, direction: bool) -> None:
@ -61,11 +51,11 @@ class ice40_flasher:
value -- True: Set pin as output, False: set pin as input value -- True: Set pin as output, False: set pin as input
""" """
msg = struct.pack('>II', msg = struct.pack('>II',
(1<<pin), (1 << pin),
((1 if direction else 0)<<pin), ((1 if direction else 0) << pin),
) )
#self._write_bulk(self.FLASHER_REQUEST_PIN_DIRECTION_SET, msg) # self._write_bulk(self.FLASHER_REQUEST_PIN_DIRECTION_SET, msg)
self._write(self.FLASHER_REQUEST_PIN_DIRECTION_SET, msg) self._write(self.FLASHER_REQUEST_PIN_DIRECTION_SET, msg)
def gpio_set_pulls(self, pin: int, pullup: bool, pulldown: bool) -> None: def gpio_set_pulls(self, pin: int, pullup: bool, pulldown: bool) -> None:
@ -78,9 +68,9 @@ class ice40_flasher:
pulldown -- True: Enable pulldown, False: Disable pulldown pulldown -- True: Enable pulldown, False: Disable pulldown
""" """
msg = struct.pack('>III', msg = struct.pack('>III',
(1<<pin), (1 << pin),
((1 if pullup else 0)<<pin), ((1 if pullup else 0) << pin),
((1 if pulldown else 0)<<pin), ((1 if pulldown else 0) << pin),
) )
self._write(self.FLASHER_REQUEST_PULLUPS_SET, msg) self._write(self.FLASHER_REQUEST_PULLUPS_SET, msg)
@ -101,7 +91,7 @@ class ice40_flasher:
def gpio_get_all(self) -> int: def gpio_get_all(self) -> int:
"""Read the input levels of all GPIO pins""" """Read the input levels of all GPIO pins"""
msg_in = self._read(self.FLASHER_REQUEST_PIN_VALUES_GET,4) msg_in = self._read(self.FLASHER_REQUEST_PIN_VALUES_GET, 4)
[gpio_states] = struct.unpack('>I', msg_in) [gpio_states] = struct.unpack('>I', msg_in)
return gpio_states return gpio_states
@ -112,7 +102,7 @@ class ice40_flasher:
Keyword arguments: Keyword arguments:
pin -- GPIO pin number pin -- GPIO pin number
""" """
gpio_states = gpio_get_all() gpio_states = self.gpio_get_all()
return ((gpio_states >> pin) & 0x01) == 0x01 return ((gpio_states >> pin) & 0x01) == 0x01
@ -138,12 +128,12 @@ class ice40_flasher:
msg = bytearray() msg = bytearray()
msg.extend(header) msg.extend(header)
self._write(self.FLASHER_REQUEST_SPI_PINS_SET,msg) self._write(self.FLASHER_REQUEST_SPI_PINS_SET, msg)
def spi_bitbang( def spi_bitbang(
self, self,
buf: bytearray, buf: bytes,
toggle_cs: bool = True) -> bytearray: toggle_cs: bool = True) -> bytes:
"""Bitbang a SPI transfer """Bitbang a SPI transfer
Keyword arguments: Keyword arguments:
@ -153,7 +143,7 @@ class ice40_flasher:
ret = bytearray() ret = bytearray()
max_chunk_size = (1024-8) max_chunk_size = (1024 - 8)
for i in range(0, len(buf), max_chunk_size): for i in range(0, len(buf), max_chunk_size):
chunk = buf[i:i + max_chunk_size] chunk = buf[i:i + max_chunk_size]
ret.extend( ret.extend(
@ -161,13 +151,13 @@ class ice40_flasher:
buf=chunk, buf=chunk,
toggle_cs=toggle_cs)) toggle_cs=toggle_cs))
return ret return bytes(ret)
def spi_bitbang_inner( def spi_bitbang_inner(
self, self,
buf: bytearray, buf: bytes,
bit_count: int = -1, bit_count: int = -1,
toggle_cs: bool = True) -> bytearray: toggle_cs: bool = True) -> bytes:
"""Bitbang a SPI transfer using the specificed GPIO pins """Bitbang a SPI transfer using the specificed GPIO pins
Note that this command does not handle setting a CS pin, that must be accomplished Note that this command does not handle setting a CS pin, that must be accomplished
@ -181,16 +171,16 @@ class ice40_flasher:
if bit_count == -1: if bit_count == -1:
bit_count = len(buf) * 8 bit_count = len(buf) * 8
byte_length = (bit_count+7)//8 byte_length = (bit_count + 7) // 8
if byte_length > (1024-8): if byte_length > (1024 - 8):
print('Message too large, bit_count:{:}'.format(bit_count)) print('Message too large, bit_count:{:}'.format(bit_count))
exit(1) exit(1)
if byte_length != len(buf): if byte_length != len(buf):
print( print(
'Bit count size mismatch, bit_count:{:} len(buf):{:}'.format(bit_count), 'Bit count size mismatch, bit_count:{:} len(buf):{:}'.format(
len(buf) * 8) bit_count, len(buf) * 8))
exit(1) exit(1)
header = struct.pack('>I', header = struct.pack('>I',
@ -200,43 +190,30 @@ class ice40_flasher:
msg.extend(buf) msg.extend(buf)
if toggle_cs: if toggle_cs:
self._write(self.FLASHER_REQUEST_SPI_BITBANG_CS,msg) self._write(self.FLASHER_REQUEST_SPI_BITBANG_CS, msg)
msg_in = self._read(self.FLASHER_REQUEST_SPI_BITBANG_CS, byte_length) msg_in = self._read(
self.FLASHER_REQUEST_SPI_BITBANG_CS,
byte_length)
else: else:
self._write(self.FLASHER_REQUEST_SPI_BITBANG_NO_CS,msg) self._write(self.FLASHER_REQUEST_SPI_BITBANG_NO_CS, msg)
msg_in = self._read(self.FLASHER_REQUEST_SPI_BITBANG_NO_CS, byte_length) msg_in = self._read(
self.FLASHER_REQUEST_SPI_BITBANG_NO_CS,
byte_length)
return msg_in return msg_in
def adc_read_all(self) -> list[float]: def adc_read_all(self) -> tuple[float, float, float]:
"""Read the voltage values of ADC 0, 1, and 2 """Read the voltage values of ADC 0, 1, and 2
The firmware will read the values for each input multiple times, and return averaged values for each input. The firmware will read the values for each input multiple times, and return averaged values for each input.
""" """
msg_in = self._read(self.FLASHER_REQUEST_ADC_READ,3*4) msg_in = self._read(self.FLASHER_REQUEST_ADC_READ, 3 * 4)
[ch0, ch1, ch2] = struct.unpack('>III', msg_in) [ch0, ch1, ch2] = struct.unpack('>III', msg_in)
return ch0/1000000, ch1/1000000, ch2/1000000 return ch0 / 1000000, ch1 / 1000000, ch2 / 1000000
if __name__ == '__main__': if __name__ == '__main__':
flasher = ice40_flasher() flasher = ice40_flasher()
print(flasher.gpio_get_all()) print(flasher.gpio_get_all())
print(flasher.adc_read_all()) print(flasher.adc_read_all())
# for pin in range(10,13):
# flasher.gpio_set_direction(pin, True)
# while True:
# for pin in range(10,13):
# flasher.gpio_put(pin, True)
# flasher.gpio_put(pin, False)
flasher.gpio_set_direction(10, True)
flasher.gpio_set_direction(11, True)
flasher.gpio_set_direction(13, False)
buf = [0x01,0x02,0x03, 0xFE]
while True:
flasher.spi_bitbang_inner(sck_pin=10, mosi_pin=11, miso_pin=13, buf=buf)