Husky: CW312 iCE40 custom NEORV32 target not responding over UART

Hello everyone,

I am trying to capture power traces using ChipWhisperer Husky but I cannot get any UART communication with my custom FPGA target. I don’t use a ChipWhisperer reference hardware design, but a custom RISC-V–based FPGA design running on the CW312T iCE40(NEORV32 softcore).

I wrote the following script to load the FPGA bitstream manually:

However, even after loading the bitstream, target.read() doesn’t return any data, and UART communication appears to be completely non-functional.

As a result, I encounter the following errors during uploading the firmware(simpleserial-aes.c) to the target:

OSError:b”Sync error with bootloader- invalid response to “h“ command()”

On the firmware side, I am using the standard ChipWhisperer simpleserial-aes.c firmware without functional modifications.

Environment & Setup

  • OS: Linux

  • Tool: ChipWhisperer Husky (Ver. 5.7.0) + CW313 baseboard

  • Target board: CW312T (Lattice iCE40)

  • Target core: custom NEORV32 (RISC-V)

  • Crypto: AES (TINYAES128C)

I am using SimpleSerial1_1:

SCOPETYPE = 'OPENADC'
PLATFORM = 'CW308_NEORV32'
CRYPTO_TARGET='TINYAES128C'
SS_VER='SS_VER_1_1'

For the pin mapping, I used the CW312 iCE40 constraints from the hdl/constraints repository as a reference (board iCE40CW312, constraints.pcf) and then created my own constraint file for my build flow (UART + trigger pins based on that reference).

Below is the constraint file I am currently using.

Constrait file
# Radiant pin mapping for CW312T-iCE40 board

## Clock
ldc_set_location -site B3 [get_ports clk_i]

## UART
ldc_set_location -site E5 [get_ports uart_txd_o]
ldc_set_location -site D5 [get_ports uart_rxd_i]

## SPI - on-board flash
ldc_set_location -site F1 [get_ports flash_sdo_o]
ldc_set_location -site D1 [get_ports flash_sck_o]
ldc_set_location -site C1 [get_ports flash_csn_o]
ldc_set_location -site E1 [get_ports flash_sdi_i]

## GPIO - on-board low-active LEDs
ldc_set_location -site A5 [get_ports gpio_o{0}]
ldc_set_location -site A1 [get_ports gpio_o{1}]


##set_io --warn-no-port iCE40CW312_CLK B3

#> UART
##set_io --warn-no-port iCE40CW312_TX E5
##set_io --warn-no-port iCE40CW312_RX D5


#> SPI [user port]
##set_io --warn-no-port iCE40CW312_SPI_SDO F1
##set_io --warn-no-port iCE40CW312_SPI_SCK D1
##set_io --warn-no-port iCE40CW312_SPI_CSN C1
##set_io --warn-no-port iCE40CW312_SPI_SDI E1


#> GPIO [input]
##set_io --warn-no-port iCE40CW312_GPIO_I[0] A5
##set_io --warn-no-port iCE40CW312_GPIO_I[1] A1


#> GPIO [output]
##set_io --warn-no-port iCE40CW312_GPIO_O[0] A5
##set_io --warn-no-port iCE40CW312_GPIO_O[1] A1

Goal
Capture power traces of a single AES encryption.
The trigger should be asserted during the encryption phase.

What I already tried

  • Uncommenting putch(‘h’);putch(‘e’); putch(‘l’); putch(‘l’); putch(‘o’); putch(‘\n’); in simpleseria-aes.c and manually testing communication with target.read()

But so far that haven’t resolved the issue.

At this point, I suspect a pin mapping or IO constraint issue, but I would appreciate any guidance on what to verify next or what might be missing in my setup.

Thank you in advance.

It sounds like you could be running into this issue.

First, I recommend upgrading to CW 6.0, and programming the target as shown in the linked post.

Then, if you still get the bootloader error, that is what needs to be resolved first; there is no point in attempting communication with the target firmware until that is fixed.

1 Like

Thank you for your quick response! I upgraded to CW 6.0 and I tried running the script below based on the linked post, but the error “b"Sync error with bootloader - invalid response to ‘h’ command (Aborted.\r\n\r\nType ‘h’ for help.\r\nCMD:> )“ still remains.

Could you please check whether there is anything wrong with this script?

SCOPETYPE = 'OPENADC'
PLATFORM = 'CW308_NEORV32'
CRYPTO_TARGET='TINYAES128C'
SS_VER='SS_VER_1_1'
%run "../Setup_Scripts/Setup_Generic.ipynb"
%%bash -s "$PLATFORM" "$CRYPTO_TARGET" "$SS_VER"
cd ../../firmware/mcu/simpleserial-aes
make PLATFORM=$1 CRYPTO_TARGET=$2 SS_VER=$3 -j
import chipwhisperer as cw
neorv = cw.programmers.Neorv32Programmer(scope)
fw_path = "../../firmware/mcu/simpleserial-aes/simpleserial-aes-CW308_NEORV32.bin"
neorv.open_port()
neorv.program(fw_path, bsfile="lattice_ice40_impl_1.new.bin")
neorv.close_port()

I noticed your comment mentioning that there may be some issue with the GitHub Actions–generated bitfile. To make sure I understand correctly: is it likely that this problem is caused by the pre-built NEORV32 bitstream itself (in my case “lattice_ice40_impl_1.new.bin“)? The bitstream we are using was built locally by us.

I suspect both.

I suspect that at some point since when we built the FPGA binary that’s included in our repo (early 2022), something in the neorv32 repository changed that broke our programmer’s interaction with the bootloader. We haven’t had time to figure out why yet.

Thanks for the insight — that matches what I am seeing as well.

Following your comment, I updated the ChipWhisperer programmer side to better match the current NEORV32 bootloader(neorv32/sw/image_gen at main · stnolting/neorv32 · GitHub). In particular, I modified program() and gen_app_binary() of your programmer to align with the updated bootloader behavior. After these changes, the bootloader-related errors disappeared, and the firmware upload no longer fails at the bootloader stage. This suggests that the interaction between the programmer and the bootloader is now at least partially fixed.However, I am still running into problems at the SimpleSerial level. Sending data with:

target.flush()

ktp = cw.ktp.Basic()
key,pt= ktp.next()
target.simpleserial_write('k', key)
target.simpleserial_write('p', pt)

and attempting to read the response with:

print(target.simpleserial_read(‘r’, 16))

results in errors such as “Unexpected start to command” and “Target did not ack”.

Additionally, scope.adc.trig_count remains 0, which indicates that the trigger signal is never asserted.

Any suggestions on how to further isolate whether this is a SimpleSerial protocol mismatch, a UART timing issue, or a trigger integration problem would be greatly appreciated.

Thanks!

If you can share the modifications you did to our programmer (pull request would be great), I can have a look. If you have a oscilloscope / logic analyzer, I would look for signs of life on the UART lines.

I’ve created a pull request with the changes to the programmer.
I’ve linked it here for reference : Fix NEORV32 programmer for updated bootloader behavior by uio-uio · Pull Request #567 · newaetech/chipwhisperer
It would be great if you could have a look and let me know whether this makes sense.

Thank you, this is very helpful - we are looking at this, hopefully we’ll have this sorted out soon.

I see the same thing you mentioned- the target appears to get programmed normally, but it’s unresponsive.

After the following changes, “hello” became visible via trigger.read() after program():

  1. Synchronized the NEORV32 HAL with the official repository: Updated all .c, .h, .S, and .ld files under /hal to match the upstream NEORV32 HAL.
  2. Manually updated the Makefile and neorv32_hal.h to avoid dependency mismatches.
  3. Replaced putch() in main.c with neorv32_uart0_printf() for debugging.
  4. Set the baud rate of uart_init() for simpleserial1 to 19200 bps.

It is unclear whether changing the baud rate is required on all boards, but I hope this is useful as a reference.