The TinyFPGA USB Bootloader is an open source IP for programming FPGAs without extra USB interface chips. It implements a USB virtual serial port to SPI flash bridge on the FPGA fabric itself. For FPGAs that support loading multiple configurations it is possible for the bootloader to be completely unloaded from the FPGA before the user configuration is loaded in.
From the host computer's perspective, the bootloader looks like a serial port. This method was chosen because programming with serial ports is generally easier to understand than other USB-specific protocols. Commands to boot into the user configuration or access the SPI flash are all transfered over this interface. Using this serial interface, a programmer application on the host computer can issue commands to the SPI flash directly through the bootloader. All of the details about programming the SPI flash are handled by the programmer application.
In order to implement the TinyFPGA USB Bootloader, an FPGA system...
usb_pu
signal from the bootloader.Each FPGA board implementing the TinyFPGA USB Bootloader may have different locations for the bootloader image, user image, user data, and other information. These differences are driven by the FPGA's multi-boot capabilities/requirements and the size of the FPGA configuration image.
In order for a common bootloader and programmer application to program user images and user data to the correct locations, the programmer must know where these locations are in the SPI flash. It is also useful to identify the board with a name and unique serial number. This information along with other required and optional metadata is stored in the non-volatile security register pages of the SPI flash and optionally in the main SPI flash memory.
The programmer application will search security register pages 0-3 for valid metadata. The metadata is stored in JSON format. JSON was choosen because it is compact enough, is well understood, and easier to read and understand than a binary format.
Below is an example of how the metadata may be structured and formatted for the TinyFPGA BX board:
One of the SPI flash security register pages contains fixed data about the board that does not change. This is the name of the board, the hardware revision of the board, and serial number unique to the board name. This security register page should be write protected as it should never be changed. If the rest of the SPI flash is erased, this minimal amount of information will help the user to find recovery instructions.
{"boardmeta":{ "name": "TinyFPGA BX", "fpga": "ice40lp8k-cm81", "hver": "1.0.0", "serial": 10034 }}
A seperate SPI flash security register page should contain or point to information that can change. This includes the bootloader version number, update URL for new bootloader releases for this board, and an address map for the SPI flash that describes where the bootloader, user image, and user data belong. Using this information the programmer application is able to discover where to put new user images and data without any input from the user or built-in knowledge about the board. It makes the board plug-and-play.
Optionally, an additional desc.tgz
file may be included in the SPI flash itself, or on the update page. This desc.gz
file contains the information necessary to develop with the board. At a minimum it describes the FPGA name, package, and a mapping from FPGA pins to board IOs and peripherals.
{"bootmeta": "@0xFF000+445"}
{ "bootloader": "TinyFPGA USB Bootloader", "bver": "2.0.0", "update": "https://tinyfpga.com/update/tinyfpga-bx", "addrmap": { "bootloader": "0x00000+131542", "userimage": "0x30000+131542", "userdata": "0x50000+710000", "desc.tgz": "0xFC000+16000" } }
A detailed explanation of the metadata structure will be added here.
The protocol on top of the USB virtual serial port takes the form of requests and responses. Only the host computer is able to initiate requests. The bootloader on the FPGA can only respond to requests.
Length: 1 byte +=====================+ | Request Data | +=====+========+======+ | Byte| Field |Value | +=====+========+======+ | 0 | Opcode | 0x00 | +-----+--------+------+
The Boot
command forces the TinyFPGA B-series board to exit the bootloader and configure the FPGA with the user design from SPI flash. Once the user design is loaded onto the FPGA, the bootloader is no longer present and the user design has full control over the FPGA, including the USB interface.
Length: Variable +================================+ | Request Data | +=====+===================+======+ | Byte| Field | Value| +=====+===================+======+ | 0 | Opcode | 0x01 | +-----+-------------------+------+ | 1 | Write Length Lo | 0xWW | +-----+-------------------+------+ | 2 | Write Length Hi | 0xWW | +-----+-------------------+------+ | 3 | Read Length Lo | 0xRR | +-----+-------------------+------+ | 4 | Read Length Hi | 0xRR | +-----+-------------------+------+ | 5 | Write Data Byte 0 | 0xDD | +-----+-------------------+------+ +-----+-------------------+------+ | n+5 | Write Data Byte n | 0xDD | +-----+-------------------+------+ +================================+ | Response Data | +=====+===================+======+ | Byte| Field | Value| +=====+===================+======+ | 0 | Read Data Byte 0 | 0xDD | +-----+-------------------+------+ +-----+-------------------+------+ | n | Read Data Byte n | 0xDD | +-----+-------------------+------+
The Access SPI
command executes a transfer with the SPI flash. SPI flash commands can have two phases:
The Write Length
and Read Length
in the Access SPI
command refer to these two phases. In order to fully understand how to interact with the SPI flash you need to read the datasheet for the SPI flash chip. The datasheet contains a table that lists the commands the SPI flash chip supports.
Here's a summary of the commands used to properly erase, program, and verify bitstreams on the SPI flash:
Command | Opcode | Address Bytes | Dummy Bytes | Data Bytes | Datasheet Section |
---|---|---|---|---|---|
Resume | 0xAB | 0 | 0 | 0 | 11.4 |
Read Man. and Dev. ID | 0x9F | 0 | 0 | 0 | 11.1 |
Read Status Reg 1 | 0x05 | 0 | 0 | 1 | 10.1 |
Block Erase 4 KBytes | 0x20 | 3 | 0 | 0 | 7.2 |
Block Erase 32 KBytes | 0x52 | 3 | 0 | 0 | 7.2 |
Block Erase 64 KBytes | 0xD8 | 3 | 0 | 0 | 7.2 |
Write Enable | 0x06 | 0 | 0 | 0 | 8.1 |
Byte/Page Program | 0x02 | 3 | 0 | 1+ | 7.1 |
Read Array | 0x0B | 3 | 1 | 1+ | 6.1 |
The SPI flash needs to be accessed in a specific order to successfully program the bitstream. The following pseudocode illustrates how the tinyprog
programmer programs user FPGA bitstreams into the SPI flash:
// SPI flash will be in deep sleep, we need to wake it up issue resume command // read FPGA board metadata for each SPI flash security register page 0-3: read the security register page and metadata JSON recursively read any other locations in SPI flash referenced by the metadata userimage_addr = metadata["bootmeta"]["addrmap"]["userimage"] // erase user flash area for each 4k block from (userimage_addr) to (userimage_addr + length(bitstream)): issue write enable command issue block erase 4 KBytes at current block address poll status reg 1 until bit 0 is cleared // program new user bitstream into user flash area for each 256 bytes from (userimage_addr) to (userimage_addr + length(bitstream)): issue write enable command write 256 bytes of of the bitstream and increment write offset by 256 bytes poll status reg 1 until bit 0 is cleared // verify bitstream data is correct read length(bitstream) bytes of of the bitstream, compare to bitstream file if verify is successful then issue boot command to the bootloader
For exact details, see the tinyprog/__init__.py
Python module in this repo.