How to Upload a Bitstream to a CW-Husky FPGA

Hello,
I recently became interested in the CW-Husky and started researching it, but there are still many parts I don’t fully understand.

Using Vivado 2022.1 with cwhusky.xpr, I generated a custom .bit file. Now, I would like to upload this .bit to the FPGA and use it, but Vivado cannot detect the Husky device. I saw that the README mentions on-target testing, but I don’t fully understand the instructions.

In practice, what steps should I follow to upload my custom bitstream to the Husky FPGA and test it?

To program the FPGA using Vivado, you’ll need a Xilinx Platform Cable (or the Digilent alternatives).

But you can also program the FPGA using ChipWhisperer as follows:

scope = cw.scope(bitstream=/path/to/bitfile)

If you’ve made changes to the registers.v file and you want the chipwhisperer Python code to be aware of those changes, then you must also point to that file:

scope = cw.scope(bitstream=/path/to/bitfile, registers=/path/to/registers.v)

Note that the Xilinx platform cable is required if you have ILAs.

1 Like

Thank you for your reply! It was very helpful.

After your response, I downloaded the project from chipwhisperer-husky-fpga, generated a custom .bit file through Vivado, and tried to program the FPGA using ChipWhisperer as you described. I have been experimenting with both version 6.0.0 and 5.7.0 from the GitHub repository. During these experiments, I encountered a few questions that I would like to ask.

  1. When I do not upload a custom .bit file (i.e., simply define scope = cw.scope()), what .bit file is uploaded to the FPGA by default? Where is it stored, and how is it loaded? Even though I am using the same Husky device, when I executed print(len(scope.SAD.reference)), version 6.0.0 output 512 while version 5.7.0 output 32. From this, I concluded that the default FPGA configuration differs depending on the version, and I am curious about the underlying mechanism of how the FPGA is initialized by default.
  2. To practice the basics of programming the FPGA using ChipWhisperer, I followed these steps:
    a. Downloaded the project from chipwhisperer-husky-fpga.
    b. Copied sad.v, renamed it customSad.v, and modified the module name from module sad to module customSad.
    c. Edited openadc_interface.v to add the CUSTOMSAD definition.
`elsif SAD_SINGLE
       sad_single_counter #(
           .pBYTECNT_SIZE           (pBYTECNT_SIZE),
           .pREF_SAMPLES            (pREF_SAMPLES),
           .pSAD_COUNTER_WIDTH      (pSAD_COUNTER_WIDTH),
           .pBITS_PER_SAMPLE        (8)
       ) U_sad (
           .reset                   (reset        ),
           .xadc_error              (xadc_error   ),
           .adc_datain              (ADC_data_tofifo[11:4]),
           .adc_sampleclk           (ADC_clk_sample),
           .armed_and_ready         (armed_and_ready),
           .active                  (sad_trigger_in_use),
           .trigger_allowed         (sad_active   ),
           .clk_usb                 (clk_usb      ),
           .reg_address             (reg_address  ),
           .reg_bytecnt             (reg_bytecnt  ),
           .reg_datai               (reg_datai    ),
           .reg_datao               (reg_datao_sad),
           .reg_read                (reg_read     ),
           .reg_write               (reg_write    ),
           .ext_trigger             (DUT_trigger_i),
           .io4                     (trigger_io4_i),
           .trigger                 (trigger_sad  )
       );

`elsif CUSTOMSAD
       customSad #(
           .pBYTECNT_SIZE           (pBYTECNT_SIZE),
           .pREF_SAMPLES            (pREF_SAMPLES),
           .pSAD_COUNTER_WIDTH      (pSAD_COUNTER_WIDTH),
           .pBITS_PER_SAMPLE        (8),
           .pNUM_GROUPS             (pNUM_GROUPS)
       ) U_sad_custom (
           .reset                   (reset        ),
           .xadc_error              (xadc_error   ),
           .adc_datain              (ADC_data_tofifo[11:4]),
           .adc_sampleclk           (ADC_clk_sample),
           .armed_and_ready         (armed_and_ready),
           .active                  (sad_trigger_in_use),
           .trigger_allowed         (sad_active   ),
           .clk_usb                 (clk_usb      ),
           .reg_address             (reg_address  ),
           .reg_bytecnt             (reg_bytecnt  ),
           .reg_datai               (reg_datai    ),
           .reg_datao               (reg_datao_sad),
           .reg_read                (reg_read     ),
           .reg_write               (reg_write    ),
           .ext_trigger             (DUT_trigger_i),
           .io4                     (trigger_io4_i),
           .sad_debug               (sad_debug    ),
           .trigger                 (trigger_sad  )
       );

d. Did not modify registers.v.
e. In Vivado TCL, executed set_property verilog_define {CUSTOMSAD} [get_filesets sources_1] to enable CUSTOMSAD and generated a .bit file.
f. Successfully uploaded the file using scope = cw.scope(bitstream=/path/to/bitfile).

However, when I ran print(len(scope.SAD.reference)), version 6.0.0 returned 0 and version 5.7.0 returned 1. My expectation was that since I only changed the module name in sad.v, it should still output 32. Why is this not the case? Did I miss something important?

This is because you are switching back and forth between versions 5.7 and 6.0. Don’t do that.

The default / supported FPGA bitfile is part of the chipwhisperer package; you’ll find it here. In general, you can’t mix and match chipwhisperer software installations and bitfiles. In this particular case here, the SAD implementation underwent major changes in the 6.0 release, so if you try to use a 6.0 or 6.0-derived bitfile with a 5.7 software installation (or vice-versa), it’s not going to work.

I didn’t realize that! From now on, I’ll stick with just one version. For FPGA experiments, I suppose version 6.0.0 would be the better choice, right?

In addition, I still have one unresolved question. You mentioned the location of the default/supported FPGA bitfile — does this file get uploaded to the FPGA when I run cw.scope(bitstream=/path/to/bitfile)? Or is it automatically loaded from something like flash memory whenever the device is powered on?

Yes, you should use 6.0.0 on the Python side.

For your FPGA work, you should use the 6.0.0 tag, which at the moment is essentially identical to the develop branch, but that will not remain the case for long.

When you run scope = cw.scope() on an freshly powered-up Husky, the default bitfile is programmed to the FPGA.

When you run scope = cw.scope(bitstream=/path/to/bitfile) instead, then /path/to/bitfile is programmed to the FPGA instead of the default one.

There is no flash on the Husky.

1 Like

Default bitfile ChipWhisperer5_70:

InsallDir\ChipWhisperer5_70\cw\home\portable\chipwhisperer\software\chipwhisperer\hardware\firmware\cwhusky.py
‘husky_firmware.zip’:'BASE64 zip fw

1 Like