Fix .bin, .hex and .uf2 with new linker sections

Also, format perfbench output in table with reference timing from
the host.
This commit is contained in:
Scott Shawcroft 2023-03-17 08:49:47 -07:00
parent 5bb8a7a7c6
commit bdf592089a
No known key found for this signature in database
GPG Key ID: 0DFD512649C052DA
6 changed files with 110 additions and 48 deletions

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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