From ac174afb8ffdcf29573488e39eb73f467f72056d Mon Sep 17 00:00:00 2001 From: Matt Mets Date: Thu, 2 Mar 2023 16:12:38 +0100 Subject: [PATCH] Use streamlined USB interface, make NVCM a class --- hw/production_test/icenvcm.py | 846 +++++++++++++++++---------------- hw/production_test/usb_test.py | 102 ++-- 2 files changed, 489 insertions(+), 459 deletions(-) diff --git a/hw/production_test/icenvcm.py b/hw/production_test/icenvcm.py index 444326a..b2f7493 100644 --- a/hw/production_test/icenvcm.py +++ b/hw/production_test/icenvcm.py @@ -26,394 +26,440 @@ from time import sleep import re import os -debug = False - -# todo: add expected bitstream sizes -nvcm_id_table = { - 0x06: "ICE40LP8K / ICE40HX8K", - 0x07: "ICE40LP4K / ICE40HX4K", - 0x08: "ICE40LP1K / ICE40HX1K", - 0x09: "ICE40LP384", - 0x0E: "ICE40LP1K_SWG16", - 0x0F: "ICE40LP640_SWG16", - 0x10: "ICE5LP1K", - 0x11: "ICE5LP2K", - 0x12: "ICE5LP4K", - 0x14: "ICE40UL1K", - 0x15: "ICE40UL640", - 0x20: "ICE40UP5K", - 0x21: "ICE40UP3K", -} - def die(s): print(s, file=sys.stderr) exit(1) -def enable(cs,reset=1): - #gpio.write(cs << cs_pin | reset << reset_pin) - flasher.gpio_put(tp1_pins['ss'], cs) - flasher.gpio_put(tp1_pins['crst'], reset) +class Nvcm(): + # todo: add expected bitstream sizes + id_table = { + 0x06: "ICE40LP8K / ICE40HX8K", + 0x07: "ICE40LP4K / ICE40HX4K", + 0x08: "ICE40LP1K / ICE40HX1K", + 0x09: "ICE40LP384", + 0x0E: "ICE40LP1K_SWG16", + 0x0F: "ICE40LP640_SWG16", + 0x10: "ICE5LP1K", + 0x11: "ICE5LP2K", + 0x12: "ICE5LP4K", + 0x14: "ICE40UL1K", + 0x15: "ICE40UL640", + 0x20: "ICE40UP5K", + 0x21: "ICE40UP3K", + } -def sendhex(s): - if debug and not s == "0500": - print("TX", s) - x = bytes.fromhex(s) + def __init__(self, pins, debug=False): + self.pins = pins + self.debug = debug - #b = dev.exchange(x, duplex=True, readlen=len(x)) - b = flasher.spi_bitbang(sck_pin=tp1_pins['sck'], mosi_pin=tp1_pins['mosi'], miso_pin=tp1_pins['miso'], buf=x) + self.flasher = usb_test.ice40_flasher() - if debug and not s == "0500": - print("RX", b.hex()) - return int.from_bytes(b, byteorder='big') + self.flasher.gpio_put(self.pins['5v_en'], False) + self.flasher.gpio_put(self.pins['crst'], False) -def delay(count: int): - # run the clock with no CS asserted - #dev.exchange(b'\x00', duplex=True, readlen=count) - sendhex('00' * count) + # Configure pins for talking to ice40 + self.flasher.gpio_set_direction(pins['ss'], True) + self.flasher.gpio_set_direction(pins['mosi'], True) + self.flasher.gpio_set_direction(pins['sck'], True) + self.flasher.gpio_set_direction(pins['miso'], False) + self.flasher.gpio_set_direction(pins['5v_en'], True) + self.flasher.gpio_set_direction(pins['crst'], True) + self.flasher.gpio_set_direction(pins['cdne'], False) -def tck(count: int, dly: int = 0): - delay(count >> 3) - delay(count >> 3) - delay(count >> 3) - delay(count >> 3) - delay(count >> 3) - delay(count >> 3) + self.flasher.spi_pins_set( + pins['sck'], + pins['ss'], + pins['mosi'], + pins['miso'] + ) -def init(): - if debug: - print("init") - enable(1, 1) - enable(1, 0) # reset high - sleep(0.15) + def power_on(self): + self.flasher.gpio_put(self.pins['5v_en'], True) - enable(0, 0) # enable and reset high - sleep(0.12) - enable(0, 1) # enable low, reset high - sleep(0.12) - enable(1, 1) # enable and reset low - sleep(0.12) - return True + def power_off(self): + self.flasher.gpio_put(self.pins['5v_en'], False) -def status_wait(count=1000): - for i in range(0,count): - tck(5000) - enable(0) - x = sendhex("0500") - enable(1) + def enable(self,cs,reset=1): + #gpio.write(cs << cs_pin | reset << reset_pin) + self.flasher.gpio_put(self.pins['ss'], cs) + self.flasher.gpio_put(self.pins['crst'], reset) - #print("x=%04x" %(x)) + def sendhex(self,s): + 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 = 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): + if self.debug and not s == "0500": + print("TX", s) + x = bytes.fromhex(s) + + #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 - if (x & 0x00c1) == 0: - return True + def delay(self,count: int): + # run the clock with no CS asserted + #dev.exchange(b'\x00', duplex=True, readlen=count) + self.sendhex('00' * count) - print("status failed to clear", file=sys.stdout) - return False + def tck(self,count: int): + self.delay(count >> 3) + self.delay(count >> 3) + self.delay(count >> 3) + self.delay(count >> 3) + self.delay(count >> 3) + self.delay(count >> 3) -def nvcm_command(cmd): - enable(0) - sendhex(cmd) - enable(1) - if not status_wait(): - return False - tck(8) - return True + def init(self): + if self.debug: + print("init") + self.enable(1, 1) + self.enable(1, 0) # 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 nvcm_pgm_enable(): - return nvcm_command("06") -def nvcm_pgm_disable(): - return nvcm_command("04") - -def nvcm_enable_access(): - # ! Shift in Access-NVCM instruction; - # SMCInstruction[1] = 0x70807E99557E; - return nvcm_command("7eaa997e010e") - -# Returns a big integer -def nvcm_read(address, length=8, cmd=0x03): - enable(0) - sendhex("%02x%06x" % (cmd, address)) - sendhex("00" * 9) # dummy bytes - x = 0 - for i in range(0,length): - x = x << 8 | sendhex("00") - enable(1) - return x - -# Returns a byte array of the contents -def nvcm_read_bytes(address, length=8): - return nvcm_read(address, length).to_bytes(length, byteorder="big") - -def nvcm_write(address, data, cmd=0x02): - enable(0) - sendhex("%02x%06x" % (cmd, address)) - sendhex(data) - enable(1) - - if not status_wait(): - print("WRITE FAILED: cmd=%02x address=%06x data=%s" % (cmd, address, data.hex()), file=sys.stderr) + def status_wait(self,count=1000): + 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)) + + if (x & 0x00c1) == 0: + return True + + print("status failed to clear", file=sys.stdout) return False - tck(8) - return True + def command(self,cmd): + self.sendhex_cs(cmd) + if not self.status_wait(): + return False + self.tck(8) + return True -def nvcm_bank_select(bank): - return nvcm_write(cmd=0x83, address=0x000025, data="%02x" % (bank)) + def pgm_enable(self): + return self.command("06") -def nvcm_select_nvcm(): - # ! Shift in Restore Access-NVCM instruction; - # SDR 40 TDI(0x00A40000C1); - return nvcm_bank_select(0x00) + def pgm_disable(self): + return self.command("04") -def nvcm_select_trim(): - # ! Shift in Trim setup-NVCM instruction; - # SDR 40 TDI(0x08A40000C1); - return nvcm_bank_select(0x10) + def enable_access(self): + # ! Shift in Access-NVCM instruction; + # SMCInstruction[1] = 0x70807E99557E; + return self.command("7eaa997e010e") -def nvcm_select_sig(): - # ! Shift in Access Silicon Signature instruction; - # IDInstruction[1] = 0x04A40000C1; - # SDR 40 TDI(IDInstruction[1]); - return nvcm_bank_select(0x20) + def read(self, address, length=8, cmd=0x03): + """Returns a big integer""" + # enable(0) + # sendhex("%02x%06x" % (cmd, address)) + # sendhex("00" * 9) # dummy bytes + # x = 0 + # 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 + ret = self.sendhex_cs(msg) + + x = 0 + for i in range(0,length): + x = x << 8 | ret[i + 4+9] + return x -def nvcm_read_trim(): - # ! Shift in Access-NVCM instruction; - # SMCInstruction[1] = 0x70807E99557E; - if not nvcm_enable_access(): - return + def read_bytes(self, address, length=8): + """Returns a byte array of the contents""" + return self.read(address, length).to_bytes(length, byteorder="big") - # ! Shift in READ_RF(0x84) instruction; - # SDR 104 TDI(0x00000000000000000004000021); - x = nvcm_read(cmd=0x84, address=0x000020, length=8) - tck(8) + def write(self,address, data, cmd=0x02): + 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 + + self.tck(8) + return True - #print("FSM Trim Register %x" % (x)) + def bank_select(self,bank): + return self.write(cmd=0x83, address=0x000025, data="%02x" % (bank)) - nvcm_select_nvcm() - return x + def select_nvcm(self): + # ! Shift in Restore Access-NVCM instruction; + # SDR 40 TDI(0x00A40000C1); + return self.bank_select(0x00) + + def select_trim(self): + # ! Shift in Trim setup-NVCM instruction; + # SDR 40 TDI(0x08A40000C1); + return self.bank_select(0x10) + + def select_sig(self): + # ! Shift in Access Silicon Signature instruction; + # IDInstruction[1] = 0x04A40000C1; + # SDR 40 TDI(IDInstruction[1]); + return self.bank_select(0x20) -def nvcm_write_trim(data): - # ! Setup Programming Parameter in Trim Registers; - # ! Shift in Trim setup-NVCM instruction; - # TRIMInstruction[1] = 0x000000430F4FA80004000041; - return nvcm_write(cmd=0x82, address=0x000020, data=data) + def read_trim(self): + # ! Shift in Access-NVCM instruction; + # SMCInstruction[1] = 0x70807E99557E; + if not self.enable_access(): + return + + # ! 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)) + + self.select_nvcm() + return x + + def write_trim(self,data): + # ! Setup Programming Parameter in Trim Registers; + # ! Shift in Trim setup-NVCM instruction; + # TRIMInstruction[1] = 0x000000430F4FA80004000041; + return self.write(cmd=0x82, address=0x000020, data=data) -def nvcm_enable(): - if debug: - print("nvcm_enable") - # ! Shift in Access-NVCM instruction; - # SMCInstruction[1] = 0x70807E99557E; - if not nvcm_enable_access(): - return + def nvcm_enable(self): + if self.debug: + print("enable") + # ! Shift in Access-NVCM instruction; + # SMCInstruction[1] = 0x70807E99557E; + if not self.enable_access(): + return + + # ! 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): + # ! 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) - # ! Setup Reading Parameter in Trim Registers; - # ! Shift in Trim setup-NVCM instruction; - # TRIMInstruction[1] = 0x000000230000000004000041; - if debug: - print("setup_nvcm") - return nvcm_write_trim("00000000c4000000") - -def nvcm_enable_trim(): - # ! Setup Programming Parameter in Trim Registers; - # ! Shift in Trim setup-NVCM instruction; - # TRIMInstruction[1] = 0x000000430F4FA80004000041; - return nvcm_write_trim("0015f2f0c2000000") - - -def nvcm_disable(): - if not nvcm_select_nvcm(): - return - - reset(1) - tck(8, 1) - rest(0) - tck(8, 2000) - -def nvcm_trim_blank_check(): - print ("NVCM Trim_Parameter_OTP blank check"); - - if not nvcm_select_trim(): - return - - x = nvcm_read(0x000020, 1) - nvcm_select_nvcm() - - if x != 0: - die ("NVCM Trim_Parameter_OTP Block is not blank. (%02x)" % x); - - return True - -def nvcm_blank_check(total_fuse): - nvcm_select_nvcm() - - status = True - print ("NVCM main memory blank check"); - contents = nvcm_read_bytes(0x000000, total_fuse) - - for i in range(0,total_fuse): - x = contents[i] - if debug: - print("%08x: %02x" % (i, x)) + 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: - print ("%08x: NVCM Main Memory Block is not blank." % (i), file=sys.stderr) - status = False - #break + die ("NVCM Trim_Parameter_OTP Block is not blank. (%02x)" % x); + + return True + + def blank_check(self,total_fuse): + self.select_nvcm() + + status = True + print ("NVCM main memory blank check"); + contents = self.read_bytes(0x000000, total_fuse) + + for i in range(0,total_fuse): + x = contents[i] + if debug: + print("%08x: %02x" % (i, x)) + if x != 0: + print ("%08x: NVCM Main Memory Block is not blank." % (i), file=sys.stderr) + status = False + #break + + self.select_nvcm() + return status - nvcm_select_nvcm() - return status + def program(self,rows): + self.select_nvcm() + + if not self.enable_trim(): + return False + + print ("NVCM Program main memory") + + if not self.pgm_enable(): + return False + + 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 -def nvcm_program(rows): - nvcm_select_nvcm() + 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); + # ! Program Security Bit row 2; + # SDR 96 TDI(0x000000008000000C06000040); + # ! Program Security Bit row 3; + # 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.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 - if not nvcm_enable_trim(): - return False - - print ("NVCM Program main memory") - - if not nvcm_pgm_enable(): - return False - - status = True - - i = 0 - for row in rows: - if i % 1024 == 0: - print("%6d / %6d bytes" % (i, len(rows) * 8)) - i += 8 - if not nvcm_command(row): - status = False - break - - nvcm_pgm_disable() - - if not status: - print("PROGRAMMING FAILED", file=sys.stderr) - return status - -def nvcm_write_trim_pages(lock_bits): - if not nvcm_select_nvcm(): - die("select trim failed") - - if not nvcm_enable_trim(): - die("write trim command failed") - - if not nvcm_select_trim(): - die("select trim failed") - - if not nvcm_pgm_enable(): - die("write enable failed") - - # ! Program Security Bit row 1; - # ! Shift in PAGEPGM instruction; - # SDR 96 TDI(0x000000008000000C04000040); - # ! Program Security Bit row 2; - # SDR 96 TDI(0x000000008000000C06000040); - # ! Program Security Bit row 3; - # SDR 96 TDI(0x000000008000000C05000040); - # ! Program Security Bit row 4; - # SDR 96 TDI(0x00000000800000C07000040); - if not nvcm_write(0x000020, lock_bits): - die("trim write 0x20 failed") - if not nvcm_write(0x000060, lock_bits): - die("trim write 0x60 failed") - if not nvcm_write(0x0000a0, lock_bits): - die("trim write 0xa0 failed") - if not nvcm_write(0x0000e0, lock_bits): - die("trim write 0xe0 failed") - - nvcm_pgm_disable() - - # verify a read back - x = nvcm_read(0x000020, 8) - - nvcm_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 nvcm_trim_secure(): - print ("NVCM Secure") - trim = nvcm_read_trim() - if (trim >> 60) & 0x3 != 0: - print("NVCM already secure? trim=%016x" % (trim), file=sys.stderr) - - return nvcm_write_trim_pages("3000000100000000") + def trim_secure(self): + 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") -def nvcm_trim_program(): - print ("NVCM Program Trim_Parameter_OTP"); - return nvcm_write_trim_pages("0015f2f1c4000000") + def trim_program(self): + print ("NVCM Program Trim_Parameter_OTP"); + return self.write_trim_pages("0015f2f1c4000000") -def nvcm_info(): - nvcm_select_sig() - sig1 = nvcm_read(0x000000, 8) + def info(self): + 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, + secured + )) + 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 - nvcm_select_sig() - sig2 = nvcm_read(0x000008, 8) - - # have to switch back to nvcm bank before switching to trim? - nvcm_select_nvcm() - trim = nvcm_read_trim() - - nvcm_select_nvcm() - - nvcm_select_trim() - trim0 = nvcm_read(0x000020, 8) - - nvcm_select_trim() - trim1 = nvcm_read(0x000060, 8) - - nvcm_select_trim() - trim2 = nvcm_read(0x0000a0, 8) - - nvcm_select_trim() - trim3 = nvcm_read(0x0000e0, 8) - - nvcm_select_nvcm() - - secured = ((trim >> 60) & 0x3) - device_id = (sig1 >> 56) & 0xFF - - print("Device: %s (%02x) secure=%d" % ( - nvcm_id_table.get(device_id, "Unknown"), - device_id, - secured - )) - 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 nvcm_read_file(filename): - nvcm_select_nvcm() - - total_fuse = 104090 - - contents = b'' - - for offset in range(0,total_fuse,8): - if offset % 1024 == 0: - print("%6d / %6d bytes" % (offset, total_fuse)) - contents += nvcm_read_bytes(offset, 8) - - if filename == '-': - with os.fdopen(sys.stdout.fileno(), "wb", closefd=False) as f: - f.write(contents) - f.flush() - else: - with open(filename, "wb") as f: - f.write(contents) - f.flush() + def read_file(self,filename): + self.select_nvcm() + + total_fuse = 104090 + + contents = b'' + + 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) + f.flush() + else: + with open(filename, "wb") as f: + f.write(contents) + f.flush() # @@ -456,58 +502,55 @@ def bitstream2nvcm(bitstream): return rows -def sleep_flash(): +def sleep_flash(pins): + flasher = usb_test.ice40_flasher() + # Disable board power - flasher.gpio_put(tp1_pins['5v_en'], False) - flasher.gpio_set_direction(tp1_pins['5v_en'], True) + flasher.gpio_put(pins['5v_en'], False) + flasher.gpio_set_direction(pins['5v_en'], True) # Pull CRST low to prevent FPGA from starting - flasher.gpio_set_direction(tp1_pins['crst'], True) - flasher.gpio_put(tp1_pins['crst'], False) + flasher.gpio_set_direction(pins['crst'], True) + flasher.gpio_put(pins['crst'], False) # Enable board power - flasher.gpio_put(tp1_pins['5v_en'], True) + flasher.gpio_put(pins['5v_en'], True) # Configure pins for talking to flash - flasher.gpio_set_direction(tp1_pins['ss'], True) - flasher.gpio_set_direction(tp1_pins['mosi'], False) - flasher.gpio_set_direction(tp1_pins['sck'], True) - flasher.gpio_set_direction(tp1_pins['miso'], True) + flasher.gpio_set_direction(pins['ss'], True) + flasher.gpio_set_direction(pins['mosi'], False) + flasher.gpio_set_direction(pins['sck'], True) + flasher.gpio_set_direction(pins['miso'], True) - flasher.gpio_put(tp1_pins['ss'], False) - flasher.spi_bitbang(sck_pin=tp1_pins['sck'], mosi_pin=tp1_pins['miso'], miso_pin=tp1_pins['mosi'], buf=[0xAB]) - flasher.gpio_put(tp1_pins['ss'], True) + flasher.spi_pins_set( + pins['sck'], + pins['ss'], + pins['miso'], + pins['mosi'] + ) + + flasher.spi_bitbang([0xAB]) # Confirm we can talk to flash - flasher.gpio_put(tp1_pins['ss'], False) - data = flasher.spi_bitbang(sck_pin=tp1_pins['sck'], mosi_pin=tp1_pins['miso'], miso_pin=tp1_pins['mosi'], buf=[0x9f, 0,0]) - flasher.gpio_put(tp1_pins['ss'], True) + data = flasher.spi_bitbang([0x9f, 0,0]) 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.gpio_put(tp1_pins['ss'], False) - flasher.spi_bitbang(sck_pin=tp1_pins['sck'], mosi_pin=tp1_pins['miso'], miso_pin=tp1_pins['mosi'], buf=[0, 0xb9]) - flasher.gpio_put(tp1_pins['ss'], True) + flasher.spi_bitbang([0, 0xb9]) # Confirm we can talk to flash - flasher.gpio_put(tp1_pins['ss'], False) - data = flasher.spi_bitbang(sck_pin=tp1_pins['sck'], mosi_pin=tp1_pins['miso'], miso_pin=tp1_pins['mosi'], buf=[0x9f, 0,0]) - flasher.gpio_put(tp1_pins['ss'], True) + data = flasher.spi_bitbang([0x9f, 0,0]) 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.gpio_put(tp1_pins['ss'], False) - flasher.spi_bitbang(sck_pin=tp1_pins['sck'], mosi_pin=tp1_pins['miso'], miso_pin=tp1_pins['mosi'], buf=[0xb9]) - flasher.gpio_put(tp1_pins['ss'], True) + flasher.spi_bitbang([0xb9]) # Confirm flash is asleep - flasher.gpio_put(tp1_pins['ss'], False) - data = flasher.spi_bitbang(sck_pin=tp1_pins['sck'], mosi_pin=tp1_pins['miso'], miso_pin=tp1_pins['mosi'], buf=[0x9f, 0,0]) - flasher.gpio_put(tp1_pins['ss'], True) + data = flasher.spi_bitbang(buf=[0x9f, 0,0]) print('flash ID while asleep:', ' '.join(['{:02x}'.format(b) for b in data])) assert(data == bytes([0xff, 0xff, 0xff])) @@ -573,7 +616,6 @@ if __name__ == "__main__": args = parser.parse_args() - debug = args.verbose if not args.good_enough \ and (args.write_file or args.set_secure): @@ -582,7 +624,6 @@ if __name__ == "__main__": # Instantiate a SPI controller, with separately managed CS line #spi = SpiController() - flasher = usb_test.ice40_flasher() # Configure the first interface (IF/1) of the FTDI device as a SPI controller #spi.configure(args.port) @@ -600,6 +641,11 @@ if __name__ == "__main__": #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 + + # # Reset pin values + # for pin in tp1_pins: + # flasher.gpio_set_direction(tp1_pins[pin], False) + tp1_pins = { '5v_en' : 7, 'sck' : 10, @@ -610,30 +656,18 @@ if __name__ == "__main__": 'cdne' : 15 } - # # Reset pin values - # for pin in tp1_pins: - # flasher.gpio_set_direction(tp1_pins[pin], False) - if args.sleep_flash: - sleep_flash() + sleep_flash(tp1_pins) - # Configure pins for talking to ice40 - flasher.gpio_set_direction(tp1_pins['ss'], True) - flasher.gpio_set_direction(tp1_pins['mosi'], True) - flasher.gpio_set_direction(tp1_pins['sck'], True) - flasher.gpio_set_direction(tp1_pins['miso'], False) + nvcm = Nvcm(tp1_pins, debug=args.verbose) + nvcm.power_on() # # Turn on ICE40 in CRAM boot mode - - enable(1, 0) # enable and reset high - sleep(0.2) - enable(1, 1) # enable low, reset high - - init() or exit(1) - nvcm_enable() or exit(1) + nvcm.init() or exit(1) + nvcm.nvcm_enable() or exit(1) if args.read_info: - nvcm_info() or exit(1) + nvcm.info() or exit(1) if args.write_file: with open(args.write_file, "rb") as f: @@ -644,25 +678,25 @@ if __name__ == "__main__": exit(1) if not args.ignore_blank: - nvcm_trim_blank_check() or exit(1) + nvcm.trim_blank_check() or exit(1) # how much should we check? - nvcm_blank_check(0x100) or exit(1) + nvcm.blank_check(0x100) or exit(1) # this is it! - nvcm_program(cmds) or exit(1) + nvcm.program(cmds) or exit(1) # update the trim to boot from nvcm - nvcm_trim_program() or exit(1) + nvcm.trim_program() or exit(1) 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) or exit(1) if args.set_secure: - nvcm_trim_secure() or exit(1) + nvcm.trim_secure() or exit(1) if args.do_boot: # hold reset low for half a second - enable(1,0) + nvcm.enable(1,0) sleep(0.5) - enable(1,1) + nvcm.enable(1,1) diff --git a/hw/production_test/usb_test.py b/hw/production_test/usb_test.py index c025c69..c72e373 100755 --- a/hw/production_test/usb_test.py +++ b/hw/production_test/usb_test.py @@ -9,8 +9,11 @@ class ice40_flasher: FLASHER_REQUEST_PULLUPS_SET = 0x12 FLASHER_REQUEST_PIN_VALUES_SET = 0x20 FLASHER_REQUEST_PIN_VALUES_GET = 0x30 - FLASHER_REQUEST_SPI_BITBANG = 0x40 + FLASHER_REQUEST_SPI_BITBANG_CS = 0x41 + FLASHER_REQUEST_SPI_BITBANG_NO_CS = 0x42 + FLASHER_REQUEST_SPI_PINS_SET = 0x43 FLASHER_REQUEST_ADC_READ = 0x50 + FLASHER_REQUEST_BOOTLOADRE = 0xFF def __init__(self): # self.dev = None @@ -32,10 +35,16 @@ class ice40_flasher: #self.dev.close() pass - def _write(self, request_id, data): + def _write(self, request_id: int, data: bytes): self.dev.ctrl_transfer(0x40, request_id,0,0,data) - def _read(self, request_id, length): + def _write_bulk(self, request_id: int, data: bytes): + 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): # Request type: # bit 7: direction 0:host to device (OUT), 1: device to host (IN) @@ -44,21 +53,6 @@ class ice40_flasher: ret = self.dev.ctrl_transfer(0xC0, request_id,0,0,length) return ret -# def led_set(self, value: bool) -> None: -# """Set the state of the onboard LED - -# Keyword arguments: -# value -- True: On, False: Off -# """ -# msg = struct.pack('>BBB', -# 0x0, -# 0x00, -# (1 if value else 0) -# ) - -# #print(['{:02x}'.format(b) for b in msg]) -# self.dev.write(msg) - def gpio_set_direction(self, pin: int, direction: bool) -> None: """Set the direction of a single GPIO pin @@ -71,6 +65,7 @@ class ice40_flasher: ((1 if direction else 0)< None: @@ -121,23 +116,39 @@ class ice40_flasher: return ((gpio_states >> pin) & 0x01) == 0x01 - def spi_bitbang( + def spi_pins_set( self, sck_pin: int, + cs_pin: int, mosi_pin: int, - miso_pin: int, - buf: bytearray) -> bytearray: - """Bitbang a SPI transfer using the specificed GPIO pins - - Note that this command does not handle setting a CS pin, that must be accomplished - separately, for instance by calling gpio_set() on the pin controlling the CS line. + miso_pin: int) -> None: + """Set the pins to use for SPI transfers Keyword arguments: sck_pin -- GPIO pin number to use as the SCK signal + cs_pin -- GPIO pin number to use as the CS signal mosi_pin -- GPIO pin number to use as the MOSI signal miso_pin -- GPIO pin number to use as the MISO signal + """ + header = struct.pack('>BBBB', + sck_pin, + cs_pin, + mosi_pin, + miso_pin) + msg = bytearray() + msg.extend(header) + + self._write(self.FLASHER_REQUEST_SPI_PINS_SET,msg) + + def spi_bitbang( + self, + buf: bytearray, + toggle_cs: bool = True) -> bytearray: + """Bitbang a SPI transfer + + Keyword arguments: buf -- Byte buffer to send. If the bit_count is smaller than the buffer size, some data will not be sent. - bit_count -- (Optional) Number of bits (not bytes) to bitbang. If left unspecificed, defaults to the size of buf. + toggle_cs: (Optional) If true, toggle the CS line """ ret = bytearray() @@ -147,31 +158,25 @@ class ice40_flasher: chunk = buf[i:i + max_chunk_size] ret.extend( self.spi_bitbang_inner( - sck_pin=sck_pin, - mosi_pin=mosi_pin, - miso_pin=miso_pin, - buf=chunk)) + buf=chunk, + toggle_cs=toggle_cs)) return ret def spi_bitbang_inner( self, - sck_pin: int, - mosi_pin: int, - miso_pin: int, buf: bytearray, - bit_count: int = -1) -> bytearray: + bit_count: int = -1, + toggle_cs: bool = True) -> bytearray: """Bitbang a SPI transfer using the specificed GPIO pins Note that this command does not handle setting a CS pin, that must be accomplished separately, for instance by calling gpio_set() on the pin controlling the CS line. Keyword arguments: - sck_pin -- GPIO pin number to use as the SCK signal - mosi_pin -- GPIO pin number to use as the MOSI signal - miso_pin -- GPIO pin number to use as the MISO signal buf -- Byte buffer to send. If the bit_count is smaller than the buffer size, some data will not be sent. bit_count -- (Optional) Number of bits (not bytes) to bitbang. If left unspecificed, defaults to the size of buf. + toggle_cs: (Optional) If true, toggle the CS line """ if bit_count == -1: bit_count = len(buf) * 8 @@ -188,18 +193,18 @@ class ice40_flasher: len(buf) * 8) exit(1) - header = struct.pack('>BBBI', - sck_pin, - mosi_pin, - miso_pin, + header = struct.pack('>I', bit_count) msg = bytearray() msg.extend(header) msg.extend(buf) - self._write(self.FLASHER_REQUEST_SPI_BITBANG,msg) - - msg_in = self._read(self.FLASHER_REQUEST_SPI_BITBANG, byte_length) + if toggle_cs: + 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) return msg_in @@ -232,15 +237,6 @@ if __name__ == '__main__': buf = [0x01,0x02,0x03, 0xFE] - header = struct.pack('>BBBI', - 10, - 11, - 13, - len(buf)*8) - msg = bytearray() - msg.extend(header) - msg.extend(buf) while True: - flasher.spi_bitbang_inner(sck_pin=10, mosi_pin=11, miso_pin=13, buf=buf)