diff --git a/doc/figs/max10_flash-memory.png b/doc/figs/max10_flash-memory.png new file mode 100644 index 0000000..657d12f Binary files /dev/null and b/doc/figs/max10_flash-memory.png differ diff --git a/doc/vendors/intel.rst b/doc/vendors/intel.rst index 464c58c..00d9cd6 100644 --- a/doc/vendors/intel.rst +++ b/doc/vendors/intel.rst @@ -93,12 +93,32 @@ Supported Boards: Supported File Types: * ``svf`` -* ``svf`` +* ``pof`` * ``bin`` (arbitrary binary files) +Internal Flash Organization +--------------------------- + +The internal flash is divided into five sections: + +- ``UFM1`` and ``UFM0`` for user data +- ``CFM2``, ``CFM1``, and ``CFM0`` for storing one or two bitstreams + +.. image:: ../figs/max10_flash-memory.png + :alt: max10 internal flash memory structure + +Flash usage depends on the configuration mode. In all modes: + +- ``CFM0`` is used to store a bitstream +- ``UFM0`` and ``UFM1`` are available for user data +- The remaining ``CFMx`` sections (``CFM1``, ``CFM2``) can be used for + additional bitstreams or user data + Using ``svf`` ------------- +This method is the **simplest** (and slowest) way to load or write a bitstream. + .. note:: This method is required to load a bitstream into *SRAM*. @@ -107,7 +127,7 @@ Using ``svf`` openFPGALoader [-b boardname] -c cablename the_svf_file.svf -Parameters: +**Parameters:** * ``boardname``: One of the boards supported by ``openFPGALoader`` (optional). * ``cablename``: One of the supported cables (see ``--list-cables``). @@ -115,53 +135,69 @@ Parameters: Using ``pof`` ------------- -When writing the bitstream to internal flash, using a ``pof`` file is the fastest approach. +To write a bitstream into the internal flash, using a ``pof`` file is the +**fastest** approach. .. code-block:: bash openFPGALoader [-b boardname] [--flash-sector] -c cablename the_pof_file.pof -Parameters: +**Parameters:** -* ``boardname``: One of the boards supported by ``openFPGALoader`` (optional). +* ``boardname``: A board supported by ``openFPGALoader`` (optional). * ``cablename``: One of the supported cables (see ``--list-cables``). -* ``--flash-sector``: Specifies which internal flash sectors to erase/update instead of modifying the entire flash. One - or more section may be provided, with ``,`` as separator. When this option isn't provided a full internal flash erase/ - update is performed +* ``--flash-sector``: Optional. Comma-separated list of sectors to update. + If omitted, the entire flash is erased and reprogrammed. Accepted Flash Sectors: * ``UFM0``, ``UFM1``: User Flash Memory sections. * ``CFM0``, ``CFM1``, ``CFM2``: Configuration Flash Memory sectors. -Example: +**Example:** .. code-block:: bash openFPGALoader -c usb-blaster --flash-sector UFM1,CFM0,CFM2 the_pof_file.pof -This command updates ``UFM1``, ``CFM0``, and ``CFM2``, while leaving other sectors unchanged. +This command updates ``UFM1``, ``CFM0``, and ``CFM2``, leaving all other +sectors unchanged. Using an arbitrary binary file ------------------------------ -This command updates only *User Flash Memory* sectors without modifying ``CFMx``. Unlike Altera Quartus, it supports -any binary format without limitations (not limited to a ``.bin``. +Unlike Altera Quartus, it supports any binary format without limitations +(not limited to a ``.bin``). +With this feature, it's not required to provides the file at gateware build +time: it may be updated at any time without gateware modification/rebuild. .. note:: This approach is useful to updates, for example, a softcore CPU firmware. +**Basic usage:** + .. code-block:: bash - openFPGALoader [-b boardname] -c cablename the_bin_file.bin + openFPGALoader [-b boardname] -c cablename [--offset $OFFSET] the_bin_file.bin -* ``boardname``: One of the boards supported by ``openFPGALoader`` (optional). +* ``boardname``: a boards supported by ``openFPGALoader`` (optional). * ``cablename``: One of the supported cables (see ``--list-cables``). +* ``$OFFSET``: To start writing ``$OFFSET`` bytes after *User Flash memory* + start address (optional, default: 0x00). -Behavior: +This command erases and writes the contents of ``the_bin_file.bin`` into +``UFM1`` and ``UFM0``. If ``--offset`` is specified, the binary content is +written starting from that offset. -``UFM0`` and ``UFM1`` will be erased before writing the binary file. +Depending on the max10 configuration mode (see picture), it's possible to +extend *User Flash Memory* area by using `CFM2` and `CFM1`. This is not the +default behavior and user must explictly change this by using +`--flash-sector` argument: -.. note:: Depending on the internal flash configuration, ``CFM1`` and ``CFM2`` may also store arbitrary data. However, currently, ``openFPGALoader`` only supports writing to ``UFMx``. +* ``--flash-sector UFMx`` or ``--flash-sector CFMy`` (with x= 1 or 0 and + y = 2 or 1) to specify only one sector +* ``--flash-sector UFM1,UFM0`` is equivalent to the default behavior +* ``--flash-sector UFM1,CFM2`` to erase and update ``UFM1``, ``UFM0`` + and ``CFM2`` (equivalent to ``--flash-sector UFM1,UFM0,CFM2``) Intel/Altera (Old Boards) ========================= diff --git a/src/altera.cpp b/src/altera.cpp index 22ac908..448c317 100644 --- a/src/altera.cpp +++ b/src/altera.cpp @@ -341,17 +341,47 @@ const std::map Altera::max10_memory_map = { }, }; -/* Write an arbitrary file in UFM1 and UFM0 - * FIXME: in some mode its also possible to uses CFM2 & CFM1 +/* Write an arbitrary file in UFM1, UFM0 by default and also CFM2 and CFM1 if + * requested. */ -bool Altera::max10_program_ufm(const Altera::max10_mem_t *mem, unsigned int offset) +bool Altera::max10_program_ufm(const Altera::max10_mem_t *mem, uint32_t offset, + uint8_t update_sectors) { + uint32_t start_addr = 0; // 32bit align + uint32_t end_addr = 0; // 32bit align + uint8_t erase_sectors_mask; + + /* check CFM0 is not mentionned */ + if (update_sectors & (1 << 4)) + std::runtime_error("Error: CFM0 cant't be used to store User Binary"); + + /* First task: search for the first and the last sector to use */ + sectors_mask_start_end_addr(mem, update_sectors, + &start_addr, &end_addr, &erase_sectors_mask); + RawParser _bit(_filename, true); _bit.parse(); - _bit.displayHeader(); + if (_verbose) + _bit.displayHeader(); + const uint8_t *data = _bit.getData(); const uint32_t length = _bit.getLength() / 8; - const uint32_t base_addr = mem->ufm_addr + offset; + const uint32_t base_addr = start_addr + offset / 4; // 32bit align + const uint32_t flash_len = (end_addr - base_addr) * 4; // Byte align + + /* check */ + if (base_addr > end_addr) { // wrong offset + printError("Error: start offset is out of xFM region"); + return false; + } + if (flash_len < length) { // too big file + printError("Error: no enough space to write\n"); + return false; + } + if (base_addr + (length / 4) > end_addr) { + printError("Error: end address is out of xFM region"); + return false; + } uint8_t *buff = (uint8_t *)malloc(length); if (!buff) { @@ -359,17 +389,6 @@ bool Altera::max10_program_ufm(const Altera::max10_mem_t *mem, unsigned int offs return false; } - /* check */ - const uint32_t ufmx_len = 4 * (mem->ufm_len[0] + mem->ufm_len[1]); - if (base_addr > length) { - printError("Error: start offset is out of UFM region"); - return false; - } - if (base_addr + length > ufmx_len) { - printError("Error: end address is out of UFM region"); - return false; - } - /* data needs to be re-ordered */ for (uint32_t i = 0; i < length; i+=4) { for (int b = 0; b < 4; b++) { @@ -377,18 +396,23 @@ bool Altera::max10_program_ufm(const Altera::max10_mem_t *mem, unsigned int offs } } + printf("%x %x %x %x\n", update_sectors, erase_sectors_mask, + base_addr, end_addr); + // Start! max10_flow_enable(); - /* Erase UFM1 & UFM0 */ - printInfo("Erase UFM ", false); - max10_flow_erase(mem, 0x3); + /* Erase xFM sectors */ + printInfo("Erase xFM ", false); + max10_flow_erase(mem, erase_sectors_mask); printInfo("Done"); - /* Program UFM1 & UFM0 */ + /* Program xFM */ // Simplify code: - // UFM0 follows UFM1, so we don't need to iterate - printInfo("Write UFM"); + // UFM0 follows UFM1, + // CFM2 follow UFM0, etc... + // so we don't need to iterate + printInfo("Write xFM"); writeXFM(buff, base_addr, 0, length / 4); /* Verify */ @@ -417,8 +441,12 @@ void Altera::max10_program(unsigned int offset) } const Altera::max10_mem_t mem = mem_map->second; + /* Check for a full update or only for a subset */ + update_sectors = max10_flash_sectors_to_mask(_flash_sectors); + if (_file_extension != "pof") { - max10_program_ufm(&mem, offset); + max10_program_ufm(&mem, offset, + (_flash_sectors.size() == 0) ? 0 : update_sectors); return; } @@ -471,10 +499,10 @@ void Altera::max10_program(unsigned int offset) // UFM Mapping ufm_data[1] = _bit.getData("UFM"); - ufm_data[0] = &ufm_data[1][mem.ufm_len[0] * 4]; // Just after UFM1 (but size may differs + ufm_data[0] = &ufm_data[1][mem.ufm_len[1] * 4]; // Just after UFM1 (but size may differs // CFM Mapping - cfm_data[2] = &ufm_data[0][mem.ufm_len[1] * 4]; // First CFM section in FPGA internal flash + cfm_data[2] = &ufm_data[0][mem.ufm_len[0] * 4]; // First CFM section in FPGA internal flash cfm_data[1] = &cfm_data[2][mem.cfm_len[2] * 4]; // Second CFM section but just after CFM2 cfm_data[0] = &cfm_data[1][mem.cfm_len[1] * 4]; // last CFM section but just after CFM1 @@ -482,28 +510,6 @@ void Altera::max10_program(unsigned int offset) const uint8_t *dsm_data = _bit.getData("ICB"); const int dsm_len = _bit.getLength("ICB") / 32; // getLength (bits) dsm_len in 32bits word - /* Check for a full update or only for a subset */ - if (_flash_sectors.size() > 0) { - const std::vector sectors = splitString(_flash_sectors, ','); - update_sectors = 0; - for (const auto §or: sectors) { - if (sector == "UFM1") - update_sectors |= (1 << 0); - else if (sector == "UFM0") - update_sectors |= (1 << 1); - else if (sector == "CFM2") - update_sectors |= (1 << 2); - else if (sector == "CFM1") - update_sectors |= (1 << 3); - else if (sector == "CFM0") - update_sectors |= (1 << 4); - else - throw std::runtime_error("Unknown sector " + sector); - } - } else { // full update - update_sectors = 0x1F; - } - // Start! max10_flow_enable(); @@ -913,6 +919,79 @@ bool Altera::max10_dump() return true; } +uint8_t Altera::max10_flash_sectors_to_mask(std::string flash_sectors) +{ + uint8_t mask = 0; + + if (flash_sectors.size() > 0) { + const std::vector sectors = splitString(flash_sectors, ','); + for (const auto §or: sectors) { + if (sector == "UFM1") + mask |= (1 << 0); + else if (sector == "UFM0") + mask |= (1 << 1); + else if (sector == "CFM2") + mask |= (1 << 2); + else if (sector == "CFM1") + mask |= (1 << 3); + else if (sector == "CFM0") + mask |= (1 << 4); + else + throw std::runtime_error("Unknown sector " + sector); + } + } else { // full update + mask = 0x1F; + } + + return mask; +} + +bool Altera::sectors_mask_start_end_addr(const Altera::max10_mem_t *mem, + const uint8_t update_sectors, uint32_t *start, uint32_t *end, + uint8_t *sectors_mask) +{ + uint32_t saddr = mem->ufm_addr; + uint32_t eaddr = saddr; + uint8_t start_bit = 0, end_bit = 0; + /* For sake of simplicity: create an array with all length aligned + * as it in MAX10 devices + */ + const uint32_t mem_map_length[] = { + mem->ufm_len[1], mem->ufm_len[0], + mem->cfm_len[2], mem->cfm_len[1] + }; + + if (update_sectors == 0) { + eaddr = mem->ufm_addr + (mem->ufm_len[0] + mem->ufm_len[1]); + *sectors_mask = 0x3; + } else { + /* eaddr start with full memory size */ + for (uint8_t i = 0; i < 4; i++) + eaddr += mem_map_length[i]; + /* search first bit == 1 and increment start address */ + for (uint8_t i = 0; i < 4; i++) { + if (update_sectors & (1 << i)) { + start_bit = i; + break; + } + saddr += mem_map_length[i]; + } + + /* decrement eaddr until last bit == 1 found */ + for (uint8_t i = 3; i >= 0; i--) { + if (update_sectors & (1 << i)) { + end_bit = i + 1; + break; + } + eaddr -= mem_map_length[i]; + } + *sectors_mask = ((1 << end_bit) - 1) - ((1 << start_bit) - 1); + } + *start = saddr; + *end = eaddr; + return true; +} + /* SPI interface */ int Altera::spi_put(uint8_t cmd, const uint8_t *tx, uint8_t *rx, uint32_t len) diff --git a/src/altera.hpp b/src/altera.hpp index 8c0e9f9..82a7bb7 100644 --- a/src/altera.hpp +++ b/src/altera.hpp @@ -97,13 +97,16 @@ class Altera: public Device, SPIInterface { /*************************/ /* max10 specific */ /*************************/ +public: struct max10_mem_t; +private: static const std::map max10_memory_map; /* Write a full POF file, or updates UFM with an arbitrary binary file */ void max10_program(uint32_t offset); /* Write something in UFMx sections after erase */ - bool max10_program_ufm(const max10_mem_t *mem, uint32_t offset); + bool max10_program_ufm(const max10_mem_t *mem, uint32_t offset, + uint8_t update_sectors); /* Write len Word from cfg_data at a specific address */ void writeXFM(const uint8_t *cfg_data, uint32_t base_addr, uint32_t offset, uint32_t len); /* Compare cfg_data with data stored at base_addr */ @@ -120,7 +123,13 @@ class Altera: public Device, SPIInterface { bool max10_dsm_verify(); bool max10_dump(); bool max10_read_section(FILE *fd, const uint32_t base_addr, const uint32_t addr); - + /* Utils methods */ +public: + static uint8_t max10_flash_sectors_to_mask(std::string flash_sectors); + static bool sectors_mask_start_end_addr(const Altera::max10_mem_t *mem, + const uint8_t update_sectors, uint32_t *start, uint32_t *end, + uint8_t *sectors_mask); +private: /*! * \brief with intel devices SPI flash direct access is not possible * so a bridge must be loaded in RAM to access flash