New FPGA self test routine
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
This commit is contained in:
parent
a1ae09acdd
commit
68da02427c
1 changed files with 516 additions and 167 deletions
683
main/fpga_test.c
683
main/fpga_test.c
|
@ -1,17 +1,20 @@
|
|||
#include "fpga_test.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <driver/gpio.h>
|
||||
#include "hardware.h"
|
||||
#include "ili9341.h"
|
||||
#include "ice40.h"
|
||||
#include "rp2040.h"
|
||||
#include "hardware.h"
|
||||
#include "fpga_test.h"
|
||||
|
||||
extern const uint8_t fpga_selftest_bin_start[] asm("_binary_fpga_selftest_bin_start");
|
||||
extern const uint8_t fpga_selftest_bin_end[] asm("_binary_fpga_selftest_bin_end");
|
||||
|
||||
|
||||
static const char *TAG = "fpga_test";
|
||||
|
||||
esp_err_t load_file_into_psram(ICE40* ice40, FILE* fd) {
|
||||
|
@ -22,207 +25,553 @@ esp_err_t load_file_into_psram(ICE40* ice40, FILE* fd) {
|
|||
uint8_t* tx_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (tx_buffer == NULL) return ESP_FAIL;
|
||||
|
||||
while(1) {
|
||||
tx_buffer[0] = write_cmd;
|
||||
tx_buffer[1] = (position >> 16);
|
||||
tx_buffer[2] = (position >> 8) & 0xFF;
|
||||
tx_buffer[3] = position & 0xFF;
|
||||
amount_read = fread(&tx_buffer[4], 1, SPI_MAX_TRANSFER_SIZE - 4, fd);
|
||||
if (amount_read < 1) break;
|
||||
ESP_LOGI(TAG, "Writing PSRAM @ %u (%u bytes)", position, amount_read);
|
||||
esp_err_t res = ice40_transaction(ice40, tx_buffer, amount_read + 4, NULL, 0);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Write transaction failed @ %u", position);
|
||||
free(tx_buffer);
|
||||
return res;
|
||||
}
|
||||
position += amount_read;
|
||||
};
|
||||
free(tx_buffer);
|
||||
return ESP_OK;
|
||||
}
|
||||
/* SPI commands */
|
||||
#define SPI_CMD_NOP1 0x00
|
||||
#define SPI_CMD_SOC_MSG 0x10
|
||||
#define SPI_CMD_REG_ACCESS 0xf0
|
||||
#define SPI_CMD_LOOPBACK 0xf1
|
||||
#define SPI_CMD_LCD_PASSTHROUGH 0xf2
|
||||
#define SPI_CMD_BUTTON_REPORT 0xf4
|
||||
#define SPI_CMD_IRQ_ACK 0xfd
|
||||
#define SPI_CMD_RESP_ACK 0xfe
|
||||
#define SPI_CMD_NOP2 0xff
|
||||
|
||||
esp_err_t load_buffer_into_psram(ICE40* ice40, uint8_t* buffer, uint32_t buffer_length) {
|
||||
const uint8_t write_cmd = 0x02;
|
||||
uint32_t position = 0;
|
||||
uint8_t* tx_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (tx_buffer == NULL) return ESP_FAIL;
|
||||
while(1) {
|
||||
tx_buffer[0] = write_cmd;
|
||||
tx_buffer[1] = (position >> 16);
|
||||
tx_buffer[2] = (position >> 8) & 0xFF;
|
||||
tx_buffer[3] = position & 0xFF;
|
||||
uint32_t length = buffer_length - position;
|
||||
if (length > SPI_MAX_TRANSFER_SIZE - 4) length = SPI_MAX_TRANSFER_SIZE - 4;
|
||||
memcpy(&tx_buffer[4], &buffer[position], length);
|
||||
if (length == 0) break;
|
||||
ESP_LOGI(TAG, "Writing PSRAM @ %u (%u bytes)", position, length);
|
||||
esp_err_t res = ice40_transaction(ice40, tx_buffer, length + 4, NULL, 0);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Write transaction failed @ %u", position);
|
||||
free(tx_buffer);
|
||||
return res;
|
||||
}
|
||||
position += length;
|
||||
};
|
||||
free(tx_buffer);
|
||||
return ESP_OK;
|
||||
}
|
||||
/* Messages to self-test SoC */
|
||||
#define SOC_CMD_PING 0x00
|
||||
#define SOC_CMD_PING_PARAM 0xc0ffee
|
||||
#define SOC_CMD_PING_RESP 0xcafebabe
|
||||
|
||||
esp_err_t verify_file_in_psram(ICE40* ice40, FILE* fd) {
|
||||
fseek(fd, 0, SEEK_SET);
|
||||
const uint8_t read_cmd = 0x03;
|
||||
uint32_t amount_read;
|
||||
uint32_t position = 0;
|
||||
uint8_t* tx_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (tx_buffer == NULL) return ESP_FAIL;
|
||||
memset(tx_buffer, 0, SPI_MAX_TRANSFER_SIZE);
|
||||
uint8_t* verify_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (verify_buffer == NULL) return ESP_FAIL;
|
||||
uint8_t* rx_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (rx_buffer == NULL) return ESP_FAIL;
|
||||
#define SOC_CMD_RGB_STATE_SET 0x10
|
||||
#define SOC_CMD_IRQN_SET 0x11
|
||||
#define SOC_CMD_LCD_RGB_CYCLE_SET 0x12
|
||||
#define SOC_CMD_PMOD_CYCLE_SET 0x13
|
||||
#define SOC_CMD_LCD_PASSTHROUGH_SET 0x14
|
||||
|
||||
while(1) {
|
||||
tx_buffer[0] = read_cmd;
|
||||
tx_buffer[1] = (position >> 16);
|
||||
tx_buffer[2] = (position >> 8) & 0xFF;
|
||||
tx_buffer[3] = position & 0xFF;
|
||||
amount_read = fread(&verify_buffer[4], 1, SPI_MAX_TRANSFER_SIZE - 4, fd);
|
||||
if (amount_read < 1) break;
|
||||
ESP_LOGI(TAG, "Reading PSRAM @ %u (%u bytes)", position, amount_read);
|
||||
esp_err_t res = ice40_transaction(ice40, tx_buffer, amount_read + 4, rx_buffer, amount_read + 4);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Read transaction failed @ %u", position);
|
||||
free(tx_buffer);
|
||||
return res;
|
||||
}
|
||||
position += amount_read;
|
||||
ESP_LOGI(TAG, "Verifying PSRAM @ %u (%u bytes)", position, amount_read);
|
||||
for (uint32_t i = 4; i < amount_read; i++) {
|
||||
if (rx_buffer[i] != verify_buffer[i]) {
|
||||
ESP_LOGE(TAG, "Verifying PSRAM @ %u failed: %02X != %02X", position + i, rx_buffer[i], verify_buffer[i]);
|
||||
free(tx_buffer);
|
||||
free(rx_buffer);
|
||||
free(verify_buffer);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
};
|
||||
free(tx_buffer);
|
||||
free(rx_buffer);
|
||||
free(verify_buffer);
|
||||
ESP_LOGI(TAG, "PSRAM contents verified!");
|
||||
return ESP_OK;
|
||||
}
|
||||
#define SOC_CMD_PSRAM_TEST 0x20
|
||||
#define SOC_CMD_UART_LOOPBACK_TEST 0x21
|
||||
#define SOC_CMD_PMOD_OPEN_TEST 0x22
|
||||
#define SOC_CMD_PMOD_PLUG_TEST 0x23
|
||||
#define SOC_CMD_LCD_INIT_TEST 0x24
|
||||
|
||||
bool test_spi(ICE40* ice40) {
|
||||
#define SOC_CMD_LCD_CHECK_MODE 0x30
|
||||
|
||||
#define SOC_RESP_OK 0x00000000
|
||||
|
||||
|
||||
/* SoC commands */
|
||||
|
||||
static bool soc_message(ICE40* ice40, uint8_t cmd, uint32_t param, uint32_t *resp, TickType_t ticks_to_wait) {
|
||||
esp_err_t res;
|
||||
uint8_t data_tx[256];
|
||||
uint8_t data_rx[128];
|
||||
uint8_t data_tx[6];
|
||||
uint8_t data_rx[6];
|
||||
|
||||
// Generate pseudo random sequence
|
||||
data_tx[0] = 1;
|
||||
for (int i = 1; i < 256; i++)
|
||||
/* Default delay */
|
||||
ticks_to_wait /= 10; /* We do 10 retries */
|
||||
if (!ticks_to_wait)
|
||||
ticks_to_wait = pdMS_TO_TICKS(50);
|
||||
|
||||
/* Prepare message */
|
||||
data_tx[0] = SPI_CMD_SOC_MSG;
|
||||
data_tx[1] = cmd;
|
||||
data_tx[2] = (param >> 16) & 0xff;
|
||||
data_tx[3] = (param >> 8) & 0xff;
|
||||
data_tx[4] = (param ) & 0xff;
|
||||
|
||||
/* Send message to PicoRV */
|
||||
res = ice40_send_turbo(ice40, data_tx, 5);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "SoC message TX failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Poll until we get a response */
|
||||
data_tx[0] = SPI_CMD_RESP_ACK;
|
||||
|
||||
for (int i=0; i<10; i++) {
|
||||
/* Poll */
|
||||
res = ice40_transaction(ice40, data_tx, 6, data_rx, 6);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "SoC response RX failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Was response valid ? */
|
||||
if (data_rx[1] & 0x80)
|
||||
break;
|
||||
|
||||
/* Wait before retry */
|
||||
vTaskDelay(ticks_to_wait);
|
||||
}
|
||||
|
||||
if (!(data_rx[1] & 0x80)) {
|
||||
ESP_LOGE(TAG, "SoC response RX timeout");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Report response */
|
||||
if (resp) {
|
||||
*resp = 0;
|
||||
for (int i=0; i<4; i++)
|
||||
*resp = (*resp << 8) | data_rx[2+i];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* Test routines */
|
||||
|
||||
static bool test_bitstream_load(ICE40* ice40, uint32_t *rc) {
|
||||
esp_err_t res;
|
||||
|
||||
res = ice40_load_bitstream(ice40, fpga_selftest_bin_start, fpga_selftest_bin_end - fpga_selftest_bin_start);
|
||||
if (res != ESP_OK) {
|
||||
*rc = res;
|
||||
return false;
|
||||
}
|
||||
|
||||
*rc = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test_spi_loopback_one(ICE40* ice40) {
|
||||
esp_err_t res;
|
||||
uint8_t data_tx[257];
|
||||
uint8_t data_rx[258];
|
||||
|
||||
/* Generate pseudo random sequence */
|
||||
data_tx[1] = 1;
|
||||
for (int i = 2; i < 257; i++)
|
||||
data_tx[i] = (data_tx[i-1] << 1) ^ ((data_tx[i-1] & 0x80) ? 0x1d : 0x00);
|
||||
|
||||
// Send first 128 byte at high speed
|
||||
res = ice40_send_turbo(ice40, &data_tx[0], 128);
|
||||
/* Send 256 bytes at high speed with echo command */
|
||||
data_tx[0] = SPI_CMD_LOOPBACK;
|
||||
|
||||
res = ice40_send_turbo(ice40, data_tx, 257);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Transaction 1 failed (Turbo TX)");
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 1 failed (Turbo TX)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute full duplex transaction with next 128 bytes
|
||||
res = ice40_transaction(ice40, &data_tx[128], 128, data_rx, 128);
|
||||
/* Execute full duplex transaction with next 128 bytes */
|
||||
res = ice40_transaction(ice40, data_tx, 257, data_rx, 257);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Transaction 2 failed (Full Duplex)");
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 2 failed (Full Duplex)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate RX data
|
||||
if (memcmp(&data_rx[1], &data_tx[0], 127)) {
|
||||
printf("Transaction 1->2 integrity fail:\n");
|
||||
for (int i = 0; i < 128; i++)
|
||||
/* Validate response present */
|
||||
if ((data_rx[1] & 0x80) == 0) {
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 2 reports no response available\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Validate RX data (only 254 byte got read) */
|
||||
if (memcmp(&data_rx[2], &data_tx[1], 254)) {
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 1->2 integrity fail:\n");
|
||||
for (int i = 0; i < 254; i++)
|
||||
printf("%02X%c", data_rx[i], ((i&0xf)==0xf) ? '\n' : ' ');
|
||||
printf("\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Receive half duplex
|
||||
res = ice40_receive(ice40, data_rx, 128);
|
||||
/* Read two responses and ack them */
|
||||
for (int t = 0; t < 2; t++) {
|
||||
/* Receive half duplex */
|
||||
res = ice40_receive(ice40, data_rx, 258);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 3.%d failed (Half Duplex RX)", t);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Short acknowledge command */
|
||||
data_tx[0] = SPI_CMD_RESP_ACK;
|
||||
|
||||
res = ice40_send_turbo(ice40, data_tx, 1);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 4.%d failed (Turbo ACK)", t);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Validate response present */
|
||||
if ((data_rx[1] & 0x80) == 0) {
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 3.%d reports no response available\n", t);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Validate RX data (only 254 byte got read) */
|
||||
if (memcmp(&data_rx[2], &data_tx[1], 254)) {
|
||||
ESP_LOGE(TAG, "SPI loopback transaction %d->3.%d integrity fail:\n", 1+t, t);
|
||||
for (int i = 0; i < 254; i++)
|
||||
printf("%02X%c", data_rx[i], ((i&0xf)==0xf) ? '\n' : ' ');
|
||||
printf("\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check there is no more responses pending */
|
||||
data_tx[0] = SPI_CMD_NOP2;
|
||||
|
||||
res = ice40_transaction(ice40, data_tx, 2, data_rx, 2);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Transaction 3 failed (Half Duplex RX)");
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 5 failed (Full Duplex)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate RX data
|
||||
if (memcmp(&data_rx[1], &data_tx[128], 127)) {
|
||||
printf("Transaction 2->3 integrity fail:\n");
|
||||
for (int i = 0; i < 128; i++)
|
||||
printf("%02X%c", data_rx[i], ((i&0xf)==0xf) ? '\n' : ' ');
|
||||
printf("\n");
|
||||
if ((data_rx[1] & 0x80) != 0) {
|
||||
ESP_LOGE(TAG, "SPI loopback transaction 5 reports response available\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void fpga_test(ILI9341* ili9341, ICE40* ice40, xQueueHandle buttonQueue) {
|
||||
static bool test_spi_loopback(ICE40* ice40, uint32_t *rc) {
|
||||
int i;
|
||||
|
||||
/* Run test 256 times */
|
||||
for (i=0; i<256; i++) {
|
||||
if (!test_spi_loopback_one(ice40))
|
||||
break;
|
||||
}
|
||||
|
||||
/* Failure ? */
|
||||
if (i != 256) {
|
||||
*rc = i + 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* OK ! */
|
||||
*rc = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test_soc_loopback(ICE40 *ice40, uint32_t *rc) {
|
||||
/* Execute command */
|
||||
if (!soc_message(ice40, SOC_CMD_PING, SOC_CMD_PING_PARAM, rc, 0)) {
|
||||
*rc = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check response */
|
||||
if (*rc != SOC_CMD_PING_RESP)
|
||||
return false;
|
||||
|
||||
/* Success */
|
||||
*rc = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test_uart_loopback(ICE40* ice40, uint32_t *rc) {
|
||||
/* Enable loopback mode of RP2040 */
|
||||
rp2040_set_fpga_loopback(get_rp2040(), true, true);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
/* Execute command */
|
||||
if (!soc_message(ice40, SOC_CMD_UART_LOOPBACK_TEST, 0, rc, 0)) {
|
||||
*rc = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Disable loopback mode of RP2040 */
|
||||
rp2040_set_fpga_loopback(get_rp2040(), true, false);
|
||||
|
||||
/* Check response */
|
||||
return *rc == SOC_RESP_OK;
|
||||
}
|
||||
|
||||
static bool test_psram(ICE40* ice40, uint32_t *rc) {
|
||||
/* Execute command */
|
||||
if (!soc_message(ice40, SOC_CMD_PSRAM_TEST, 0, rc, pdMS_TO_TICKS(1000))) {
|
||||
*rc = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check response */
|
||||
return *rc == SOC_RESP_OK;
|
||||
}
|
||||
|
||||
static bool test_irq_n(ICE40* ice40, uint32_t *rc) {
|
||||
esp_err_t res;
|
||||
bool reload_fpga = false;
|
||||
do {
|
||||
printf("Start FPGA test...\n");
|
||||
reload_fpga = false;
|
||||
printf("LCD deinit...\n");
|
||||
ili9341_deinit(ili9341);
|
||||
|
||||
printf("FPGA load...\n");
|
||||
res = ice40_load_bitstream(ice40, fpga_selftest_bin_start, fpga_selftest_bin_end - fpga_selftest_bin_start);
|
||||
if (res != ESP_OK) {
|
||||
printf("Failed to load app bitstream into FPGA (%d)\n", res);
|
||||
ice40_disable(ice40);
|
||||
ili9341_init(ili9341);
|
||||
return;
|
||||
} else {
|
||||
printf("Bitstream loaded succesfully!\n");
|
||||
}
|
||||
/* Set pin as input */
|
||||
res = gpio_set_direction(GPIO_INT_FPGA, GPIO_MODE_INPUT);
|
||||
if (res != ESP_OK) {
|
||||
*rc = 32;
|
||||
return false;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 256; i++)
|
||||
if (!test_spi(ice40))
|
||||
break;
|
||||
if (i == 256)
|
||||
printf("SPI test success\n");
|
||||
else
|
||||
printf("SPI test failure at iteration %d\n", i);
|
||||
/* Assert interrupt line */
|
||||
if (!soc_message(ice40, SOC_CMD_IRQN_SET, 1, rc, 0)) {
|
||||
*rc = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool waitForChoice = true;
|
||||
while (waitForChoice) {
|
||||
rp2040_input_message_t buttonMessage = {0};
|
||||
printf("Waiting for button press...\n");
|
||||
if (xQueueReceive(buttonQueue, &buttonMessage, portMAX_DELAY) == pdTRUE) {
|
||||
printf("Button: %u, %u\n", buttonMessage.input, buttonMessage.state);
|
||||
if (buttonMessage.state) {
|
||||
switch(buttonMessage.input) {
|
||||
case RP2040_INPUT_BUTTON_HOME:
|
||||
case RP2040_INPUT_BUTTON_MENU:
|
||||
waitForChoice = false;
|
||||
break;
|
||||
case RP2040_INPUT_BUTTON_BACK:
|
||||
reload_fpga = true;
|
||||
waitForChoice = false;
|
||||
break;
|
||||
case RP2040_INPUT_BUTTON_ACCEPT:
|
||||
reload_fpga = true;
|
||||
waitForChoice = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (*rc != SOC_RESP_OK)
|
||||
return false;
|
||||
|
||||
/* Check level is 0 */
|
||||
if (gpio_get_level(GPIO_INT_FPGA) != 0) {
|
||||
*rc = 16;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Release interrupt line */
|
||||
if (!soc_message(ice40, SOC_CMD_IRQN_SET, 0, rc, 0)) {
|
||||
*rc = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*rc != SOC_RESP_OK)
|
||||
return false;
|
||||
|
||||
/* Check level is 1 */
|
||||
if (gpio_get_level(GPIO_INT_FPGA) != 1) {
|
||||
*rc = 16;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test_lcd_mode(ICE40* ice40, uint32_t *rc) {
|
||||
esp_err_t res;
|
||||
bool ok;
|
||||
|
||||
/* Defaults */
|
||||
ok = true;
|
||||
*rc = 0;
|
||||
|
||||
/* Check state is 0 */
|
||||
if (!soc_message(ice40, SOC_CMD_LCD_CHECK_MODE, 0, rc, 0)) {
|
||||
*rc = 16;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*rc != SOC_RESP_OK)
|
||||
return false;
|
||||
|
||||
/* Set LCD mode to 1 */
|
||||
res = gpio_set_level(GPIO_LCD_MODE, 1);
|
||||
if (res != ESP_OK) {
|
||||
*rc = 32;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check state is 1 */
|
||||
if (!soc_message(ice40, SOC_CMD_LCD_CHECK_MODE, 1, rc, 0)) {
|
||||
*rc = 17;
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (*rc != SOC_RESP_OK)
|
||||
ok = false;
|
||||
|
||||
/* Set LCD mode back to 0 */
|
||||
res = gpio_set_level(GPIO_LCD_MODE, 0);
|
||||
if (res != ESP_OK) {
|
||||
*rc = 33;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* All good */
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool test_pmod_open(ICE40* ice40, uint32_t *rc) {
|
||||
/* Execute command */
|
||||
if (!soc_message(ice40, SOC_CMD_PMOD_OPEN_TEST, 0, rc, 0)) {
|
||||
*rc = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check response */
|
||||
return *rc == SOC_RESP_OK;
|
||||
}
|
||||
|
||||
static bool test_pmod_plug(ICE40* ice40, uint32_t *rc) {
|
||||
/* Execute command */
|
||||
if (!soc_message(ice40, SOC_CMD_PMOD_PLUG_TEST, 0, rc, 0)) {
|
||||
*rc = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check response */
|
||||
return *rc == SOC_RESP_OK;
|
||||
}
|
||||
|
||||
static bool test_lcd_init(ICE40* ice40, uint32_t *rc) {
|
||||
/* Execute command */
|
||||
if (!soc_message(ice40, SOC_CMD_LCD_INIT_TEST, 0, rc, 0)) {
|
||||
*rc = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check response */
|
||||
return *rc == SOC_RESP_OK;
|
||||
}
|
||||
|
||||
|
||||
typedef bool (*test_fn)(ICE40 *ice40, uint32_t *rc);
|
||||
|
||||
|
||||
static bool wait_button(xQueueHandle buttonQueue) {
|
||||
rp2040_input_message_t buttonMessage = {0};
|
||||
|
||||
while (1) {
|
||||
if (xQueueReceive(buttonQueue, &buttonMessage, 0) == pdTRUE) {
|
||||
if (buttonMessage.state) {
|
||||
switch(buttonMessage.input) {
|
||||
case RP2040_INPUT_BUTTON_HOME:
|
||||
case RP2040_INPUT_BUTTON_MENU:
|
||||
case RP2040_INPUT_BUTTON_BACK:
|
||||
return false;
|
||||
case RP2040_INPUT_BUTTON_ACCEPT:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
ice40_disable(ice40);
|
||||
ili9341_init(ili9341);
|
||||
} while (reload_fpga);
|
||||
}
|
||||
}
|
||||
|
||||
static bool run_test(ICE40* ice40, pax_buf_t* pax_buffer, const pax_font_t *font, ILI9341* ili9341, int line,
|
||||
const char *test_name, test_fn fn) {
|
||||
bool rv;
|
||||
uint32_t rc;
|
||||
|
||||
/* Test name */
|
||||
pax_draw_text(pax_buffer, 0xffffffff, font, 18, 0, 20*line, test_name);
|
||||
if (ili9341)
|
||||
ili9341_write(ili9341, pax_buffer->buf);
|
||||
|
||||
/* Run the test */
|
||||
rv = fn(ice40, &rc);
|
||||
|
||||
/* Display result */
|
||||
if (!rv) {
|
||||
/* Error */
|
||||
char buf[10];
|
||||
snprintf(buf, sizeof(buf), "%08x", rc);
|
||||
pax_draw_text(pax_buffer, 0xffff0000, font, 18, 200, 20*line, buf);
|
||||
} else {
|
||||
/* OK ! */
|
||||
pax_draw_text(pax_buffer, 0xff00ff00, font, 18, 200, 20*line, " OK");
|
||||
}
|
||||
|
||||
if (ili9341)
|
||||
ili9341_write(ili9341, pax_buffer->buf);
|
||||
|
||||
/* Pass through the 'OK' status */
|
||||
return rv;
|
||||
}
|
||||
|
||||
#define RUN_TEST(name, fn) do {\
|
||||
ok &= run_test(ice40, pax_buffer, font, ili9341, line++, name, fn); \
|
||||
} while (0)
|
||||
|
||||
#define RUN_TEST_MANDATORY(name, fn) do {\
|
||||
if (!run_test(ice40, pax_buffer, font, ili9341, line++, name, fn)) { \
|
||||
pax_draw_text(pax_buffer, 0xffff0000, font, 18, 0, 20*line, "Aborted"); \
|
||||
ili9341_write(ili9341, pax_buffer->buf); \
|
||||
ok = false; \
|
||||
goto error; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define RUN_TEST_BLIND(name, fn) do {\
|
||||
ok &= run_test(ice40, pax_buffer, font, NULL, line++, name, fn); \
|
||||
} while (0)
|
||||
|
||||
|
||||
static void
|
||||
run_all_tests(xQueueHandle buttonQueue, ICE40* ice40, pax_buf_t* pax_buffer, ILI9341* ili9341)
|
||||
{
|
||||
const pax_font_t *font;
|
||||
int line = 0;
|
||||
bool ok = true;
|
||||
|
||||
/* Screen init */
|
||||
font = pax_get_font("sky mono");
|
||||
|
||||
pax_noclip(pax_buffer);
|
||||
pax_background(pax_buffer, 0x8060f0);
|
||||
ili9341_write(ili9341, pax_buffer->buf);
|
||||
|
||||
/* Run mandatory tests */
|
||||
RUN_TEST_MANDATORY("Bitstream load", test_bitstream_load);
|
||||
RUN_TEST_MANDATORY("SPI loopback", test_spi_loopback);
|
||||
RUN_TEST_MANDATORY("SoC loopback", test_soc_loopback);
|
||||
|
||||
/* Set indicator to "in-progress" */
|
||||
soc_message(ice40, SOC_CMD_RGB_STATE_SET, 1, NULL, 0);
|
||||
|
||||
/* Run non-interactive tests */
|
||||
RUN_TEST("UART loopback", test_uart_loopback);
|
||||
RUN_TEST("PSRAM", test_psram);
|
||||
RUN_TEST("IRQ_n signal", test_irq_n);
|
||||
RUN_TEST("LCD_MODE signal", test_lcd_mode);
|
||||
RUN_TEST("PMOD open", test_pmod_open);
|
||||
|
||||
/* Show instructions for interactive test */
|
||||
pax_draw_text(pax_buffer, 0xffc0c0c0, font, 9, 25, 20*line+ 0, "Insert PMOD plug");
|
||||
pax_draw_text(pax_buffer, 0xffc0c0c0, font, 9, 25, 20*line+10, "Then press button for interactive test");
|
||||
pax_draw_text(pax_buffer, 0xffc0c0c0, font, 9, 25, 20*line+20, " - Check LCD color bars");
|
||||
pax_draw_text(pax_buffer, 0xffc0c0c0, font, 9, 25, 20*line+30, " - Then LCD & RGB led color cycling");
|
||||
ili9341_write(ili9341, pax_buffer->buf);
|
||||
|
||||
/* Wait for button */
|
||||
wait_button(buttonQueue);
|
||||
|
||||
/* Clear the instructions from buffer */
|
||||
pax_draw_rect(pax_buffer, 0xff8060f0, 0, 20*line, 320, 240-20*line);
|
||||
|
||||
/* Handover LCD to FPGA */
|
||||
ili9341_deinit(ili9341);
|
||||
|
||||
/* Run interactive tests */
|
||||
RUN_TEST("PMOD plug", test_pmod_plug);
|
||||
RUN_TEST("LCD init", test_lcd_init);
|
||||
|
||||
/* Wait a second (for user to see color bars) */
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
/* Start LCD / RGB cycling */
|
||||
soc_message(ice40, SOC_CMD_LCD_RGB_CYCLE_SET, 1, NULL, 0);
|
||||
|
||||
/* Wait for button */
|
||||
wait_button(buttonQueue);
|
||||
|
||||
/* Stop LCD / RGB cycling */
|
||||
soc_message(ice40, SOC_CMD_LCD_RGB_CYCLE_SET, 0, NULL, 0);
|
||||
|
||||
/* Take control of the LCD back and refresh screen */
|
||||
ili9341_init(ili9341);
|
||||
|
||||
error:
|
||||
/* Update indicator */
|
||||
soc_message(ice40, SOC_CMD_RGB_STATE_SET, ok ? 2 : 3, NULL, 0);
|
||||
|
||||
/* Pass / Fail result on screen */
|
||||
if (ok)
|
||||
pax_draw_text(pax_buffer, 0xff00ff00, font, 36, 100, 20*line, "PASS");
|
||||
else
|
||||
pax_draw_text(pax_buffer, 0xffff0000, font, 36, 100, 20*line, "FAIL");
|
||||
|
||||
ili9341_write(ili9341, pax_buffer->buf);
|
||||
|
||||
/* Done, just wait for button */
|
||||
wait_button(buttonQueue);
|
||||
|
||||
/* Cleanup */
|
||||
ice40_disable(ice40);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void fpga_test(xQueueHandle buttonQueue, ICE40* ice40, pax_buf_t* pax_buffer, ILI9341* ili9341) {
|
||||
run_all_tests(buttonQueue, ice40, pax_buffer, ili9341);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue