2023-03-02 17:43:26 -05:00
|
|
|
#!/usr/bin/env python
|
2023-02-23 05:17:15 -05:00
|
|
|
#
|
|
|
|
# Copyright (C) 2021
|
|
|
|
#
|
|
|
|
# * Trammell Hudson <hudson@trmm.net>
|
|
|
|
# * Matthew Mets https://github.com/cibomahto
|
|
|
|
# * Peter Lawrence https://github.com/majbthrd
|
|
|
|
#
|
2023-03-02 12:37:11 -05:00
|
|
|
# Permission to use, copy, modify, and/or distribute this software
|
|
|
|
# for any purpose with or without fee is hereby granted, provided
|
|
|
|
# that the above copyright notice and this permission notice
|
|
|
|
# appear in all copies.
|
2023-02-23 05:17:15 -05:00
|
|
|
#
|
2023-03-02 12:37:11 -05:00
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
|
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
|
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
|
|
|
|
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
|
|
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
|
|
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
2023-02-23 05:17:15 -05:00
|
|
|
#
|
|
|
|
|
|
|
|
import usb_test
|
2023-03-02 17:43:26 -05:00
|
|
|
from icebin2nvcm import icebin2nvcm
|
2023-02-23 05:17:15 -05:00
|
|
|
import sys
|
|
|
|
from time import sleep
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
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",
|
|
|
|
}
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-04 06:42:26 -05:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
pins: dict,
|
|
|
|
spi_speed: int = 12,
|
|
|
|
debug: bool = False) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
self.pins = pins
|
|
|
|
self.debug = debug
|
|
|
|
|
|
|
|
self.flasher = usb_test.ice40_flasher()
|
|
|
|
|
|
|
|
self.flasher.gpio_put(self.pins['5v_en'], False)
|
|
|
|
self.flasher.gpio_put(self.pins['crst'], False)
|
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
self.flasher.spi_pins_set(
|
2023-03-02 12:17:11 -05:00
|
|
|
pins['sck'],
|
|
|
|
pins['ss'],
|
|
|
|
pins['mosi'],
|
2023-03-04 06:42:26 -05:00
|
|
|
pins['miso'],
|
|
|
|
spi_speed
|
2023-03-02 12:17:11 -05:00
|
|
|
)
|
2023-03-02 10:12:38 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def power_on(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
self.flasher.gpio_put(self.pins['5v_en'], True)
|
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def power_off(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
self.flasher.gpio_put(self.pins['5v_en'], False)
|
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def enable(self, cs: bool, reset: bool = True) -> None:
|
|
|
|
# gpio.write(cs << cs_pin | reset << reset_pin)
|
2023-03-02 10:12:38 -05:00
|
|
|
self.flasher.gpio_put(self.pins['ss'], cs)
|
|
|
|
self.flasher.gpio_put(self.pins['crst'], reset)
|
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
def sendhex_cs(self, s: str, toggle_cs: bool = True) -> bytes:
|
2023-03-02 10:12:38 -05:00
|
|
|
if self.debug and not s == "0500":
|
|
|
|
print("TX", s)
|
|
|
|
x = bytes.fromhex(s)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
# b = dev.exchange(x, duplex=True, readlen=len(x))
|
2023-03-02 17:43:26 -05:00
|
|
|
b = self.flasher.spi_bitbang(x, toggle_cs)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
if self.debug and not s == "0500":
|
|
|
|
print("RX", b.hex())
|
|
|
|
return b
|
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def delay(self, count: int) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
# run the clock with no CS asserted
|
2023-03-02 12:17:11 -05:00
|
|
|
# dev.exchange(b'\x00', duplex=True, readlen=count)
|
2023-03-02 17:43:26 -05:00
|
|
|
self.sendhex_cs('00' * count, False)
|
2023-03-02 10:12:38 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def tck(self, count: int) -> None:
|
2023-03-02 17:43:26 -05:00
|
|
|
self.delay((count >> 3) * 2)
|
2023-03-02 10:12:38 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def init(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
if self.debug:
|
|
|
|
print("init")
|
2023-03-02 12:17:11 -05:00
|
|
|
self.enable(True, True)
|
|
|
|
self.enable(True, False) # reset high
|
2023-03-02 10:12:38 -05:00
|
|
|
sleep(0.15)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
self.enable(False, False) # enable and reset high
|
2023-03-02 10:12:38 -05:00
|
|
|
sleep(0.12)
|
2023-03-02 12:17:11 -05:00
|
|
|
self.enable(False, True) # enable low, reset high
|
2023-03-02 10:12:38 -05:00
|
|
|
sleep(0.12)
|
2023-03-02 12:17:11 -05:00
|
|
|
self.enable(True, True) # enable and reset low
|
2023-03-02 10:12:38 -05:00
|
|
|
sleep(0.12)
|
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def status_wait(self, count: int = 1000) -> None:
|
|
|
|
for i in range(0, count):
|
2023-03-02 10:12:38 -05:00
|
|
|
self.tck(5000)
|
|
|
|
ret = self.sendhex_cs("0500")
|
|
|
|
x = int.from_bytes(ret, byteorder='big')
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
# print("x=%04x" %(x))
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
if (x & 0x00c1) == 0:
|
2023-03-02 12:17:11 -05:00
|
|
|
return
|
|
|
|
|
|
|
|
raise Exception("status failed to clear")
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def command(self, cmd: str) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
self.sendhex_cs(cmd)
|
2023-03-02 12:17:11 -05:00
|
|
|
self.status_wait()
|
2023-03-02 10:12:38 -05:00
|
|
|
self.tck(8)
|
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def pgm_enable(self) -> None:
|
|
|
|
self.command("06")
|
2023-03-02 10:12:38 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def pgm_disable(self) -> None:
|
|
|
|
self.command("04")
|
2023-03-02 10:12:38 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def enable_access(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Shift in Access-NVCM instruction;
|
|
|
|
# SMCInstruction[1] = 0x70807E99557E;
|
2023-03-02 12:17:11 -05:00
|
|
|
self.command("7eaa997e010e")
|
2023-03-02 10:12:38 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
def read_bytes(
|
2023-03-02 12:37:11 -05:00
|
|
|
self,
|
|
|
|
address: int,
|
|
|
|
length: int = 8,
|
2023-03-02 17:43:26 -05:00
|
|
|
cmd: int = 0x03) -> bytes:
|
|
|
|
"""Returns a byte array of the contents"""
|
2023-03-02 10:12:38 -05:00
|
|
|
msg = ''
|
|
|
|
msg += ("%02x%06x" % (cmd, address))
|
2023-03-02 12:17:11 -05:00
|
|
|
msg += ("00" * 9) # dummy bytes
|
|
|
|
msg += ("00" * length) # read
|
2023-03-02 10:12:38 -05:00
|
|
|
ret = self.sendhex_cs(msg)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
return ret[4 + 9:]
|
|
|
|
|
|
|
|
def read(
|
|
|
|
self,
|
|
|
|
address: int,
|
|
|
|
length: int = 8,
|
|
|
|
cmd: int = 0x03) -> int:
|
|
|
|
"""Returns a big integer"""
|
|
|
|
|
|
|
|
val = self.read_bytes(address, length, cmd)
|
2023-03-02 10:12:38 -05:00
|
|
|
x = 0
|
2023-03-02 12:17:11 -05:00
|
|
|
for i in range(0, length):
|
2023-03-02 17:43:26 -05:00
|
|
|
x = x << 8 | val[i]
|
2023-03-02 10:12:38 -05:00
|
|
|
return x
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def write(self, address: int, data: str, cmd: int = 0x02) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
self.sendhex_cs("%02x%06x" % (cmd, address) + data)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
try:
|
|
|
|
self.status_wait()
|
|
|
|
except Exception as e:
|
2023-03-02 12:37:11 -05:00
|
|
|
raise Exception(
|
|
|
|
"WRITE FAILED: cmd=%02x address=%06x data=%s" %
|
|
|
|
(cmd, address, data))
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.tck(8)
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def bank_select(self, bank: int) -> None:
|
|
|
|
self.write(cmd=0x83, address=0x000025, data="%02x" % (bank))
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def select_nvcm(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Shift in Restore Access-NVCM instruction;
|
|
|
|
# SDR 40 TDI(0x00A40000C1);
|
2023-03-02 12:17:11 -05:00
|
|
|
self.bank_select(0x00)
|
|
|
|
|
|
|
|
def select_trim(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Shift in Trim setup-NVCM instruction;
|
|
|
|
# SDR 40 TDI(0x08A40000C1);
|
2023-03-02 12:17:11 -05:00
|
|
|
self.bank_select(0x10)
|
|
|
|
|
|
|
|
def select_sig(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Shift in Access Silicon Signature instruction;
|
|
|
|
# IDInstruction[1] = 0x04A40000C1;
|
|
|
|
# SDR 40 TDI(IDInstruction[1]);
|
2023-03-02 12:17:11 -05:00
|
|
|
self.bank_select(0x20)
|
2023-03-02 10:12:38 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def read_trim(self) -> int:
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Shift in Access-NVCM instruction;
|
|
|
|
# SMCInstruction[1] = 0x70807E99557E;
|
2023-03-02 12:17:11 -05:00
|
|
|
self.enable_access()
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Shift in READ_RF(0x84) instruction;
|
|
|
|
# SDR 104 TDI(0x00000000000000000004000021);
|
|
|
|
x = self.read(cmd=0x84, address=0x000020, length=8)
|
|
|
|
self.tck(8)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
# print("FSM Trim Register %x" % (x))
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_nvcm()
|
|
|
|
return x
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
def write_trim(self, data: str) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Setup Programming Parameter in Trim Registers;
|
|
|
|
# ! Shift in Trim setup-NVCM instruction;
|
|
|
|
# TRIMInstruction[1] = 0x000000430F4FA80004000041;
|
2023-03-02 12:17:11 -05:00
|
|
|
self.write(cmd=0x82, address=0x000020, data=data)
|
2023-03-02 10:12:38 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def nvcm_enable(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
if self.debug:
|
|
|
|
print("enable")
|
|
|
|
# ! Shift in Access-NVCM instruction;
|
|
|
|
# SMCInstruction[1] = 0x70807E99557E;
|
2023-03-02 12:17:11 -05:00
|
|
|
self.enable_access()
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Setup Reading Parameter in Trim Registers;
|
|
|
|
# ! Shift in Trim setup-NVCM instruction;
|
|
|
|
# TRIMInstruction[1] = 0x000000230000000004000041;
|
|
|
|
if self.debug:
|
|
|
|
print("setup_nvcm")
|
2023-03-02 12:17:11 -05:00
|
|
|
self.write_trim("00000000c4000000")
|
|
|
|
|
|
|
|
def enable_trim(self) -> None:
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! Setup Programming Parameter in Trim Registers;
|
|
|
|
# ! Shift in Trim setup-NVCM instruction;
|
|
|
|
# TRIMInstruction[1] = 0x000000430F4FA80004000041;
|
2023-03-02 12:17:11 -05:00
|
|
|
self.write_trim("0015f2f0c2000000")
|
|
|
|
|
|
|
|
def trim_blank_check(self) -> bool:
|
|
|
|
print("NVCM Trim_Parameter_OTP blank check")
|
|
|
|
|
|
|
|
self.select_trim()
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
x = self.read(0x000020, 1)
|
|
|
|
self.select_nvcm()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
if x != 0:
|
2023-03-02 12:37:11 -05:00
|
|
|
print(
|
|
|
|
"NVCM Trim_Parameter_OTP Block is not blank. (%02x)" %
|
|
|
|
x)
|
2023-03-02 12:17:11 -05:00
|
|
|
return False
|
2023-03-02 10:12:38 -05:00
|
|
|
return True
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
def blank_check(self, total_fuse: int) -> bool:
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_nvcm()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
status = True
|
2023-03-02 12:17:11 -05:00
|
|
|
print("NVCM main memory blank check")
|
2023-03-02 10:12:38 -05:00
|
|
|
contents = self.read_bytes(0x000000, total_fuse)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
for i in range(0, total_fuse):
|
2023-03-02 10:12:38 -05:00
|
|
|
x = contents[i]
|
2023-03-02 12:17:11 -05:00
|
|
|
if self.debug:
|
2023-03-02 10:12:38 -05:00
|
|
|
print("%08x: %02x" % (i, x))
|
|
|
|
if x != 0:
|
2023-03-02 12:17:11 -05:00
|
|
|
print(
|
|
|
|
"%08x: NVCM Main Memory Block is not blank." %
|
|
|
|
(i), file=sys.stderr)
|
2023-03-02 10:12:38 -05:00
|
|
|
status = False
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_nvcm()
|
|
|
|
return status
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def program(self, rows: list[str]) -> None:
|
|
|
|
print("NVCM Program main memory")
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_nvcm()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
self.enable_trim()
|
|
|
|
|
|
|
|
self.pgm_enable()
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
status = True
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
i = 0
|
|
|
|
for row in rows:
|
2023-03-02 17:43:26 -05:00
|
|
|
# print('data for row:',i, row)
|
2023-03-02 10:12:38 -05:00
|
|
|
if i % 1024 == 0:
|
|
|
|
print("%6d / %6d bytes" % (i, len(rows) * 8))
|
|
|
|
i += 8
|
2023-03-02 12:17:11 -05:00
|
|
|
try:
|
|
|
|
self.command(row)
|
|
|
|
except Exception as e:
|
|
|
|
raise Exception("programming failed, row:", row)
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.pgm_disable()
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def write_trim_pages(self, lock_bits: str) -> None:
|
|
|
|
self.select_nvcm()
|
|
|
|
|
|
|
|
self.enable_trim()
|
|
|
|
|
|
|
|
self.select_trim()
|
|
|
|
|
|
|
|
self.pgm_enable()
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
# ! 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);
|
2023-03-02 12:17:11 -05:00
|
|
|
self.write(0x000020, lock_bits)
|
|
|
|
self.write(0x000060, lock_bits)
|
|
|
|
self.write(0x0000a0, lock_bits)
|
|
|
|
self.write(0x0000e0, lock_bits)
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.pgm_disable()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
# verify a read back
|
|
|
|
x = self.read(0x000020, 8)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_nvcm()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
lock_bits_int = int(lock_bits, 16)
|
|
|
|
if x & lock_bits_int != lock_bits_int:
|
|
|
|
raise Exception(
|
2023-03-02 12:37:11 -05:00
|
|
|
"Failed to write trim lock bits: " +
|
|
|
|
"%016x != expected %016x" %
|
2023-03-02 12:17:11 -05:00
|
|
|
(x, lock_bits_int))
|
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
print("New state %016x" % (x))
|
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def trim_secure(self) -> None:
|
|
|
|
print("NVCM Secure")
|
2023-03-02 10:12:38 -05:00
|
|
|
trim = self.read_trim()
|
|
|
|
if (trim >> 60) & 0x3 != 0:
|
2023-03-02 12:37:11 -05:00
|
|
|
print(
|
|
|
|
"NVCM already secure? trim=%016x" %
|
|
|
|
(trim), file=sys.stderr)
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
self.write_trim_pages("3000000100000000")
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def trim_program(self) -> None:
|
|
|
|
print("NVCM Program Trim_Parameter_OTP")
|
|
|
|
self.write_trim_pages("0015f2f1c4000000")
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
def info(self) -> None:
|
2023-03-02 17:43:26 -05:00
|
|
|
""" Print the contents of the configuration registers """
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_sig()
|
|
|
|
sig1 = self.read(0x000000, 8)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_sig()
|
|
|
|
sig2 = self.read(0x000008, 8)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
# have to switch back to nvcm bank before switching to trim?
|
|
|
|
self.select_nvcm()
|
|
|
|
trim = self.read_trim()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
# self.select_nvcm()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_trim()
|
|
|
|
trim0 = self.read(0x000020, 8)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_trim()
|
|
|
|
trim1 = self.read(0x000060, 8)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_trim()
|
|
|
|
trim2 = self.read(0x0000a0, 8)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_trim()
|
|
|
|
trim3 = self.read(0x0000e0, 8)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
self.select_nvcm()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
secured = ((trim >> 60) & 0x3)
|
|
|
|
device_id = (sig1 >> 56) & 0xFF
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
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))
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
print("TrimRF: %016x" % (trim))
|
|
|
|
print("Trim 0: %016x" % (trim0))
|
|
|
|
print("Trim 1: %016x" % (trim1))
|
|
|
|
print("Trim 2: %016x" % (trim2))
|
|
|
|
print("Trim 3: %016x" % (trim3))
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
def read_nvcm(self, length: int) -> bytes:
|
|
|
|
""" Read out the contents of the NVCM fuses
|
|
|
|
|
|
|
|
Keyword arguments:
|
|
|
|
length: Length of data to read
|
|
|
|
"""
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
self.select_nvcm()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
contents = bytearray()
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
for offset in range(0, length, 8):
|
2023-03-02 10:12:38 -05:00
|
|
|
if offset % 1024 == 0:
|
2023-03-02 17:43:26 -05:00
|
|
|
print("%6d / %6d bytes" % (offset, length))
|
|
|
|
|
|
|
|
nvcm_addr = int(offset / 328) * 4096 + (offset % 328)
|
|
|
|
contents += self.read_bytes(nvcm_addr, 8)
|
|
|
|
self.tck(8)
|
|
|
|
|
|
|
|
return bytes(contents)
|
|
|
|
|
|
|
|
def read_file(self, filename: str, length: int) -> None:
|
|
|
|
""" Read the contents of the NVCM to a file
|
|
|
|
|
|
|
|
Keyword arguments:
|
|
|
|
filename -- File to write to, or '-' to write to stdout
|
|
|
|
"""
|
|
|
|
|
|
|
|
contents = bytearray()
|
|
|
|
|
|
|
|
# prepend a header to the file, to identify it as an FPGA
|
|
|
|
# bitstream
|
|
|
|
contents += bytes([0xff, 0x00, 0x00, 0xff])
|
|
|
|
|
|
|
|
contents += self.read_nvcm(length)
|
2023-03-02 12:17:11 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
if filename == '-':
|
2023-03-02 12:37:11 -05:00
|
|
|
with os.fdopen(sys.stdout.fileno(),
|
|
|
|
"wb",
|
|
|
|
closefd=False) as f:
|
2023-03-02 10:12:38 -05:00
|
|
|
f.write(contents)
|
|
|
|
f.flush()
|
|
|
|
else:
|
|
|
|
with open(filename, "wb") as f:
|
|
|
|
f.write(contents)
|
|
|
|
f.flush()
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
def verify(self, filename: str) -> None:
|
|
|
|
""" Verify that the contents of the NVCM match a file
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
Keyword arguments:
|
|
|
|
filename -- File to compare
|
|
|
|
"""
|
|
|
|
with open(filename, "rb") as f:
|
|
|
|
compare = f.read()
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
assert (len(compare) > 0)
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
contents = bytearray()
|
|
|
|
contents += bytes([0xff, 0x00, 0x00, 0xff])
|
|
|
|
contents += self.read_nvcm(len(compare))
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
# We might have read more than needed because of read
|
|
|
|
# boundaries
|
|
|
|
if len(contents) > len(compare):
|
|
|
|
contents = contents[:len(compare)]
|
2023-02-23 05:17:15 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
assert (compare == contents)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
|
|
|
|
def sleep_flash(pins: dict) -> None:
|
|
|
|
""" Put the SPI bootloader flash in deep sleep mode"""
|
2023-03-02 10:12:38 -05:00
|
|
|
flasher = usb_test.ice40_flasher()
|
|
|
|
|
2023-02-23 09:47:44 -05:00
|
|
|
# Disable board power
|
2023-03-02 10:12:38 -05:00
|
|
|
flasher.gpio_put(pins['5v_en'], False)
|
|
|
|
flasher.gpio_set_direction(pins['5v_en'], True)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# Pull CRST low to prevent FPGA from starting
|
2023-03-02 10:12:38 -05:00
|
|
|
flasher.gpio_set_direction(pins['crst'], True)
|
|
|
|
flasher.gpio_put(pins['crst'], False)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# Enable board power
|
2023-03-02 10:12:38 -05:00
|
|
|
flasher.gpio_put(pins['5v_en'], True)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# Configure pins for talking to flash
|
2023-03-02 10:12:38 -05:00
|
|
|
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.spi_pins_set(
|
2023-03-02 12:17:11 -05:00
|
|
|
pins['sck'],
|
|
|
|
pins['ss'],
|
|
|
|
pins['miso'],
|
2023-03-04 06:42:26 -05:00
|
|
|
pins['mosi'],
|
|
|
|
12
|
2023-03-02 12:17:11 -05:00
|
|
|
)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
flasher.spi_bitbang(bytes([0xAB]))
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# Confirm we can talk to flash
|
2023-03-02 12:17:11 -05:00
|
|
|
data = flasher.spi_bitbang(bytes([0x9f, 0, 0]))
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
print('flash ID while awake:', ' '.join(
|
|
|
|
['{:02x}'.format(b) for b in data]))
|
|
|
|
assert (data == bytes([0xff, 0xef, 0x40]))
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:37:11 -05:00
|
|
|
# Test that the flash will ignore a sleep command that doesn't
|
|
|
|
# start on the first byte
|
2023-03-02 12:17:11 -05:00
|
|
|
flasher.spi_bitbang(bytes([0, 0xb9]))
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# Confirm we can talk to flash
|
2023-03-02 12:17:11 -05:00
|
|
|
data = flasher.spi_bitbang(bytes([0x9f, 0, 0]))
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
print('flash ID while awake:', ' '.join(
|
|
|
|
['{:02x}'.format(b) for b in data]))
|
|
|
|
assert (data == bytes([0xff, 0xef, 0x40]))
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# put the flash to sleep
|
2023-03-02 12:17:11 -05:00
|
|
|
flasher.spi_bitbang(bytes([0xb9]))
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# Confirm flash is asleep
|
2023-03-02 12:17:11 -05:00
|
|
|
data = flasher.spi_bitbang(bytes([0x9f, 0, 0]))
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
print('flash ID while asleep:', ' '.join(
|
|
|
|
['{:02x}'.format(b) for b in data]))
|
|
|
|
assert (data == bytes([0xff, 0xff, 0xff]))
|
2023-02-23 05:17:15 -05:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
2023-02-23 09:47:44 -05:00
|
|
|
import argparse
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
parser.add_argument('--port',
|
|
|
|
type=str,
|
|
|
|
default='ftdi://::/1',
|
|
|
|
help='FTDI port of the form ftdi://::/1')
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:37:11 -05:00
|
|
|
parser.add_argument(
|
|
|
|
'-v',
|
|
|
|
'--verbose',
|
|
|
|
dest='verbose',
|
|
|
|
action='store_true',
|
|
|
|
help='Show debug information and serial read/writes')
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
parser.add_argument(
|
|
|
|
'-f',
|
|
|
|
'--sleep_flash',
|
2023-02-23 09:47:44 -05:00
|
|
|
dest='sleep_flash',
|
|
|
|
action='store_true',
|
2023-03-02 12:37:11 -05:00
|
|
|
help='Put an attached SPI flash chip in deep sleep')
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
parser.add_argument(
|
|
|
|
'-b',
|
|
|
|
'--boot',
|
2023-02-23 09:47:44 -05:00
|
|
|
dest='do_boot',
|
|
|
|
action='store_true',
|
|
|
|
help='Deassert the reset line to allow the FPGA to boot')
|
|
|
|
|
|
|
|
parser.add_argument('-i', '--info',
|
2023-03-02 12:17:11 -05:00
|
|
|
dest='read_info',
|
|
|
|
action='store_true',
|
|
|
|
help='Read chip ID, trim and other info')
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
parser.add_argument('--read',
|
2023-03-02 12:17:11 -05:00
|
|
|
dest='read_file',
|
|
|
|
type=str,
|
|
|
|
default=None,
|
|
|
|
help='Read contents of NVCM')
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 17:43:26 -05:00
|
|
|
parser.add_argument('--verify',
|
|
|
|
dest='verify_file',
|
|
|
|
type=str,
|
|
|
|
default=None,
|
|
|
|
help='Verify the contents of NVCM')
|
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
parser.add_argument(
|
|
|
|
'--write',
|
2023-02-23 09:47:44 -05:00
|
|
|
dest='write_file',
|
|
|
|
type=str,
|
|
|
|
default=None,
|
2023-03-02 12:37:11 -05:00
|
|
|
help='bitstream file to write to NVCM ' +
|
|
|
|
'(warning: not reversable!)')
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
parser.add_argument('--ignore-blank',
|
2023-03-02 12:17:11 -05:00
|
|
|
dest='ignore_blank',
|
|
|
|
action='store_true',
|
|
|
|
help='Proceed even if the chip is not blank')
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
parser.add_argument(
|
|
|
|
'--secure',
|
2023-02-23 09:47:44 -05:00
|
|
|
dest='set_secure',
|
|
|
|
action='store_true',
|
2023-03-02 12:37:11 -05:00
|
|
|
help='Set security bits to prevent modification ' +
|
|
|
|
'(warning: not reversable!)')
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 12:17:11 -05:00
|
|
|
parser.add_argument(
|
|
|
|
'--my-design-is-good-enough',
|
2023-02-23 09:47:44 -05:00
|
|
|
dest='good_enough',
|
|
|
|
action='store_true',
|
|
|
|
help='Enable the dangerous commands --write and --secure')
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
if not args.good_enough \
|
2023-03-02 12:17:11 -05:00
|
|
|
and (args.write_file or args.set_secure):
|
2023-03-02 12:37:11 -05:00
|
|
|
print(
|
|
|
|
"Are you sure your design is good enough?",
|
|
|
|
file=sys.stderr)
|
2023-02-23 09:47:44 -05:00
|
|
|
exit(1)
|
|
|
|
|
|
|
|
tp1_pins = {
|
2023-03-02 12:17:11 -05:00
|
|
|
'5v_en': 7,
|
|
|
|
'sck': 10,
|
|
|
|
'mosi': 11,
|
|
|
|
'ss': 12,
|
|
|
|
'miso': 13,
|
|
|
|
'crst': 14,
|
|
|
|
'cdne': 15
|
2023-02-23 09:47:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if args.sleep_flash:
|
2023-03-02 10:12:38 -05:00
|
|
|
sleep_flash(tp1_pins)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
2023-03-02 10:12:38 -05:00
|
|
|
nvcm = Nvcm(tp1_pins, debug=args.verbose)
|
|
|
|
nvcm.power_on()
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# # Turn on ICE40 in CRAM boot mode
|
2023-03-02 12:17:11 -05:00
|
|
|
nvcm.init()
|
|
|
|
nvcm.nvcm_enable()
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
if args.read_info:
|
2023-03-02 12:17:11 -05:00
|
|
|
nvcm.info()
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
if args.write_file:
|
|
|
|
with open(args.write_file, "rb") as f:
|
|
|
|
bitstream = f.read()
|
|
|
|
print("read %d bytes" % (len(bitstream)))
|
2023-03-02 17:43:26 -05:00
|
|
|
cmds = icebin2nvcm(bitstream)
|
2023-02-23 09:47:44 -05:00
|
|
|
if not cmds:
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
if not args.ignore_blank:
|
2023-03-02 10:12:38 -05:00
|
|
|
nvcm.trim_blank_check() or exit(1)
|
2023-02-23 09:47:44 -05:00
|
|
|
# how much should we check?
|
2023-03-02 10:12:38 -05:00
|
|
|
nvcm.blank_check(0x100) or exit(1)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# this is it!
|
2023-03-02 12:17:11 -05:00
|
|
|
nvcm.program(cmds)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
# update the trim to boot from nvcm
|
2023-03-02 12:17:11 -05:00
|
|
|
nvcm.trim_program()
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
if args.read_file:
|
|
|
|
# read back after writing to the NVCM
|
2023-03-02 17:43:26 -05:00
|
|
|
nvcm.read_file(args.read_file, 104090)
|
|
|
|
|
|
|
|
if args.verify_file:
|
|
|
|
# read back after writing to the NVCM
|
|
|
|
nvcm.verify(args.verify_file)
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
if args.set_secure:
|
2023-03-02 12:17:11 -05:00
|
|
|
nvcm.trim_secure()
|
2023-02-23 09:47:44 -05:00
|
|
|
|
|
|
|
if args.do_boot:
|
|
|
|
# hold reset low for half a second
|
2023-03-02 12:17:11 -05:00
|
|
|
nvcm.enable(True, False)
|
2023-02-23 09:47:44 -05:00
|
|
|
sleep(0.5)
|
2023-03-02 12:17:11 -05:00
|
|
|
nvcm.enable(True, True)
|