summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-07-24 18:38:40 +0700
committerhathach <[email protected]>2026-07-24 18:38:40 +0700
commite65368ea16975710f029f7ff7e2089b9ea90186d (patch)
treed9fea14ee369f90b2887db11a52e3c74e34d9b90
parentd3eaeb06e04519b480637f6e7cb007a3de08d923 (diff)
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.
-rw-r--r--.claude/skills/etm-trace/boards.md24
-rw-r--r--.claude/skills/etm-trace/scripts/etm_capture.py11
-rw-r--r--.claude/skills/etm-trace/scripts/etm_profile.py54
-rw-r--r--hw/bsp/imxrt/boards/metro_m7_1011/ozone/metro_m7_1011.jdebug1
-rw-r--r--hw/bsp/lpc18/boards/lpcxpresso18s37/board.h6
-rw-r--r--hw/bsp/lpc43/boards/lpcxpresso43s67/board.h6
-rw-r--r--hw/bsp/ra/boards/ra8m1_ek/ozone/ra8m1.jdebug1
-rw-r--r--hw/bsp/ra/family.c30
-rw-r--r--hw/bsp/same7x/family.c1
-rw-r--r--hw/bsp/stm32h7/boards/stm32h743eval/ozone/stm32h743.jdebug1
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);