diff --git a/firmware/common/fpga.c b/firmware/common/fpga.c index f00db3e9..6a5ad42d 100644 --- a/firmware/common/fpga.c +++ b/firmware/common/fpga.c @@ -74,7 +74,7 @@ static size_t fpga_image_read_block_cb(void* _ctx, uint8_t* out_buffer) bool fpga_image_load(unsigned int index) { #if defined(DFU_MODE) || defined(RAM_MODE) - selftest.fpga_image_load_ok = false; + selftest.fpga_image_load = SKIPPED; selftest.report.pass = false; return false; #endif @@ -108,22 +108,33 @@ bool fpga_image_load(unsigned int index) ssp1_set_mode_max283x(); // Update selftest result. - selftest.fpga_image_load_ok = success; - if (!selftest.fpga_image_load_ok) { + selftest.fpga_image_load = success ? PASSED : FAILED; + if (selftest.fpga_image_load != PASSED) { selftest.report.pass = false; } return success; } -static void rx_samples(const unsigned int num_samples) +static int rx_samples(const unsigned int num_samples, uint32_t max_cycles) { + uint32_t cycle_count = 0; + int rc = 0; + m0_set_mode(M0_MODE_RX); m0_state.shortfall_limit = 0; baseband_streaming_enable(&sgpio_config); - while (m0_state.m0_count < num_samples) {} + while (m0_state.m0_count < num_samples) { + cycle_count++; + if ((max_cycles > 0) && (cycle_count >= max_cycles)) { + rc = -1; + break; + } + } baseband_streaming_disable(&sgpio_config); m0_set_mode(M0_MODE_IDLE); + + return rc; } static uint8_t lfsr_advance(uint8_t v) @@ -135,12 +146,15 @@ static uint8_t lfsr_advance(uint8_t v) bool fpga_sgpio_selftest() { #if defined(DFU_MODE) || defined(RAM_MODE) + selftest.sgpio_rx = SKIPPED; return false; #endif + bool timeout = false; + // Skip if FPGA configuration failed. - if (!selftest.fpga_image_load_ok) { - selftest.sgpio_rx_ok = false; + if (selftest.fpga_image_load != PASSED) { + selftest.sgpio_rx = SKIPPED; return false; } @@ -151,7 +165,9 @@ bool fpga_sgpio_selftest() // Stream 512 samples from the FPGA. sgpio_configure(&sgpio_config, SGPIO_DIRECTION_RX); - rx_samples(512); + if (rx_samples(512, 10000) == -1) { + timeout = true; + } // Disable PRBS mode. ssp1_set_mode_ice40(); @@ -170,12 +186,18 @@ bool fpga_sgpio_selftest() } // Update selftest result. - selftest.sgpio_rx_ok = seq_in_sync; - if (!selftest.sgpio_rx_ok) { + if (seq_in_sync) { + selftest.sgpio_rx = PASSED; + } else if (timeout) { + selftest.sgpio_rx = TIMEOUT; + } else { + selftest.sgpio_rx = FAILED; + } + if (selftest.sgpio_rx != PASSED) { selftest.report.pass = false; } - return selftest.sgpio_rx_ok; + return selftest.sgpio_rx == PASSED; } static void measure_tone(int8_t* samples, size_t len, struct xcvr_measurements* results) @@ -220,12 +242,15 @@ static bool in_range(int value, int expected, int error) bool fpga_if_xcvr_selftest() { #if defined(DFU_MODE) || defined(RAM_MODE) + selftest.xcvr_loopback = SKIPPED; return false; #endif + bool timeout = false; + // Skip if FPGA configuration failed. - if (!selftest.fpga_image_load_ok) { - selftest.xcvr_loopback_ok = false; + if (selftest.fpga_image_load != PASSED) { + selftest.xcvr_loopback = SKIPPED; return false; } @@ -245,7 +270,9 @@ bool fpga_if_xcvr_selftest() // Capture 1: 4 Msps, tone at 0.5 MHz, narrowband filter OFF sample_rate_frac_set(4000000 * 2, 1); delay_us_at_mhz(1000, 204); - rx_samples(num_samples); + if (rx_samples(num_samples, 2000000) == -1) { + timeout = true; + } measure_tone( (int8_t*) usb_bulk_buffer, num_samples, @@ -254,7 +281,9 @@ bool fpga_if_xcvr_selftest() // Capture 2: 4 Msps, tone at 0.5 MHz, narrowband filter ON narrowband_filter_set(1); delay_us_at_mhz(1000, 204); - rx_samples(num_samples); + if (rx_samples(num_samples, 2000000) == -1) { + timeout = true; + } measure_tone( (int8_t*) usb_bulk_buffer, num_samples, @@ -267,7 +296,9 @@ bool fpga_if_xcvr_selftest() sample_rate_frac_set(20000000 * 2, 1); narrowband_filter_set(0); delay_us_at_mhz(1000, 204); - rx_samples(num_samples); + if (rx_samples(num_samples, 2000000) == -1) { + timeout = true; + } measure_tone( (int8_t*) usb_bulk_buffer, num_samples, @@ -276,7 +307,9 @@ bool fpga_if_xcvr_selftest() // Capture 4: 20 Msps, tone at 5 MHz, narrowband filter ON narrowband_filter_set(1); delay_us_at_mhz(1000, 204); - rx_samples(num_samples); + if (rx_samples(num_samples, 2000000) == -1) { + timeout = true; + } measure_tone( (int8_t*) usb_bulk_buffer, num_samples, @@ -291,6 +324,12 @@ bool fpga_if_xcvr_selftest() ice40_spi_write(&ice40, 0x03, 0); ssp1_set_mode_max283x(); + if (timeout) { + selftest.xcvr_loopback = TIMEOUT; + selftest.report.pass = false; + return false; + } + unsigned int expected_zcs; bool i_in_range; bool q_in_range; @@ -344,11 +383,13 @@ bool fpga_if_xcvr_selftest() bool capture_3_test = energy_in_range; // Update selftest result. - selftest.xcvr_loopback_ok = - capture_0_test && capture_1_test && capture_2_test && capture_3_test; - if (!selftest.xcvr_loopback_ok) { + selftest.xcvr_loopback = + (capture_0_test && capture_1_test && capture_2_test && capture_3_test) ? + PASSED : + FAILED; + if (selftest.xcvr_loopback != PASSED) { selftest.report.pass = false; } - return selftest.xcvr_loopback_ok; + return selftest.xcvr_loopback == PASSED; } diff --git a/firmware/common/selftest.h b/firmware/common/selftest.h index 674f73da..3860cf76 100644 --- a/firmware/common/selftest.h +++ b/firmware/common/selftest.h @@ -25,6 +25,15 @@ #include #include +enum { + FAILED = 0, + PASSED = 1, + SKIPPED = 2, + TIMEOUT = 3, +}; + +typedef uint8_t test_result_t; + typedef struct { uint16_t mixer_id; #ifdef PRALINE @@ -41,9 +50,9 @@ typedef struct { uint8_t si5351_rev_id; bool si5351_readback_ok; #ifdef PRALINE - bool fpga_image_load_ok; - bool sgpio_rx_ok; - bool xcvr_loopback_ok; + test_result_t fpga_image_load; + test_result_t sgpio_rx; + test_result_t xcvr_loopback; struct xcvr_measurements { uint32_t zcs_i; diff --git a/firmware/hackrf_usb/usb_api_selftest.c b/firmware/hackrf_usb/usb_api_selftest.c index 947b19c6..ce7d86ad 100644 --- a/firmware/hackrf_usb/usb_api_selftest.c +++ b/firmware/hackrf_usb/usb_api_selftest.c @@ -54,6 +54,21 @@ void append(char** dest, size_t* capacity, const char* str) } } +static const char* test_result_to_str(test_result_t result) +{ + switch (result) { + case FAILED: + return "FAIL"; + case PASSED: + return "PASS"; + case SKIPPED: + return "SKIP"; + case TIMEOUT: + return "TIMEOUT"; + } + return "????"; +} + void generate_selftest_report(void) { char* s = &selftest.report.msg[0]; @@ -99,13 +114,13 @@ void generate_selftest_report(void) #endif #ifdef PRALINE append(&s, &c, "FPGA configuration: "); - append(&s, &c, selftest.fpga_image_load_ok ? "PASS" : "FAIL"); + append(&s, &c, test_result_to_str(selftest.fpga_image_load)); append(&s, &c, "\n"); append(&s, &c, "SGPIO RX test: "); - append(&s, &c, selftest.sgpio_rx_ok ? "PASS" : "FAIL"); + append(&s, &c, test_result_to_str(selftest.sgpio_rx)); append(&s, &c, "\n"); append(&s, &c, "Loopback test: "); - append(&s, &c, selftest.xcvr_loopback_ok ? "PASS" : "FAIL"); + append(&s, &c, test_result_to_str(selftest.xcvr_loopback)); append(&s, &c, "\n"); // Dump transceiver loopback measurements. for (int i = 0; i < 4; ++i) {