mirror of
https://github.com/tillitis/tillitis-key1.git
synced 2024-12-24 06:59:24 -05:00
Add type annotations, lint for pep8
This commit is contained in:
parent
ac174afb8f
commit
84d020e3c0
8
hw/production_test/Makefile
Normal file
8
hw/production_test/Makefile
Normal 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
|
551
hw/production_test/icenvcm.py
Normal file → Executable file
551
hw/production_test/icenvcm.py
Normal file → Executable file
@ -20,15 +20,10 @@
|
||||
#
|
||||
|
||||
import usb_test
|
||||
from binascii import unhexlify, hexlify
|
||||
import sys
|
||||
from time import sleep
|
||||
import re
|
||||
import os
|
||||
|
||||
def die(s):
|
||||
print(s, file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
class Nvcm():
|
||||
# todo: add expected bitstream sizes
|
||||
@ -48,7 +43,7 @@ class Nvcm():
|
||||
0x21: "ICE40UP3K",
|
||||
}
|
||||
|
||||
def __init__(self, pins, debug=False):
|
||||
def __init__(self, pins: dict, debug: bool = False) -> None:
|
||||
self.pins = pins
|
||||
self.debug = debug
|
||||
|
||||
@ -67,53 +62,53 @@ class Nvcm():
|
||||
self.flasher.gpio_set_direction(pins['cdne'], False)
|
||||
|
||||
self.flasher.spi_pins_set(
|
||||
pins['sck'],
|
||||
pins['ss'],
|
||||
pins['mosi'],
|
||||
pins['miso']
|
||||
)
|
||||
pins['sck'],
|
||||
pins['ss'],
|
||||
pins['mosi'],
|
||||
pins['miso']
|
||||
)
|
||||
|
||||
def power_on(self):
|
||||
def power_on(self) -> None:
|
||||
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)
|
||||
|
||||
def enable(self,cs,reset=1):
|
||||
#gpio.write(cs << cs_pin | reset << reset_pin)
|
||||
def enable(self, cs: bool, reset: bool = True) -> None:
|
||||
# gpio.write(cs << cs_pin | reset << reset_pin)
|
||||
self.flasher.gpio_put(self.pins['ss'], cs)
|
||||
self.flasher.gpio_put(self.pins['crst'], reset)
|
||||
|
||||
def sendhex(self,s):
|
||||
if self.debug and not s == "0500": # supress status check messages
|
||||
def sendhex(self, s: str) -> int:
|
||||
if self.debug and not s == "0500": # supress status check messages
|
||||
print("TX", 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)
|
||||
|
||||
|
||||
if self.debug and not s == "0500":
|
||||
print("RX", b.hex())
|
||||
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":
|
||||
print("TX", 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)
|
||||
|
||||
|
||||
if self.debug and not s == "0500":
|
||||
print("RX", b.hex())
|
||||
return b
|
||||
|
||||
def delay(self,count: int):
|
||||
def delay(self, count: int) -> None:
|
||||
# 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)
|
||||
|
||||
def tck(self,count: int):
|
||||
def tck(self, count: int) -> None:
|
||||
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)
|
||||
|
||||
def init(self):
|
||||
def init(self) -> None:
|
||||
if self.debug:
|
||||
print("init")
|
||||
self.enable(1, 1)
|
||||
self.enable(1, 0) # reset high
|
||||
self.enable(True, True)
|
||||
self.enable(True, False) # reset high
|
||||
sleep(0.15)
|
||||
|
||||
self.enable(0, 0) # enable and reset high
|
||||
sleep(0.12)
|
||||
self.enable(0, 1) # enable low, reset high
|
||||
sleep(0.12)
|
||||
self.enable(1, 1) # enable and reset low
|
||||
sleep(0.12)
|
||||
return True
|
||||
|
||||
def status_wait(self,count=1000):
|
||||
for i in range(0,count):
|
||||
self.enable(False, False) # enable and reset high
|
||||
sleep(0.12)
|
||||
self.enable(False, True) # enable low, reset high
|
||||
sleep(0.12)
|
||||
self.enable(True, True) # enable and reset low
|
||||
sleep(0.12)
|
||||
|
||||
def status_wait(self, count: int = 1000) -> None:
|
||||
for i in range(0, count):
|
||||
self.tck(5000)
|
||||
ret = self.sendhex_cs("0500")
|
||||
x = int.from_bytes(ret, byteorder='big')
|
||||
|
||||
#print("x=%04x" %(x))
|
||||
|
||||
|
||||
# print("x=%04x" %(x))
|
||||
|
||||
if (x & 0x00c1) == 0:
|
||||
return True
|
||||
|
||||
print("status failed to clear", file=sys.stdout)
|
||||
return False
|
||||
return
|
||||
|
||||
def command(self,cmd):
|
||||
raise Exception("status failed to clear")
|
||||
|
||||
def command(self, cmd: str) -> None:
|
||||
self.sendhex_cs(cmd)
|
||||
if not self.status_wait():
|
||||
return False
|
||||
self.status_wait()
|
||||
self.tck(8)
|
||||
return True
|
||||
|
||||
def pgm_enable(self):
|
||||
return self.command("06")
|
||||
def pgm_enable(self) -> None:
|
||||
self.command("06")
|
||||
|
||||
def pgm_disable(self):
|
||||
return self.command("04")
|
||||
def pgm_disable(self) -> None:
|
||||
self.command("04")
|
||||
|
||||
def enable_access(self):
|
||||
def enable_access(self) -> None:
|
||||
# ! Shift in Access-NVCM instruction;
|
||||
# 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"""
|
||||
# enable(0)
|
||||
# sendhex("%02x%06x" % (cmd, address))
|
||||
@ -177,177 +168,158 @@ class Nvcm():
|
||||
# for i in range(0,length):
|
||||
# x = x << 8 | sendhex("00")
|
||||
# enable(1)
|
||||
|
||||
|
||||
msg = ''
|
||||
msg += ("%02x%06x" % (cmd, address))
|
||||
msg += ("00" * 9) # dummy bytes
|
||||
msg += ("00" * length) # read
|
||||
msg += ("00" * 9) # dummy bytes
|
||||
msg += ("00" * length) # read
|
||||
ret = self.sendhex_cs(msg)
|
||||
|
||||
|
||||
x = 0
|
||||
for i in range(0,length):
|
||||
x = x << 8 | ret[i + 4+9]
|
||||
for i in range(0, length):
|
||||
x = x << 8 | ret[i + 4 + 9]
|
||||
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"""
|
||||
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)
|
||||
|
||||
if not self.status_wait():
|
||||
print("WRITE FAILED: cmd=%02x address=%06x data=%s" % (cmd, address, data.hex()), file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
self.status_wait()
|
||||
except Exception as e:
|
||||
raise Exception("WRITE FAILED: cmd=%02x address=%06x data=%s" %
|
||||
(cmd, address, data))
|
||||
|
||||
self.tck(8)
|
||||
return True
|
||||
|
||||
def bank_select(self,bank):
|
||||
return self.write(cmd=0x83, address=0x000025, data="%02x" % (bank))
|
||||
def bank_select(self, bank: int) -> None:
|
||||
self.write(cmd=0x83, address=0x000025, data="%02x" % (bank))
|
||||
|
||||
def select_nvcm(self):
|
||||
def select_nvcm(self) -> None:
|
||||
# ! Shift in Restore Access-NVCM instruction;
|
||||
# SDR 40 TDI(0x00A40000C1);
|
||||
return self.bank_select(0x00)
|
||||
|
||||
def select_trim(self):
|
||||
self.bank_select(0x00)
|
||||
|
||||
def select_trim(self) -> None:
|
||||
# ! Shift in Trim setup-NVCM instruction;
|
||||
# SDR 40 TDI(0x08A40000C1);
|
||||
return self.bank_select(0x10)
|
||||
|
||||
def select_sig(self):
|
||||
self.bank_select(0x10)
|
||||
|
||||
def select_sig(self) -> None:
|
||||
# ! Shift in Access Silicon Signature instruction;
|
||||
# IDInstruction[1] = 0x04A40000C1;
|
||||
# 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;
|
||||
# SMCInstruction[1] = 0x70807E99557E;
|
||||
if not self.enable_access():
|
||||
return
|
||||
|
||||
self.enable_access()
|
||||
|
||||
# ! Shift in READ_RF(0x84) instruction;
|
||||
# SDR 104 TDI(0x00000000000000000004000021);
|
||||
x = self.read(cmd=0x84, address=0x000020, length=8)
|
||||
self.tck(8)
|
||||
|
||||
#print("FSM Trim Register %x" % (x))
|
||||
|
||||
|
||||
# print("FSM Trim Register %x" % (x))
|
||||
|
||||
self.select_nvcm()
|
||||
return x
|
||||
|
||||
def write_trim(self,data):
|
||||
|
||||
def write_trim(self, data: str) -> None:
|
||||
# ! Setup Programming Parameter in Trim Registers;
|
||||
# ! Shift in Trim setup-NVCM instruction;
|
||||
# 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:
|
||||
print("enable")
|
||||
# ! Shift in Access-NVCM instruction;
|
||||
# SMCInstruction[1] = 0x70807E99557E;
|
||||
if not self.enable_access():
|
||||
return
|
||||
|
||||
self.enable_access()
|
||||
|
||||
# ! Setup Reading Parameter in Trim Registers;
|
||||
# ! Shift in Trim setup-NVCM instruction;
|
||||
# TRIMInstruction[1] = 0x000000230000000004000041;
|
||||
if self.debug:
|
||||
print("setup_nvcm")
|
||||
return self.write_trim("00000000c4000000")
|
||||
|
||||
def enable_trim(self):
|
||||
self.write_trim("00000000c4000000")
|
||||
|
||||
def enable_trim(self) -> None:
|
||||
# ! Setup Programming Parameter in Trim Registers;
|
||||
# ! Shift in Trim setup-NVCM instruction;
|
||||
# TRIMInstruction[1] = 0x000000430F4FA80004000041;
|
||||
return self.write_trim("0015f2f0c2000000")
|
||||
|
||||
def disable(self):
|
||||
if not self.select_nvcm():
|
||||
return
|
||||
|
||||
self.reset(1)
|
||||
self.tck(8)
|
||||
self.reset(0)
|
||||
self.tck(8)
|
||||
self.write_trim("0015f2f0c2000000")
|
||||
|
||||
def trim_blank_check(self) -> bool:
|
||||
print("NVCM Trim_Parameter_OTP blank check")
|
||||
|
||||
self.select_trim()
|
||||
|
||||
def trim_blank_check(self):
|
||||
print ("NVCM Trim_Parameter_OTP blank check");
|
||||
|
||||
if not self.select_trim():
|
||||
return
|
||||
|
||||
x = self.read(0x000020, 1)
|
||||
self.select_nvcm()
|
||||
|
||||
|
||||
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
|
||||
|
||||
def blank_check(self,total_fuse):
|
||||
|
||||
def blank_check(self, total_fuse: int) -> bool:
|
||||
self.select_nvcm()
|
||||
|
||||
|
||||
status = True
|
||||
print ("NVCM main memory blank check");
|
||||
print("NVCM main memory blank check")
|
||||
contents = self.read_bytes(0x000000, total_fuse)
|
||||
|
||||
for i in range(0,total_fuse):
|
||||
|
||||
for i in range(0, total_fuse):
|
||||
x = contents[i]
|
||||
if debug:
|
||||
if self.debug:
|
||||
print("%08x: %02x" % (i, x))
|
||||
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
|
||||
#break
|
||||
|
||||
|
||||
self.select_nvcm()
|
||||
return status
|
||||
|
||||
def program(self,rows):
|
||||
def program(self, rows: list[str]) -> None:
|
||||
print("NVCM Program main memory")
|
||||
|
||||
self.select_nvcm()
|
||||
|
||||
if not self.enable_trim():
|
||||
return False
|
||||
|
||||
print ("NVCM Program main memory")
|
||||
|
||||
if not self.pgm_enable():
|
||||
return False
|
||||
|
||||
|
||||
self.enable_trim()
|
||||
|
||||
self.pgm_enable()
|
||||
|
||||
status = True
|
||||
|
||||
|
||||
i = 0
|
||||
for row in rows:
|
||||
if i % 1024 == 0:
|
||||
print("%6d / %6d bytes" % (i, len(rows) * 8))
|
||||
i += 8
|
||||
if not self.command(row):
|
||||
status = False
|
||||
break
|
||||
|
||||
self.pgm_disable()
|
||||
|
||||
if not status:
|
||||
print("PROGRAMMING FAILED", file=sys.stderr)
|
||||
return status
|
||||
try:
|
||||
self.command(row)
|
||||
except Exception as e:
|
||||
raise Exception("programming failed, row:", row)
|
||||
|
||||
self.pgm_disable()
|
||||
|
||||
def write_trim_pages(self, lock_bits: str) -> None:
|
||||
self.select_nvcm()
|
||||
|
||||
self.enable_trim()
|
||||
|
||||
self.select_trim()
|
||||
|
||||
self.pgm_enable()
|
||||
|
||||
def write_trim_pages(self,lock_bits):
|
||||
if not self.select_nvcm():
|
||||
die("select trim failed")
|
||||
|
||||
if not self.enable_trim():
|
||||
die("write trim command failed")
|
||||
|
||||
if not self.select_trim():
|
||||
die("select trim failed")
|
||||
|
||||
if not self.pgm_enable():
|
||||
die("write enable failed")
|
||||
|
||||
# ! Program Security Bit row 1;
|
||||
# ! Shift in PAGEPGM instruction;
|
||||
# SDR 96 TDI(0x000000008000000C04000040);
|
||||
@ -357,72 +329,68 @@ class Nvcm():
|
||||
# SDR 96 TDI(0x000000008000000C05000040);
|
||||
# ! Program Security Bit row 4;
|
||||
# SDR 96 TDI(0x00000000800000C07000040);
|
||||
if not self.write(0x000020, lock_bits):
|
||||
die("trim write 0x20 failed")
|
||||
if not self.write(0x000060, lock_bits):
|
||||
die("trim write 0x60 failed")
|
||||
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.write(0x000020, lock_bits)
|
||||
self.write(0x000060, lock_bits)
|
||||
self.write(0x0000a0, lock_bits)
|
||||
self.write(0x0000e0, lock_bits)
|
||||
|
||||
self.pgm_disable()
|
||||
|
||||
|
||||
# verify a read back
|
||||
x = self.read(0x000020, 8)
|
||||
|
||||
self.select_nvcm()
|
||||
|
||||
lock_bits = int(lock_bits,16)
|
||||
if x & lock_bits != lock_bits:
|
||||
die("Failed to write trim lock bits: %016x != expected %016x" % (x,lock_bits))
|
||||
|
||||
print("New state %016x" % (x))
|
||||
return True
|
||||
|
||||
def trim_secure(self):
|
||||
print ("NVCM Secure")
|
||||
self.select_nvcm()
|
||||
|
||||
lock_bits_int = int(lock_bits, 16)
|
||||
if x & lock_bits_int != lock_bits_int:
|
||||
raise Exception(
|
||||
"Failed to write trim lock bits: %016x != expected %016x" %
|
||||
(x, lock_bits_int))
|
||||
|
||||
print("New state %016x" % (x))
|
||||
|
||||
def trim_secure(self) -> None:
|
||||
print("NVCM Secure")
|
||||
trim = self.read_trim()
|
||||
if (trim >> 60) & 0x3 != 0:
|
||||
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):
|
||||
print ("NVCM Program Trim_Parameter_OTP");
|
||||
return self.write_trim_pages("0015f2f1c4000000")
|
||||
def trim_program(self) -> None:
|
||||
print("NVCM Program Trim_Parameter_OTP")
|
||||
self.write_trim_pages("0015f2f1c4000000")
|
||||
|
||||
def info(self):
|
||||
def info(self) -> None:
|
||||
self.select_sig()
|
||||
sig1 = self.read(0x000000, 8)
|
||||
|
||||
|
||||
self.select_sig()
|
||||
sig2 = self.read(0x000008, 8)
|
||||
|
||||
|
||||
# have to switch back to nvcm bank before switching to trim?
|
||||
self.select_nvcm()
|
||||
trim = self.read_trim()
|
||||
|
||||
|
||||
self.select_nvcm()
|
||||
|
||||
|
||||
self.select_trim()
|
||||
trim0 = self.read(0x000020, 8)
|
||||
|
||||
|
||||
self.select_trim()
|
||||
trim1 = self.read(0x000060, 8)
|
||||
|
||||
|
||||
self.select_trim()
|
||||
trim2 = self.read(0x0000a0, 8)
|
||||
|
||||
|
||||
self.select_trim()
|
||||
trim3 = self.read(0x0000e0, 8)
|
||||
|
||||
|
||||
self.select_nvcm()
|
||||
|
||||
|
||||
secured = ((trim >> 60) & 0x3)
|
||||
device_id = (sig1 >> 56) & 0xFF
|
||||
|
||||
|
||||
print("Device: %s (%02x) secure=%d" % (
|
||||
self.id_table.get(device_id, "Unknown"),
|
||||
device_id,
|
||||
@ -430,28 +398,25 @@ class Nvcm():
|
||||
))
|
||||
print("Sig 0: %016x" % (sig1))
|
||||
print("Sig 1: %016x" % (sig2))
|
||||
|
||||
|
||||
|
||||
print("TrimRF: %016x" % (trim))
|
||||
print("Trim 0: %016x" % (trim0))
|
||||
print("Trim 1: %016x" % (trim1))
|
||||
print("Trim 2: %016x" % (trim2))
|
||||
print("Trim 3: %016x" % (trim3))
|
||||
|
||||
return True
|
||||
|
||||
def read_file(self,filename):
|
||||
def read_file(self, filename: str) -> None:
|
||||
self.select_nvcm()
|
||||
|
||||
|
||||
total_fuse = 104090
|
||||
|
||||
|
||||
contents = b''
|
||||
|
||||
for offset in range(0,total_fuse,8):
|
||||
|
||||
for offset in range(0, total_fuse, 8):
|
||||
if offset % 1024 == 0:
|
||||
print("%6d / %6d bytes" % (offset, total_fuse))
|
||||
contents += self.read_bytes(offset, 8)
|
||||
|
||||
|
||||
if filename == '-':
|
||||
with os.fdopen(sys.stdout.fileno(), "wb", closefd=False) as f:
|
||||
f.write(contents)
|
||||
@ -466,15 +431,14 @@ class Nvcm():
|
||||
# bistream to NVCM command conversion is based on majbthrd's work in
|
||||
# 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
|
||||
for origin in range(0,len(bitstream)):
|
||||
if bitstream[origin:origin+4] == bytes.fromhex('7EAA997E'):
|
||||
for origin in range(0, len(bitstream)):
|
||||
if bitstream[origin:origin + 4] == bytes.fromhex('7EAA997E'):
|
||||
break
|
||||
|
||||
if origin == len(bitstream):
|
||||
print("Preamble not found", file=sys.stderr)
|
||||
return False
|
||||
raise Exception("Preamble not found")
|
||||
|
||||
print("Found preamable at %08x" % (origin), file=sys.stderr)
|
||||
|
||||
@ -486,7 +450,7 @@ def bitstream2nvcm(bitstream):
|
||||
rows = []
|
||||
|
||||
for pos in range(origin, len(bitstream), 8):
|
||||
row = bitstream[pos:pos+8]
|
||||
row = bitstream[pos:pos + 8]
|
||||
|
||||
# pad out to 8-bytes
|
||||
row += b'\0' * (8 - len(row))
|
||||
@ -498,11 +462,13 @@ def bitstream2nvcm(bitstream):
|
||||
# NVCM addressing is very weird
|
||||
addr = pos - origin
|
||||
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
|
||||
|
||||
def sleep_flash(pins):
|
||||
|
||||
def sleep_flash(pins: dict) -> None:
|
||||
""" Put the SPI bootloader flash in deep sleep mode"""
|
||||
flasher = usb_test.ice40_flasher()
|
||||
|
||||
# Disable board power
|
||||
@ -523,37 +489,41 @@ def sleep_flash(pins):
|
||||
flasher.gpio_set_direction(pins['miso'], True)
|
||||
|
||||
flasher.spi_pins_set(
|
||||
pins['sck'],
|
||||
pins['ss'],
|
||||
pins['miso'],
|
||||
pins['mosi']
|
||||
)
|
||||
pins['sck'],
|
||||
pins['ss'],
|
||||
pins['miso'],
|
||||
pins['mosi']
|
||||
)
|
||||
|
||||
flasher.spi_bitbang([0xAB])
|
||||
flasher.spi_bitbang(bytes([0xAB]))
|
||||
|
||||
# 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]))
|
||||
assert(data == bytes([0xff, 0xef, 0x40]))
|
||||
print('flash ID while awake:', ' '.join(
|
||||
['{: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
|
||||
flasher.spi_bitbang([0, 0xb9])
|
||||
# Test that the flash will ignore a sleep command that doesn't start on
|
||||
# the first byte
|
||||
flasher.spi_bitbang(bytes([0, 0xb9]))
|
||||
|
||||
# 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]))
|
||||
assert(data == bytes([0xff, 0xef, 0x40]))
|
||||
print('flash ID while awake:', ' '.join(
|
||||
['{:02x}'.format(b) for b in data]))
|
||||
assert (data == bytes([0xff, 0xef, 0x40]))
|
||||
|
||||
# put the flash to sleep
|
||||
flasher.spi_bitbang([0xb9])
|
||||
flasher.spi_bitbang(bytes([0xb9]))
|
||||
|
||||
# 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]))
|
||||
assert(data == bytes([0xff, 0xff, 0xff]))
|
||||
print('flash ID while asleep:', ' '.join(
|
||||
['{:02x}'.format(b) for b in data]))
|
||||
assert (data == bytes([0xff, 0xff, 0xff]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@ -562,83 +532,88 @@ if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument( '--port',
|
||||
type=str,
|
||||
default='ftdi://::/1',
|
||||
help='FTDI port of the form ftdi://::/1')
|
||||
parser.add_argument('--port',
|
||||
type=str,
|
||||
default='ftdi://::/1',
|
||||
help='FTDI port of the form ftdi://::/1')
|
||||
|
||||
parser.add_argument( '-v', '--verbose',
|
||||
dest='verbose',
|
||||
action='store_true',
|
||||
help='Show debug information and serial read/writes')
|
||||
parser.add_argument('-v', '--verbose',
|
||||
dest='verbose',
|
||||
action='store_true',
|
||||
help='Show debug information and serial read/writes')
|
||||
|
||||
parser.add_argument('-f', '--sleep_flash',
|
||||
parser.add_argument(
|
||||
'-f',
|
||||
'--sleep_flash',
|
||||
dest='sleep_flash',
|
||||
action='store_true',
|
||||
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',
|
||||
action='store_true',
|
||||
help='Deassert the reset line to allow the FPGA to boot')
|
||||
|
||||
parser.add_argument('-i', '--info',
|
||||
dest='read_info',
|
||||
action='store_true',
|
||||
help='Read chip ID, trim and other info')
|
||||
dest='read_info',
|
||||
action='store_true',
|
||||
help='Read chip ID, trim and other info')
|
||||
|
||||
parser.add_argument('--read',
|
||||
dest='read_file',
|
||||
type=str,
|
||||
default=None,
|
||||
help='Read contents of NVCM')
|
||||
dest='read_file',
|
||||
type=str,
|
||||
default=None,
|
||||
help='Read contents of NVCM')
|
||||
|
||||
parser.add_argument('--write',
|
||||
parser.add_argument(
|
||||
'--write',
|
||||
dest='write_file',
|
||||
type=str,
|
||||
default=None,
|
||||
help='bitstream file to write to NVCM (warning: not reversable!)')
|
||||
|
||||
parser.add_argument('--ignore-blank',
|
||||
dest='ignore_blank',
|
||||
action='store_true',
|
||||
help='Proceed even if the chip is not blank')
|
||||
dest='ignore_blank',
|
||||
action='store_true',
|
||||
help='Proceed even if the chip is not blank')
|
||||
|
||||
parser.add_argument('--secure',
|
||||
parser.add_argument(
|
||||
'--secure',
|
||||
dest='set_secure',
|
||||
action='store_true',
|
||||
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',
|
||||
action='store_true',
|
||||
help='Enable the dangerous commands --write and --secure')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
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)
|
||||
exit(1)
|
||||
|
||||
# Instantiate a SPI controller, with separately managed CS line
|
||||
#spi = SpiController()
|
||||
|
||||
# Configure the first interface (IF/1) of the FTDI device as a SPI controller
|
||||
#spi.configure(args.port)
|
||||
# spi = SpiController()
|
||||
|
||||
# Configure the first interface (IF/1) of the FTDI device as a SPI controller
|
||||
# spi.configure(args.port)
|
||||
|
||||
|
||||
# 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
|
||||
#dev = spi.get_port(cs=0, freq=12E6, mode=0)
|
||||
|
||||
#reset_pin = 7
|
||||
#cs_pin = 4
|
||||
|
||||
# dev = spi.get_port(cs=0, freq=12E6, mode=0)
|
||||
|
||||
# reset_pin = 7
|
||||
# cs_pin = 4
|
||||
|
||||
# Get GPIO port to manage the CS and RESET pins
|
||||
#gpio = spi.get_gpio()
|
||||
#gpio.set_direction(1 << reset_pin | 1 << cs_pin, 1 << reset_pin | 1 << cs_pin)
|
||||
# gpio = spi.get_gpio()
|
||||
# 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
|
||||
|
||||
@ -647,13 +622,13 @@ if __name__ == "__main__":
|
||||
# flasher.gpio_set_direction(tp1_pins[pin], False)
|
||||
|
||||
tp1_pins = {
|
||||
'5v_en' : 7,
|
||||
'sck' : 10,
|
||||
'mosi' : 11,
|
||||
'ss' : 12,
|
||||
'miso' : 13,
|
||||
'crst' : 14,
|
||||
'cdne' : 15
|
||||
'5v_en': 7,
|
||||
'sck': 10,
|
||||
'mosi': 11,
|
||||
'ss': 12,
|
||||
'miso': 13,
|
||||
'crst': 14,
|
||||
'cdne': 15
|
||||
}
|
||||
|
||||
if args.sleep_flash:
|
||||
@ -663,11 +638,11 @@ if __name__ == "__main__":
|
||||
nvcm.power_on()
|
||||
|
||||
# # Turn on ICE40 in CRAM boot mode
|
||||
nvcm.init() or exit(1)
|
||||
nvcm.nvcm_enable() or exit(1)
|
||||
nvcm.init()
|
||||
nvcm.nvcm_enable()
|
||||
|
||||
if args.read_info:
|
||||
nvcm.info() or exit(1)
|
||||
nvcm.info()
|
||||
|
||||
if args.write_file:
|
||||
with open(args.write_file, "rb") as f:
|
||||
@ -683,20 +658,20 @@ if __name__ == "__main__":
|
||||
nvcm.blank_check(0x100) or exit(1)
|
||||
|
||||
# this is it!
|
||||
nvcm.program(cmds) or exit(1)
|
||||
nvcm.program(cmds)
|
||||
|
||||
# update the trim to boot from nvcm
|
||||
nvcm.trim_program() or exit(1)
|
||||
nvcm.trim_program()
|
||||
|
||||
if args.read_file:
|
||||
# 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:
|
||||
nvcm.trim_secure() or exit(1)
|
||||
nvcm.trim_secure()
|
||||
|
||||
if args.do_boot:
|
||||
# hold reset low for half a second
|
||||
nvcm.enable(1,0)
|
||||
nvcm.enable(True, False)
|
||||
sleep(0.5)
|
||||
nvcm.enable(1,1)
|
||||
nvcm.enable(True, True)
|
||||
|
@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
import usb.core
|
||||
import usb.util
|
||||
import usb.core # type: ignore
|
||||
import usb.util # type: ignore
|
||||
import struct
|
||||
|
||||
|
||||
class ice40_flasher:
|
||||
FLASHER_REQUEST_LED_SET = 0x00,
|
||||
FLASHER_REQUEST_PIN_DIRECTION_SET = 0x10
|
||||
@ -15,42 +16,31 @@ class ice40_flasher:
|
||||
FLASHER_REQUEST_ADC_READ = 0x50
|
||||
FLASHER_REQUEST_BOOTLOADRE = 0xFF
|
||||
|
||||
def __init__(self):
|
||||
# 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))
|
||||
|
||||
def __init__(self) -> None:
|
||||
# See: https://github.com/pyusb/pyusb/blob/master/docs/tutorial.rst
|
||||
self.dev = usb.core.find(idVendor=0xcafe, idProduct=0x4010)
|
||||
|
||||
|
||||
if self.dev is None:
|
||||
raise ValueError('Device not found')
|
||||
|
||||
|
||||
self.dev.set_configuration()
|
||||
|
||||
def close(self):
|
||||
"""Close the HID device"""
|
||||
#self.dev.close()
|
||||
pass
|
||||
def _write(self, request_id: int, data: bytes) -> None:
|
||||
self.dev.ctrl_transfer(0x40, request_id, 0, 0, data)
|
||||
|
||||
def _write(self, request_id: int, data: bytes):
|
||||
self.dev.ctrl_transfer(0x40, request_id,0,0,data)
|
||||
|
||||
def _write_bulk(self, request_id: int, data: bytes):
|
||||
def _write_bulk(self, request_id: int, data: bytes) -> None:
|
||||
msg = bytearray()
|
||||
msg.append(request_id)
|
||||
msg.extend(data)
|
||||
self.dev.write(0x01, data)
|
||||
|
||||
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:
|
||||
# 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 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
|
||||
|
||||
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
|
||||
"""
|
||||
msg = struct.pack('>II',
|
||||
(1<<pin),
|
||||
((1 if direction else 0)<<pin),
|
||||
)
|
||||
(1 << 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)
|
||||
|
||||
def gpio_set_pulls(self, pin: int, pullup: bool, pulldown: bool) -> None:
|
||||
@ -78,10 +68,10 @@ class ice40_flasher:
|
||||
pulldown -- True: Enable pulldown, False: Disable pulldown
|
||||
"""
|
||||
msg = struct.pack('>III',
|
||||
(1<<pin),
|
||||
((1 if pullup else 0)<<pin),
|
||||
((1 if pulldown else 0)<<pin),
|
||||
)
|
||||
(1 << pin),
|
||||
((1 if pullup else 0) << pin),
|
||||
((1 if pulldown else 0) << pin),
|
||||
)
|
||||
|
||||
self._write(self.FLASHER_REQUEST_PULLUPS_SET, msg)
|
||||
|
||||
@ -93,15 +83,15 @@ class ice40_flasher:
|
||||
val -- True: High, False: Low
|
||||
"""
|
||||
msg = struct.pack('>II',
|
||||
1 << pin,
|
||||
(1 if val else 0) << pin,
|
||||
)
|
||||
1 << pin,
|
||||
(1 if val else 0) << pin,
|
||||
)
|
||||
|
||||
self._write(self.FLASHER_REQUEST_PIN_VALUES_SET, msg)
|
||||
|
||||
def gpio_get_all(self) -> int:
|
||||
"""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)
|
||||
|
||||
return gpio_states
|
||||
@ -112,7 +102,7 @@ class ice40_flasher:
|
||||
Keyword arguments:
|
||||
pin -- GPIO pin number
|
||||
"""
|
||||
gpio_states = gpio_get_all()
|
||||
gpio_states = self.gpio_get_all()
|
||||
|
||||
return ((gpio_states >> pin) & 0x01) == 0x01
|
||||
|
||||
@ -138,12 +128,12 @@ class ice40_flasher:
|
||||
msg = bytearray()
|
||||
msg.extend(header)
|
||||
|
||||
self._write(self.FLASHER_REQUEST_SPI_PINS_SET,msg)
|
||||
self._write(self.FLASHER_REQUEST_SPI_PINS_SET, msg)
|
||||
|
||||
def spi_bitbang(
|
||||
self,
|
||||
buf: bytearray,
|
||||
toggle_cs: bool = True) -> bytearray:
|
||||
buf: bytes,
|
||||
toggle_cs: bool = True) -> bytes:
|
||||
"""Bitbang a SPI transfer
|
||||
|
||||
Keyword arguments:
|
||||
@ -153,7 +143,7 @@ class ice40_flasher:
|
||||
|
||||
ret = bytearray()
|
||||
|
||||
max_chunk_size = (1024-8)
|
||||
max_chunk_size = (1024 - 8)
|
||||
for i in range(0, len(buf), max_chunk_size):
|
||||
chunk = buf[i:i + max_chunk_size]
|
||||
ret.extend(
|
||||
@ -161,13 +151,13 @@ class ice40_flasher:
|
||||
buf=chunk,
|
||||
toggle_cs=toggle_cs))
|
||||
|
||||
return ret
|
||||
return bytes(ret)
|
||||
|
||||
def spi_bitbang_inner(
|
||||
self,
|
||||
buf: bytearray,
|
||||
buf: bytes,
|
||||
bit_count: int = -1,
|
||||
toggle_cs: bool = True) -> bytearray:
|
||||
toggle_cs: bool = True) -> bytes:
|
||||
"""Bitbang a SPI transfer using the specificed GPIO pins
|
||||
|
||||
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:
|
||||
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))
|
||||
exit(1)
|
||||
|
||||
|
||||
if byte_length != len(buf):
|
||||
print(
|
||||
'Bit count size mismatch, bit_count:{:} len(buf):{:}'.format(bit_count),
|
||||
len(buf) * 8)
|
||||
'Bit count size mismatch, bit_count:{:} len(buf):{:}'.format(
|
||||
bit_count, len(buf) * 8))
|
||||
exit(1)
|
||||
|
||||
header = struct.pack('>I',
|
||||
@ -200,43 +190,30 @@ class ice40_flasher:
|
||||
msg.extend(buf)
|
||||
|
||||
if toggle_cs:
|
||||
self._write(self.FLASHER_REQUEST_SPI_BITBANG_CS,msg)
|
||||
msg_in = self._read(self.FLASHER_REQUEST_SPI_BITBANG_CS, byte_length)
|
||||
self._write(self.FLASHER_REQUEST_SPI_BITBANG_CS, msg)
|
||||
msg_in = self._read(
|
||||
self.FLASHER_REQUEST_SPI_BITBANG_CS,
|
||||
byte_length)
|
||||
else:
|
||||
self._write(self.FLASHER_REQUEST_SPI_BITBANG_NO_CS,msg)
|
||||
msg_in = self._read(self.FLASHER_REQUEST_SPI_BITBANG_NO_CS, byte_length)
|
||||
self._write(self.FLASHER_REQUEST_SPI_BITBANG_NO_CS, msg)
|
||||
msg_in = self._read(
|
||||
self.FLASHER_REQUEST_SPI_BITBANG_NO_CS,
|
||||
byte_length)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
return ch0/1000000, ch1/1000000, ch2/1000000
|
||||
return ch0 / 1000000, ch1 / 1000000, ch2 / 1000000
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
flasher = ice40_flasher()
|
||||
print(flasher.gpio_get_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)
|
||||
|
Loading…
Reference in New Issue
Block a user