Writing Altera EPCS with Wishbone SPI Core

Aug 23, 2017
This should have been before the article on "Altera Remote System Update".  In that article I used the Quartus Programmer to program a second FPGA Bit Image into the EPCS in order to load a User Image.  This is how to update an FPGA Bit Image without the Quartus Programmer.

This is Altera's recommendation:

That's a lot of IO and something I wasn't particularly interested in connecting to the Wishbone Bus of the system I was working on. I did have a few things working for me that would simplify this problem.

1. The EPCS devices used by Altera to program their FPGA are just re-branded SPI EEPROMS.  Some manufacturers will work some will not.  A little researching allowed a substitution.
EPCS16SI8N (Digikey PN 544-2567-5-ND) is $17.10 while a Cyprus Semi 16Mbit SPI EEPROM (Digikey PN 1274-1124-1-ND) is $0.66.  

2. The EPCS (or SPI EEPROM) is connected to four dual-purpose pins to the FPGA. 
         DATA1, ASDO/IO:  SPI DO
         FLASH_nCE, nCSO/IO: SPI CS
         DCLK/DCLK: SPI CLK
         DATA0/IO: SPI DI

These two pieces of information tell me that I can use pre-built Wishbone Bus to SPI bus Core and that I can use the same programming pins as Regular IO.

To change ASDO, nCSO, DCLK and DATA0 into regular IO (after FPGA programmed), edit Dual-purpose pins in the Device properties.



In addition, DCLK pin had errors during Quartus compiling that the pin could not be used as a clock. After hours of online searching, I found that there was an additional compiling directive that I had to added.
Error (169181): Cannot place I/O pin SPI_P2_MOSI_O with I/O standard 3.3-V LVCMOS in pin location 13 -- possible switch coupling with I/O pin SPI_P2_CLK_O in pin location 12.


In Assignment Editor, select Category "I/O Features" from the drop-down list.  Add an entry To the SPI Clock signal that is assigned to the EPCS part and set "I/O Maximum Toggle Rate" to "0".  See SPI_P2_CLK_O below.



The SOF file created during FPGA compilation needs to be converted to POF (Programming Object File for local programming) file then RPD (Raw Programming Data) file using Quartus "Convert Programming File" application.  This is the conversion sequence SOF->POF->RPD.  Alternatively, POF file can be generated during compilation.

The final setup to generate a programmable file is that the bits in the byte need to be reversed. This was probably the most confusing, but I was able to discern from a few mentions in forum posts and from checking the EEPROM contents between identical files that were programmed with Quartus and my own EEPROM writing.

This is the sequence:
Compile->SOF->POF->RPD->Reverse->EEPROM Write.

This is what I do:

  1. Quartus compiles bit image and creates SOF and POF.
  2. I verify that SOF file works by programming the FPGA via JTAG.
  3. Convert POF file to RPD file with "Convert Programming File"
  4. Run "rpd_reverse.exe" (see the end of the article for code) which will make a BIN file
  5. Use Xmodem to upload BIN File to FPGA running a small processor+memory+serial port.
  6. Another program on FPGA processor loads the uploaded BIN File to SPI EEPROM
  7. Bonus: use RSU from the previous article to boot new FPGA Bit Image.
Please direct questions to Contactpage.







#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PAGESIZE 0x80000
static unsigned char reverse(unsigned char b);

//reverse bits in a byte
unsigned char reverse(unsigned char b) {
   b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
   b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
   b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
   return b;


int main(int argc, char *argv[])
{
  FILE * pFile;
  char filename[80] = "a.out";
  long lSize = PAGESIZE;
  int m = 0;
  int adr_start;
  unsigned char * buffer;
  size_t result;

  FILE *f; // txt output file
  FILE *d; // binary output file


  if (argc > 1)
  {
     while(argv[1][m] != '\0')
     {
       filename[m] = argv[1][m];
       m++;
     }
  }


  pFile = fopen (filename , "rb" );
  if (pFile==NULL)
  {
   printf("Cannot open file 'a.out'\n");
   exit (1);
  }

  // obtain file size:


  // allocate memory to contain the whole file:
  buffer = (unsigned char*) malloc (sizeof(unsigned char)*lSize);
  if (buffer == NULL)
  {
  printf("Memory error\n");
  exit (2);
  }

  // copy the file into the buffer:
  result = fread (buffer,1,lSize,pFile);
  if (result != lSize)
  {
  printf("Reading error\n");
  exit (3);
  }




  // Open dat output file
  d = fopen("fpga_reverse.bin","wb+");
  if(d == NULL)
  {
  printf("Error creating binary output file\n");
  exit(11);
  }

  unsigned long mnemonic_beginning = 0;

  unsigned long mnemonic_length = lSize;

 if(mnemonic_length == 0)
  {
  printf("Invalid assembler file\n"); //x38 x58
  exit(4);
  }

  if (mnemonic_length == 0)
  {
  printf("Assembler file is empty\n");
  exit(5);
  }

  printf("Program start: 0x%.8X\n", mnemonic_beginning);
  printf("Program size:  0x%.8X\n", mnemonic_length-4);

   int j = mnemonic_beginning;
   int i = 0;
   int k = 0;
   char txt_string[32];
   char dat_string;
   i = 0;


   while (j != (mnemonic_length + mnemonic_beginning))
   {

 dat_string = (signed char) buffer[j];
 
 fputc(reverse(dat_string), d);

j++;

   }
  fclose(pFile);
  fclose(d);
  free (buffer);
  return 0;
}