Embedded Linux

Saturn, Microblaze and Linux – How to Run Linux on Saturn Spartan 6 FPGA Module – Part IV

2139 views March 9, 2016 admin 9

microblaze_Linux

In previous articles in this series, we saw how to create a Microblaze based embedded platform for Saturn Spartan 6 FPGA module, how to build Linux kernel and boot Linux on Saturn using Xilinx Platform cable USB and XMD. Though this is the easier method, there are a couple of drawbacks associated to it. You will need a Xilinx Platform Cable USB which is slightly expensive and the images are programmed SRAM/DDR instead of SPI flash. It is possible to program the onboard SPI flash with the images which would retain contents even after power cycle and this can be done without buying expensive JTAG cables.

Before we can proceed, we will need to make a minor change to our XPS project and rebuild it. XPS always generate a new project with startup clock set to JTAG clock. This is great when we use Xilinx Platform Cable USB to download the bit file directly to FPGA SRAM. But since in this part of the article we are going to finally download the image in to SPI flash and let the FPGA load the configuration on it’s own from SPI flash, we will need to change the startup clock to CCLK. To do this, open the XPS project and go to the “Project” tab next to “IP Catalog” tab and locate Bitgen options “bitgen.ut”. Double click to open it and change the line “-g StartUpClk:JTAGCLK” to “-g StartUpClk:CCLK”. Save bitgen.ut and rebuild the entire project by clicking “Generate Bitstream” button on the left side toolbar. This step is very critical to generate a bootable image and please don’t skip it.

To program the onboard SPI flash with, we will need to make a single binary image that will contain the following individual images.

  • FPGA bit file for Microblaze
  • Linux kernel
  • A bootloader

A bootloader is necessary here to copy the Linux kernel from SPI flash to DDR and execute it. the primary requirements for the bootloader is that it should be capable of reading data from the SPI flash and should fit within the BRAM cache size.

To build a bootloader, create a new SDK workspace and create a new application project. Let’s call this project “saturn_v3_bootloader”. You can find more information about creating a SDK project in the article Creating Xilinx EDK test project for Saturn – Your first Microblaze processor based embedded design. When selecting the application template, select “Hello World”. Once the project is created, open the “SaturnV3Bootloader” project in the “Project Explorer” on the left. Double click on the linker script lscript.ld and make sure that all sections are mapped to BRAM (should look something like microblaze_0_i_bram_ctrl_microblaze_0_d_bram_ctrl). If not, map all sections to BRAM manually and save the linker script. After saving linker script, build the project and make sure that no errors are reported.

Open HelloWorld.c in the project and replace the entire code with the code below. Please note, the code below is a bare-bones bootloader designed to be simpler than being robust.

#include <stdio.h>
#include "platform.h"
#include "xspi.h"
#include "xparameters.h"
#include "xil_cache.h"

void print(char *str);

//Set the offset and size of image in SPI flash
#define FLASH_IMAGE_START_ADDRESS 0x500000
#define FLASH_IMAGE_SIZE 0x600000

//Set the address where image will be loaded. This will usually point to
//DDR or SRAM depending on the board architecture. Remember to build
//your application/Linux kernel with this address as base address
#define IMAGE_LOAD_ADDRESS XPAR_LPDDR_S0_AXI_BASEADDR

//Define ID of the SPI peripheral that is connected to the SPI flash
#define SPI_DEVICE_ID XPAR_AXI_SPI_0_DEVICE_ID

void (*imageEntry)();
XSpi Spi;

//This function reads a byte from SPI peripheral
u8 spiReadData()
{
    while(!(XSpi_ReadReg(Spi.BaseAddr, XSP_SR_OFFSET) & 0x02));
    return XSpi_ReadReg(Spi.BaseAddr,XSP_DRR_OFFSET);
}

//This function writes one byte to the SPI peripheral
void spiWriteData(u8 data)
{
    while(XSpi_GetStatusReg(&Spi) & 0x08);
    XSpi_WriteReg(Spi.BaseAddr, XSP_DTR_OFFSET, data);
}

//This function loads the image to the destination (DDR/SRAM) and executes it
int loadAppImage()
{
    XSpi_Config *cfgPtr;
    u8 recBuffer[4];
    u32 i = 0, index = 0, ddrPtr = 0;

    print("Initializing Numato Saturn V3 SPI Image Loader...\n\r");
    print("*** http://numato.com ***\n\r");
    print("\n\r");

    //Lookup SPI peripheral configuration details
    cfgPtr = XSpi_LookupConfig(SPI_DEVICE_ID);
    if (cfgPtr == NULL)
    {
        return XST_DEVICE_NOT_FOUND;
    }

    if(XSpi_CfgInitialize(&Spi, cfgPtr, cfgPtr->BaseAddress) != XST_SUCCESS)
    {
        return XST_FAILURE;
    }

    //Beyond this point we will use only low level APIs in favor of smaller
    //and simpler code.

    //Set up SPI controller. Master, manual slave select. The SPI peripheral
    //is configured with no FIFO
    XSpi_SetControlReg(&Spi, 0x86);

    //Disable interrupts
    XSpi_IntrGlobalDisable(&Spi);

    //Cycle CS to reset the flash to known state
    XSpi_WriteReg(Spi.BaseAddr, XSP_SSR_OFFSET, 0x00);
    XSpi_WriteReg(Spi.BaseAddr, XSP_SSR_OFFSET, 0x01);
    XSpi_WriteReg(Spi.BaseAddr, XSP_SSR_OFFSET, 0x00);

    //Write command 0x0b (fast read) to SPI flash and do a dummy read
    spiWriteData(0x0b);
    spiReadData();

    //Send the address from where the image needs to be loaded.
    //Dummy read after every write as usual
    spiWriteData((FLASH_IMAGE_START_ADDRESS >> 16) & 0xff);
    spiReadData();
    spiWriteData((FLASH_IMAGE_START_ADDRESS >> 8) & 0xff);
    spiReadData();
    spiWriteData((FLASH_IMAGE_START_ADDRESS) & 0xff);
    spiReadData();

    //A dummy write/read as per W25Q128FV datasheet
    spiWriteData(0x00);
    spiReadData();

    print("Loading application image...\n\r");

    for(i=0; i<=FLASH_IMAGE_SIZE; i++)
    {
        //Do a dummy write
        spiWriteData(0x00);

        //Read data back
        recBuffer[index] = spiReadData();
        index++;

        //Write the data to DDR/SRAM four bytes at a time
        if(index >= 4)
        {
            *((u32*)(ddrPtr + IMAGE_LOAD_ADDRESS)) = *((u32*)(&recBuffer));
            ddrPtr += 4;
            index = 0;
        }
    }

    print("Executing application image...\n\r");
    //Invalidate instruction cache to clean up all existing entries
    Xil_ICacheInvalidate();
    //Execute the loaded image
    imageEntry = (void (*)())IMAGE_LOAD_ADDRESS;
    (*imageEntry)();

    //We shouldn't be here
    return 0;
}

int main()
{
    init_platform();
    loadAppImage();
    return 0;
}

Rebuild the project with newly added code and make sure that no errors reported. If any errors pop up, please fix them before continuing.
The above code will load the kernel image from SPI flash memory and write to DDR and jump to kernel entry point. This code assumes that the kernel image is sitting at a specific address in the SPI flash (0x500000 in this case) and has a specific size (0x600000) in this case. 0x600000 is little bigger than the actual kernel image but that shouldn’t cause any problems. The area starting from 0x000000 to 0x4FFFFF is reserved for FPGA configuration data (enough to hold configuration data for devices as big as XC6SLX150). So in essence, we are targeting the following memory map for the SPI flash binary image we are going to create.

  1. 0x000000 – 0x4FFFFF -> FPGA configuration data
  2. 0x500000 – 0xAFFFFF -> Kernel image
  3. 0xB00000 upwards -> User data (not used in our case)

At this point, we have the bit file for FPGA configuration, Linux kernel image and bootloader image we just built. Before we can proceed with packing these images in to a single binary file, there is one crucial step we need to do. You may remember that fact the the kernel image we built (simpleImage.saturn_v3) is in ELF format. This is helpful when downloading kernel using XMD but the metadata (in addition to executable code) is going to be a problem since our bootloader does not understand ELF format. One option is to add ELF loader code to bootloader but it is going to be time consuming and simply an overkill for such a simple implementation like this. Another option would be to strip the metadata and generate a pure executable binary file. This can be easily done with objcopy tool (objcopy is part of gnu binutils). But the tricky thing here is that you can not use any objcopy (eg: objcopy built for host machine). You will need to use objcopy that is built for Microblaze. Fortunately, buildroot already built cross objcopy that runs on host machine but can work on Microblaze executables. This happened when we built the Linux kernel earlier (buildroot builds all cross tools needed for building the kernel). All that we need to do is to find where these cross compiled tools are placed by buildroot. With the specific buildroot version we used, the objcopy executable we need was placed in the directory “output/host/usr/microblazeel-buildroot-linux-gnu/bin” under the buildroot root directory.
Switch to output/images directory under buildroot root directory where Linux kernel image is available in ELF format and execute the following command.

cmd>../host/usr/microblazeel-buildroot-linux-gnu/bin/objcopy -O binary simpleImage.saturn_v3 saturn_linux.bin

If the command was successful, you will see a new file created with the name saturn_linux.bin. Copy this file over to the Windows machine.

On the Windows machine, place the following files in the same directory.

  1. The Linux image in binary format (saturn_linux.bin we just created)
  2. The bit file (.bit) for the Microblaze system and the Block ram Memory Map file (.bmm) (Both files can be found in the folder SDK\SDK_Export\hw under the XPS project folder)
  3. The bootloader executable (This file can be found in the directory SaturnV3Bootloader\Debug under the SDK bootloader project folder and has .elf extension)

In my case, I have the following files in my directory as shown in the image. You file names may be different depending on the project names etc..

allimages1

With all necessary images available now, let us move forward with creating the final binary image. We will do the following steps to create a single binary image.

  1. Merge the FPGA configuration (.bit file) and the Bootloader executable image (.elf). This is because the contents of the executable needs to go in to the FPGA BRAM when FPGA initialize after power up.
  2. Concatenate the combined FPGA configuration file (generated in previous step) with Linux kernel image.

To merge Bootloader executable image with bit file, run the following command.

cmd>c:\Xilinx\14.6\ISE_DS\settings64.bat
cmd>data2mem -bm SaturnV3Linux_bd.bmm -bd SaturnV3Bootloader.elf -bt SaturnV3Linux.bit -w

If the command ran successfully, you will see a new file created in the directory with name “SaturnV3Linux_rp.bit”. This file now has both the FPGA configuration stream and Bootloader code.

Now we can concatenate the FPGA configuration file we just generated (SaturnV3Linux_rp.bit) with Linux kernel binary image (saturn_linux.bin) to create a single binary image that can be downloaded to the SPI flash. Run the following command to generate single binary image.

cmd>promgen -w -p bin -c FF -o download -s 16384 -u 0 SaturnV3Linux_rp.bit -data_file up 500000 saturn_linux.bin -spi

If this command succeeded, you will see a few new files in the directory and one of them would be “download.bin”. This is the final binary image that we need to download to Saturn’s SPI flash. The following image shows all files in my directory after executing all above mentioned steps.

allimages_afterbuild1

The final image is approximately 10MB in size and this leaves with approximately 6MB of available space in the SPI flash for custom user data.

Now download the latest Saturn Spartan 6 FPGA Module configuration tool from the product page and use that to download the final binary image “download.bin” to the SPI flash (see image below).

downloading_linux_image1

It may take a couple of minutes for the download to complete. While download is in progress, go ahead and start you favorite serial terminal emulation program (Hyperterminal, PUTTY, Tertarerm etc..) and open the COM port corresponding to Saturn’s FT2232 channel B and set baud rate to 115200 and set handshaking to off/none (You should have configured channel B of Saturn’s FT2232H as serial port. If not, please follow this tutorial to do so).

Once the binary image is completed downloading, the tool will reboot the FPGA and let it load the configuration data from newly downloaded image. If everything went well, the bootloader will start copying Linux kernel image to DDR and you will see the below message in the serial terminal emulation software.

bootloader_loading_linux1

It may take a few seconds for the bootloader to copy the Linux kernel. Once copying is completed, bootloader will start the kernel and you will see the kernel booting as shown below.

SaturnSpartan6_Booting_Linux1

You can login to Linux as root with no password. Image below shows cpuinfo output.

saturn_cpuinfo1

All files and projects that are used/created while writing this article series is available for download here.

Was this helpful?

Leave A Comment
*
*