From 2cd7c9f8e3e1ac803745b68596f8701efb76c8dc Mon Sep 17 00:00:00 2001 From: Matt Mets Date: Wed, 8 Mar 2023 13:36:02 +0100 Subject: [PATCH] Linting --- hw/production_test/Makefile | 3 +- hw/production_test/binaries/main.uf2 | Bin 66560 -> 47616 bytes hw/production_test/production_test_runner.py | 47 +++-- hw/production_test/production_tests.py | 206 +++++++++---------- hw/production_test/pynvcm.py | 69 ++++--- hw/production_test/usb_test.py | 5 +- 6 files changed, 170 insertions(+), 160 deletions(-) diff --git a/hw/production_test/Makefile b/hw/production_test/Makefile index 8c2cfba..c64fc7f 100644 --- a/hw/production_test/Makefile +++ b/hw/production_test/Makefile @@ -6,7 +6,6 @@ PYTHON_FILES = \ production_tests.py \ pybin2nvcm.py \ pynvcm.py \ - random_production_test.py \ reset.py \ usb_test.py @@ -17,7 +16,7 @@ PYTHON_FILES = \ lint: autopep8 --in-place --max-line-length 70 --aggressive --aggressive ${PYTHON_FILES} mypy --disallow-untyped-defs ${PYTHON_FILES} - pylint --generated-member=usb1.TRANSFER_COMPLETED,usb1.USBErrorInterrupted,usb1.USBErrorIO ${PYTHON_FILES} + pylint --generated-member=usb1.TRANSFER_COMPLETED,usb1.USBErrorInterrupted,usb1.USBErrorIO --max-line-length 70 ${PYTHON_FILES} # Check that the NVCM generator gives a correct output for a known binary verify-pybin2nvcm: diff --git a/hw/production_test/binaries/main.uf2 b/hw/production_test/binaries/main.uf2 index a1490e06ea780b4fe1c3f0264df4b16e64b2a8be..751694e31c72f7679c27d05c05b394cf71c25d60 100644 GIT binary patch literal 47616 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 DP`x~9 diff --git a/hw/production_test/production_test_runner.py b/hw/production_test/production_test_runner.py index dd40e19..c72d05b 100755 --- a/hw/production_test/production_test_runner.py +++ b/hw/production_test/production_test_runner.py @@ -2,7 +2,7 @@ from typing import Any import production_tests -pass_msg = ''' +PASS_MSG = ''' _____ _____ _____ | __ \\ /\\ / ____| / ____| | |__) | / \\ | (___ | (___ @@ -11,7 +11,7 @@ pass_msg = ''' |_| /_/ \\_\\ |_____/ |_____/ ''' -fail_msg = ''' +FAIL_MSG = ''' ______ _____ _ | ____| /\\ |_ _| | | | |__ / \\ | | | | @@ -44,6 +44,16 @@ ANSI = { def run_tests(test_list: list[Any]) -> bool: + """ Run a test list + + This executes the tests from the given list in order. The test + sequence is stopped if a test raises an assertion, or returns + False. If all tests return True successfully, then the test + sequence is considered to have passed. + + Keyword parameters: + test_list -- List of test functions to call + """ try: for test in test_list: print("\n{:}Test step: {:}{:} ({:})".format( @@ -66,12 +76,8 @@ def run_tests(test_list: list[Any]) -> bool: return True -if __name__ == '__main__': - last_a = '1' - - # Allow any of the settings in the parameters structure to be - # overridden - import argparse +def production_test_runner() -> None: + """Interactive production test environment""" parser = argparse.ArgumentParser() parser.add_argument( '-l', @@ -100,7 +106,7 @@ if __name__ == '__main__': [test.__name__ for test in test_list]))) for test in production_tests.manual_tests: print('{:}: {:}'.format(test.__name__, test.__doc__)) - exit(0) + sys.exit(0) if args.run_test is not None: result = False @@ -118,17 +124,17 @@ if __name__ == '__main__': break if not found: - print('Test not found:{:}'.format(args.run_test)) - exit(1) + raise ValueError('Test not found:{args.run_test}') if not result: production_tests.reset() - exit(1) + sys.exit(1) - exit(0) + sys.exit(0) print('\n\nProduction test system') + last_a = '1' while True: print('\n\n') @@ -161,17 +167,24 @@ if __name__ == '__main__': try: test_sequence = options[int(a) - 1] - except IndexError as e: + except IndexError: print('Invalid input') continue - except ValueError as e: + except ValueError: print('Invalid input') continue if not run_tests(test_sequence): - print(ANSI['bg_red'] + fail_msg + ANSI['reset']) + print(ANSI['bg_red'] + FAIL_MSG + ANSI['reset']) production_tests.reset() else: - print(ANSI['bg_green'] + pass_msg + ANSI['reset']) + print(ANSI['bg_green'] + PASS_MSG + ANSI['reset']) last_a = a + + +if __name__ == '__main__': + import argparse + import sys + + production_test_runner() diff --git a/hw/production_test/production_tests.py b/hw/production_test/production_tests.py index 42eeee8..8d6398c 100755 --- a/hw/production_test/production_tests.py +++ b/hw/production_test/production_tests.py @@ -54,7 +54,7 @@ parameters = { 'pico_bootloader_target_dir': '/media/lab/RPI-RP2/' } -tp1_pins = { +TP1_PINS = { '5v_en': 7, 'tx': 8, 'rx': 9, @@ -75,9 +75,9 @@ tp1_pins = { def enable_power() -> bool: """Enable power to the TK-1""" - d = IceFlasher() - d.gpio_set_direction(tp1_pins['5v_en'], True) - d.gpio_put(tp1_pins['5v_en'], True) + flasher = IceFlasher() + flasher.gpio_set_direction(TP1_PINS['5v_en'], True) + flasher.gpio_put(TP1_PINS['5v_en'], True) time.sleep(0.3) return True @@ -86,9 +86,9 @@ def enable_power() -> bool: def disable_power() -> bool: """Disable power to the TK-1""" time.sleep(.1) - d = IceFlasher() - d.gpio_set_direction(tp1_pins['5v_en'], True) - d.gpio_put(tp1_pins['5v_en'], False) + flasher = IceFlasher() + flasher.gpio_set_direction(TP1_PINS['5v_en'], True) + flasher.gpio_put(TP1_PINS['5v_en'], False) return True @@ -102,7 +102,7 @@ def measure_voltages(device: IceFlasher, sample_count -- number of samples to average """ adc_vals = [0.0, 0.0, 0.0] - for i in range(0, sample_count): + for _ in range(0, sample_count): adc_vals = [ total + sample for total, sample in zip( @@ -119,18 +119,16 @@ def voltage_test() -> bool: enable_power() - d = IceFlasher() - vals = measure_voltages(d, 20) - d.close() + flasher = IceFlasher() + vals = measure_voltages(flasher, 20) + flasher.close() disable_power() print( 'voltages:', ', '.join( - '{:}V:{:.3f}'.format( - val[0], - val[1]) for val in vals.items())) + f'{val[0]}V:{val[1]:.3f}' for val in vals.items())) if ( (abs(vals['1.2'] - 1.2) > .2) | (abs(vals['2.5'] - 2.5) > .2) @@ -142,12 +140,13 @@ def voltage_test() -> bool: def flash_validate_id() -> bool: - """Read the ID from TK-1 SPI flash, and verify that it matches the expected value""" + """Read the ID from TK-1 SPI flash, and compare to known values""" result = run([ parameters['iceprog'], '-t' ], - capture_output=True) + capture_output=True, + check=True) disable_power() err = result.stderr.split(b'\n') @@ -167,7 +166,7 @@ def flash_validate_id() -> bool: if flash_type is None: print('Flash ID invalid') return False - print('Detected flash type: {:}'.format(flash_type)) + print(f'Detected flash type: {flash_type}') return True return result.returncode == 0 @@ -178,7 +177,8 @@ def flash_program() -> bool: result = run([ parameters['iceprog'], parameters['app_gateware'] - ]) + ], + check=True) disable_power() print(result) @@ -191,7 +191,8 @@ def flash_check() -> bool: parameters['iceprog'], '-c', parameters['app_gateware'] - ]) + ], + check=True) disable_power() print(result) @@ -199,12 +200,12 @@ def flash_check() -> bool: def test_extra_io() -> bool: - """Test the TK-1 RTS, CTS, and GPIO1-4 lines by measuring a test pattern generated by the app_test gateware""" + """Test the TK-1 RTS, CTS, and GPIO1-4 lines""" - d = IceFlasher() - for pin in tp1_pins.values(): - d.gpio_set_direction(pin, False) - d.close() + flasher = IceFlasher() + for pin in TP1_PINS.values(): + flasher.gpio_set_direction(pin, False) + flasher.close() disable_power() time.sleep(1) @@ -212,24 +213,24 @@ def test_extra_io() -> bool: time.sleep(0.2) - d = IceFlasher() - d.gpio_put(tp1_pins['rts'], False) - d.gpio_set_direction(tp1_pins['rts'], True) + flasher = IceFlasher() + flasher.gpio_put(TP1_PINS['rts'], False) + flasher.gpio_set_direction(TP1_PINS['rts'], True) expected_results = [1 << (i % 5) for i in range(9, -1, -1)] results = [] - for i in range(0, 10): - vals = d.gpio_get_all() + for _ in range(0, 10): + vals = flasher.gpio_get_all() pattern = (vals >> 17) & 0b11111 # print(f'{vals:016x} {pattern:04x}') results.append(pattern) - d.gpio_put(tp1_pins['rts'], True) - d.gpio_put(tp1_pins['rts'], False) + flasher.gpio_put(TP1_PINS['rts'], True) + flasher.gpio_put(TP1_PINS['rts'], False) - d.gpio_set_direction(tp1_pins['rts'], False) - d.close() + flasher.gpio_set_direction(TP1_PINS['rts'], False) + flasher.close() disable_power() @@ -269,22 +270,22 @@ def inject_serial_number( "68de5d27-e223-4874-bc76-a54d6e84068f") replacement = encode_usb_strings.string_to_descriptor(serial_num) - f = bytearray(open(infile, 'rb').read()) + with open(infile, 'rb') as fin: + firmware_data = bytearray(fin.read()) - pos = f.find(magic) + pos = firmware_data.find(magic) if pos < 0: - print('failed to find magic string') - exit(1) + raise ValueError('failed to find magic string') - f[pos:(pos + len(magic))] = replacement + firmware_data[pos:(pos + len(magic))] = replacement - with open(outfile, 'wb') as of: - of.write(f) + with open(outfile, 'wb') as fout: + fout.write(firmware_data) def flash_ch552(serial_num: str) -> bool: - """Flash an attached CH552 device with the USB CDC firmware, injected with the given serial number""" + """Flash an attached CH552 device with the USB CDC firmware""" print(serial_num) inject_serial_number( @@ -296,7 +297,8 @@ def flash_ch552(serial_num: str) -> bool: result = run([ parameters['chprog'], parameters['ch552_firmware_injected'] - ]) + ], + check=True) print(result) return result.returncode == 0 @@ -308,7 +310,8 @@ def erase_ch552() -> bool: result = run([ parameters['chprog'], parameters['ch552_firmware_blank'] - ]) + ], + check=True) print(result) return result.returncode == 0 @@ -330,7 +333,7 @@ def find_serial_device(desc: dict[str, Any]) -> str: def find_ch552(serial_num: str) -> bool: - """Search all serial devices for one that has the correct description and serial number""" + """Search for a serial device that has the correct description""" time.sleep(1) description = { @@ -350,18 +353,18 @@ def find_ch552(serial_num: str) -> bool: def ch552_program() -> bool: - """Load the CDC ACM firmware onto a CH552 with a randomly generated serial number, and verify that it boots correctly""" + """Load the firmware onto a CH552, and verify that it boots""" if not test_found_bootloader(): print('Error finding CH552!') return False - serial = str(uuid.uuid4()) + serial_num = str(uuid.uuid4()) - if not flash_ch552(serial): + if not flash_ch552(serial_num): print('Error flashing CH552!') return False - if not find_ch552(serial): + if not find_ch552(serial_num): print('Error finding flashed CH552!') return False @@ -382,7 +385,7 @@ def ch552_erase() -> bool: def test_txrx_touchpad() -> bool: - """Test UART communication, RGB LED, and touchpad by asking the operator to interact with the touch pad""" + """Test UART communication, RGB LED, and touchpad""" description = { 'vid': 0x1207, 'pid': 0x8887, @@ -390,36 +393,33 @@ def test_txrx_touchpad() -> bool: 'product': 'MTA1-USB-V1' } - s = serial.Serial( + dev = serial.Serial( find_serial_device(description), 9600, timeout=.2) - if not s.isOpen(): + if not dev.isOpen(): print('couldn\'t find/open serial device') return False for _ in range(0, 5): # Attempt to clear any buffered data from the serial port - s.write(b'0123') + dev.write(b'0123') time.sleep(0.2) - s.read(20) + dev.read(20) try: - s.write(b'0') - [count, touch_count] = s.read(2) + dev.write(b'0') + [count, touch_count] = dev.read(2) print( - 'read count:{:}, touch count:{:}'.format( - count, touch_count)) + f'read count:{count}, touch count:{touch_count}') input( '\n\n\nPress touch pad once and check LED, then press Enter') - s.write(b'0') - [count_post, touch_count_post] = s.read(2) + dev.write(b'0') + [count_post, touch_count_post] = dev.read(2) print( - 'read count:{:}, touch count:{:}'.format( - count_post, - touch_count_post)) + 'read count:{count_post}, touch count:{touch_count_post}') if (count_post - count != 1) or (touch_count_post - @@ -428,8 +428,8 @@ def test_txrx_touchpad() -> bool: continue return True - except ValueError as e: - print(e) + except ValueError as error: + print(error) continue print('Max retries exceeded, failure!') @@ -450,9 +450,6 @@ def program_pico() -> bool: parameters['pico_bootloader_target_dir'] + firmware_filename) - # TODO: Test if the pico identifies as a USB-HID device - # after programming - return True except FileNotFoundError: time.sleep(0.1) @@ -514,64 +511,45 @@ def reset() -> None: """Attempt to reset the board after test failure""" try: disable_power() - except AttributeError as e: + except AttributeError: pass - except OSError as e: + except OSError: pass - except ValueError as e: + except ValueError: pass -if __name__ == "__main__": - # Runs the non-interactive production tests continuously in a - # random order, to look for interaction bugs +def random_test_runner() -> None: + """"Run the non-interactive production tests in a random order + + This routine is intended to be used for finding edge-cases with + the production tests. It runs the non-interactive tests (as well + as some nondestructive tests from the nvcm module) in a random + order, and runs continuously. + """ - import random - import pynvcm - def nvcm_read_info() -> bool: - tp1_pins = { - '5v_en': 7, - 'sck': 10, - 'mosi': 11, - 'ss': 12, - 'miso': 13, - 'crst': 14, - 'cdne': 15 - } - - pynvcm.sleep_flash(tp1_pins, 15) - - nvcm = pynvcm.Nvcm(tp1_pins, 15) + """Check that the nvcm read info command runs""" + pynvcm.sleep_flash(TP1_PINS, 15) + nvcm = pynvcm.Nvcm(TP1_PINS, 15) nvcm.power_on() nvcm.init() nvcm.nvcm_enable() nvcm.info() nvcm.power_off() return True - + def nvcm_verify_blank() -> bool: - tp1_pins = { - '5v_en': 7, - 'sck': 10, - 'mosi': 11, - 'ss': 12, - 'miso': 13, - 'crst': 14, - 'cdne': 15 - } - - pynvcm.sleep_flash(tp1_pins, 15) - - nvcm = pynvcm.Nvcm(tp1_pins, 15) + """Verify that the NVCM memory is blank""" + pynvcm.sleep_flash(TP1_PINS, 15) + nvcm = pynvcm.Nvcm(TP1_PINS, 15) nvcm.power_on() nvcm.init() nvcm.nvcm_enable() nvcm.trim_blank_check() nvcm.power_off() return True - - + tests = [ nvcm_read_info, nvcm_verify_blank, @@ -583,11 +561,21 @@ if __name__ == "__main__": enable_power, disable_power ] - + parameters['iceprog'] = '/home/matt/repos/tillitis--icestorm/iceprog/iceprog' - + + pass_count = 0 while True: i = random.randint(0, (len(tests) - 1)) - print(f'\n\n{i} running: {tests[i].__name__}') + print(f'\n\n{pass_count}: running: {tests[i].__name__}') if not tests[i](): - raise Exception('oops') + sys.exit(1) + pass_count += 1 + + +if __name__ == "__main__": + import random + import pynvcm + import sys + + random_test_runner() diff --git a/hw/production_test/pynvcm.py b/hw/production_test/pynvcm.py index 11fa14b..db8405f 100755 --- a/hw/production_test/pynvcm.py +++ b/hw/production_test/pynvcm.py @@ -34,6 +34,13 @@ def assert_bytes_equal( name: str, expected: bytes, val: bytes) -> None: + """ Check if two bytes objects are equal + + Keyword arguments: + name -- Description to print if the assertion fails + expected -- Expected value + val -- Value to check + """ if expected != val: expected_str = ' '.join([f'{x:02x}' for x in expected]) val_str = ' '.join([f'{x:02x}' for x in val]) @@ -103,40 +110,40 @@ class Nvcm(): """Disable power to the DUT""" self.flasher.gpio_put(self.pins['5v_en'], False) - def enable(self, cs: bool, reset: bool = True) -> None: + def enable(self, chip_select: bool, reset: bool = True) -> None: """Set the CS and Reset pin states""" - self.flasher.gpio_put(self.pins['ss'], cs) + self.flasher.gpio_put(self.pins['ss'], chip_select) self.flasher.gpio_put(self.pins['crst'], reset) - def writehex(self, s: str, toggle_cs: bool = True) -> None: + def writehex(self, hex_data: str, toggle_cs: bool = True) -> None: """Write SPI data to the target device Keyword arguments: - s -- data to send (formatted as a string of hex data) + hex_data -- data to send (formatted as a string of hex data) toggle_cs -- If true, automatically lower the CS pin before transmit, and raise it after transmit """ - if self.debug and not s == "0500": - print("TX", s) - data = bytes.fromhex(s) + if self.debug and not hex_data == "0500": + print("TX", hex_data) + data = bytes.fromhex(hex_data) self.flasher.spi_write(data, toggle_cs) - def sendhex(self, s: str) -> bytes: + def sendhex(self, hex_data: str) -> bytes: """Perform a full-duplex write/read on the target device Keyword arguments: s -- data to send (formatted as a string of hex data) """ - if self.debug and not s == "0500": - print("TX", s) - x = bytes.fromhex(s) + if self.debug and not hex_data == "0500": + print("TX", hex_data) + bytes_data = bytes.fromhex(hex_data) - b = self.flasher.spi_rxtx(x) + ret = self.flasher.spi_rxtx(bytes_data) - if self.debug and not s == "0500": - print("RX", b.hex()) - return b + if self.debug and not hex_data == "0500": + print("RX", ret.hex()) + return ret def delay(self, count: int) -> None: """'Delay' by sending clocks with CS de-asserted @@ -151,16 +158,16 @@ class Nvcm(): """Reboot the part and enter SPI command mode""" if self.debug: print("init") - self.enable(cs=True, reset=True) - self.enable(cs=True, reset=False) - self.enable(cs=False, reset=False) - self.enable(cs=False, reset=True) + self.enable(True, True) + self.enable(True, False) + self.enable(False, False) + self.enable(False, True) sleep(0.1) - self.enable(cs=True, reset=True) + self.enable(True, True) def status_wait(self) -> None: """Wait for the status register to clear""" - for i in range(0, 1000): + for _ in range(0, 1000): self.delay(1250) ret = self.sendhex("0500") status = struct.unpack('>H', ret)[0] @@ -206,7 +213,7 @@ class Nvcm(): """ msg = '' - msg += ("%02x%06x" % (cmd, address)) + msg += (f"{cmd:02x}{address:06x}") msg += ("00" * 9) # dummy bytes msg += ("00" * length) # read ret = self.sendhex(msg) @@ -237,14 +244,14 @@ class Nvcm(): address -- NVCM memory address to write to length -- Number of bytes to write """ - self.writehex("%02x%06x" % (cmd, address) + data) + self.writehex(f"{cmd:02x}{address:06x}" + data) try: self.status_wait() except Exception as exc: - raise Exception( - "WRITE FAILED: cmd=%02x address=%06x data=%s" % - (cmd, address, data)) from exc + raise IOError( + f"WRITE FAILED: cmd={cmd:02x} address={address:%06x} data={data}" + ) from exc self.delay(2) @@ -371,7 +378,7 @@ class Nvcm(): try: self.command(row) except Exception as exc: - raise Exception( + raise IOError( "programming failed, row:{row}" ) from exc @@ -503,8 +510,8 @@ class Nvcm(): self.bank_select('nvcm') - contents = bytearray() - + # contents = bytearray() + # # for offset in range(0, length, 8): # if offset % (1024 * 8) == 0: # print("%6d / %6d bytes" % (offset, length)) @@ -761,6 +768,6 @@ if __name__ == "__main__": if args.do_boot: # hold reset low for half a second - nvcm.enable(cs=True, reset=False) + nvcm.enable(True, False) sleep(0.5) - nvcm.enable(cs=True, reset=True) + nvcm.enable(True, True) diff --git a/hw/production_test/usb_test.py b/hw/production_test/usb_test.py index 81773c2..0975c1a 100755 --- a/hw/production_test/usb_test.py +++ b/hw/production_test/usb_test.py @@ -60,14 +60,17 @@ class IceFlasher: bcd_device = self.handle.getDevice().getbcdDevice() if bcd_device != 0x0200: raise ValueError( - 'Pico firmware version out of date- please upgrade it!') + 'Pico firmware version out of date- please upgrade') self.handle.claimInterface(0) + self.cs_pin = -1 + def __del__(self) -> None: self.close() def close(self) -> None: + """ Release the USB device handle """ self._wait_async() if self.handle is not None: