From e65368ea16975710f029f7ff7e2089b9ea90186d Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 24 Jul 2026 18:38:40 +0700 Subject: [PATCH] address #3787 reviews: bsp fixes, script hardening, board-note accuracy Bot findings (Copilot/Codex): no-op board_trace_pinmux stubs for lpcxpresso18s37/43s67 (TRACE_ETM otherwise broke their build), SAME70 ID_PIOD clock enable, capture-script duplicate BeforeTargetConnect on the RA references, profile-script support for --no-timestamps itraces. Deep review (whole branch): same70_xplained board row + caveat restored, stale pico2 72 MHz claim corrected to the shipped 48, explicit SetTracePortWidth(4) in the three references that relied on Ozone's default, coverage-cell guard, median-based SysTick calibration, dead session flag removed, stale RA8M1 divider comment fixed (0x02 = /4 is the validated chip max) and the debugger guard indented. EVKB bench findings: only R1884/D3 remains open (D1/D2 meter-verified); RT1176 trace width is 1 or 4 only - J-Link arms the CSSYS TPIU and its own sampler at 4-bit for any width>=2 request; a powered MCU-Link USB breaks the external probe even with JP4 shorted. --- .claude/skills/etm-trace/boards.md | 24 +++++++-- .../skills/etm-trace/scripts/etm_capture.py | 11 ++-- .../skills/etm-trace/scripts/etm_profile.py | 54 ++++++++++++++----- .../metro_m7_1011/ozone/metro_m7_1011.jdebug | 1 + hw/bsp/lpc18/boards/lpcxpresso18s37/board.h | 6 +++ hw/bsp/lpc43/boards/lpcxpresso43s67/board.h | 6 +++ hw/bsp/ra/boards/ra8m1_ek/ozone/ra8m1.jdebug | 1 + hw/bsp/ra/family.c | 30 ++++++----- hw/bsp/same7x/family.c | 1 + .../stm32h743eval/ozone/stm32h743.jdebug | 1 + 10 files changed, 100 insertions(+), 35 deletions(-) diff --git a/.claude/skills/etm-trace/boards.md b/.claude/skills/etm-trace/boards.md index ea6c6181e..ccebefbf2 100644 --- a/.claude/skills/etm-trace/boards.md +++ b/.claude/skills/etm-trace/boards.md @@ -23,11 +23,12 @@ reference. | ea4088_quickstart | 120 MHz | 120 MHz | 4 | 0 (unset) | J7 (fully wired) | — | | nrf52840dk | 64 MHz | 16 MHz (hw cap) | 4 | 0 (unset) | solder P25, SW7 → Alt | — | | nrf5340dk (M33) | 64 MHz | 16 MHz (TAD, forced) | 4 | +3 ns | mount P25; cut SB27/SB28 | — | -| mimxrt1170_evkb | 996 MHz | 50 MHz (root/2) | 1 | 0 | weld 0 Ω R1881-R1886; JP4 shorted; J58 (populated) | reflow R1882-R1884 (D1-D3 open) → width 4 | +| mimxrt1170_evkb | 996 MHz | 50 MHz (root/2) | 1 | 0 | weld 0 Ω R1881-R1886; JP4 shorted; J58 (populated) | re-weld R1884 (D3 open; D1/D2 meter-verified good) → width 4 | | ra6m5_ek (M33) | 200 MHz | 25 MHz (TRCLK/4 /2) | 4 | 0 (unset) | J9 closed; native J20 trace | — | | ra8m1_ek (M85) | 480 MHz | 60 MHz (TRCLK/4 /2) | 4 | 0 (unset) | J9 closed + Table 7 jumpers | — | | raspberry_pi_pico2 (RP2350 M33) | 48 MHz | 24 MHz (clk_sys/2) | 4 | 0 (unset) | fly-wire GPIO1-5 → MIPI20 (map in jdebug) | 72-80 MHz per seating (re-qualify); >80 needs V3 probe + trace board | | same54_xplained (E54 M4F) | 120 MHz | 60 MHz (CPU/2) | 4 | 0 (unset) | none — populated 20-pin ETM header | — | +| same70_xplained (E70 M7) | 300 MHz | 37.5 MHz (PCK3/2) | 1 | 0 (unset) | solder 20-pin header on J403 (bottom) | width 4 blocked: D1 (J403.16) dead at speed — probe-channel crosscheck pending | | SEGGER H7/F407 ref | demo defaults | demo | 4 | demo | probe-powered: add `--power` | — | Board caveats (beyond the table): @@ -35,7 +36,7 @@ Board caveats (beyond the table): - **stm32h743eval**: startup-burst overflow at 400 MHz is normal (reduce PLLN in board.h for overflow-free capture); timestamp ref 200 MHz. - **stm32n657nucleo** (M55, flashless): **JP2 (BOOT1) must be 1** — the app - is a RAM image the debugger loads (Development boot); in flash boot theb + is a RAM image the debugger loads (Development boot); in flash boot the bootROM parks the chip un-attachable ("Can not attach to CPU"). SEGGER's KB says BOOT0/BOOT1 = 0/0 for their example — that is flash boot and it does NOT attach; the board manual's Table 11 is right. 600 MHz core kills @@ -76,7 +77,12 @@ Board caveats (beyond the table): and enables the CM7 platform trace-funnel port, which J-Link doesn't program: without it everything reads register-perfect yet zero data arrives. JP4 must be shorted (disables MCU-Link SWD) for the external - J-Trace on J58. FlexSPI apps: ROM bootloader must set SP/PC — the + J-Trace on J58; a powered MCU-Link USB breaks the external probe's connect + even with JP4 shorted - power the board from another port. **Width is 1 or + 4 only**: a width-2 request arms the CSSYS TPIU (E004_6000) and the probe + sampler at 4-bit anyway (CSPSR reads 0x8 after a width-2 session; a live + CSPSR=2 poke with LAR unlock + Trace.Clear still captures nothing because + the probe keeps sampling 4-bit). FlexSPI apps: ROM bootloader must set SP/PC — the committed reset/download hooks handle this. Startup-burst overflow at 996 MHz is normal. No Ethernet (100M) while tracing. - **ra6m5_ek**: TRCKCR div-2 (100 MHz TRCLK = 50 MHz pin, the chip max) is @@ -118,7 +124,7 @@ Board caveats (beyond the table): trace component map (funnel/TPIU/ETM are not in the ROM table → "Required trace components for pin trace not found", 0 fetches) and re-arms the whole chip-side path via `OnTraceStart` at every resume. Firmware therefore does - no trace setup; TRACE_ETM builds only (a) pin clk_sys to 72 MHz from crt0 + no trace setup; TRACE_ETM builds only (a) pin clk_sys to 48 MHz from crt0 (board.cmake) — the fly-wire ceiling: 96/150 MHz kill the stream in the startup burst at any sample timing (and at 150 MHz the saturated probe stops answering halts, "CPU could not be halted"); any post-arm clock @@ -136,5 +142,15 @@ Board caveats (beyond the table): `trace_etm_init` feeds it GCLK0. Pins PC24-28 mux to function H. The populated 20-pin header runs chip-max 60 MHz TRACECLK width 4 with no timing adjustment - the connector-vs-flywire contrast board. +- **same70_xplained**: J403 is a bottom-side bare footprint — solder the + header. Trace pins PD4-7 double as the KSZ8081 PHY's RMII receive outputs: + TRACE_ETM builds hold it in reset (PHY_RESET=PC10) or it drives against + the stream. TPIU clock = PCK3 (datasheet 16.7.4), run at MCK/2; TPIU + programming while PCK3 is stopped is silently LOST — the reference starts + PCK3 in the post-reset/download hooks (a reset wipes the PMC, so + AfterTargetConnect is too early). Width 1 validated at the stock 300 MHz + core; width 2/4 blocked on a dead D1 line at J403.16 (clean at DC by + meter, dead at speed — probe-channel crosscheck on a known-good width-4 + board pending). No Ethernet while tracing. - **SEGGER ref boards**: run their own demo (ladder step 3 flags); `--isr` degrades gracefully without a live SysTick. diff --git a/.claude/skills/etm-trace/scripts/etm_capture.py b/.claude/skills/etm-trace/scripts/etm_capture.py index 6468be12d..af6c1ec68 100644 --- a/.claude/skills/etm-trace/scripts/etm_capture.py +++ b/.claude/skills/etm-trace/scripts/etm_capture.py @@ -126,6 +126,12 @@ def resolve_board(board): name, block = m.group(1), m.group(0) if name == "OnProjectLoad": continue + elif name == "BeforeTargetConnect": + # the generated project synthesizes its own BeforeTargetConnect + # (JLINK_SCRIPT_HOOK) from the SetJLinkScript regex below; + # inheriting the reference's copy too would emit a duplicate + # function definition + continue elif name == "AfterTargetReset": cfg["reset_hook"] = block elif name == "AfterTargetDownload": @@ -472,7 +478,6 @@ def main(): start_new_session=True) profile_out = os.path.join(outdir, "code_profile.txt") itrace_out = os.path.join(outdir, "itrace.csv") - ok = False try: ses.connect(20) ses.drain(3) # version banner @@ -513,7 +518,6 @@ def main(): ses.wait_echo(f'Export.PowerGraphs ("{outdir}/power.csv")', 60) ses.send("Debug.Stop", 5) ses.send("File.Exit", 2) - ok = True finally: for _ in range(15): if proc.poll() is not None: @@ -551,9 +555,6 @@ def main(): sys.exit("error: session completed but NO trace data was collected " "(profile totals are zero) - trace signal not reaching the " "probe: check wiring/connector, trace pinmux, sample timing.") - if not ok: - sys.exit("error: session did not complete cleanly (see session.log)") - print(f"\ncapture OK: {outdir}") print(f" code_profile.txt ({os.path.getsize(profile_out)} bytes)") if args.trace_csv: diff --git a/.claude/skills/etm-trace/scripts/etm_profile.py b/.claude/skills/etm-trace/scripts/etm_profile.py index c5ddf27a7..b00f6f1d2 100644 --- a/.claude/skills/etm-trace/scripts/etm_profile.py +++ b/.claude/skills/etm-trace/scripts/etm_profile.py @@ -66,7 +66,7 @@ def parse_profile(path): for module, name, cells in rows(lines[cov_start:prof_start]): m_src = cov_pat.match(cells[0]) if cells else None m_inst = cov_pat.match(cells[1]) if len(cells) > 1 else None - if name == "Total" and m_inst: + if name == "Total" and m_inst and m_src: totals["src_cov"] = num(m_src.group(1)), num(m_src.group(2)) totals["inst_cov"] = num(m_inst.group(1)), num(m_inst.group(2)) elif name in funcs and m_inst: @@ -120,14 +120,29 @@ def iter_itrace(path): caller reads unit separately with itrace_unit().""" with open(path, newline="", errors="replace") as f: rd = csv.reader(f) - next(rd, None) + hdr = next(rd, None) or [] + # --no-timestamps captures drop the Timestamp column entirely: locate + # the Address column from the header and yield t=None for such rows + # (consumers count instructions but skip time math) + has_ts = any("Timestamp" in c for c in hdr) + try: + addr_i = next(i for i, c in enumerate(hdr) if "Address" in c) + except StopIteration: + addr_i = 1 if has_ts else 0 for row in rd: - if not row or row[0] == "PC" or len(row) < 2: + if not row or len(row) <= addr_i: continue try: - yield float(row[0]), int(row[1], 16) + addr = int(row[addr_i], 16) except ValueError: continue + if has_ts and row[0] != "PC": + try: + yield float(row[0]), addr + continue + except ValueError: + pass + yield None, addr def itrace_unit(path): @@ -144,6 +159,8 @@ def time_by_func(path, syms): sample = [] t_prev = None for t, _ in iter_itrace(path): + if t is None: + continue if t_prev is not None and t_prev - t > 0: sample.append(t_prev - t) if len(sample) >= 200000: @@ -158,6 +175,8 @@ def time_by_func(path, syms): fn = addr_to_func(syms, a) if fn: cf[fn] = cf.get(fn, 0) + 1 + if t is None: + continue if t_prev is not None: d = t_prev - t if 0 < d < cap and fn: @@ -180,6 +199,8 @@ def isr_report(itrace, elf, isr_arg, top): usb_rows, tick_rows, tmin, tmax = [], [], None, None for t, a in iter_itrace(itrace): + if t is None: + continue # --no-timestamps capture: the <20-rows message below applies tmin = t if tmin is None else min(tmin, t) tmax = t if tmax is None else max(tmax, t) if any(lo <= a < hi for lo, hi in body): @@ -195,10 +216,12 @@ def isr_report(itrace, elf, isr_arg, top): f"({len(tick_rows)} rows) - capture with timestamps enabled") return - # rough raw-units-per-1ms from the large mode of consecutive tick deltas + # rough raw-units-per-1ms from the large mode of consecutive tick deltas; + # threshold from the median, not the max - one trace-overflow gap would + # otherwise inflate the cut and leave only outliers in the sample deltas = [b[0] - a[0] for a, b in zip(tick_rows, tick_rows[1:])] - big = [d for d in deltas if d > max(deltas) / 10] - raw_ms = statistics.median(big) + big = [d for d in deltas if d > 10 * statistics.median(deltas)] + raw_ms = statistics.median(big) if big else statistics.median(deltas) gap = 0.03 * raw_ms # 30 us in raw units edge = 0.05 * raw_ms @@ -390,11 +413,18 @@ def main(): cshare = {k: v / tc for k, v in cf.items()} unit = itrace_unit(itrace) print(f"\n## Instruction history (itrace.csv, unit '{unit}')\n") - print(f"- {n:,} instructions; top {args.top} by TIME share " - f"(vs instruction share):") - for name, ts in sorted(tshare.items(), key=lambda kv: -kv[1])[:args.top]: - print(f" - `{short(name)}`: {100 * ts:.1f}% time, " - f"{100 * cshare.get(name, 0):.1f}% instructions") + if not tf: + print(f"- {n:,} instructions, NO timestamps (--no-timestamps " + f"capture): time shares unavailable, top {args.top} by " + f"instruction share:") + for name, cs in sorted(cshare.items(), key=lambda kv: -kv[1])[:args.top]: + print(f" - `{short(name)}`: {100 * cs:.1f}% instructions") + else: + print(f"- {n:,} instructions; top {args.top} by TIME share " + f"(vs instruction share):") + for name, ts in sorted(tshare.items(), key=lambda kv: -kv[1])[:args.top]: + print(f" - `{short(name)}`: {100 * ts:.1f}% time, " + f"{100 * cshare.get(name, 0):.1f}% instructions") lines_csv = os.path.join(args.capture_dir, "profile_lines.csv") if os.path.isfile(lines_csv): diff --git a/hw/bsp/imxrt/boards/metro_m7_1011/ozone/metro_m7_1011.jdebug b/hw/bsp/imxrt/boards/metro_m7_1011/ozone/metro_m7_1011.jdebug index fdb8b30a0..80489b61c 100644 --- a/hw/bsp/imxrt/boards/metro_m7_1011/ozone/metro_m7_1011.jdebug +++ b/hw/bsp/imxrt/boards/metro_m7_1011/ozone/metro_m7_1011.jdebug @@ -10,6 +10,7 @@ */ void OnProjectLoad (void) { Project.SetTraceSource ("Trace Pins"); + Project.SetTracePortWidth (4); Project.SetTraceTiming (50, 50, 50, 50); Project.SetDevice ("MIMXRT1011xxx4A"); Project.SetHostIF ("USB", ""); diff --git a/hw/bsp/lpc18/boards/lpcxpresso18s37/board.h b/hw/bsp/lpc18/boards/lpcxpresso18s37/board.h index 2cf4dbdf8..1be07c49e 100644 --- a/hw/bsp/lpc18/boards/lpcxpresso18s37/board.h +++ b/hw/bsp/lpc18/boards/lpcxpresso18s37/board.h @@ -76,6 +76,12 @@ static inline void board_lpc18_pinmux(void) Chip_SCU_SetPinMuxing(pinmuxing, sizeof(pinmuxing) / sizeof(PINMUX_GRP_T)); } + +// TRACE_ETM builds: no trace header is wired out on the LPCXpresso18S37 - +// provide the no-op the family init expects (see mcb1800/ea4357 for a +// board that routes the trace pins) +static inline void board_trace_pinmux(void) {} + #ifdef __cplusplus } #endif diff --git a/hw/bsp/lpc43/boards/lpcxpresso43s67/board.h b/hw/bsp/lpc43/boards/lpcxpresso43s67/board.h index 4427905e8..6a317b5dc 100644 --- a/hw/bsp/lpc43/boards/lpcxpresso43s67/board.h +++ b/hw/bsp/lpc43/boards/lpcxpresso43s67/board.h @@ -71,6 +71,12 @@ static const PINMUX_GRP_T pinmuxing[] = { {0x2, 5, SCU_MODE_INBUFF_EN | SCU_MODE_PULLUP | SCU_MODE_FUNC4 }, }; + +// TRACE_ETM builds: no trace header is wired out on the LPCXpresso43S67 - +// provide the no-op the family init expects (see mcb1800/ea4357 for a +// board that routes the trace pins) +static inline void board_trace_pinmux(void) {} + #ifdef __cplusplus } #endif diff --git a/hw/bsp/ra/boards/ra8m1_ek/ozone/ra8m1.jdebug b/hw/bsp/ra/boards/ra8m1_ek/ozone/ra8m1.jdebug index f0a43bf5f..927eeda72 100644 --- a/hw/bsp/ra/boards/ra8m1_ek/ozone/ra8m1.jdebug +++ b/hw/bsp/ra/boards/ra8m1_ek/ozone/ra8m1.jdebug @@ -10,6 +10,7 @@ */ void OnProjectLoad (void) { Project.SetTraceSource ("Trace Pins"); + Project.SetTracePortWidth (4); Project.SetDevice ("R7FA8M1AH"); Project.SetHostIF ("USB", ""); Project.SetTargetIF ("SWD"); diff --git a/hw/bsp/ra/family.c b/hw/bsp/ra/family.c index 3c548fa7f..307644972 100644 --- a/hw/bsp/ra/family.c +++ b/hw/bsp/ra/family.c @@ -103,25 +103,27 @@ void board_init(void) { // standalone boot must skip trace init or the write wedges the chip into // an un-attachable crash loop (recover: power-cycle + immediate erase) if (DCB->DHCSR & DCB_DHCSR_C_DEBUGEN_Msk) { - // TRCKCR is protected by PRCR bit0 register - R_SYSTEM->PRCR = (uint16_t) (BSP_PRV_PRCR_KEY | 0x01); + // TRCKCR is protected by PRCR bit0 register + R_SYSTEM->PRCR = (uint16_t) (BSP_PRV_PRCR_KEY | 0x01); - // TCLK pin = TRCLK/2; set the divider with TRCKEN=0 first (HUM procedure). - // Values are the empirical per-board ceilings: one step below the divider - // at which the stream dies mid-run. + // TCLK pin = TRCLK/2; set the divider with TRCKEN=0 first (HUM procedure). + // Values are the empirical per-board ceilings. #if defined(BSP_MCU_GROUP_RA8M1) - // 480 MHz CPU: /8 -> 60 MHz TRCLK, 30 MHz pin (/4 = 60 MHz pin dies) - R_SYSTEM->TRCKCR = 0x02; - R_SYSTEM->TRCKCR = R_SYSTEM_TRCKCR_TRCKEN_Msk | 0x02; + // 480 MHz CPU: /4 -> 120 MHz TRCLK, 60 MHz pin - chip max, clean on + // EK-RA8M1 with the committed empty-OnTraceStart JLinkScript (which + // defers the trace clock to firmware; without it the FSP MOCO->PLL + // switch steps the clock mid-stream and any divider fails) + R_SYSTEM->TRCKCR = 0x02; + R_SYSTEM->TRCKCR = R_SYSTEM_TRCKCR_TRCKEN_Msk | 0x02; #else - // RA6M5 200 MHz CPU: /4 -> 50 MHz TRCLK, 25 MHz pin. /2 (50 MHz pin) is - // silent on the EK-RA6M5 in every combination - board path ceiling, - // reconfirmed with J9 closed and the OnTraceStart override - R_SYSTEM->TRCKCR = 0x02; - R_SYSTEM->TRCKCR = R_SYSTEM_TRCKCR_TRCKEN_Msk | 0x02; + // RA6M5 200 MHz CPU: /4 -> 50 MHz TRCLK, 25 MHz pin. /2 (50 MHz pin) is + // silent on the EK-RA6M5 in every combination - board path ceiling, + // reconfirmed with J9 closed and the OnTraceStart override + R_SYSTEM->TRCKCR = 0x02; + R_SYSTEM->TRCKCR = R_SYSTEM_TRCKCR_TRCKEN_Msk | 0x02; #endif - R_SYSTEM->PRCR = (uint16_t) BSP_PRV_PRCR_KEY; + R_SYSTEM->PRCR = (uint16_t) BSP_PRV_PRCR_KEY; } #endif diff --git a/hw/bsp/same7x/family.c b/hw/bsp/same7x/family.c index 6a3466354..d99c17efb 100644 --- a/hw/bsp/same7x/family.c +++ b/hw/bsp/same7x/family.c @@ -82,6 +82,7 @@ void board_init(void) { PMC->PMC_SCER = PMC_SCER_PCK3; while (!(PMC->PMC_SR & PMC_SR_PCKRDY3)) {} } + _pmc_enable_periph_clock(ID_PIOD); uint32_t const clk_pin = PIO_PD8D_TPIU_TRACECLK; uint32_t const dat_pin = PIO_PD4C_TPIU_TRACED0 | PIO_PD5C_TPIU_TRACED1 | PIO_PD6C_TPIU_TRACED2 | PIO_PD7C_TPIU_TRACED3; diff --git a/hw/bsp/stm32h7/boards/stm32h743eval/ozone/stm32h743.jdebug b/hw/bsp/stm32h7/boards/stm32h743eval/ozone/stm32h743.jdebug index a8645c372..f9780147d 100644 --- a/hw/bsp/stm32h7/boards/stm32h743eval/ozone/stm32h743.jdebug +++ b/hw/bsp/stm32h7/boards/stm32h743eval/ozone/stm32h743.jdebug @@ -10,6 +10,7 @@ */ void OnProjectLoad (void) { Project.SetTraceSource ("Trace Pins"); + Project.SetTracePortWidth (4); Project.SetTraceTiming (100, 100, 100, 100); Project.SetSWO (0); Edit.SysVar (VAR_TRACE_CORE_CLOCK, 200000000);