Wgrywanie wsadu do FPGA Lattice ice40 przez SPI z STM32 #1

Opis

Układy FPGA firmy Lattice można skonfigurować poprzez interface SPI z poziomu podłączonego mikrokontrolera. Układ FPGA pracuje wtedy jako Slave, a mikrokontroler jako master magistrali. Wejście w ten tryb uzyskuje się przytrzymując linię SS w stanie niskim podczas wychodzenia z resetu kontrolera FPGA. Szczegóły opisuje dokument:

iCE40_Programming_Configuration_2022.pdf

Do układu wgrywamy wsad w postaci strumienia bajtów z pliku binarnego uzyskanego z procesu syntezy kodu VHDL/Verilog. Co ciekawe, plik binarny oprócz samej konfiguracji komórek programowalnych zawiera także metadane, które jednakże są ignorowane przez sam układ programowalny:

Zaznaczone bajty 0x7EAA997E są wzorcem synchronizacyjnym, po wykryciu którego układ zaczyna kolejne bajty interpretować jako użyteczne dane. Również na końcu ciągu znajduje się specjalna sekwencja 0x010600 sygnalizująca koniec strumienia (https://github.com/TiNredmc/OpeniCELink/blob/master/Core/Src/main.c).

Podobno można również zaprogramować FPGA na stałe, jednak datasheet nie ujawnia jak 🙁

Setup testowy

Kod

W STM32Cube został skonfigurowany interface SPI w trybie Master 2MHz MSB First na wybranych pinach + dodatkowe piny GPIO:

  • PA7 MOSI (SPI)
  • PA6 MISO (SPI)
  • PA11 SS (GPIO output)
  • PA12 CRESETB (GPIO output)
  • PA5 SCK (SPI)
  • PA4 CDONE (GPIO input)

Dodatkowo podczas dalszych prób wykorzystano kanał DMA do transferu strumienia danych.

Wsad z pliku .bin przekonwertowano jako tablicę bajtów w formacie języka C i umieszczono w kodzie programu. Do konwersji zostało użyte narzędzie https://notisrac.github.io/FileToCArray/

Co ciekawe, plik ma rozmiar 7413B, gdzie datasheet wpomina, że powinien mieć 7417B – skąd ta różnica 4 bajtów? Nie wiem, ale na razie działa 😉

 

Kluczowy kod (zakomentowane funkcje SPI niewykorzystujące DMA):

 const uint8_t zero = 0;
HAL_GPIO_WritePin(CRESET_B_GPIO_Port, CRESET_B_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(SS_GPIO_Port, SS_Pin, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(CRESET_B_GPIO_Port, CRESET_B_Pin, GPIO_PIN_SET);
HAL_Delay(2);
printf("FPGA in slave mode\r\n");
HAL_GPIO_WritePin(SS_GPIO_Port, SS_Pin, GPIO_PIN_SET);

//HAL_SPI_Transmit(&hspi1,(uint8_t *) &zero, 1, HAL_MAX_DELAY);
done = 0;
HAL_SPI_Transmit_DMA(&hspi1,(uint8_t *) &zero, 1);
while(done==0);
HAL_GPIO_WritePin(SS_GPIO_Port, SS_Pin, GPIO_PIN_RESET);
//HAL_SPI_Transmit(&hspi1,(uint8_t *) not_gate_bitmap, sizeof(not_gate_bitmap), HAL_MAX_DELAY);
done = 0;
HAL_SPI_Transmit_DMA(&hspi1,(uint8_t *) not_gate_bitmap, sizeof(not_gate_bitmap));
while(done==0);
HAL_GPIO_WritePin(SS_GPIO_Port, SS_Pin, GPIO_PIN_SET);

const uint8_t zeroes[] = {0,0,0,0,0,0,0,0,0,0,0,0,0};
//HAL_SPI_Transmit(&hspi1,(uint8_t *) zeroes, 13, HAL_MAX_DELAY);
done = 0;
HAL_SPI_Transmit_DMA(&hspi1,(uint8_t *) zeroes, 13);
while(done==0);
if(HAL_GPIO_ReadPin(CDONE_GPIO_Port, CDONE_Pin) == GPIO_PIN_SET){
printf("Programming succesful!\r\n");
}
else{
printf("Programming failed!\r\n");
}
//HAL_SPI_Transmit(&hspi1,(uint8_t *) zeroes, 7, HAL_MAX_DELAY);
done = 0;
HAL_SPI_Transmit_DMA(&hspi1,(uint8_t *) zeroes, 7);
while(done==0);
printf("Finished!\r\n");

Przebiegi

Można zauważyć podniesienie linii CRESETB przy opuszczonej linii SS, Sygnał SCK podczas transferu strumienia, podniesienie linii CDONE sygnalizującej przez układ FPGA poprawne skonfigurowanie, oraz dodatkowe stany na liniach wymagane przez dokumentację – chociaż nie wiem czy w praktyce niezbędne, skoro linia CDONE osiąga stan wysoki wcześniej.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.