') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Added generic BSP framework and PolarFire SoC Icicle Kit target by AmmarOkla12772 · Pull Request #49 · eclipse-threadx/samplex · GitHub
Skip to content

Added generic BSP framework and PolarFire SoC Icicle Kit target - #49

Merged
fdesbiens merged 9 commits into
eclipse-threadx:devfrom
AmmarOkla12772:feat/polarfire-framework
Aug 31, 2026
Merged

Added generic BSP framework and PolarFire SoC Icicle Kit target#49
fdesbiens merged 9 commits into
eclipse-threadx:devfrom
AmmarOkla12772:feat/polarfire-framework

Conversation

@AmmarOkla12772

@AmmarOkla12772AmmarOkla12772 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Adds a Microchip PolarFire SoC Icicle Kit target for ThreadX, built on a new
reusable BSP framework, with a Renode-based CI pipeline that runs the demo in
emulation on every push.

What the contribution adds

Thanks to @AmmarOkla12772 for the target and the framework behind it:

  • PolarFire SoC target — startup, linker script, MMUART1 console, CLINT tick
    source, PLIC configuration for Hart 1 (u54_1), and a ThreadX demo exercising
    threads, queues, and timers.
  • Reusable BSP frameworkboard_init, hwtimer, and console behind a
    board-agnostic interface with a board_config.h per target, plus a
    templates/target skeleton for new ports.
  • Renode CI pipeline — builds the demo and boots it under emulation, with a
    test_renode.py harness and a polarfire_demo.robot suite.
  • Runtime self-tests — dynamic checks for _sbrk bounds, timer catch-up, and
    queue integrity, so the demo verifies its own platform layer at startup rather
    than trusting it.
  • Trap handling — early mtvec initialisation, synchronous trap dispatch
    separated from interrupt context save, deliberate timeout and trap hooks for
    testing, and a port collision fix.

Review follow-ups

The remaining commits address the outstanding review items. Three of them are
correctness fixes where the code looked right and the tests agreed with it.

PLIC machine-mode context

plic.h programmed context 2, the supervisor-mode context for u54_1; the
machine-mode context for that hart is context 1. The PLIC silently accepts a
write to the supervisor context, but the machine-mode enable bitmap stays clear,
so MEIP never asserts and MMUART1 interrupts were never delivered.

Threshold/claim move to 0x0C201000/0x0C201004 and the enable word to
0x0C002088. Both the context and the enable-word offset are now derived from
the source ID instead of hardcoded.

Self-tests could not fail the build

run_startup_self_tests() printed All startup verification tests PASSED!
unconditionally, and test_renode.py grepped for exactly that string — so a
failing sub-test still produced a green build. Results now feed a failure counter
that decides the summary line, and the harness fails on any [-] FAIL:.

The harness exit code also considered only ticks and the alarm, discarding the
self-test and PLIC-RX flags it had already computed. All four assertions now gate
the result, and a failing self-test no longer stops the run — one failure reports
the state of every other assertion instead of hiding it.

The PLIC path was never actually exercised

Register readback cannot tell a correct PLIC context from an incorrect one; only
a delivered interrupt can. The harness wrote a byte to MMUART1 to trigger one,
but Renode does not read its monitor from stdin (it logs "Monitor available in
telnet mode on port 1234"
), so the byte was discarded and the RX assertion never
fired.

renode/polarfire_ci.resc now drives the run: fixed virtual-time steps via
emulation RunFor, the byte injected with WriteChar, then quit. The run is
reproducible and terminates on its own instead of depending on wall clock. It
duplicates the machine setup rather than including polarfire_demo.resc, because
Renode expands $ORIGIN only in variable assignment — an included script cannot
be located relative to its includer, and both include @$ORIGIN/... and a bare
relative include hang the process rather than reporting an error.
polarfire_demo.resc stays free-running for interactive use.

Verified locally against Renode 1.16.1, both directions:

PLIC contextResultHarness exit
1 (machine)[Console RX] PLIC IRQ 91 handled: byte 'X'0
2 (supervisor)no RX line1

Both builds print an identical [+] PASS: PLIC Hart 1 (IRQ 91 prio=1 en=0x08000000 thresh=0 mie=0x800) line, which is the point.

Timer catch-up self-test

The self-test staged mtimecmp eight ticks behind by subtracting from mtime.
Renode starts mtime near zero, so the subtraction underflowed to 2^64-79900
and the test failed itself in CI. The production clamp was never wrong; the test
fed it a value real code cannot produce.

The catch-up decision is now hwtimer_next_cmp(), a pure function of the current
comparand and mtime, with hwtimer_ack() a thin wrapper. The test covers both
branches with synthetic values and touches no CLINT register — so the demo no
longer writes mtime at all, which matters because that register is the
platform-wide monotonic counter shared by every hart and this code is meant to be
copied.

Also

  • Hardened _sbrk() in templates/target — the copy-me template: bounds check
    against BSP_RAM_END, reject underflow and pointer overflow, return
    (void *)-1 with errno, and take ptrdiff_t so it stays correct on 64-bit.
  • Implemented __malloc_lock/__malloc_unlock instead of leaving them empty, so
    the newlib arena is genuinely serialised rather than only appearing to be.
  • Gave the tick source one owner: _tx_initialize_low_level calls
    hwtimer_init, board_init handles board peripherals — so board_init no
    longer runs twice.
  • Replaced the TX_TIMER_TICKS_PER_SECOND fallback in hwtimer.h with
    BSP_TICK_RATE_HZ in board_config.h. The BSP does not see the ThreadX
    headers, so the fallback silently applied whenever the two disagreed; main.c
    sees both and now carries a C99 compile-time check that they match.
  • Derived the demo sleep intervals from TX_TIMER_TICKS_PER_SECOND.
  • Pinned Renode to 1.16.1, checksum-verified both CI downloads, and added
    timeout-minutes to both jobs as a backstop.
  • Updated polarfire_demo.robot, which asserted on telemetry strings the demo no
    longer emits, and added an RX interrupt case; corrected the target README,
    whose expected-output block showed the same stale strings.
  • Corrected the architecture.md tree and normalised the licence URL to the form
    used in AGENTS.md.

Co-authored-by: Ammar Okla ammargawkla@gmail.com
Co-authored-by: Frédéric Desbiens frederic.desbiens@eclipse-foundation.org

@AmmarOkla12772AmmarOkla12772 self-assigned this Aug 26, 2026
@AmmarOkla12772
AmmarOkla12772 marked this pull request as ready for review August 26, 2026 18:20
fdesbiensand others added 3 commits August 28, 2026 12:03
Addressed the outstanding review items on the PolarFire SoC target.
PLIC: plic.h programmed context 2, which is the supervisor-mode context for
u54_1 on PolarFire SoC; the machine-mode context for that hart is context 1.
Writing the supervisor context is silently accepted by the PLIC but leaves the
machine-mode enable bitmap clear, so MEIP never asserts and MMUART1 interrupts
could not be delivered. Threshold/claim move to 0x0C201000/0x0C201004 and the
enable word to 0x0C002088. The context and enable-word offsets are now derived
from the source ID rather than hardcoded.
Self-tests: run_startup_self_tests() printed "All startup verification tests
PASSED!" unconditionally, and test_renode.py grepped for exactly that string,
so a failing sub-test still produced a green build. Results now feed a failure
counter that decides the summary line, and the harness fails on any "[-] FAIL:".
Renode harness: the exit code considered only ticks and the alarm, discarding
the self-test and PLIC-RX flags it computed. All four assertions now gate the
result, and the harness injects a byte into MMUART1 so the PLIC path is
actually exercised instead of only having its registers inspected.
The timer catch-up self-test no longer writes mtime. That register is the
platform-wide monotonic counter shared by every hart, and this demo is meant to
be copied. Only the per-hart mtimecmp is staged into the past, with the expected
result bracketed by mtime sampled either side of hwtimer_ack().
Also:
- Hardened _sbrk() in templates/target, which is the copy-me template: bounds
check against BSP_RAM_END, reject underflow and pointer overflow, return
(void *)-1 with errno, and take ptrdiff_t so it stays correct on 64-bit.
- Implemented __malloc_lock/__malloc_unlock instead of leaving them empty, so
the newlib arena is genuinely serialised rather than only appearing to be.
- Gave the tick source one owner: _tx_initialize_low_level calls hwtimer_init,
board_init handles board peripherals, so board_init no longer runs twice.
- Pinned Renode to 1.16.1 and checksum-verified both CI downloads.
- Updated polarfire_demo.robot, which asserted on telemetry strings the demo no
longer emits, and added an RX interrupt case.
- Derived the demo sleep intervals from TX_TIMER_TICKS_PER_SECOND.
- Corrected the architecture.md tree and normalised the licence URL to the form
used in AGENTS.md.
Assisted-by: Claude Code (Opus 5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous self-test staged mtimecmp eight ticks behind by subtracting from
mtime. Renode starts mtime near zero, so that subtraction underflowed to
2^64-79900 and the test failed itself in CI. The production clamp was never
wrong; the test fed it a value real code cannot produce.
Extracted the catch-up decision into hwtimer_next_cmp(), a pure function of the
current comparand and mtime, and left hwtimer_ack() as a thin wrapper. The test
now covers both branches with synthetic values and touches no CLINT register at
all, which also removes the last reason for the demo to write timer state.
Replaced the TX_TIMER_TICKS_PER_SECOND fallback in hwtimer.h with
BSP_TICK_RATE_HZ in board_config.h. The BSP does not see the ThreadX headers,
so that fallback silently applied whenever the two disagreed; main.c sees both
and now carries a C99 compile-time check that they match.
The Renode harness no longer stops at the first failing self-test, so a single
failure reports the state of every other assertion instead of hiding it.
Assisted-by: Claude Code (Opus 5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…xercised
The RX assertion added in the previous commit never fired in CI. Renode does not
read its monitor from stdin (it logs "Monitor available in telnet mode on port
1234"), so the byte the harness wrote was silently discarded and the PLIC path
still went untested.
Added renode/polarfire_ci.resc, which steps through fixed virtual-time intervals
with emulation RunFor, injects the byte itself via WriteChar, and quits. The run
is reproducible and terminates on its own rather than depending on wall clock.
It duplicates the machine setup instead of including polarfire_demo.resc because
Renode expands $ORIGIN only in variable assignment, so an included script cannot
be located relative to the file including it; both "include @$ORIGIN/..." and a
bare relative include hang the process rather than reporting an error.
polarfire_demo.resc is left free-running for interactive use.
Verified locally against Renode 1.16.1, both directions:
context 1 -> [Console RX] PLIC IRQ 91 handled: byte 'X' ... harness exit 0
context 2 -> no RX line harness exit 1
Both builds print an identical "[+] PASS: PLIC Hart 1 (IRQ 91 prio=1
en=0x08000000 thresh=0 mie=0x800)" self-test line, which is the point: register
readback cannot separate a correct context from an incorrect one, and only a
delivered interrupt can.
Also added timeout-minutes to both CI jobs as a backstop, and corrected the
target README, whose expected-output block still showed telemetry strings the
demo stopped emitting.
Assisted-by: Claude Code (Opus 5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiensfdesbiens changed the title Add generic BSP framework and PolarFire SoC Icicle Kit targetAdded generic BSP framework and PolarFire SoC Icicle Kit targetAug 31, 2026
@fdesbiens
fdesbiens merged commit 82f7ad5 into eclipse-threadx:devAug 31, 2026
3 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@AmmarOkla12772@fdesbiens@ammarokla123