Skip to content

Moved the demo into apps/ and made both targets build it - #57

Open
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app
Open

Moved the demo into apps/ and made both targets build it#57
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this changes

The framework documented a portable application layer it did not have. Each target owned its own demo under app/, so nothing checked that a demo could actually move between boards — which is how apps/ stayed documented for weeks while not existing (#52 walked the claim back).

This adds apps/, moves the NUCLEO-F401RE demo into apps/threadx_demo/main.c, and has the PolarFire SoC Icicle Kit build that same source as a second executable beside its LM75 monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and asserts on the same console output from both.

The rule, now written into docs/architecture.md and templates/target/README.md:

apps/ holds portable demos. targets/<Vendor>/<BOARD>/app/ holds board-specific ones. Both are legitimate; a target's app/CMakeLists.txt picks either.

PolarFire's LM75 monitor stays a target app because it genuinely models a sensor.

A third BSP interface was unavoidable

polarfire_bsp's trap.c carried a hard undefined reference to console_rx_isr_callback(), a symbol only its own demo defined. Any second executable linking that BSP failed to link, and a portable application would have had to define a PolarFire-specific ISR callback purely to say it wanted nothing. That is the same violation this effort exists to remove — the BSP reaching up into the application — expressed through the linker rather than a header.

bsp/console.h gains a registration call in the shape bsp_self_test() already established:

typedefvoid (*bsp_console_rx_fn)(charc, void*context);
voidbsp_console_set_rx_handler(bsp_console_rx_fnhandler, void*context);

The board stores a nullable pointer and checks it before dispatching, so bytes arriving with no handler attached are dropped rather than faulting. The LM75 demo registers its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a handler it never invokes, keeping the contract uniform enough to register against unconditionally. A __attribute__((weak)) default was rejected: it compiles, but keeps the up-call and is a GCC extension in a C99/MISRA codebase.

This brought the usual obligations with it — an architecture entry, a templates/target/ stub, and README rows, as PR #56 did for the first two interfaces.

Building for a second architecture found two real defects

Both were latent and neither was reachable with one target:

  • Every %lu in the demo was wrong on one of the two boards. ThreadX defines ULONG as unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 port. Each value now casts to unsigned long, matching the idiom already used in the LM75 demo.
  • Both boards' _sbrk() underflow self-test asked for a fixed -128, which is an underflow only when nothing has allocated yet. The NUCLEO's passed by accident — its printf buffer malloc fails against a small reservation, leaving the break at base. The PolarFire's failed outright once an application reached printf first. Both now hand back one byte more than has ever been taken, which underflows regardless of what ran before.

Measurements

NUCLEO-F401REPolarFire Icicle
Self-tests87
Thread stack size1024 B (unchanged)2048 B, measured peak 1048 B
Static RAM6000 B (unchanged)
Heap use under printf2944 B of 64 KB reserved

THREAD_STACK_SIZE is written in machine words rather than bytes, since every saved register and the newlib printf() call chain doubles in width on a 64-bit hart. BSP_HEAP_RESERVE_BYTES needed no change: the whole run also completes with the reservation temporarily cut to 4 KB, which bounds peak heap use across the run rather than just at startup.

Verification

Run locally on both toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1) — the versions CI pins.

  • Three Renode suites green: NUCLEO shared demo, PolarFire --app lm75, PolarFire --app threadx_demo.
  • NUCLEO builds clean under -Werror; both PolarFire ELFs build warning-free.
  • Self-tests still have teeth. With _sbrk()'s bound deliberately widened to the end of RAM, the checks still fail: two on the NUCLEO-F401RE, one on each PolarFire executable. The NUCLEO count is two rather than the three seen before this PR, because the underflow check now correctly stays green — its old failure was incidental to the fixed -128, not that assertion doing its job.

The PolarFire runner is one script with an --app argument rather than two scripts, since the ~150 lines of Renode process handling are what would drift if copied.

Notes for review

  • app/starter/ is gone, including the empty cloud_config.h placeholder that Moved the startup self-tests and RAM sizing behind new BSP interfaces #56 stopped including.
  • The banner is now board-neutral (Eclipse ThreadX Device Monitor Demo); both Renode suites and the Robot suite assert on that one line.
  • This PR also removes the "a goal, not something it provides yet" wording from docs/architecture.md and templates/target/README.md. That was scheduled for a follow-up, but its precondition — the shared demo green on both architectures — is met by this PR, and leaving those statements in place would have shipped documentation that this change makes false.

The framework documented a portable application layer it did not have: each
target owned its own demo under app/, so nothing checked that a demo could
actually move between boards. This adds apps/, moves the NUCLEO-F401RE demo
into apps/threadx_demo/main.c unchanged in behaviour, and has the PolarFire
SoC Icicle Kit build that same source as a second executable beside its LM75
monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and
asserts on the same console output from both, so the claim is now enforced
rather than stated.
apps/ holds portable demos; targets/<Vendor>/<BOARD>/app/ holds board-specific
ones, and a target's app/CMakeLists.txt picks either. The PolarFire LM75
monitor stays a target app because it genuinely models a sensor.
Linking a second executable first required removing a hard undefined reference:
polarfire_bsp's trap.c called console_rx_isr_callback(), a symbol only its own
demo defined, so any other application had to define a PolarFire-specific ISR
callback just to link. bsp/console.h gains a registration call in the shape
bsp_self_test() already established:
typedef void (*bsp_console_rx_fn)(char c, void *context);
void bsp_console_set_rx_handler(bsp_console_rx_fn handler, void *context);
The board stores a nullable pointer and checks it before dispatching, so bytes
arriving with no handler attached are dropped instead of faulting, and an
application that ignores console input defines nothing. The LM75 demo registers
its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a
handler it never invokes, which keeps the contract uniform enough to register
against unconditionally. A weak symbol was rejected: it keeps the up-call and
is a GCC extension in a C99 codebase.
Building the demo for a second architecture found two real defects:
- Every %lu in the demo was wrong on one target. ThreadX defines ULONG as
unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 one,
so each value now casts to unsigned long, matching the existing idiom in
the LM75 demo.
- Both boards' _sbrk() underflow self-test asked for a fixed -128, which is
an underflow only when nothing has allocated yet. The NUCLEO's passed by
accident of a printf buffer malloc that failed against a small reservation;
the PolarFire's failed outright once a demo reached printf first. Both now
hand back one byte more than has ever been taken, which underflows whatever
ran before them.
Thread stacks are sized in machine words rather than bytes, since every saved
register and the newlib printf() call chain double in width on a 64-bit hart:
1024 bytes on Cortex-M4 as before, 2048 on RISC-V, measured peak 1048.
Measured PolarFire heap use under printf, the first stdio on that board, is
2944 bytes; the run also completes with BSP_HEAP_RESERVE_BYTES cut to 4 KB, so
the 64 KB reservation holds unchanged.
Verified locally on both toolchains: three Renode suites green, and with the
_sbrk() bound deliberately widened the self-tests still fail - two checks on
the NUCLEO-F401RE, one on each PolarFire executable.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant

@fdesbiens
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Moved the demo into apps/ and made both targets build it by fdesbiens · Pull Request #57 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the demo into apps/ and made both targets build it - #57

Open
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app
Open

Moved the demo into apps/ and made both targets build it#57
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this changes

The framework documented a portable application layer it did not have. Each target owned its own demo under app/, so nothing checked that a demo could actually move between boards — which is how apps/ stayed documented for weeks while not existing (#52 walked the claim back).

This adds apps/, moves the NUCLEO-F401RE demo into apps/threadx_demo/main.c, and has the PolarFire SoC Icicle Kit build that same source as a second executable beside its LM75 monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and asserts on the same console output from both.

The rule, now written into docs/architecture.md and templates/target/README.md:

apps/ holds portable demos. targets/<Vendor>/<BOARD>/app/ holds board-specific ones. Both are legitimate; a target's app/CMakeLists.txt picks either.

PolarFire's LM75 monitor stays a target app because it genuinely models a sensor.

A third BSP interface was unavoidable

polarfire_bsp's trap.c carried a hard undefined reference to console_rx_isr_callback(), a symbol only its own demo defined. Any second executable linking that BSP failed to link, and a portable application would have had to define a PolarFire-specific ISR callback purely to say it wanted nothing. That is the same violation this effort exists to remove — the BSP reaching up into the application — expressed through the linker rather than a header.

bsp/console.h gains a registration call in the shape bsp_self_test() already established:

typedefvoid (*bsp_console_rx_fn)(charc, void*context);
voidbsp_console_set_rx_handler(bsp_console_rx_fnhandler, void*context);

The board stores a nullable pointer and checks it before dispatching, so bytes arriving with no handler attached are dropped rather than faulting. The LM75 demo registers its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a handler it never invokes, keeping the contract uniform enough to register against unconditionally. A __attribute__((weak)) default was rejected: it compiles, but keeps the up-call and is a GCC extension in a C99/MISRA codebase.

This brought the usual obligations with it — an architecture entry, a templates/target/ stub, and README rows, as PR #56 did for the first two interfaces.

Building for a second architecture found two real defects

Both were latent and neither was reachable with one target:

  • Every %lu in the demo was wrong on one of the two boards. ThreadX defines ULONG as unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 port. Each value now casts to unsigned long, matching the idiom already used in the LM75 demo.
  • Both boards' _sbrk() underflow self-test asked for a fixed -128, which is an underflow only when nothing has allocated yet. The NUCLEO's passed by accident — its printf buffer malloc fails against a small reservation, leaving the break at base. The PolarFire's failed outright once an application reached printf first. Both now hand back one byte more than has ever been taken, which underflows regardless of what ran before.

Measurements

NUCLEO-F401REPolarFire Icicle
Self-tests87
Thread stack size1024 B (unchanged)2048 B, measured peak 1048 B
Static RAM6000 B (unchanged)
Heap use under printf2944 B of 64 KB reserved

THREAD_STACK_SIZE is written in machine words rather than bytes, since every saved register and the newlib printf() call chain doubles in width on a 64-bit hart. BSP_HEAP_RESERVE_BYTES needed no change: the whole run also completes with the reservation temporarily cut to 4 KB, which bounds peak heap use across the run rather than just at startup.

Verification

Run locally on both toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1) — the versions CI pins.

  • Three Renode suites green: NUCLEO shared demo, PolarFire --app lm75, PolarFire --app threadx_demo.
  • NUCLEO builds clean under -Werror; both PolarFire ELFs build warning-free.
  • Self-tests still have teeth. With _sbrk()'s bound deliberately widened to the end of RAM, the checks still fail: two on the NUCLEO-F401RE, one on each PolarFire executable. The NUCLEO count is two rather than the three seen before this PR, because the underflow check now correctly stays green — its old failure was incidental to the fixed -128, not that assertion doing its job.

The PolarFire runner is one script with an --app argument rather than two scripts, since the ~150 lines of Renode process handling are what would drift if copied.

Notes for review

  • app/starter/ is gone, including the empty cloud_config.h placeholder that Moved the startup self-tests and RAM sizing behind new BSP interfaces #56 stopped including.
  • The banner is now board-neutral (Eclipse ThreadX Device Monitor Demo); both Renode suites and the Robot suite assert on that one line.
  • This PR also removes the "a goal, not something it provides yet" wording from docs/architecture.md and templates/target/README.md. That was scheduled for a follow-up, but its precondition — the shared demo green on both architectures — is met by this PR, and leaving those statements in place would have shipped documentation that this change makes false.

The framework documented a portable application layer it did not have: each
target owned its own demo under app/, so nothing checked that a demo could
actually move between boards. This adds apps/, moves the NUCLEO-F401RE demo
into apps/threadx_demo/main.c unchanged in behaviour, and has the PolarFire
SoC Icicle Kit build that same source as a second executable beside its LM75
monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and
asserts on the same console output from both, so the claim is now enforced
rather than stated.
apps/ holds portable demos; targets/<Vendor>/<BOARD>/app/ holds board-specific
ones, and a target's app/CMakeLists.txt picks either. The PolarFire LM75
monitor stays a target app because it genuinely models a sensor.
Linking a second executable first required removing a hard undefined reference:
polarfire_bsp's trap.c called console_rx_isr_callback(), a symbol only its own
demo defined, so any other application had to define a PolarFire-specific ISR
callback just to link. bsp/console.h gains a registration call in the shape
bsp_self_test() already established:
typedef void (*bsp_console_rx_fn)(char c, void *context);
void bsp_console_set_rx_handler(bsp_console_rx_fn handler, void *context);
The board stores a nullable pointer and checks it before dispatching, so bytes
arriving with no handler attached are dropped instead of faulting, and an
application that ignores console input defines nothing. The LM75 demo registers
its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a
handler it never invokes, which keeps the contract uniform enough to register
against unconditionally. A weak symbol was rejected: it keeps the up-call and
is a GCC extension in a C99 codebase.
Building the demo for a second architecture found two real defects:
- Every %lu in the demo was wrong on one target. ThreadX defines ULONG as
unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 one,
so each value now casts to unsigned long, matching the existing idiom in
the LM75 demo.
- Both boards' _sbrk() underflow self-test asked for a fixed -128, which is
an underflow only when nothing has allocated yet. The NUCLEO's passed by
accident of a printf buffer malloc that failed against a small reservation;
the PolarFire's failed outright once a demo reached printf first. Both now
hand back one byte more than has ever been taken, which underflows whatever
ran before them.
Thread stacks are sized in machine words rather than bytes, since every saved
register and the newlib printf() call chain double in width on a 64-bit hart:
1024 bytes on Cortex-M4 as before, 2048 on RISC-V, measured peak 1048.
Measured PolarFire heap use under printf, the first stdio on that board, is
2944 bytes; the run also completes with BSP_HEAP_RESERVE_BYTES cut to 4 KB, so
the 64 KB reservation holds unchanged.
Verified locally on both toolchains: three Renode suites green, and with the
_sbrk() bound deliberately widened the self-tests still fail - two checks on
the NUCLEO-F401RE, one on each PolarFire executable.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant

@fdesbiens
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Moved the demo into apps/ and made both targets build it by fdesbiens · Pull Request #57 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the demo into apps/ and made both targets build it - #57

Open
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app
Open

Moved the demo into apps/ and made both targets build it#57
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this changes

The framework documented a portable application layer it did not have. Each target owned its own demo under app/, so nothing checked that a demo could actually move between boards — which is how apps/ stayed documented for weeks while not existing (#52 walked the claim back).

This adds apps/, moves the NUCLEO-F401RE demo into apps/threadx_demo/main.c, and has the PolarFire SoC Icicle Kit build that same source as a second executable beside its LM75 monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and asserts on the same console output from both.

The rule, now written into docs/architecture.md and templates/target/README.md:

apps/ holds portable demos. targets/<Vendor>/<BOARD>/app/ holds board-specific ones. Both are legitimate; a target's app/CMakeLists.txt picks either.

PolarFire's LM75 monitor stays a target app because it genuinely models a sensor.

A third BSP interface was unavoidable

polarfire_bsp's trap.c carried a hard undefined reference to console_rx_isr_callback(), a symbol only its own demo defined. Any second executable linking that BSP failed to link, and a portable application would have had to define a PolarFire-specific ISR callback purely to say it wanted nothing. That is the same violation this effort exists to remove — the BSP reaching up into the application — expressed through the linker rather than a header.

bsp/console.h gains a registration call in the shape bsp_self_test() already established:

typedefvoid (*bsp_console_rx_fn)(charc, void*context);
voidbsp_console_set_rx_handler(bsp_console_rx_fnhandler, void*context);

The board stores a nullable pointer and checks it before dispatching, so bytes arriving with no handler attached are dropped rather than faulting. The LM75 demo registers its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a handler it never invokes, keeping the contract uniform enough to register against unconditionally. A __attribute__((weak)) default was rejected: it compiles, but keeps the up-call and is a GCC extension in a C99/MISRA codebase.

This brought the usual obligations with it — an architecture entry, a templates/target/ stub, and README rows, as PR #56 did for the first two interfaces.

Building for a second architecture found two real defects

Both were latent and neither was reachable with one target:

  • Every %lu in the demo was wrong on one of the two boards. ThreadX defines ULONG as unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 port. Each value now casts to unsigned long, matching the idiom already used in the LM75 demo.
  • Both boards' _sbrk() underflow self-test asked for a fixed -128, which is an underflow only when nothing has allocated yet. The NUCLEO's passed by accident — its printf buffer malloc fails against a small reservation, leaving the break at base. The PolarFire's failed outright once an application reached printf first. Both now hand back one byte more than has ever been taken, which underflows regardless of what ran before.

Measurements

NUCLEO-F401REPolarFire Icicle
Self-tests87
Thread stack size1024 B (unchanged)2048 B, measured peak 1048 B
Static RAM6000 B (unchanged)
Heap use under printf2944 B of 64 KB reserved

THREAD_STACK_SIZE is written in machine words rather than bytes, since every saved register and the newlib printf() call chain doubles in width on a 64-bit hart. BSP_HEAP_RESERVE_BYTES needed no change: the whole run also completes with the reservation temporarily cut to 4 KB, which bounds peak heap use across the run rather than just at startup.

Verification

Run locally on both toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1) — the versions CI pins.

  • Three Renode suites green: NUCLEO shared demo, PolarFire --app lm75, PolarFire --app threadx_demo.
  • NUCLEO builds clean under -Werror; both PolarFire ELFs build warning-free.
  • Self-tests still have teeth. With _sbrk()'s bound deliberately widened to the end of RAM, the checks still fail: two on the NUCLEO-F401RE, one on each PolarFire executable. The NUCLEO count is two rather than the three seen before this PR, because the underflow check now correctly stays green — its old failure was incidental to the fixed -128, not that assertion doing its job.

The PolarFire runner is one script with an --app argument rather than two scripts, since the ~150 lines of Renode process handling are what would drift if copied.

Notes for review

  • app/starter/ is gone, including the empty cloud_config.h placeholder that Moved the startup self-tests and RAM sizing behind new BSP interfaces #56 stopped including.
  • The banner is now board-neutral (Eclipse ThreadX Device Monitor Demo); both Renode suites and the Robot suite assert on that one line.
  • This PR also removes the "a goal, not something it provides yet" wording from docs/architecture.md and templates/target/README.md. That was scheduled for a follow-up, but its precondition — the shared demo green on both architectures — is met by this PR, and leaving those statements in place would have shipped documentation that this change makes false.

The framework documented a portable application layer it did not have: each
target owned its own demo under app/, so nothing checked that a demo could
actually move between boards. This adds apps/, moves the NUCLEO-F401RE demo
into apps/threadx_demo/main.c unchanged in behaviour, and has the PolarFire
SoC Icicle Kit build that same source as a second executable beside its LM75
monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and
asserts on the same console output from both, so the claim is now enforced
rather than stated.
apps/ holds portable demos; targets/<Vendor>/<BOARD>/app/ holds board-specific
ones, and a target's app/CMakeLists.txt picks either. The PolarFire LM75
monitor stays a target app because it genuinely models a sensor.
Linking a second executable first required removing a hard undefined reference:
polarfire_bsp's trap.c called console_rx_isr_callback(), a symbol only its own
demo defined, so any other application had to define a PolarFire-specific ISR
callback just to link. bsp/console.h gains a registration call in the shape
bsp_self_test() already established:
typedef void (*bsp_console_rx_fn)(char c, void *context);
void bsp_console_set_rx_handler(bsp_console_rx_fn handler, void *context);
The board stores a nullable pointer and checks it before dispatching, so bytes
arriving with no handler attached are dropped instead of faulting, and an
application that ignores console input defines nothing. The LM75 demo registers
its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a
handler it never invokes, which keeps the contract uniform enough to register
against unconditionally. A weak symbol was rejected: it keeps the up-call and
is a GCC extension in a C99 codebase.
Building the demo for a second architecture found two real defects:
- Every %lu in the demo was wrong on one target. ThreadX defines ULONG as
unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 one,
so each value now casts to unsigned long, matching the existing idiom in
the LM75 demo.
- Both boards' _sbrk() underflow self-test asked for a fixed -128, which is
an underflow only when nothing has allocated yet. The NUCLEO's passed by
accident of a printf buffer malloc that failed against a small reservation;
the PolarFire's failed outright once a demo reached printf first. Both now
hand back one byte more than has ever been taken, which underflows whatever
ran before them.
Thread stacks are sized in machine words rather than bytes, since every saved
register and the newlib printf() call chain double in width on a 64-bit hart:
1024 bytes on Cortex-M4 as before, 2048 on RISC-V, measured peak 1048.
Measured PolarFire heap use under printf, the first stdio on that board, is
2944 bytes; the run also completes with BSP_HEAP_RESERVE_BYTES cut to 4 KB, so
the 64 KB reservation holds unchanged.
Verified locally on both toolchains: three Renode suites green, and with the
_sbrk() bound deliberately widened the self-tests still fail - two checks on
the NUCLEO-F401RE, one on each PolarFire executable.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant

@fdesbiens
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', '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('^' + ".*" + ' Moved the demo into apps/ and made both targets build it by fdesbiens · Pull Request #57 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the demo into apps/ and made both targets build it - #57

Open
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app
Open

Moved the demo into apps/ and made both targets build it#57
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this changes

The framework documented a portable application layer it did not have. Each target owned its own demo under app/, so nothing checked that a demo could actually move between boards — which is how apps/ stayed documented for weeks while not existing (#52 walked the claim back).

This adds apps/, moves the NUCLEO-F401RE demo into apps/threadx_demo/main.c, and has the PolarFire SoC Icicle Kit build that same source as a second executable beside its LM75 monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and asserts on the same console output from both.

The rule, now written into docs/architecture.md and templates/target/README.md:

apps/ holds portable demos. targets/<Vendor>/<BOARD>/app/ holds board-specific ones. Both are legitimate; a target's app/CMakeLists.txt picks either.

PolarFire's LM75 monitor stays a target app because it genuinely models a sensor.

A third BSP interface was unavoidable

polarfire_bsp's trap.c carried a hard undefined reference to console_rx_isr_callback(), a symbol only its own demo defined. Any second executable linking that BSP failed to link, and a portable application would have had to define a PolarFire-specific ISR callback purely to say it wanted nothing. That is the same violation this effort exists to remove — the BSP reaching up into the application — expressed through the linker rather than a header.

bsp/console.h gains a registration call in the shape bsp_self_test() already established:

typedefvoid (*bsp_console_rx_fn)(charc, void*context);
voidbsp_console_set_rx_handler(bsp_console_rx_fnhandler, void*context);

The board stores a nullable pointer and checks it before dispatching, so bytes arriving with no handler attached are dropped rather than faulting. The LM75 demo registers its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a handler it never invokes, keeping the contract uniform enough to register against unconditionally. A __attribute__((weak)) default was rejected: it compiles, but keeps the up-call and is a GCC extension in a C99/MISRA codebase.

This brought the usual obligations with it — an architecture entry, a templates/target/ stub, and README rows, as PR #56 did for the first two interfaces.

Building for a second architecture found two real defects

Both were latent and neither was reachable with one target:

  • Every %lu in the demo was wrong on one of the two boards. ThreadX defines ULONG as unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 port. Each value now casts to unsigned long, matching the idiom already used in the LM75 demo.
  • Both boards' _sbrk() underflow self-test asked for a fixed -128, which is an underflow only when nothing has allocated yet. The NUCLEO's passed by accident — its printf buffer malloc fails against a small reservation, leaving the break at base. The PolarFire's failed outright once an application reached printf first. Both now hand back one byte more than has ever been taken, which underflows regardless of what ran before.

Measurements

NUCLEO-F401REPolarFire Icicle
Self-tests87
Thread stack size1024 B (unchanged)2048 B, measured peak 1048 B
Static RAM6000 B (unchanged)
Heap use under printf2944 B of 64 KB reserved

THREAD_STACK_SIZE is written in machine words rather than bytes, since every saved register and the newlib printf() call chain doubles in width on a 64-bit hart. BSP_HEAP_RESERVE_BYTES needed no change: the whole run also completes with the reservation temporarily cut to 4 KB, which bounds peak heap use across the run rather than just at startup.

Verification

Run locally on both toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1) — the versions CI pins.

  • Three Renode suites green: NUCLEO shared demo, PolarFire --app lm75, PolarFire --app threadx_demo.
  • NUCLEO builds clean under -Werror; both PolarFire ELFs build warning-free.
  • Self-tests still have teeth. With _sbrk()'s bound deliberately widened to the end of RAM, the checks still fail: two on the NUCLEO-F401RE, one on each PolarFire executable. The NUCLEO count is two rather than the three seen before this PR, because the underflow check now correctly stays green — its old failure was incidental to the fixed -128, not that assertion doing its job.

The PolarFire runner is one script with an --app argument rather than two scripts, since the ~150 lines of Renode process handling are what would drift if copied.

Notes for review

  • app/starter/ is gone, including the empty cloud_config.h placeholder that Moved the startup self-tests and RAM sizing behind new BSP interfaces #56 stopped including.
  • The banner is now board-neutral (Eclipse ThreadX Device Monitor Demo); both Renode suites and the Robot suite assert on that one line.
  • This PR also removes the "a goal, not something it provides yet" wording from docs/architecture.md and templates/target/README.md. That was scheduled for a follow-up, but its precondition — the shared demo green on both architectures — is met by this PR, and leaving those statements in place would have shipped documentation that this change makes false.

The framework documented a portable application layer it did not have: each
target owned its own demo under app/, so nothing checked that a demo could
actually move between boards. This adds apps/, moves the NUCLEO-F401RE demo
into apps/threadx_demo/main.c unchanged in behaviour, and has the PolarFire
SoC Icicle Kit build that same source as a second executable beside its LM75
monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and
asserts on the same console output from both, so the claim is now enforced
rather than stated.
apps/ holds portable demos; targets/<Vendor>/<BOARD>/app/ holds board-specific
ones, and a target's app/CMakeLists.txt picks either. The PolarFire LM75
monitor stays a target app because it genuinely models a sensor.
Linking a second executable first required removing a hard undefined reference:
polarfire_bsp's trap.c called console_rx_isr_callback(), a symbol only its own
demo defined, so any other application had to define a PolarFire-specific ISR
callback just to link. bsp/console.h gains a registration call in the shape
bsp_self_test() already established:
typedef void (*bsp_console_rx_fn)(char c, void *context);
void bsp_console_set_rx_handler(bsp_console_rx_fn handler, void *context);
The board stores a nullable pointer and checks it before dispatching, so bytes
arriving with no handler attached are dropped instead of faulting, and an
application that ignores console input defines nothing. The LM75 demo registers
its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a
handler it never invokes, which keeps the contract uniform enough to register
against unconditionally. A weak symbol was rejected: it keeps the up-call and
is a GCC extension in a C99 codebase.
Building the demo for a second architecture found two real defects:
- Every %lu in the demo was wrong on one target. ThreadX defines ULONG as
unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 one,
so each value now casts to unsigned long, matching the existing idiom in
the LM75 demo.
- Both boards' _sbrk() underflow self-test asked for a fixed -128, which is
an underflow only when nothing has allocated yet. The NUCLEO's passed by
accident of a printf buffer malloc that failed against a small reservation;
the PolarFire's failed outright once a demo reached printf first. Both now
hand back one byte more than has ever been taken, which underflows whatever
ran before them.
Thread stacks are sized in machine words rather than bytes, since every saved
register and the newlib printf() call chain double in width on a 64-bit hart:
1024 bytes on Cortex-M4 as before, 2048 on RISC-V, measured peak 1048.
Measured PolarFire heap use under printf, the first stdio on that board, is
2944 bytes; the run also completes with BSP_HEAP_RESERVE_BYTES cut to 4 KB, so
the 64 KB reservation holds unchanged.
Verified locally on both toolchains: three Renode suites green, and with the
_sbrk() bound deliberately widened the self-tests still fail - two checks on
the NUCLEO-F401RE, one on each PolarFire executable.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant

@fdesbiens
, '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" + ' Moved the demo into apps/ and made both targets build it by fdesbiens · Pull Request #57 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the demo into apps/ and made both targets build it - #57

Open
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app
Open

Moved the demo into apps/ and made both targets build it#57
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this changes

The framework documented a portable application layer it did not have. Each target owned its own demo under app/, so nothing checked that a demo could actually move between boards — which is how apps/ stayed documented for weeks while not existing (#52 walked the claim back).

This adds apps/, moves the NUCLEO-F401RE demo into apps/threadx_demo/main.c, and has the PolarFire SoC Icicle Kit build that same source as a second executable beside its LM75 monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and asserts on the same console output from both.

The rule, now written into docs/architecture.md and templates/target/README.md:

apps/ holds portable demos. targets/<Vendor>/<BOARD>/app/ holds board-specific ones. Both are legitimate; a target's app/CMakeLists.txt picks either.

PolarFire's LM75 monitor stays a target app because it genuinely models a sensor.

A third BSP interface was unavoidable

polarfire_bsp's trap.c carried a hard undefined reference to console_rx_isr_callback(), a symbol only its own demo defined. Any second executable linking that BSP failed to link, and a portable application would have had to define a PolarFire-specific ISR callback purely to say it wanted nothing. That is the same violation this effort exists to remove — the BSP reaching up into the application — expressed through the linker rather than a header.

bsp/console.h gains a registration call in the shape bsp_self_test() already established:

typedefvoid (*bsp_console_rx_fn)(charc, void*context);
voidbsp_console_set_rx_handler(bsp_console_rx_fnhandler, void*context);

The board stores a nullable pointer and checks it before dispatching, so bytes arriving with no handler attached are dropped rather than faulting. The LM75 demo registers its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a handler it never invokes, keeping the contract uniform enough to register against unconditionally. A __attribute__((weak)) default was rejected: it compiles, but keeps the up-call and is a GCC extension in a C99/MISRA codebase.

This brought the usual obligations with it — an architecture entry, a templates/target/ stub, and README rows, as PR #56 did for the first two interfaces.

Building for a second architecture found two real defects

Both were latent and neither was reachable with one target:

  • Every %lu in the demo was wrong on one of the two boards. ThreadX defines ULONG as unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 port. Each value now casts to unsigned long, matching the idiom already used in the LM75 demo.
  • Both boards' _sbrk() underflow self-test asked for a fixed -128, which is an underflow only when nothing has allocated yet. The NUCLEO's passed by accident — its printf buffer malloc fails against a small reservation, leaving the break at base. The PolarFire's failed outright once an application reached printf first. Both now hand back one byte more than has ever been taken, which underflows regardless of what ran before.

Measurements

NUCLEO-F401REPolarFire Icicle
Self-tests87
Thread stack size1024 B (unchanged)2048 B, measured peak 1048 B
Static RAM6000 B (unchanged)
Heap use under printf2944 B of 64 KB reserved

THREAD_STACK_SIZE is written in machine words rather than bytes, since every saved register and the newlib printf() call chain doubles in width on a 64-bit hart. BSP_HEAP_RESERVE_BYTES needed no change: the whole run also completes with the reservation temporarily cut to 4 KB, which bounds peak heap use across the run rather than just at startup.

Verification

Run locally on both toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1) — the versions CI pins.

  • Three Renode suites green: NUCLEO shared demo, PolarFire --app lm75, PolarFire --app threadx_demo.
  • NUCLEO builds clean under -Werror; both PolarFire ELFs build warning-free.
  • Self-tests still have teeth. With _sbrk()'s bound deliberately widened to the end of RAM, the checks still fail: two on the NUCLEO-F401RE, one on each PolarFire executable. The NUCLEO count is two rather than the three seen before this PR, because the underflow check now correctly stays green — its old failure was incidental to the fixed -128, not that assertion doing its job.

The PolarFire runner is one script with an --app argument rather than two scripts, since the ~150 lines of Renode process handling are what would drift if copied.

Notes for review

  • app/starter/ is gone, including the empty cloud_config.h placeholder that Moved the startup self-tests and RAM sizing behind new BSP interfaces #56 stopped including.
  • The banner is now board-neutral (Eclipse ThreadX Device Monitor Demo); both Renode suites and the Robot suite assert on that one line.
  • This PR also removes the "a goal, not something it provides yet" wording from docs/architecture.md and templates/target/README.md. That was scheduled for a follow-up, but its precondition — the shared demo green on both architectures — is met by this PR, and leaving those statements in place would have shipped documentation that this change makes false.

The framework documented a portable application layer it did not have: each
target owned its own demo under app/, so nothing checked that a demo could
actually move between boards. This adds apps/, moves the NUCLEO-F401RE demo
into apps/threadx_demo/main.c unchanged in behaviour, and has the PolarFire
SoC Icicle Kit build that same source as a second executable beside its LM75
monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and
asserts on the same console output from both, so the claim is now enforced
rather than stated.
apps/ holds portable demos; targets/<Vendor>/<BOARD>/app/ holds board-specific
ones, and a target's app/CMakeLists.txt picks either. The PolarFire LM75
monitor stays a target app because it genuinely models a sensor.
Linking a second executable first required removing a hard undefined reference:
polarfire_bsp's trap.c called console_rx_isr_callback(), a symbol only its own
demo defined, so any other application had to define a PolarFire-specific ISR
callback just to link. bsp/console.h gains a registration call in the shape
bsp_self_test() already established:
typedef void (*bsp_console_rx_fn)(char c, void *context);
void bsp_console_set_rx_handler(bsp_console_rx_fn handler, void *context);
The board stores a nullable pointer and checks it before dispatching, so bytes
arriving with no handler attached are dropped instead of faulting, and an
application that ignores console input defines nothing. The LM75 demo registers
its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a
handler it never invokes, which keeps the contract uniform enough to register
against unconditionally. A weak symbol was rejected: it keeps the up-call and
is a GCC extension in a C99 codebase.
Building the demo for a second architecture found two real defects:
- Every %lu in the demo was wrong on one target. ThreadX defines ULONG as
unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 one,
so each value now casts to unsigned long, matching the existing idiom in
the LM75 demo.
- Both boards' _sbrk() underflow self-test asked for a fixed -128, which is
an underflow only when nothing has allocated yet. The NUCLEO's passed by
accident of a printf buffer malloc that failed against a small reservation;
the PolarFire's failed outright once a demo reached printf first. Both now
hand back one byte more than has ever been taken, which underflows whatever
ran before them.
Thread stacks are sized in machine words rather than bytes, since every saved
register and the newlib printf() call chain double in width on a 64-bit hart:
1024 bytes on Cortex-M4 as before, 2048 on RISC-V, measured peak 1048.
Measured PolarFire heap use under printf, the first stdio on that board, is
2944 bytes; the run also completes with BSP_HEAP_RESERVE_BYTES cut to 4 KB, so
the 64 KB reservation holds unchanged.
Verified locally on both toolchains: three Renode suites green, and with the
_sbrk() bound deliberately widened the self-tests still fail - two checks on
the NUCLEO-F401RE, one on each PolarFire executable.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant

@fdesbiens
, '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('^' + ".*" + ' Moved the demo into apps/ and made both targets build it by fdesbiens · Pull Request #57 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the demo into apps/ and made both targets build it - #57

Open
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app
Open

Moved the demo into apps/ and made both targets build it#57
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this changes

The framework documented a portable application layer it did not have. Each target owned its own demo under app/, so nothing checked that a demo could actually move between boards — which is how apps/ stayed documented for weeks while not existing (#52 walked the claim back).

This adds apps/, moves the NUCLEO-F401RE demo into apps/threadx_demo/main.c, and has the PolarFire SoC Icicle Kit build that same source as a second executable beside its LM75 monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and asserts on the same console output from both.

The rule, now written into docs/architecture.md and templates/target/README.md:

apps/ holds portable demos. targets/<Vendor>/<BOARD>/app/ holds board-specific ones. Both are legitimate; a target's app/CMakeLists.txt picks either.

PolarFire's LM75 monitor stays a target app because it genuinely models a sensor.

A third BSP interface was unavoidable

polarfire_bsp's trap.c carried a hard undefined reference to console_rx_isr_callback(), a symbol only its own demo defined. Any second executable linking that BSP failed to link, and a portable application would have had to define a PolarFire-specific ISR callback purely to say it wanted nothing. That is the same violation this effort exists to remove — the BSP reaching up into the application — expressed through the linker rather than a header.

bsp/console.h gains a registration call in the shape bsp_self_test() already established:

typedefvoid (*bsp_console_rx_fn)(charc, void*context);
voidbsp_console_set_rx_handler(bsp_console_rx_fnhandler, void*context);

The board stores a nullable pointer and checks it before dispatching, so bytes arriving with no handler attached are dropped rather than faulting. The LM75 demo registers its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a handler it never invokes, keeping the contract uniform enough to register against unconditionally. A __attribute__((weak)) default was rejected: it compiles, but keeps the up-call and is a GCC extension in a C99/MISRA codebase.

This brought the usual obligations with it — an architecture entry, a templates/target/ stub, and README rows, as PR #56 did for the first two interfaces.

Building for a second architecture found two real defects

Both were latent and neither was reachable with one target:

  • Every %lu in the demo was wrong on one of the two boards. ThreadX defines ULONG as unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 port. Each value now casts to unsigned long, matching the idiom already used in the LM75 demo.
  • Both boards' _sbrk() underflow self-test asked for a fixed -128, which is an underflow only when nothing has allocated yet. The NUCLEO's passed by accident — its printf buffer malloc fails against a small reservation, leaving the break at base. The PolarFire's failed outright once an application reached printf first. Both now hand back one byte more than has ever been taken, which underflows regardless of what ran before.

Measurements

NUCLEO-F401REPolarFire Icicle
Self-tests87
Thread stack size1024 B (unchanged)2048 B, measured peak 1048 B
Static RAM6000 B (unchanged)
Heap use under printf2944 B of 64 KB reserved

THREAD_STACK_SIZE is written in machine words rather than bytes, since every saved register and the newlib printf() call chain doubles in width on a 64-bit hart. BSP_HEAP_RESERVE_BYTES needed no change: the whole run also completes with the reservation temporarily cut to 4 KB, which bounds peak heap use across the run rather than just at startup.

Verification

Run locally on both toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1) — the versions CI pins.

  • Three Renode suites green: NUCLEO shared demo, PolarFire --app lm75, PolarFire --app threadx_demo.
  • NUCLEO builds clean under -Werror; both PolarFire ELFs build warning-free.
  • Self-tests still have teeth. With _sbrk()'s bound deliberately widened to the end of RAM, the checks still fail: two on the NUCLEO-F401RE, one on each PolarFire executable. The NUCLEO count is two rather than the three seen before this PR, because the underflow check now correctly stays green — its old failure was incidental to the fixed -128, not that assertion doing its job.

The PolarFire runner is one script with an --app argument rather than two scripts, since the ~150 lines of Renode process handling are what would drift if copied.

Notes for review

  • app/starter/ is gone, including the empty cloud_config.h placeholder that Moved the startup self-tests and RAM sizing behind new BSP interfaces #56 stopped including.
  • The banner is now board-neutral (Eclipse ThreadX Device Monitor Demo); both Renode suites and the Robot suite assert on that one line.
  • This PR also removes the "a goal, not something it provides yet" wording from docs/architecture.md and templates/target/README.md. That was scheduled for a follow-up, but its precondition — the shared demo green on both architectures — is met by this PR, and leaving those statements in place would have shipped documentation that this change makes false.

The framework documented a portable application layer it did not have: each
target owned its own demo under app/, so nothing checked that a demo could
actually move between boards. This adds apps/, moves the NUCLEO-F401RE demo
into apps/threadx_demo/main.c unchanged in behaviour, and has the PolarFire
SoC Icicle Kit build that same source as a second executable beside its LM75
monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and
asserts on the same console output from both, so the claim is now enforced
rather than stated.
apps/ holds portable demos; targets/<Vendor>/<BOARD>/app/ holds board-specific
ones, and a target's app/CMakeLists.txt picks either. The PolarFire LM75
monitor stays a target app because it genuinely models a sensor.
Linking a second executable first required removing a hard undefined reference:
polarfire_bsp's trap.c called console_rx_isr_callback(), a symbol only its own
demo defined, so any other application had to define a PolarFire-specific ISR
callback just to link. bsp/console.h gains a registration call in the shape
bsp_self_test() already established:
typedef void (*bsp_console_rx_fn)(char c, void *context);
void bsp_console_set_rx_handler(bsp_console_rx_fn handler, void *context);
The board stores a nullable pointer and checks it before dispatching, so bytes
arriving with no handler attached are dropped instead of faulting, and an
application that ignores console input defines nothing. The LM75 demo registers
its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a
handler it never invokes, which keeps the contract uniform enough to register
against unconditionally. A weak symbol was rejected: it keeps the up-call and
is a GCC extension in a C99 codebase.
Building the demo for a second architecture found two real defects:
- Every %lu in the demo was wrong on one target. ThreadX defines ULONG as
unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 one,
so each value now casts to unsigned long, matching the existing idiom in
the LM75 demo.
- Both boards' _sbrk() underflow self-test asked for a fixed -128, which is
an underflow only when nothing has allocated yet. The NUCLEO's passed by
accident of a printf buffer malloc that failed against a small reservation;
the PolarFire's failed outright once a demo reached printf first. Both now
hand back one byte more than has ever been taken, which underflows whatever
ran before them.
Thread stacks are sized in machine words rather than bytes, since every saved
register and the newlib printf() call chain double in width on a 64-bit hart:
1024 bytes on Cortex-M4 as before, 2048 on RISC-V, measured peak 1048.
Measured PolarFire heap use under printf, the first stdio on that board, is
2944 bytes; the run also completes with BSP_HEAP_RESERVE_BYTES cut to 4 KB, so
the 64 KB reservation holds unchanged.
Verified locally on both toolchains: three Renode suites green, and with the
_sbrk() bound deliberately widened the self-tests still fail - two checks on
the NUCLEO-F401RE, one on each PolarFire executable.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant

@fdesbiens
, '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); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Moved the demo into apps/ and made both targets build it by fdesbiens · Pull Request #57 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the demo into apps/ and made both targets build it - #57

Open
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app
Open

Moved the demo into apps/ and made both targets build it#57
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this changes

The framework documented a portable application layer it did not have. Each target owned its own demo under app/, so nothing checked that a demo could actually move between boards — which is how apps/ stayed documented for weeks while not existing (#52 walked the claim back).

This adds apps/, moves the NUCLEO-F401RE demo into apps/threadx_demo/main.c, and has the PolarFire SoC Icicle Kit build that same source as a second executable beside its LM75 monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and asserts on the same console output from both.

The rule, now written into docs/architecture.md and templates/target/README.md:

apps/ holds portable demos. targets/<Vendor>/<BOARD>/app/ holds board-specific ones. Both are legitimate; a target's app/CMakeLists.txt picks either.

PolarFire's LM75 monitor stays a target app because it genuinely models a sensor.

A third BSP interface was unavoidable

polarfire_bsp's trap.c carried a hard undefined reference to console_rx_isr_callback(), a symbol only its own demo defined. Any second executable linking that BSP failed to link, and a portable application would have had to define a PolarFire-specific ISR callback purely to say it wanted nothing. That is the same violation this effort exists to remove — the BSP reaching up into the application — expressed through the linker rather than a header.

bsp/console.h gains a registration call in the shape bsp_self_test() already established:

typedefvoid (*bsp_console_rx_fn)(charc, void*context);
voidbsp_console_set_rx_handler(bsp_console_rx_fnhandler, void*context);

The board stores a nullable pointer and checks it before dispatching, so bytes arriving with no handler attached are dropped rather than faulting. The LM75 demo registers its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a handler it never invokes, keeping the contract uniform enough to register against unconditionally. A __attribute__((weak)) default was rejected: it compiles, but keeps the up-call and is a GCC extension in a C99/MISRA codebase.

This brought the usual obligations with it — an architecture entry, a templates/target/ stub, and README rows, as PR #56 did for the first two interfaces.

Building for a second architecture found two real defects

Both were latent and neither was reachable with one target:

  • Every %lu in the demo was wrong on one of the two boards. ThreadX defines ULONG as unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 port. Each value now casts to unsigned long, matching the idiom already used in the LM75 demo.
  • Both boards' _sbrk() underflow self-test asked for a fixed -128, which is an underflow only when nothing has allocated yet. The NUCLEO's passed by accident — its printf buffer malloc fails against a small reservation, leaving the break at base. The PolarFire's failed outright once an application reached printf first. Both now hand back one byte more than has ever been taken, which underflows regardless of what ran before.

Measurements

NUCLEO-F401REPolarFire Icicle
Self-tests87
Thread stack size1024 B (unchanged)2048 B, measured peak 1048 B
Static RAM6000 B (unchanged)
Heap use under printf2944 B of 64 KB reserved

THREAD_STACK_SIZE is written in machine words rather than bytes, since every saved register and the newlib printf() call chain doubles in width on a 64-bit hart. BSP_HEAP_RESERVE_BYTES needed no change: the whole run also completes with the reservation temporarily cut to 4 KB, which bounds peak heap use across the run rather than just at startup.

Verification

Run locally on both toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1) — the versions CI pins.

  • Three Renode suites green: NUCLEO shared demo, PolarFire --app lm75, PolarFire --app threadx_demo.
  • NUCLEO builds clean under -Werror; both PolarFire ELFs build warning-free.
  • Self-tests still have teeth. With _sbrk()'s bound deliberately widened to the end of RAM, the checks still fail: two on the NUCLEO-F401RE, one on each PolarFire executable. The NUCLEO count is two rather than the three seen before this PR, because the underflow check now correctly stays green — its old failure was incidental to the fixed -128, not that assertion doing its job.

The PolarFire runner is one script with an --app argument rather than two scripts, since the ~150 lines of Renode process handling are what would drift if copied.

Notes for review

  • app/starter/ is gone, including the empty cloud_config.h placeholder that Moved the startup self-tests and RAM sizing behind new BSP interfaces #56 stopped including.
  • The banner is now board-neutral (Eclipse ThreadX Device Monitor Demo); both Renode suites and the Robot suite assert on that one line.
  • This PR also removes the "a goal, not something it provides yet" wording from docs/architecture.md and templates/target/README.md. That was scheduled for a follow-up, but its precondition — the shared demo green on both architectures — is met by this PR, and leaving those statements in place would have shipped documentation that this change makes false.

The framework documented a portable application layer it did not have: each
target owned its own demo under app/, so nothing checked that a demo could
actually move between boards. This adds apps/, moves the NUCLEO-F401RE demo
into apps/threadx_demo/main.c unchanged in behaviour, and has the PolarFire
SoC Icicle Kit build that same source as a second executable beside its LM75
monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and
asserts on the same console output from both, so the claim is now enforced
rather than stated.
apps/ holds portable demos; targets/<Vendor>/<BOARD>/app/ holds board-specific
ones, and a target's app/CMakeLists.txt picks either. The PolarFire LM75
monitor stays a target app because it genuinely models a sensor.
Linking a second executable first required removing a hard undefined reference:
polarfire_bsp's trap.c called console_rx_isr_callback(), a symbol only its own
demo defined, so any other application had to define a PolarFire-specific ISR
callback just to link. bsp/console.h gains a registration call in the shape
bsp_self_test() already established:
typedef void (*bsp_console_rx_fn)(char c, void *context);
void bsp_console_set_rx_handler(bsp_console_rx_fn handler, void *context);
The board stores a nullable pointer and checks it before dispatching, so bytes
arriving with no handler attached are dropped instead of faulting, and an
application that ignores console input defines nothing. The LM75 demo registers
its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a
handler it never invokes, which keeps the contract uniform enough to register
against unconditionally. A weak symbol was rejected: it keeps the up-call and
is a GCC extension in a C99 codebase.
Building the demo for a second architecture found two real defects:
- Every %lu in the demo was wrong on one target. ThreadX defines ULONG as
unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 one,
so each value now casts to unsigned long, matching the existing idiom in
the LM75 demo.
- Both boards' _sbrk() underflow self-test asked for a fixed -128, which is
an underflow only when nothing has allocated yet. The NUCLEO's passed by
accident of a printf buffer malloc that failed against a small reservation;
the PolarFire's failed outright once a demo reached printf first. Both now
hand back one byte more than has ever been taken, which underflows whatever
ran before them.
Thread stacks are sized in machine words rather than bytes, since every saved
register and the newlib printf() call chain double in width on a 64-bit hart:
1024 bytes on Cortex-M4 as before, 2048 on RISC-V, measured peak 1048.
Measured PolarFire heap use under printf, the first stdio on that board, is
2944 bytes; the run also completes with BSP_HEAP_RESERVE_BYTES cut to 4 KB, so
the 64 KB reservation holds unchanged.
Verified locally on both toolchains: three Renode suites green, and with the
_sbrk() bound deliberately widened the self-tests still fail - two checks on
the NUCLEO-F401RE, one on each PolarFire executable.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant

@fdesbiens
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Moved the demo into apps/ and made both targets build it by fdesbiens · Pull Request #57 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the demo into apps/ and made both targets build it - #57

Open
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app
Open

Moved the demo into apps/ and made both targets build it#57
fdesbiens wants to merge 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/shared-portable-app

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

What this changes

The framework documented a portable application layer it did not have. Each target owned its own demo under app/, so nothing checked that a demo could actually move between boards — which is how apps/ stayed documented for weeks while not existing (#52 walked the claim back).

This adds apps/, moves the NUCLEO-F401RE demo into apps/threadx_demo/main.c, and has the PolarFire SoC Icicle Kit build that same source as a second executable beside its LM75 monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and asserts on the same console output from both.

The rule, now written into docs/architecture.md and templates/target/README.md:

apps/ holds portable demos. targets/<Vendor>/<BOARD>/app/ holds board-specific ones. Both are legitimate; a target's app/CMakeLists.txt picks either.

PolarFire's LM75 monitor stays a target app because it genuinely models a sensor.

A third BSP interface was unavoidable

polarfire_bsp's trap.c carried a hard undefined reference to console_rx_isr_callback(), a symbol only its own demo defined. Any second executable linking that BSP failed to link, and a portable application would have had to define a PolarFire-specific ISR callback purely to say it wanted nothing. That is the same violation this effort exists to remove — the BSP reaching up into the application — expressed through the linker rather than a header.

bsp/console.h gains a registration call in the shape bsp_self_test() already established:

typedefvoid (*bsp_console_rx_fn)(charc, void*context);
voidbsp_console_set_rx_handler(bsp_console_rx_fnhandler, void*context);

The board stores a nullable pointer and checks it before dispatching, so bytes arriving with no handler attached are dropped rather than faulting. The LM75 demo registers its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a handler it never invokes, keeping the contract uniform enough to register against unconditionally. A __attribute__((weak)) default was rejected: it compiles, but keeps the up-call and is a GCC extension in a C99/MISRA codebase.

This brought the usual obligations with it — an architecture entry, a templates/target/ stub, and README rows, as PR #56 did for the first two interfaces.

Building for a second architecture found two real defects

Both were latent and neither was reachable with one target:

  • Every %lu in the demo was wrong on one of the two boards. ThreadX defines ULONG as unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 port. Each value now casts to unsigned long, matching the idiom already used in the LM75 demo.
  • Both boards' _sbrk() underflow self-test asked for a fixed -128, which is an underflow only when nothing has allocated yet. The NUCLEO's passed by accident — its printf buffer malloc fails against a small reservation, leaving the break at base. The PolarFire's failed outright once an application reached printf first. Both now hand back one byte more than has ever been taken, which underflows regardless of what ran before.

Measurements

NUCLEO-F401REPolarFire Icicle
Self-tests87
Thread stack size1024 B (unchanged)2048 B, measured peak 1048 B
Static RAM6000 B (unchanged)
Heap use under printf2944 B of 64 KB reserved

THREAD_STACK_SIZE is written in machine words rather than bytes, since every saved register and the newlib printf() call chain doubles in width on a 64-bit hart. BSP_HEAP_RESERVE_BYTES needed no change: the whole run also completes with the reservation temporarily cut to 4 KB, which bounds peak heap use across the run rather than just at startup.

Verification

Run locally on both toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1) — the versions CI pins.

  • Three Renode suites green: NUCLEO shared demo, PolarFire --app lm75, PolarFire --app threadx_demo.
  • NUCLEO builds clean under -Werror; both PolarFire ELFs build warning-free.
  • Self-tests still have teeth. With _sbrk()'s bound deliberately widened to the end of RAM, the checks still fail: two on the NUCLEO-F401RE, one on each PolarFire executable. The NUCLEO count is two rather than the three seen before this PR, because the underflow check now correctly stays green — its old failure was incidental to the fixed -128, not that assertion doing its job.

The PolarFire runner is one script with an --app argument rather than two scripts, since the ~150 lines of Renode process handling are what would drift if copied.

Notes for review

  • app/starter/ is gone, including the empty cloud_config.h placeholder that Moved the startup self-tests and RAM sizing behind new BSP interfaces #56 stopped including.
  • The banner is now board-neutral (Eclipse ThreadX Device Monitor Demo); both Renode suites and the Robot suite assert on that one line.
  • This PR also removes the "a goal, not something it provides yet" wording from docs/architecture.md and templates/target/README.md. That was scheduled for a follow-up, but its precondition — the shared demo green on both architectures — is met by this PR, and leaving those statements in place would have shipped documentation that this change makes false.

The framework documented a portable application layer it did not have: each
target owned its own demo under app/, so nothing checked that a demo could
actually move between boards. This adds apps/, moves the NUCLEO-F401RE demo
into apps/threadx_demo/main.c unchanged in behaviour, and has the PolarFire
SoC Icicle Kit build that same source as a second executable beside its LM75
monitor. CI runs it under Renode on 32-bit Cortex-M4 and 64-bit RISC-V and
asserts on the same console output from both, so the claim is now enforced
rather than stated.
apps/ holds portable demos; targets/<Vendor>/<BOARD>/app/ holds board-specific
ones, and a target's app/CMakeLists.txt picks either. The PolarFire LM75
monitor stays a target app because it genuinely models a sensor.
Linking a second executable first required removing a hard undefined reference:
polarfire_bsp's trap.c called console_rx_isr_callback(), a symbol only its own
demo defined, so any other application had to define a PolarFire-specific ISR
callback just to link. bsp/console.h gains a registration call in the shape
bsp_self_test() already established:
typedef void (*bsp_console_rx_fn)(char c, void *context);
void bsp_console_set_rx_handler(bsp_console_rx_fn handler, void *context);
The board stores a nullable pointer and checks it before dispatching, so bytes
arriving with no handler attached are dropped instead of faulting, and an
application that ignores console input defines nothing. The LM75 demo registers
its existing handler in main(); the NUCLEO-F401RE polls USART2 and so stores a
handler it never invokes, which keeps the contract uniform enough to register
against unconditionally. A weak symbol was rejected: it keeps the up-call and
is a GCC extension in a C99 codebase.
Building the demo for a second architecture found two real defects:
- Every %lu in the demo was wrong on one target. ThreadX defines ULONG as
unsigned long on the Cortex-M4 port and unsigned int on the RISC-V 64 one,
so each value now casts to unsigned long, matching the existing idiom in
the LM75 demo.
- Both boards' _sbrk() underflow self-test asked for a fixed -128, which is
an underflow only when nothing has allocated yet. The NUCLEO's passed by
accident of a printf buffer malloc that failed against a small reservation;
the PolarFire's failed outright once a demo reached printf first. Both now
hand back one byte more than has ever been taken, which underflows whatever
ran before them.
Thread stacks are sized in machine words rather than bytes, since every saved
register and the newlib printf() call chain double in width on a 64-bit hart:
1024 bytes on Cortex-M4 as before, 2048 on RISC-V, measured peak 1048.
Measured PolarFire heap use under printf, the first stdio on that board, is
2944 bytes; the run also completes with BSP_HEAP_RESERVE_BYTES cut to 4 KB, so
the 64 KB reservation holds unchanged.
Verified locally on both toolchains: three Renode suites green, and with the
_sbrk() bound deliberately widened the self-tests still fail - two checks on
the NUCLEO-F401RE, one on each PolarFire executable.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant

@fdesbiens