From bdf592089ae461434a03ba0f185dfce3d09953fb Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 17 Mar 2023 08:49:47 -0700 Subject: [PATCH] Fix .bin, .hex and .uf2 with new linker sections Also, format perfbench output in table with reference timing from the host. --- ports/mimxrt10xx/Makefile | 10 ++-- ports/mimxrt10xx/linking/common.ld | 29 +++++----- supervisor/linker.h | 2 +- tests/perf_bench/benchrun.py | 2 +- tests/run-perfbench.py | 90 ++++++++++++++++++++++-------- tools/cpboard.py | 25 +++++++-- 6 files changed, 110 insertions(+), 48 deletions(-) diff --git a/ports/mimxrt10xx/Makefile b/ports/mimxrt10xx/Makefile index c561b894d3..d3db4c44e3 100644 --- a/ports/mimxrt10xx/Makefile +++ b/ports/mimxrt10xx/Makefile @@ -182,18 +182,20 @@ $(BUILD)/firmware.elf: $(OBJ) $(LD_FILES) $(STEPECHO) "LINK $@" $(Q)$(CC) -o $@ $(LDFLAGS) $(filter-out %.ld, $^) -Wl,--print-memory-usage -Wl,--start-group $(LIBS) -Wl,--end-group +# -R excludes sections from the output files. $(BUILD)/firmware.bin: $(BUILD)/firmware.elf $(STEPECHO) "Create $@" - $(Q)$(OBJCOPY) -O binary -j .flash_config -j .ivt -j .text -j .ARM.exidx -j .data -j .itcm -j .dtcm_data $^ $@ + $(Q)$(OBJCOPY) -O binary -R .stack -R .dtcm_bss $^ $@ $(BUILD)/firmware.uf2: $(BUILD)/firmware.elf $(STEPECHO) "Create $@" - $(Q)$(OBJCOPY) -O binary -j .text -j .ARM.exidx -j .data -j .itcm -j .dtcm_data $^ $@-binpart + $(Q)$(OBJCOPY) -O binary -R .stack -R .dtcm_bss -R .ivt -R .flash_config $^ $@-binpart $(Q)$(PYTHON) $(TOP)/tools/uf2/utils/uf2conv.py -b $(BOOTLOADER_SIZE) -f MIMXRT10XX -c -o $@ $@-binpart - $(Q)rm $@-binpart + +# $(Q)rm $@-binpart $(BUILD)/firmware.hex: $(BUILD)/firmware.elf - $(Q)$(OBJCOPY) -O ihex -j .flash_config -j .ivt -j .text -j .ARM.exidx -j .data -j .itcm -j .dtcm_data $< $@ + $(Q)$(OBJCOPY) -O ihex -R .stack -R .dtcm_bss $< $@ include $(TOP)/py/mkrules.mk diff --git a/ports/mimxrt10xx/linking/common.ld b/ports/mimxrt10xx/linking/common.ld index 61a9a2299f..461a9d89eb 100644 --- a/ports/mimxrt10xx/linking/common.ld +++ b/ports/mimxrt10xx/linking/common.ld @@ -6,7 +6,7 @@ Boards can setup reserved flash with _ld_reserved_flash_size in board.ld. */ ENTRY(Reset_Handler) -code_size = 2M; +code_size = _ld_flash_size >= 4M ? 2M : 1M; _ld_default_stack_size = 20K; /* Default reserved flash to nothing. */ @@ -52,6 +52,20 @@ SECTIONS . = ALIGN(4); } > FLASH_IVT + /* Align for 256 ISR entries and place first in flash. Otherwise the UF2 + bootloader can't find it because it uses its own flash_config and ivt. */ + .isr_vector : ALIGN(4 * 256) + { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + . = ALIGN(4); + } > ITCM AT> FLASH_FIRMWARE + _ld_isr_destination = ADDR(.isr_vector); + _ld_isr_flash_copy = LOADADDR(.isr_vector); + _ld_isr_size = SIZEOF(.isr_vector); + /* Used by the bootloader to start user code. */ + __VECTOR_TABLE = LOADADDR(.isr_vector); + .text : { . = ALIGN(4); @@ -146,19 +160,6 @@ SECTIONS _ld_itcm_flash_copy = LOADADDR(.itcm); _ld_itcm_size = SIZEOF(.itcm); - /* Align for 256 ISR entries */ - .isr_vector : ALIGN(4 * 256) - { - . = ALIGN(4); - KEEP(*(.isr_vector)) /* Startup code */ - . = ALIGN(4); - } > ITCM AT> FLASH_FIRMWARE - _ld_isr_destination = ADDR(.isr_vector); - _ld_isr_flash_copy = LOADADDR(.isr_vector); - _ld_isr_size = SIZEOF(.isr_vector); - /* Used by the bootloader to start user code. */ - __VECTOR_TABLE = LOADADDR(.isr_vector); - .dtcm_data : { . = ALIGN(4); diff --git a/supervisor/linker.h b/supervisor/linker.h index c6ee8b4218..9666c4ca12 100644 --- a/supervisor/linker.h +++ b/supervisor/linker.h @@ -33,7 +33,7 @@ #define PLACE_IN_DTCM_DATA(name) name __attribute__((section(".dtcm_data." #name))) #define PLACE_IN_DTCM_BSS(name) name __attribute__((section(".dtcm_bss." #name))) // Don't inline ITCM functions because that may pull them out of ITCM into other sections. -#define PLACE_IN_ITCM(name) __attribute__((section(".itcm." #name),noinline)) name +#define PLACE_IN_ITCM(name) __attribute__((section(".itcm." #name),noinline,aligned(4))) name #else #define PLACE_IN_DTCM_DATA(name) name #define PLACE_IN_DTCM_BSS(name) name diff --git a/tests/perf_bench/benchrun.py b/tests/perf_bench/benchrun.py index 0092ecaa33..87b6489d07 100644 --- a/tests/perf_bench/benchrun.py +++ b/tests/perf_bench/benchrun.py @@ -4,7 +4,7 @@ def bm_run(N, M): except ImportError: import time - ticks_us = lambda: int(time.monotonic_ns() / 1000) + ticks_us = lambda: int(time.monotonic_ns() // 1000) ticks_diff = lambda a, b: a - b # Pick sensible parameters given N, M diff --git a/tests/run-perfbench.py b/tests/run-perfbench.py index 5f299281fd..3d3639e057 100755 --- a/tests/run-perfbench.py +++ b/tests/run-perfbench.py @@ -7,8 +7,12 @@ import os import subprocess import sys +import time import argparse from glob import glob +from rich.live import Live +from rich.console import Console +from rich.table import Table sys.path.append("../tools") import pyboard @@ -37,7 +41,7 @@ def compute_stats(lst): return avg, var**0.5 -def run_script_on_target(target, script): +def run_script_on_target(target, script, run_command=None): output = b"" err = None @@ -45,50 +49,72 @@ def run_script_on_target(target, script): # Run via pyboard interface try: target.enter_raw_repl() + start_ts = time.monotonic_ns() output = target.exec_(script) + if run_command: + start_ts = time.monotonic_ns() + output = target.exec_(run_command) + end_ts = time.monotonic_ns() except pyboard.PyboardError as er: + end_ts = time.monotonic_ns() err = er + finally: + target.exit_raw_repl() else: # Run local executable try: + if run_command: + script += run_command + start_ts = time.monotonic_ns() p = subprocess.run( target, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=script ) + end_ts = time.monotonic_ns() output = p.stdout except subprocess.CalledProcessError as er: + end_ts = time.monotonic_ns() err = er - return str(output.strip(), "ascii"), err + return str(output.strip(), "ascii"), err, (end_ts - start_ts) // 1000 def run_feature_test(target, test): with open("feature_check/" + test + ".py", "rb") as f: script = f.read() - output, err = run_script_on_target(target, script) + output, err, _ = run_script_on_target(target, script) if err is None: return output else: return "CRASH: %r" % err -def run_benchmark_on_target(target, script): - output, err = run_script_on_target(target, script) +def run_benchmark_on_target(target, script, run_command=None): + output, err, runtime_us = run_script_on_target(target, script, run_command) if err is None: time, norm, result = output.split(None, 2) try: - return int(time), int(norm), result + return int(time), int(norm), result, runtime_us except ValueError: - return -1, -1, "CRASH: %r" % output + return -1, -1, "CRASH: %r" % output, runtime_us else: - return -1, -1, "CRASH: %r" % err + return -1, -1, "CRASH: %r" % err, runtime_us -def run_benchmarks(target, param_n, param_m, n_average, test_list): +def run_benchmarks(console, target, param_n, param_m, n_average, test_list): skip_complex = run_feature_test(target, "complex") != "complex" skip_native = run_feature_test(target, "native_check") != "native" + table = Table(show_header=True) + table.add_column("Test") + table.add_column("Time", justify="right") + table.add_column("Score", justify="right") + table.add_column("Ref Time", justify="right") + + live = Live(table, console=console) + live.start() + for test_file in sorted(test_list): - print(test_file + ": ", end="") + # print(test_file + ": ", end="") # Check if test should be skipped skip = ( @@ -99,6 +125,7 @@ def run_benchmarks(target, param_n, param_m, n_average, test_list): ) if skip: print("skip") + table.add_row(test_file, *(["skip"] * 6)) continue # Create test script @@ -106,7 +133,7 @@ def run_benchmarks(target, param_n, param_m, n_average, test_list): test_script = f.read() with open(BENCH_SCRIPT_DIR + "benchrun.py", "rb") as f: test_script += f.read() - test_script += b"bm_run(%u, %u)\n" % (param_n, param_m) + bm_run = b"bm_run(%u, %u)\n" % (param_n, param_m) # Write full test script if needed if 0: @@ -115,12 +142,15 @@ def run_benchmarks(target, param_n, param_m, n_average, test_list): # Run MicroPython a given number of times times = [] + runtimes = [] scores = [] error = None result_out = None for _ in range(n_average): - time, norm, result = run_benchmark_on_target(target, test_script) - if time < 0 or norm < 0: + self_time, norm, result, runtime_us = run_benchmark_on_target( + target, test_script, bm_run + ) + if self_time < 0 or norm < 0: error = result break if result_out is None: @@ -128,30 +158,43 @@ def run_benchmarks(target, param_n, param_m, n_average, test_list): elif result != result_out: error = "FAIL self" break - times.append(time) - scores.append(1e6 * norm / time) + times.append(self_time) + runtimes.append(runtime_us) + scores.append(1e6 * norm / self_time) # Check result against truth if needed if error is None and result_out != "None": - _, _, result_exp = run_benchmark_on_target(PYTHON_TRUTH, test_script) + _, _, result_exp, _ = run_benchmark_on_target(PYTHON_TRUTH, test_script, bm_run) if result_out != result_exp: error = "FAIL truth" if error is not None: - print(error) + print(test_file, error) + if error == "no matching params": + table.add_row(test_file, *([None] * 3)) + else: + table.add_row(test_file, *(["error"] * 3)) else: t_avg, t_sd = compute_stats(times) + r_avg, r_sd = compute_stats(runtimes) s_avg, s_sd = compute_stats(scores) - print( - "{:.2f} {:.4f} {:.2f} {:.4f}".format( - t_avg, 100 * t_sd / t_avg, s_avg, 100 * s_sd / s_avg - ) + # print( + # "{:.2f} {:.4f} {:.2f} {:.4f} {:.2f} {:.4f}".format( + # t_avg, 100 * t_sd / t_avg, s_avg, 100 * s_sd / s_avg, r_avg, 100 * r_sd / r_avg + # ) + # ) + table.add_row( + test_file, + f"{t_avg:.2f}±{100 * t_sd / t_avg:.1f}%", + f"{s_avg:.2f}±{100 * s_sd / s_avg:.1f}%", + f"{r_avg:.2f}±{100 * r_sd / r_avg:.1f}%", ) if 0: print(" times: ", times) print(" scores:", scores) - sys.stdout.flush() + live.update(table, refresh=True) + live.stop() def parse_output(filename): @@ -268,9 +311,10 @@ def main(): else: tests = sorted(args.files) + console = Console() print("N={} M={} n_average={}".format(N, M, n_average)) - run_benchmarks(target, N, M, n_average, tests) + run_benchmarks(console, target, N, M, n_average, tests) if isinstance(target, pyboard.Pyboard): target.exit_raw_repl() diff --git a/tools/cpboard.py b/tools/cpboard.py index 658cf512a2..12d7d374a3 100644 --- a/tools/cpboard.py +++ b/tools/cpboard.py @@ -81,6 +81,7 @@ class REPL: else: timeout_count += 1 if timeout is not None and timeout_count >= 100 * timeout: + print("timeout") raise TimeoutError(110, "timeout waiting for", ending) time.sleep(0.01) return data @@ -164,7 +165,10 @@ class Disk: self._path = mount[0][1] else: name = os.path.basename(dev) - sh.pmount("-tvfat", dev, name, _timeout=10) + try: + sh.pmount("-tvfat", dev, name, _timeout=10) + except sh.CommandNotFound: + raise ValueError() self.mountpoint = "/media/" + name self._path = self.mountpoint @@ -516,7 +520,10 @@ class CPboard: if not part: return None - return Disk(part[0]) + try: + return Disk(part[0]) + except ValueError: + return None @property def firmware(self): @@ -555,14 +562,17 @@ PyboardError = CPboardError class Pyboard: def __init__(self, device, baudrate=115200, user="micro", password="python", wait=0): self.board = CPboard.from_try_all(device, baudrate=baudrate, wait=wait) - with self.board.disk as disk: - disk.copy("skip_if.py") + disk = self.board.disk + if disk: + with disk as open_disk: + open_disk.copy("skip_if.py") def close(self): self.board.close() def enter_raw_repl(self): self.board.open() + self.board.repl.reset() def exit_raw_repl(self): self.close() @@ -571,7 +581,12 @@ class Pyboard: return self.board.execfile(filename) def exec_(self, command, data_consumer=None): - output = self.board.exec(command, timeout=20000) + try: + output, error = self.board.repl.execute(command, timeout=20000, wait_for_response=True) + except OSError as e: + raise CPboardError("timeout", e) + if error: + raise CPboardError("exception", output, error) return output