diff --git a/.gitignore b/.gitignore index b2507075..d26a771d 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,5 @@ firmware/cpld/**/*.xst firmware/cpld/**/*.xwbt firmware/**/build + +*.pyc diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index c458b7f1..8d96480e 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -25,5 +25,23 @@ set(CMAKE_TOOLCHAIN_FILE toolchain-arm-cortex-m.cmake) project (hackrf_firmware_all C) +SET(PATH_HACKRF_FIRMWARE ${CMAKE_CURRENT_LIST_DIR}) +SET(PATH_HACKRF_CPLD_XSVF ${PATH_HACKRF_FIRMWARE}/cpld/sgpio_if/default.xsvf) +SET(PATH_HACKRF ${PATH_HACKRF_FIRMWARE}/..) +SET(PATH_HACKRF_FIRMWARE_COMMON ${PATH_HACKRF_FIRMWARE}/common) +SET(LIBOPENCM3 ${PATH_HACKRF_FIRMWARE}/libopencm3) +SET(PATH_DFU_PY ${PATH_HACKRF_FIRMWARE}/dfu.py) +SET(PATH_CPLD_BITSTREAM_TOOL ${PATH_HACKRF_FIRMWARE}/tools/cpld_bitstream.py) +set(PATH_HACKRF_CPLD_DATA_C ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.c) + +include(ExternalProject) +ExternalProject_Add(libopencm3 + SOURCE_DIR "${LIBOPENCM3}" + BUILD_IN_SOURCE true + DOWNLOAD_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" +) + add_subdirectory(blinky) add_subdirectory(hackrf_usb) diff --git a/firmware/common/cpld_xc2c.c b/firmware/common/cpld_xc2c.c index 4220abb5..d43221b4 100644 --- a/firmware/common/cpld_xc2c.c +++ b/firmware/common/cpld_xc2c.c @@ -26,10 +26,6 @@ #include #include -#define CPLD_XC2C64A_ROWS (98) -#define CPLD_XC2C64A_BITS_IN_ROW (274) -#define CPLD_XC2C64A_BYTES_IN_ROW ((CPLD_XC2C64A_BITS_IN_ROW + 7) / 8) - static const uint8_t cpld_xc2c64a_row_address[CPLD_XC2C64A_ROWS] = { 0x00, 0x40, 0x60, 0x20, 0x30, 0x70, 0x50, 0x10, 0x18, 0x58, 0x78, 0x38, 0x28, 0x68, 0x48, 0x08, 0x0c, 0x4c, 0x6c, 0x2c, 0x3c, 0x7c, 0x5c, 0x1c, 0x14, 0x54, 0x74, 0x34, 0x24, 0x64, 0x44, 0x04, @@ -40,25 +36,6 @@ static const uint8_t cpld_xc2c64a_row_address[CPLD_XC2C64A_ROWS] = { 0x05, 0x45, }; -static const uint8_t cpld_xc2c64a_mask[6][CPLD_XC2C64A_BYTES_IN_ROW] = { - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xf8, 0x1f, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x80, 0x1f, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, }, - { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x80, 0x07, 0x00, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, }, -}; - -static const uint8_t cpld_xc2c64a_row_mask_index[CPLD_XC2C64A_ROWS] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 2, 2, 2, 3, 4, 2, 2, 2, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 2, 2, 2, 2, 2, 5, 2, 2, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, -}; - typedef enum { CPLD_XC2C_IR_INTEST = 0b00000010, CPLD_XC2C_IR_BYPASS = 0b11111111, @@ -121,12 +98,11 @@ static bool cpld_xc2c_jtag_clock(const jtag_t* const jtag, const uint32_t tms, c return gpio_read(jtag->gpio->gpio_tdo); } -static void cpld_xc2c_jtag_shift_ptr(const jtag_t* const jtag, uint8_t* const tdi_tdo, const size_t count) { - for(size_t i=0; i> 3; const size_t bit_n = i & 7; const uint32_t mask = (1U << bit_n); - const bool tms = (i == (count - 1)); const uint32_t tdo = cpld_xc2c_jtag_clock(jtag, tms, tdi_tdo[byte_n] & mask) ? 1 : 0; @@ -135,6 +111,13 @@ static void cpld_xc2c_jtag_shift_ptr(const jtag_t* const jtag, uint8_t* const td } } +static void cpld_xc2c_jtag_shift_ptr(const jtag_t* const jtag, uint8_t* const tdi_tdo, const size_t count) { + if( count > 0 ) { + cpld_xc2c_jtag_shift_ptr_tms(jtag, tdi_tdo, 0, count - 1, false); + cpld_xc2c_jtag_shift_ptr_tms(jtag, tdi_tdo, count - 1, count, true); + } +} + static uint32_t cpld_xc2c_jtag_shift_u32(const jtag_t* const jtag, const uint32_t tms, const uint32_t tdi, const size_t count) { uint32_t tdo = 0; @@ -189,11 +172,18 @@ static uint8_t cpld_xc2c_jtag_shift_ir(const jtag_t* const jtag, const cpld_xc2c return cpld_xc2c_jtag_shift_ir_pause(jtag, ir, 0); } +static void cpld_xc2c_jtag_reset(const jtag_t* const jtag) { + /* Five TMS=1 to reach Test-Logic-Reset from any point in the TAP state diagram. + */ + cpld_xc2c_jtag_shift_u32(jtag, 0b11111, 0, 5); +} + static void cpld_xc2c_jtag_reset_and_idle(const jtag_t* const jtag) { /* Five TMS=1 to reach Test-Logic-Reset from any point in the TAP state diagram. * One TMS=0 to move from Test-Logic-Reset to Run-Test-Idle. */ - cpld_xc2c_jtag_shift_u32(jtag, 0b011111, 0, 6); + cpld_xc2c_jtag_reset(jtag); + cpld_xc2c_jtag_shift_u32(jtag, 0, 0, 1); } static uint32_t cpld_xc2c_jtag_idcode(const jtag_t* const jtag) { @@ -215,6 +205,20 @@ static void cpld_xc2c_jtag_conld(const jtag_t* const jtag) { static void cpld_xc2c_jtag_enable(const jtag_t* const jtag) { cpld_xc2c_jtag_shift_ir(jtag, CPLD_XC2C_IR_ISC_ENABLE); + cpld_xc2c_jtag_clocks(jtag, 800); +} + +static void cpld_xc2c_jtag_disable(const jtag_t* const jtag) { + cpld_xc2c_jtag_shift_ir(jtag, CPLD_XC2C_IR_ISC_DISABLE); + cpld_xc2c_jtag_clocks(jtag, 100); +} + +static void cpld_xc2c_jtag_sram_write(const jtag_t* const jtag) { + cpld_xc2c_jtag_shift_ir(jtag, CPLD_XC2C_IR_ISC_WRITE); +} + +static void cpld_xc2c_jtag_sram_read(const jtag_t* const jtag) { + cpld_xc2c_jtag_shift_ir(jtag, CPLD_XC2C_IR_ISC_SRAM_READ); } static uint32_t cpld_xc2c_jtag_bypass(const jtag_t* const jtag, const bool shift_dr) { @@ -265,7 +269,11 @@ static void cpld_xc2c64a_jtag_read_row(const jtag_t* const jtag, uint8_t address cpld_xc2c_jtag_clocks(jtag, 100); } -bool cpld_xc2c64a_jtag_checksum(const jtag_t* const jtag, uint32_t* const crc_value) { +bool cpld_xc2c64a_jtag_checksum( + const jtag_t* const jtag, + const cpld_xc2c64a_verify_t* const verify, + uint32_t* const crc_value +) { cpld_xc2c_jtag_reset_and_idle(jtag); if( cpld_xc2c64a_jtag_idcode_ok(jtag) && cpld_xc2c_jtag_read_write_protect(jtag) && @@ -287,9 +295,9 @@ bool cpld_xc2c64a_jtag_checksum(const jtag_t* const jtag, uint32_t* const crc_va const size_t address = cpld_xc2c64a_row_address[row]; cpld_xc2c64a_jtag_read_row(jtag, address, dr); - const size_t mask_index = cpld_xc2c64a_row_mask_index[row]; + const size_t mask_index = verify->mask_index[row]; for(size_t i=0; imask[mask_index].value[i]; } /* Important checksum calculation NOTE: @@ -318,3 +326,103 @@ bool cpld_xc2c64a_jtag_checksum(const jtag_t* const jtag, uint32_t* const crc_va return false; } + +static void cpld_xc2c64a_jtag_sram_write_row(const jtag_t* const jtag, uint8_t address, const uint8_t* const data) { + uint8_t write[CPLD_XC2C64A_BYTES_IN_ROW]; + memcpy(&write[0], data, sizeof(write)); + + /* Update-IR or Run-Test/Idle -> Shift-DR */ + cpld_xc2c_jtag_shift_u32(jtag, 0b001, 0b000, 3); + + /* Shift-DR -> Shift-DR */ + cpld_xc2c_jtag_shift_ptr_tms(jtag, &write[0], 0, CPLD_XC2C64A_BITS_IN_ROW, false); + + /* Shift-DR -> Exit1-DR */ + cpld_xc2c_jtag_shift_u32(jtag, 0b1000000, address, 7); + + /* Exit1-DR -> Update-DR -> Run-Test/Idle */ + cpld_xc2c_jtag_shift_u32(jtag, 0b01, 0b00, 2); +} + +static void cpld_xc2c64a_jtag_sram_read_row(const jtag_t* const jtag, uint8_t* const data, const uint8_t next_address) { + /* Run-Test/Idle -> Shift-DR */ + cpld_xc2c_jtag_shift_u32(jtag, 0b001, 0b000, 3); + + /* Shift-DR */ + cpld_xc2c_jtag_shift_ptr_tms(jtag, data, 0, CPLD_XC2C64A_BITS_IN_ROW, false); + + /* Shift-DR -> Exit1-DR */ + cpld_xc2c_jtag_shift_u32(jtag, 0b1000000, next_address, 7); + + /* Weird, non-IEEE1532 compliant path through TAP machine, described in Xilinx + * Programmer Qualification Specification, applicable only to XC2C64/A. + * Exit1-DR -> Pause-DR -> Exit2-DR -> Update-DR -> Run-Test/Idle + */ + cpld_xc2c_jtag_shift_u32(jtag, 0b0110, 0b0000, 4); +} + +static bool cpld_xc2c64a_jtag_sram_compare_row(const jtag_t* const jtag, const uint8_t* const expected, const uint8_t* const mask, const uint8_t next_address) { + /* Run-Test/Idle -> Shift-DR */ + uint8_t read[CPLD_XC2C64A_BYTES_IN_ROW]; + memset(read, 0xff, sizeof(read)); + cpld_xc2c64a_jtag_sram_read_row(jtag, &read[0], next_address); + + bool matched = true; + if( (expected != NULL) && (mask != NULL) ) { + for(size_t i=0; irow[row].data[0]); + } + + cpld_xc2c_jtag_disable(jtag); + cpld_xc2c_jtag_bypass(jtag, false); + cpld_xc2c_jtag_reset(jtag); +} + +bool cpld_xc2c64a_jtag_sram_verify( + const jtag_t* const jtag, + const cpld_xc2c64a_program_t* const program, + const cpld_xc2c64a_verify_t* const verify +) { + cpld_xc2c_jtag_reset_and_idle(jtag); + cpld_xc2c_jtag_enable(jtag); + + cpld_xc2c_jtag_sram_read(jtag); + + /* Tricky loop to read dummy row first, then first address, then loop back to get + * the first row's data. + */ + bool matched = true; + for(size_t address_row=0; address_row<=CPLD_XC2C64A_ROWS; address_row++) { + const int data_row = (int)address_row - 1; + const size_t mask_index = (data_row >= 0) ? verify->mask_index[data_row] : 0; + const uint8_t* const expected = (data_row >= 0) ? &program->row[data_row].data[0] : NULL; + const uint8_t* const mask = (data_row >= 0) ? &verify->mask[mask_index].value[0] : NULL; + const uint8_t next_address = (address_row < CPLD_XC2C64A_ROWS) ? cpld_xc2c64a_row_address[address_row] : 0; + matched &= cpld_xc2c64a_jtag_sram_compare_row(jtag, expected, mask, next_address); + } + + cpld_xc2c_jtag_disable(jtag); + cpld_xc2c_jtag_bypass(jtag, false); + cpld_xc2c_jtag_reset(jtag); + + return matched; +} diff --git a/firmware/common/cpld_xc2c.h b/firmware/common/cpld_xc2c.h index d9b9231b..ccd3c5f1 100644 --- a/firmware/common/cpld_xc2c.h +++ b/firmware/common/cpld_xc2c.h @@ -27,6 +27,44 @@ #include "cpld_jtag.h" -bool cpld_xc2c64a_jtag_checksum(const jtag_t* const jtag, uint32_t* const crc_value); +/* Xilinx CoolRunner II XC2C64A bitstream attributes */ +#define CPLD_XC2C64A_ROWS (98) +#define CPLD_XC2C64A_BITS_IN_ROW (274) +#define CPLD_XC2C64A_BYTES_IN_ROW ((CPLD_XC2C64A_BITS_IN_ROW + 7) / 8) + +typedef struct { + uint8_t data[CPLD_XC2C64A_BYTES_IN_ROW]; +} cpld_xc2c64a_row_data_t; + +typedef struct { + cpld_xc2c64a_row_data_t row[CPLD_XC2C64A_ROWS]; +} cpld_xc2c64a_program_t; + +typedef struct { + uint8_t value[CPLD_XC2C64A_BYTES_IN_ROW]; +} cpld_xc2c64a_row_mask_t; + +typedef struct { + cpld_xc2c64a_row_mask_t mask[6]; + uint8_t mask_index[CPLD_XC2C64A_ROWS]; +} cpld_xc2c64a_verify_t; + +bool cpld_xc2c64a_jtag_checksum( + const jtag_t* const jtag, + const cpld_xc2c64a_verify_t* const verify, + uint32_t* const crc_value +); +void cpld_xc2c64a_jtag_sram_write( + const jtag_t* const jtag, + const cpld_xc2c64a_program_t* const program +); +bool cpld_xc2c64a_jtag_sram_verify( + const jtag_t* const jtag, + const cpld_xc2c64a_program_t* const program, + const cpld_xc2c64a_verify_t* const verify +); + +extern const cpld_xc2c64a_program_t cpld_hackrf_program_sram; +extern const cpld_xc2c64a_verify_t cpld_hackrf_verify; #endif/*__CPLD_XC2C_H__*/ diff --git a/firmware/common/hackrf_core.c b/firmware/common/hackrf_core.c index 03dbd06f..0dafcfdd 100644 --- a/firmware/common/hackrf_core.c +++ b/firmware/common/hackrf_core.c @@ -867,3 +867,18 @@ void led_toggle(const led_t led) { void hw_sync_enable(const hw_sync_mode_t hw_sync_mode){ gpio_write(&gpio_hw_sync_enable, hw_sync_mode==1); } + +void halt_and_flash(const uint32_t duration) { + /* blink LED1, LED2, and LED3 */ + while (1) + { + led_on(LED1); + led_on(LED2); + led_on(LED3); + delay(duration); + led_off(LED1); + led_off(LED2); + led_off(LED3); + delay(duration); + } +} \ No newline at end of file diff --git a/firmware/common/hackrf_core.h b/firmware/common/hackrf_core.h index b813d83a..3b2f7982 100644 --- a/firmware/common/hackrf_core.h +++ b/firmware/common/hackrf_core.h @@ -312,6 +312,7 @@ void led_toggle(const led_t led); void hw_sync_enable(const hw_sync_mode_t hw_sync_mode); +void halt_and_flash(const uint32_t duration); #ifdef __cplusplus } diff --git a/firmware/hackrf_usb/CMakeLists.txt b/firmware/hackrf_usb/CMakeLists.txt index 0c34cf18..cc895b6c 100644 --- a/firmware/hackrf_usb/CMakeLists.txt +++ b/firmware/hackrf_usb/CMakeLists.txt @@ -25,6 +25,12 @@ project(hackrf_usb C) include(../hackrf-common.cmake) +add_custom_command( + OUTPUT ${PATH_HACKRF_CPLD_DATA_C} + COMMAND ${PATH_CPLD_BITSTREAM_TOOL} --code ${PATH_HACKRF_CPLD_XSVF} >${PATH_HACKRF_CPLD_DATA_C} + DEPENDS ${PATH_CPLD_BITSTREAM_TOOL} ${PATH_HACKRF_CPLD_XSVF} +) + set(SRC_M4 hackrf_usb.c "${PATH_HACKRF_FIRMWARE_COMMON}/tuning.c" @@ -48,6 +54,7 @@ set(SRC_M4 "${PATH_HACKRF_FIRMWARE_COMMON}/fault_handler.c" "${PATH_HACKRF_FIRMWARE_COMMON}/cpld_jtag.c" "${PATH_HACKRF_FIRMWARE_COMMON}/cpld_xc2c.c" + "${PATH_HACKRF_CPLD_DATA_C}" "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/lenval.c" "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/micro.c" "${PATH_HACKRF_FIRMWARE_COMMON}/xapp058/ports.c" diff --git a/firmware/hackrf_usb/hackrf_usb.c b/firmware/hackrf_usb/hackrf_usb.c index 6a298c6c..4714dbfe 100644 --- a/firmware/hackrf_usb/hackrf_usb.c +++ b/firmware/hackrf_usb/hackrf_usb.c @@ -45,6 +45,7 @@ #include "usb_api_sweep.h" #include "usb_api_transceiver.h" #include "usb_bulk_buffer.h" +#include "cpld_xc2c.h" #include "hackrf-ui.h" @@ -211,6 +212,14 @@ void usb_set_descriptor_by_serial_number(void) } } +static bool cpld_jtag_sram_load(jtag_t* const jtag) { + cpld_jtag_take(jtag); + cpld_xc2c64a_jtag_sram_write(jtag, &cpld_hackrf_program_sram); + const bool success = cpld_xc2c64a_jtag_sram_verify(jtag, &cpld_hackrf_program_sram, &cpld_hackrf_verify); + cpld_jtag_release(jtag); + return success; +} + int main(void) { pin_setup(); enable_1v8_power(); @@ -222,6 +231,10 @@ int main(void) { #endif cpu_clock_init(); + if( !cpld_jtag_sram_load(&jtag_cpld) ) { + halt_and_flash(6000000); + } + #ifndef DFU_MODE usb_set_descriptor_by_serial_number(); #endif diff --git a/firmware/hackrf_usb/usb_api_cpld.c b/firmware/hackrf_usb/usb_api_cpld.c index f1cf1ec4..fc5ab51f 100644 --- a/firmware/hackrf_usb/usb_api_cpld.c +++ b/firmware/hackrf_usb/usb_api_cpld.c @@ -61,8 +61,6 @@ static void refill_cpld_buffer(void) void cpld_update(void) { - #define WAIT_LOOP_DELAY (6000000) - int i; int error; usb_queue_flush_endpoint(&usb_endpoint_bulk_in); @@ -75,20 +73,7 @@ void cpld_update(void) refill_cpld_buffer); if(error == 0) { - /* blink LED1, LED2, and LED3 on success */ - while (1) - { - led_on(LED1); - led_on(LED2); - led_on(LED3); - for (i = 0; i < WAIT_LOOP_DELAY; i++) /* Wait a bit. */ - __asm__("nop"); - led_off(LED1); - led_off(LED2); - led_off(LED3); - for (i = 0; i < WAIT_LOOP_DELAY; i++) /* Wait a bit. */ - __asm__("nop"); - } + halt_and_flash(6000000); }else { /* LED3 (Red) steady on error */ @@ -106,7 +91,7 @@ usb_request_status_t usb_vendor_request_cpld_checksum( if (stage == USB_TRANSFER_STAGE_SETUP) { cpld_jtag_take(&jtag_cpld); - const bool checksum_success = cpld_xc2c64a_jtag_checksum(&jtag_cpld, &cpld_crc); + const bool checksum_success = cpld_xc2c64a_jtag_checksum(&jtag_cpld, &cpld_hackrf_verify, &cpld_crc); cpld_jtag_release(&jtag_cpld); if(!checksum_success) { diff --git a/firmware/tools/cpld_bitstream.py b/firmware/tools/cpld_bitstream.py new file mode 100755 index 00000000..ac70742f --- /dev/null +++ b/firmware/tools/cpld_bitstream.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 + +# Xilinx CoolRunner II XC2C64A characteristics + +bits_of_address = 7 +bits_of_data = 274 +bytes_of_data = (bits_of_data + 7) // 8 +bits_in_program_row = bits_of_address + bits_of_data + +def values_list_line_wrap(values): + line_length = 16 + return [' '.join(values[n:n+line_length]) for n in range(0, len(values), line_length)] + +def dec_lines(bytes): + return values_list_line_wrap(['%d,' % n for n in bytes]) + +def hex_lines(bytes): + return values_list_line_wrap(['0x%02x,' % n for n in bytes]) + +def reverse_bits(n, bit_count): + byte_count = (bit_count + 7) >> 3 + # n = int(bytes.hex(), 16) + n_bits = bin(n)[2:].zfill(bit_count) + n_bits_reversed = n_bits[::-1] + n_reversed = int(n_bits_reversed, 2) + return n_reversed.to_bytes(byte_count, byteorder='little') + +def extract_addresses(block): + return tuple([row['address'] for row in block]) + +def extract_data(block): + return tuple([row['data'] for row in block]) + +def extract_mask(block): + return tuple([row['mask'] for row in block]) + +def equal_blocks(block1, block2, mask): + block1_data = extract_data(block1) + block2_data = extract_data(block2) + assert(len(block1_data) == len(block2_data)) + assert(len(block1_data) == len(mask)) + for row1, row2, mask in zip(block1_data, block2_data, mask): + differences = (row1 ^ row2) & mask + if differences != 0: + return False + return True + +def dump_block(rows, endian='little'): + data_bytes = (bits_of_data + 7) >> 3 + for row in rows: + print('%02x %s' % (row['address'], row['data'].to_bytes(data_bytes, byteorder=endian).hex())) + +def extract_programming_data(commands): + ir_map = { + 0x01: 'idcode', + 0xc0: 'conld', + 0xe8: 'enable', + 0xea: 'program', + 0xed: 'erase', + 0xee: 'verify', + 0xf0: 'init', + 0xff: 'bypass', + # Other instructions unimplemented and if encountered, will cause tool to crash. + } + + ir = None + program = [] + verify = [] + for command in commands: + if command['type'] == 'xsir': + ir = ir_map[command['tdi']['data'][0]] + if ir == 'program': + program.append([]) + if ir == 'verify': + verify.append([]) + elif ir == 'verify' and command['type'] == 'xsdrtdo': + tdi_length = command['tdi']['length'] + end_state = command['end_state'] + if tdi_length == bits_of_address and end_state == 1: + address = int(command['tdi']['data'].hex(), 16) + verify[-1].append({'address': address}) + elif tdi_length == bits_of_data and end_state == 0: + mask = int(command['tdo_mask']['data'].hex(), 16) + expected = int(command['tdo_expected']['data'].hex(), 16) + verify[-1][-1]['data'] = expected + verify[-1][-1]['mask'] = mask + elif ir == 'program' and command['type'] == 'xsdrtdo': + tdi_length = command['tdi']['length'] + end_state = command['end_state'] + if tdi_length == bits_in_program_row and end_state == 0: + tdi = int(command['tdi']['data'].hex(), 16) + address = (tdi >> bits_of_data) & ((1 << bits_of_address) - 1) + data = tdi & ((1 << bits_of_data) - 1) + program[-1].append({ + 'address': address, + 'data': data + }) + + return { + 'program': program, + 'verify': verify, + } + +def validate_programming_data(programming_data): + expected_address_sequence = (0x00, 0x40, 0x60, 0x20, 0x30, 0x70, 0x50, 0x10, 0x18, 0x58, 0x78, 0x38, 0x28, 0x68, 0x48, 0x08, 0x0c, 0x4c, 0x6c, 0x2c, 0x3c, 0x7c, 0x5c, 0x1c, 0x14, 0x54, 0x74, 0x34, 0x24, 0x64, 0x44, 0x04, 0x06, 0x46, 0x66, 0x26, 0x36, 0x76, 0x56, 0x16, 0x1e, 0x5e, 0x7e, 0x3e, 0x2e, 0x6e, 0x4e, 0x0e, 0x0a, 0x4a, 0x6a, 0x2a, 0x3a, 0x7a, 0x5a, 0x1a, 0x12, 0x52, 0x72, 0x32, 0x22, 0x62, 0x42, 0x02, 0x03, 0x43, 0x63, 0x23, 0x33, 0x73, 0x53, 0x13, 0x1b, 0x5b, 0x7b, 0x3b, 0x2b, 0x6b, 0x4b, 0x0b, 0x0f, 0x4f, 0x6f, 0x2f, 0x3f, 0x7f, 0x5f, 0x1f, 0x17, 0x57, 0x77, 0x37, 0x27, 0x67, 0x47, 0x07, 0x05, 0x45,) + + # Validate program blocks: + + # There should be two extracted program blocks. The first contains the + # the bitstream with done bit(s) not asserted. The second updates the + # "done" bit(s) to finish the process. + assert(len(programming_data['program']) == 2) + + # First program phase writes the bitstream to flash (or SRAM) with + # special bit(s) not asserted, so the bitstream is not yet valid. + assert(extract_addresses(programming_data['program'][0]) == expected_address_sequence) + + # Second program phase updates a single row to finish the programming + # process. + assert(len(programming_data['program'][1]) == 1) + assert(programming_data['program'][1][0]['address'] == 0x05) + + # Validate verify blocks: + + # There should be two extracted verify blocks. + assert(len(programming_data['verify']) == 2) + + # The two verify blocks should match. + assert(programming_data['verify'][0] == programming_data['verify'][1]) + + # Check the row address order of the second verify block. + assert(extract_addresses(programming_data['verify'][0]) == expected_address_sequence) + assert(extract_addresses(programming_data['verify'][1]) == expected_address_sequence) + + # Checks across programming and verification: + + # Check that program data matches data expected during verification. + assert(equal_blocks(programming_data['program'][0], programming_data['verify'][0], extract_mask(programming_data['verify'][0]))) + assert(equal_blocks(programming_data['program'][0], programming_data['verify'][1], extract_mask(programming_data['verify'][1]))) + +def make_sram_program(program_blocks): + program_sram = list(program_blocks[0]) + program_sram[-2] = program_blocks[1][0] + return program_sram + +####################################################################### +# Command line argument parsing. +####################################################################### + +import argparse + +parser = argparse.ArgumentParser() +action_group = parser.add_mutually_exclusive_group(required=True) +action_group.add_argument('--checksum', action='store_true', help='Calculate bitstream read-back CRC32 value') +action_group.add_argument('--code', action='store_true', help='Generate C code for bitstream loading/programming/verification') +parser.add_argument('--crcmod', action='store_true', help='Use Python crcmod library instead of built-in CRC32 code') +parser.add_argument('--debug', action='store_true', help='Enable debug output') +parser.add_argument('hackrf_xc2c_cpld_xsvf', type=str, help='HackRF Xilinx XC2C64A CPLD XSVF file containing erase/program/verify phases') +args = parser.parse_args() + +####################################################################### +# Generic XSVF parsing phase, produces a tree of commands performed +# against the CPLD. +####################################################################### + +with open(args.hackrf_xc2c_cpld_xsvf, "rb") as f: + from xsvf import XSVFParser + commands = XSVFParser().parse(f, debug=args.debug) + +programming_data = extract_programming_data(commands) +validate_programming_data(programming_data) + +####################################################################### +# Patch the second programming phase into the first for SRAM +# programming. +####################################################################### + +verify_blocks = programming_data['verify'] +program_blocks = programming_data['program'] + +####################################################################### +# Calculate CRC of data read from CPLD during the second verification +# pass, which is after the "done" bit is set. Mask off insignificant +# bits (turning them to zero) and extending rows to the next full byte. +####################################################################### + +if args.checksum: + if args.crcmod: + # Use a proper CRC library + import crcmod + crc = crcmod.predefined.Crc('crc-32') + else: + # Use my home-grown, simple, slow CRC32 object to avoid additional + # Python dependencies. + from dumb_crc32 import DumbCRC32 + crc = DumbCRC32() + + verify_block = verify_blocks[1] + for address, data, mask in verify_block: + valid_data = data & mask + bytes = valid_data.to_bytes(bytes_of_data, byteorder='little') + crc.update(bytes) + + print('0x%s' % crc.hexdigest().lower()) + +if args.code: + program_sram = make_sram_program(program_blocks) + verify_block = verify_blocks[1] + verify_masks = tuple(frozenset(extract_mask(verify_block))) + verify_mask_index = dict([(k, v) for v, k in enumerate(verify_masks)]) + verify_mask_row_index = [verify_mask_index[row['mask']] for row in verify_block] + + result = [] + result.extend(( + '/* WARNING: Auto-generated file. Do not edit. */', + '', + '#include ', + '', + 'const cpld_xc2c64a_program_t cpld_hackrf_program_sram = { {', + )) + data_lines = [', '.join(['0x%02x' % n for n in row['data'].to_bytes(bytes_of_data, byteorder='little')]) for row in program_sram] + result.extend(['\t{ { %s } },' % line for line in data_lines]) + result.extend(( + '} };', + '', + 'const cpld_xc2c64a_verify_t cpld_hackrf_verify = {', + '\t.mask = {', + )) + verify_mask_lines = [', '.join(['0x%02x' % n for n in mask.to_bytes(bytes_of_data, byteorder='little')]) for mask in verify_masks] + result.extend(['\t\t{ { %s } },' % line for line in verify_mask_lines]) + result.extend(( + '\t},' + '\t.mask_index = {', + )) + result.extend(['\t\t%s' % line for line in dec_lines(verify_mask_row_index)]) + result.extend(( + '\t}', + '};', + '', + )) + print('\n'.join(result)) diff --git a/firmware/tools/dumb_crc32.py b/firmware/tools/dumb_crc32.py new file mode 100644 index 00000000..411a836d --- /dev/null +++ b/firmware/tools/dumb_crc32.py @@ -0,0 +1,22 @@ + +class DumbCRC32(object): + def __init__(self): + self._remainder = 0xffffffff + self._reversed_polynomial = 0xedb88320 + self._final_xor = 0xffffffff + + def update(self, data): + bit_count = len(data) * 8 + for bit_n in range(bit_count): + bit_in = data[bit_n >> 3] & (1 << (bit_n & 7)) + self._remainder ^= 1 if bit_in != 0 else 0 + bit_out = (self._remainder & 1) + self._remainder >>= 1; + if bit_out != 0: + self._remainder ^= self._reversed_polynomial; + + def digest(self): + return self._remainder ^ self._final_xor + + def hexdigest(self): + return '%08x' % self.digest() diff --git a/firmware/tools/xsvf.py b/firmware/tools/xsvf.py new file mode 100644 index 00000000..ecd3c2c5 --- /dev/null +++ b/firmware/tools/xsvf.py @@ -0,0 +1,208 @@ + +import struct + +class XSVFParser(object): + def __init__(self): + self._handlers = { + 0x00: self.XCOMPLETE , + 0x01: self.XTDOMASK , + 0x02: self.XSIR , + 0x03: self.XSDR , + 0x04: self.XRUNTEST , + 0x07: self.XREPEAT , + 0x08: self.XSDRSIZE , + 0x09: self.XSDRTDO , + 0x0a: self.XSETSDRMASKS, + 0x0b: self.XSDRINC , + 0x0c: self.XSDRB , + 0x0d: self.XSDRC , + 0x0e: self.XSDRE , + 0x0f: self.XSDRTDOB , + 0x10: self.XSDRTDOC , + 0x11: self.XSDRTDOE , + 0x12: self.XSTATE , + 0x13: self.XENDIR , + 0x14: self.XENDDR , + 0x15: self.XSIR2 , + 0x16: self.XCOMMENT , + 0x17: self.XWAIT , + } + + def tdomask(self): + return self._xtdomask + + def read_byte(self): + return self.read_bytes(1)[0] + + def read_bytes(self, n): + c = self._f.read(n) + if len(c) == n: + return c + else: + raise RuntimeError('unexpected end of file') + + def read_bits(self, n): + length_bytes = (n + 7) >> 3 + return self.read_bytes(length_bytes) + + def read_u32(self): + return struct.unpack('>I', self.read_bytes(4))[0] + + def parse(self, f, debug=False): + self._f = f + self._debug = debug + self._xcomplete = False + self._xenddr = None + self._xendir = None + self._xruntest = 0 + self._xsdrsize = None + self._xtdomask = None + self._commands = [] + + while self._xcomplete == False: + self.read_instruction() + + self._f = None + + return self._commands + + def read_instruction(self): + instruction_id = self.read_byte() + if instruction_id in self._handlers: + instruction_handler = self._handlers[instruction_id] + result = instruction_handler() + if result is not None: + self._commands.append(result) + else: + raise RuntimeError('unexpected instruction 0x%02x' % instruction_id) + + def XCOMPLETE(self): + self._xcomplete = True + + def XTDOMASK(self): + length_bits = self._xsdrsize + self._xtdomask = self.read_bits(length_bits) + + def XSIR(self): + length_bits = self.read_byte() + tdi = self.read_bits(length_bits) + if self._debug: + print('XSIR tdi=%d:%s' % (length_bits, tdi.hex())) + return { + 'type': 'xsir', + 'tdi': { + 'length': length_bits, + 'data': tdi + }, + } + + def XSDR(self): + length_bits = self._xsdrsize + tdi = self.read_bits(length_bits) + if self._debug: + print('XSDR tdi=%d:%s' % (length_bits, tdi.hex())) + return { + 'type': 'xsdr', + 'tdi': { + 'length': length_bits, + 'data': tdi, + }, + } + + def XRUNTEST(self): + self._xruntest = self.read_u32() + if self._debug: + print('XRUNTEST number=%d' % self._xruntest) + + def XREPEAT(self): + repeat = self.read_byte() + # print('XREPEAT times=%d' % repeat) + + def XSDRSIZE(self): + self._xsdrsize = self.read_u32() + + def XSDRTDO(self): + length_bits = self._xsdrsize + tdi = self.read_bits(length_bits) + tdo_mask = self._xtdomask + self._tdo_expected = (length_bits, self.read_bits(length_bits)) + wait = self._xruntest + if wait == 0: + end_state = self._xenddr + else: + end_state = 1 # Run-Test/Idle + if self._debug: + print('XSDRTDO tdi=%d:%s tdo_mask=%d:%s tdo_expected=%d:%s end_state=%u wait=%u' % ( + length_bits, tdi.hex(), + length_bits, tdo_mask.hex(), + self._tdo_expected[0], self._tdo_expected[1].hex(), + end_state, + wait, + )) + return { + 'type': 'xsdrtdo', + 'tdi': { + 'length': length_bits, + 'data': tdi + }, + 'tdo_mask': { + 'length': length_bits, + 'data': tdo_mask, + }, + 'tdo_expected': { + 'length': self._tdo_expected[0], + 'data': self._tdo_expected[1], + }, + 'end_state': end_state, + 'wait': wait, + } + + def XSETSDRMASKS(self): + raise RuntimeError('unimplemented') + + def XSDRINC(self): + raise RuntimeError('unimplemented') + + def XSDRB(self): + raise RuntimeError('unimplemented') + + def XSDRC(self): + raise RuntimeError('unimplemented') + + def XSDRE(self): + raise RuntimeError('unimplemented') + + def XSDRTDOB(self): + raise RuntimeError('unimplemented') + + def XSDRTDOC(self): + raise RuntimeError('unimplemented') + + def XSDRTDOE(self): + raise RuntimeError('unimplemented') + + def XSTATE(self): + state = self.read_byte() + if self._debug: + print('XSTATE %u' % state) + return { + 'type': 'xstate', + 'state': state, + } + + def XENDIR(self): + self._xendir = self.read_byte() + + def XENDDR(self): + self._xenddr = self.read_byte() + + def XSIR2(self): + raise RuntimeError('unimplemented') + + def XCOMMENT(self): + raise RuntimeError('unimplemented') + + def XWAIT(self): + wait_state = self.read_byte() + end_state = self.read_byte() + wait_time = self.read_u32()