94xhn opened a new pull request, #19415:
URL: https://github.com/apache/nuttx/pull/19415

   drivers/spi/ice40: fix operator precedence in final clock cycle count
   
   ## Summary
   
     * Why change is necessary (fix, update, new feature)? Bug fix: an 
operator-precedence error causes `ice40_endwrite()` to clock out 8x more 
finalization SPI bytes than intended.
     * What functional part of the code is being changed? `ice40_endwrite()` in 
`drivers/spi/ice40.c`, the loop that clocks out dummy bytes after the FPGA 
bitstream to finish iCE40 configuration.
     * How does the change exactly work (what will change and how)? 
`ICE40_SPI_FINAL_CLK_CYCLES + 7 / 8` is parenthesized to 
`(ICE40_SPI_FINAL_CLK_CYCLES + 7) / 8`.
   
       In C, `/` binds tighter than `+`, so the unmodified expression parses as 
`ICE40_SPI_FINAL_CLK_CYCLES + (7 / 8)` = `160 + 0` = `160`; the `+ 7 / 8` part 
is a silent no-op. `ICE40_SPI_FINAL_CLK_CYCLES` is defined as `160` in 
`include/nuttx/spi/ice40.h:54`. Each `SPI_SEND()` call sends one byte = 8 SPI 
clock cycles (word size is configured to 8 bits via `SPI_SETBITS(spi, 8)` in 
`ice40_configspi()`), so the unmodified loop runs 160 times and clocks out 160 
× 8 = 1280 SPI clock cycles.
   
       The macro name (`_CLK_CYCLES`, not `_BYTES`) together with the classic 
`(n + 7) / 8` ceiling-division idiom used elsewhere in embedded code to convert 
a bit/cycle count into a byte count make it clear the intent was `bytes_to_send 
= ceil(ICE40_SPI_FINAL_CLK_CYCLES / 8) = (160 + 7) / 8 = 20` bytes, i.e. 
exactly 160 clock cycles, matching the macro name. Adding the missing 
parentheses restores that intended value (20 bytes / 160 cycles).
     * Related [NuttX Issue](https://github.com/apache/nuttx/issues/19367) 
reference if applicable. Fixes #19367, reported by @Zepp-Hanzj on 2026-07-08. I 
independently verified the analysis in the issue against the current `master` 
source before implementing this fix.
   
   ## Impact
   
     * Is new feature added? Is existing feature changed? NO new feature. 
Existing behavior of `ice40_endwrite()`'s configuration-finalization step 
changes: it now clocks out 160 SPI clock cycles instead of 1280.
     * Impact on user (will user need to adapt to change)? NO. Per Lattice's 
iCE40 Programming and Configuration technical note (FPGA-TN-02001), only >= 49 
extra `SPI_SCK` clock cycles after `CDONE` goes high are required to complete 
configuration. Both the buggy (1280) and fixed (160) cycle counts are well 
above that minimum, so this is not expected to change observable behavior on 
hardware — it corrects a value that visibly contradicts both the macro's name 
and the intent documented by the code shape, without being a functional 
regression risk.
     * Impact on build (will build process change)? NO.
     * Impact on hardware (will arch(s) / board(s) / driver(s) change)? YES, 
`drivers/spi/ice40.c` (iCE40 FPGA bitstream-loading SPI driver, used e.g. by 
`boards/risc-v/esp32c3-legacy` iCE40 board support). Only the loop trip count 
in the finalization step changes; no other logic, no ABI/API/Kconfig change.
     * Impact on documentation (is update required / provided)? NO, no 
user-visible behavior/documentation to update.
     * Impact on security (any sort of implications)? NO.
     * Impact on compatibility (backward/forward/interoperability)? NO.
     * Anything else to consider or add? See "Testing" section below for an 
important disclosure: I do not have access to real iCE-V-Wireless/iCE40 
hardware, so this could not be verified on a physical target. Verification was 
done with an isolated, faithful reproduction of the arithmetic/loop logic only.
   
   ## Testing
   
     I do **not** have access to real iCE40/iCE-V-Wireless hardware, so I could 
**not** produce a real-hardware build+runtime log for this change, which I 
understand is normally expected for driver changes. Given that, here is exactly 
what I *did* verify, so maintainers can judge whether it's sufficient or 
whether hardware confirmation from someone with the board is needed before 
merge:
   
     * Build Host(s): Windows 11, gcc (MinGW-w64) via Git Bash, `gcc -std=c11 
-Wall -Wextra -Werror`.
     * Target(s): N/A — no physical iCE40 board available. Instead of a NuttX 
board build, I wrote a standalone, non-NuttX host program that copies the exact 
unmodified loop expression from `ice40_endwrite()` (before and after the fix), 
the exact macro definition from `include/nuttx/spi/ice40.h`, and a mock 
`SPI_SEND()` that counts bytes/clock cycles the same way the real one would (1 
byte = 8 clock cycles, per `SPI_SETBITS(spi, 8)` in `ice40_configspi()`). This 
isolates and proves the arithmetic/loop-trip-count discrepancy without needing 
real SPI hardware, but it is **not** a substitute for an end-to-end hardware 
test of `ice40_endwrite()` itself.
     * I also confirmed the diff is a single line, that the changed line is 
well under the 80-column limit, and that this is the only place 
`ICE40_SPI_FINAL_CLK_CYCLES` is used in the tree (`grep -r 
ICE40_SPI_FINAL_CLK_CYCLES`), so the fix cannot affect any other code path.
   
     Reproduction steps (repository-independent, does not require NuttX build 
system):
   
     ```
     $ gcc -std=c11 -Wall -Wextra -Werror -o repro repro_ice40_clk_cycles.c
     $ ./repro
     ```
   
     Testing logs (this is the *reproduction program's* output, not a NuttX 
board build/run log):
   
     ```
     ICE40_SPI_FINAL_CLK_CYCLES = 160
   
     Expression evaluation (matches C operator precedence):
       buggy : ICE40_SPI_FINAL_CLK_CYCLES + 7 / 8   = 160 (parses as 160 + 
(7/8) = 160 + 0)
       fixed : (ICE40_SPI_FINAL_CLK_CYCLES + 7) / 8 = 20 (parses as (160+7)/8 = 
167/8)
   
     Simulated ice40_endwrite() finalization loop:
       BEFORE FIX : 160 SPI_SEND() calls -> 1280 SPI clock cycles (intended: 
160)
       AFTER FIX  : 20 SPI_SEND() calls -> 160 SPI clock cycles (intended: 160)
   
     PASS: confirmed 8x discrepancy (160 vs 20 bytes sent) caused by C operator 
precedence in the unmodified driver code, and confirmed the parenthesized 
ceiling-division fix produces exactly ICE40_SPI_FINAL_CLK_CYCLES (160) clock 
cycles as the macro name promises.
     ```
   
     (exit code 0, compiled with `-Werror` and no warnings.)
   
     <details>
     <summary>Full source of the reproduction program (click to 
expand)</summary>
   
     ```c
     /*
      * repro_ice40_clk_cycles.c
      *
      * Standalone reproduction for apache/nuttx issue #19367 / 
drivers/spi/ice40.c
      * operator-precedence bug in ice40_endwrite().
      *
      * Real (unmodified) source under test, apache/nuttx @ master,
      * drivers/spi/ice40.c line 272:
      *
      *     for (size_t i = 0; i < ICE40_SPI_FINAL_CLK_CYCLES + 7 / 8; i++)
      *       {
      *         SPI_SEND(dev->spi, 0xff);
      *       }
      *
      * ICE40_SPI_FINAL_CLK_CYCLES is defined in include/nuttx/spi/ice40.h:54 
as:
      *
      *     #define ICE40_SPI_FINAL_CLK_CYCLES 160
      *
      * Each call to SPI_SEND() transmits exactly one byte == 8 SPI clock cycles
      * (this is how every other loop in the same file computes byte counts from
      * a clock/bit budget, e.g. ice40_write() and the ICE_SPI_MAX_XFER chunking
      * just above ice40_endwrite() in the same file operate on byte units).
      *
      * Build:  gcc -std=c11 -Wall -Wextra -o repro repro_ice40_clk_cycles.c
      * Run:    ./repro
      */
   
     #include <stdio.h>
     #include <stddef.h>
     #include <assert.h>
   
     /* Verbatim from include/nuttx/spi/ice40.h:54 */
     #define ICE40_SPI_FINAL_CLK_CYCLES 160
   
     static long g_spi_send_calls;
     static long g_spi_clock_cycles; /* 8 clock cycles per SPI_SEND() byte */
   
     /* Mock of SPI_SEND(dev->spi, 0xff) from include/nuttx/spi/spi.h.
      * Each invocation clocks out exactly one byte == 8 SPI clock cycles.
      */
     static void mock_spi_send(unsigned char byte)
     {
       (void)byte;
       g_spi_send_calls++;
       g_spi_clock_cycles += 8;
     }
   
     /* Faithful copy of the loop body in ice40_endwrite(), drivers/spi/ice40.c,
      * BEFORE the fix (line 272 verbatim, only SPI_SEND() replaced by the 
mock).
      */
     static void ice40_endwrite_loop_buggy(void)
     {
       for (size_t i = 0; i < ICE40_SPI_FINAL_CLK_CYCLES + 7 / 8; i++)
         {
           mock_spi_send(0xff);
         }
     }
   
     /* Same loop AFTER the fix: parenthesize the ceiling-division. */
     static void ice40_endwrite_loop_fixed(void)
     {
       for (size_t i = 0; i < (ICE40_SPI_FINAL_CLK_CYCLES + 7) / 8; i++)
         {
           mock_spi_send(0xff);
         }
     }
   
     int main(void)
     {
       int status = 0;
   
       size_t buggy_bound = ICE40_SPI_FINAL_CLK_CYCLES + 7 / 8;
       size_t fixed_bound  = (ICE40_SPI_FINAL_CLK_CYCLES + 7) / 8;
   
       printf("ICE40_SPI_FINAL_CLK_CYCLES = %d\n\n", 
ICE40_SPI_FINAL_CLK_CYCLES);
   
       printf("Expression evaluation (matches C operator precedence):\n");
       printf("  buggy : ICE40_SPI_FINAL_CLK_CYCLES + 7 / 8   = %lu "
              "(parses as %d + (7/8) = %d + 0)\n",
              (unsigned long)buggy_bound, ICE40_SPI_FINAL_CLK_CYCLES,
              ICE40_SPI_FINAL_CLK_CYCLES);
       printf("  fixed : (ICE40_SPI_FINAL_CLK_CYCLES + 7) / 8 = %lu "
              "(parses as (%d+7)/8 = 167/8)\n\n",
              (unsigned long)fixed_bound, ICE40_SPI_FINAL_CLK_CYCLES);
   
       assert(buggy_bound == 160 && "unexpected buggy bound (repro invalid)");
       assert(fixed_bound == 20 && "unexpected fixed bound (repro invalid)");
   
       g_spi_send_calls = 0;
       g_spi_clock_cycles = 0;
       ice40_endwrite_loop_buggy();
       long buggy_calls = g_spi_send_calls;
       long buggy_cycles = g_spi_clock_cycles;
   
       g_spi_send_calls = 0;
       g_spi_clock_cycles = 0;
       ice40_endwrite_loop_fixed();
       long fixed_calls = g_spi_send_calls;
       long fixed_cycles = g_spi_clock_cycles;
   
       printf("Simulated ice40_endwrite() finalization loop:\n");
       printf("  BEFORE FIX : %ld SPI_SEND() calls -> %ld SPI clock cycles"
              " (intended: %d)\n",
              buggy_calls, buggy_cycles, ICE40_SPI_FINAL_CLK_CYCLES);
       printf("  AFTER FIX  : %ld SPI_SEND() calls -> %ld SPI clock cycles"
              " (intended: %d)\n\n",
              fixed_calls, fixed_cycles, ICE40_SPI_FINAL_CLK_CYCLES);
   
       if (buggy_calls != 160 || buggy_cycles != 1280)
         {
           printf("FAIL: expected the unmodified driver expression to produce "
                  "160 SPI_SEND calls / 1280 cycles\n");
           status = 1;
         }
   
       if (fixed_calls != 20 || fixed_cycles != 160)
         {
           printf("FAIL: expected the fixed expression to produce 20 SPI_SEND "
                  "calls / 160 cycles (== ICE40_SPI_FINAL_CLK_CYCLES)\n");
           status = 1;
         }
   
       if (status == 0)
         {
           printf("PASS: confirmed 8x discrepancy (160 vs 20 bytes sent) caused 
"
                  "by C operator precedence in the unmodified driver code, and "
                  "confirmed the parenthesized ceiling-division fix produces "
                  "exactly ICE40_SPI_FINAL_CLK_CYCLES (160) clock cycles as "
                  "the macro name promises.\n");
         }
   
       return status;
     }
     ```
   
     </details>
   
     I was not able to cross-compile or run `tools/nxstyle` cleanly in my 
environment against this file (it reports a path-resolution error on 
Windows/Git-Bash that also occurs on unmodified files, e.g. 
`drivers/spi/spi.c`, so it is an environment/tooling limitation unrelated to 
this change, not a style issue introduced by it). The changed line is 67 
columns, within the 80-column limit, and matches surrounding indentation/style.
   
   ## PR verification Self-Check
   
     * [x] This PR introduces only one functional change.
     * [x] I have updated all required description fields above.
     * [x] My PR adheres to Contributing 
[Guidelines](https://github.com/apache/nuttx/blob/master/CONTRIBUTING.md) and 
[Documentation](https://nuttx.apache.org/docs/latest/contributing/index.html) 
(git commit title and message, coding standard, etc).
     * [ ] My PR is still work in progress (not ready for review).
     * [x] My PR is ready for review and can be safely merged into a codebase.
   
   ## Disclosure
   
   I used Claude (Anthropic, model `claude-sonnet-5`, via Claude Code) to help 
investigate this issue, write the fix, write the reproduction program, and 
draft this PR description. I (the human author, credited in the `Signed-off-by` 
trailer) reviewed the diff, the reasoning, and the reproduction output before 
submitting. Per this repository's `CONTRIBUTING.md` §1.5, the commit carries an 
`Assisted-by: Claude Code:claude-sonnet-5` trailer. I do not have physical 
iCE40/iCE-V-Wireless hardware, so — as stated above in Testing — this fix has 
not been verified on real hardware, only via an isolated logic reproduction; 
I'm flagging this explicitly rather than implying hardware verification took 
place.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to