Altera ECPQ flash access with a Nios II processor + programming bitfiles

This post was written by eli on April 23, 2017
Posted Under: Altera,FPGA


This post outlines some technical details on accessing an Altera ECPQ flash from a Nios II processor for read, write and erase. A non-OS settings (“bare metal”) setting is assumed.

And as a bonus (at the bottom of this post), how to program the flash based upon a SOF file.

Hardware setup

In the Qsys project, there should be an instance of the Legacy EPCS/EPCQx1 Flash Controller, configured with the default parameters (not that there is much to configure). The peripheral’s epcs_control_port should be connected to the Nios II’s data master Avalon port (no point connecting it to the instruction master too).

In this example, we’ll assume that the name of Flash Controller in Qsys is epcs_flash_controller_0.

The interrupt signal isn’t used in the software setting given below, but as the connection to the Nios processor, as well as the interrupt number assignment is automatic, let it be.

Clock and reset — like the other peripherals.

The external conduit is connected as follows to an ECPQ flash, for a x1 access:

  • Flash pin DATA0 to epcs_flash_controller_0_sdo (FPGA pin ASDO)
  • Flash pin DCLK to epcs_flash_controller_0_dclk (FPGA pin DCLK)
  • Flash pin nCS to epcs_flash_controller_0_sce (FPGA pin NCSO)
  • Flash pin DATA1 to epcs_flash_controller_0_data (FPGA pin DATA0)

The FPGA pins above relate to dual-use of the configuration, which allows the FPGA to configure in Active Serial (AS) x 1 mode. Once the configuration is done, these pins become general-purpose I/O (when so required by assignments), which allows regular access to the flash device.

Note that the flash pin DATA1 is connected to the FPGA pin DATA0 — this is not a mistake, but the correct wiring for AS x 1 interface.

It’s of course possible to connect the flash to regular I/O pins, but then the FPGA won’t be able to configure from the flash.


Altera’s BSP includes drivers for flash operations with multiple layers of abstraction. This abstraction is not always necessary, and makes it somewhat difficult to figure out what’s going on (in particular when things go wrong). In particular, the higher-level drivers erase flash sectors automatically before writing, which can render some counterintuitive behavior, for example if multiple write requests are made on the same sector.

I therefore prefer working with the lowest-level drivers, which merely translate the flash commands into SPI communication. It leaves the user with the responsibility to erase sectors before writing to them.

The rule is simple: The flash is divided into sectors of 64 kB each. An erase operation is performed on such 64 kB sector, leaving all its bytes in all-1′s (all bytes are 0xff).

Writing can then be done to arbitrary addresses, but effectively the data in the flash is the written data ANDed with the previous content of the memory cells. Which means a plain write, if the region has been previously erased. It’s commonly believed that it’s unhealthy for the flash to write to a byte cell twice without an erase in the middle.

This is a simple program that runs on the Nios II processor, which demonstrates read, write and erase.

#include <system.h>
#include <alt_types.h>
#include <io.h>
#include "sys/alt_stdio.h"
#include "epcs_commands.h"

static void hexprint(alt_u8 *buf, int num) {
  int i;

  const char hexes[] = "0123456789abcdef";

  for (i = 0; i < num; i++) {
    alt_putchar(hexes[(buf[i] >> 4) & 0xf]);
    alt_putchar(hexes[buf[i] & 0xf]);
    if ((i & 0xf) == 0xf)
      alt_putchar(10); // "\n"
      alt_putchar(32); // " "
  alt_putchar(10); // "\n"

int main()
  alt_u32 silicon_id;

  alt_u8 buf[256];
  alt_u32 junk = 0x12345678;
  const alt_u32 flash_address = 0x100000;

  silicon_id = epcs_read_device_id(register_base);

  alt_printf("ID = %x\n", silicon_id);

  // epcs_read_buffer always returns the length of the buffer, so no
  // point checking its return value.

  alt_printf("Before doing anything:\n");

  epcs_read_buffer(register_base, flash_address, buf, sizeof(buf), 0);
  hexprint(buf, 16);

  // epcs_sector_erase erases the 64 kiB sector that contains the address
  // given as its second argument, and waits for the erasure to complete
  // by polling the status register and waiting for the WIP (write in progress)
  // bit to clear.

  epcs_sector_erase(register_base, flash_address, 0);

  alt_printf("After erasing\n");

  epcs_read_buffer(register_base, flash_address, buf, sizeof(buf), 0);
  hexprint(buf, 16);

  // epcs_write_buffer must be used on a region previously erased. The
  // command waits for the operation to complete by polling the status
  // register and waiting for the WIP (write in progress) bit to clear.
  epcs_write_buffer(register_base, flash_address, (void *) &junk, sizeof(junk), 0);

  alt_printf("After writing\n");

  epcs_read_buffer(register_base, flash_address, buf, sizeof(buf), 0);
  hexprint(buf, 16);

  /* Event loop never exits. */

  while (1);

  return 0;

The program reads 256 bytes each time, even though only 16 bytes are displayed. Any byte count is allowed in read and write. Needless to say, flash_address can be changed to any address in the device’s range. The choice of 0x100000 kept it off the configuration bitstream for the relevant FPGA.

This is the output of the program above running against an EPCQ16:

ID = 20ba15
Before doing anything:
78 56 34 12 ff ff ff ff ff ff ff ff ff ff ff ff

After erasing
ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

After writing
78 56 34 12 ff ff ff ff ff ff ff ff ff ff ff ff

The data in the “Before doing anything” part can be anything that was left in the flash when the program ran. In the case above, it’s the results of the previous run of the same program.

As a side note, all EPCQ flashes also support erasing subsectors, each of 4 kiB size (hence 16 subsectors per sectors). Altera’s low-level drivers don’t support subsector erase, but it’s quite easy to expand the code to do so.

Programming the flash with a SOF file

As promised, here’s the outline of how to program the EPCQ flash with a bitstream configuration file. Not as fancy as the topic above, but nevertheless useful. The flash needs to be connected as follows:

  • Flash pin DATA0 to FPGA pin ASDO
  • Flash pin DCLK to FPGA pin DCLK
  • Flash pin nCS to FPGA pin NCSO
  • Flash pin DATA1 to FPGA pin DATA0 (once again, this is not a mistake. DATA1 to DATA0 indeed)

First thing first: Generate a JIC file. Command-line style, e.g.:

quartus_cpf -c -d EPCQ16 -s EP4CE15 projname.sof projname.jic

In the example above, the EPCQ16 argument is the flash device, and the EP4CE15 is the FPGA that will be used to program the flash, which is most likely the same FPGA the SOF targets.

Or do it with GUI:

  • In Quartus, pick File > Convert Programming File…
  • Choose jic output file format, and set the output file name.
  • Set the configuration device to e.g. EPCQ16, Active Serial (not x4).
  • Pick the SOF Data row, Page_0, click Add File… and pick SOF file.
  • Pick the Flash Loader and click Add Device…, and choose e.g. Cyclone IV E, and then the same device as listed for the SOF file.
  • Click Generate. A window saying the JIC file has been generated successfully should appear.
  • Click Close to close this tool.

Now programming the flash:

  • Open the regular JTAG programmer in Quartus (not the one in Eclipse). The one used to configure the FPGA via JTAG with a bitstream, that is.
  • Click Add File… and select the JIC file created above.
  • The FPGA with its flash attached should appear in the diagram part of the window.
  • Select the Program/Configure checkbox on the flash’ (e.g. EPCQ16) row
  • Click Start.
  • This should take some 10 seconds or so (for EP4CE15′s bitfile), and end successfully.
  • The flash is now programmed.

Note that there’s an “Erase” checkbox on the flash’ row — there is no need to enable it along with Program/Configure, and neither is it necessary. The Programmer gets the hint, and erases the flash before programming it.

Add a Comment

required, use real name
required, will not be published
optional, your blog address