LAB 3 Bare Metal FFT app

In this lab we will compile a bare metal software program based upon the bare metal Hello World example code that ships with the SoC EDS tools, see the "Altera-SoCFPGA-HelloWorld-Linux-GNU.tar.gz" example in the SoC EDS software examples directory for more information. We will place the resultant executable on the FAT partition of the SD card and use uboot to launch it manually. Then we will combine this bare metal application with the default preloader that we created in lab 1 to make the application start up automatically.

We first need a way to access the memory map for the HPS-to-FPGA bridge and the peripherals inside the FPGA. We could manually create header files for this, but it is easier and more reliable to let the software tools generate these files from the output of the hardware tools. When the FPGA hardware system installed on the SD card was generated and compiled, the hardware memory map was saved to a file called "soc_system.sopcinfo". We copied this file into our working directory during the initial lab setup steps. We will use the "sopc-create-header-files" utility discussed in the workshop presentation to generate C header files from the "soc_system.sopcinfo" file. The "sopc-create-header-files" is a part of the ACDS tools.

Hardware Environment

home Jump to Workshop Main Page | go_start Return to Lab Intro | go_back Return to Lab 2: Verifying Hardware with System Console | printtopic Show All Steps

The image below represents an abstract view of what the hardware environment looks like that we will be interacting with in this lab. The FPGA system on the right side of the image contains an onchip RAM which FFT input samples can be store into. Then the FFT read DMA can read those samples and source them into the FFT hardware component for processing. The results from the FFT hardware are then writen into the onchip RAM by the FFT write DMA. The JTAG master and the LWH2F bridge master can both interact with the onchip RAM and the DMA engines, so we can interact with the FFT hardware from either a system-console session connecting through the JTAG master component, or through software running on the HPS reading and writing through the LWH2F master.

In this lab we will be using the bare metal application to interact through the LWH2F master.

WS1 intro to soc.png

Lab Environment

home Jump to Workshop Main Page | go_start Return to Lab Intro | go_back Return to Lab 2: Verifying Hardware with System Console | printtopic Show All Steps

To begin, make sure that you have the environment provided by the Embedded Command Shell which is delivered in the SoC EDS tools installation. There are a number of ways that you might get this environment applied to your development host, one way is to simply execute the "embedded_command_shell.sh" shell script that is provided in the SoC EDS tools, like this:

On Linux:
[~]$ <path-to-soceds-tools>/embedded/embedded_command_shell.sh
[~]$

On Windows:
Start->Altera->SoCEDS->Embedded Shell

Then from the embedded command shell, change into the "WS1-IntroToSoC/software/BareMetal_fft" directory that you should have previously copied onto the local hard drive of your development host.
[~]$ cd <path-to-lab-work>/WS1-IntroToSoC/software/BareMetal_fft
[BareMetal_fft]$

Lab Procedure

home Jump to Workshop Main Page | go_start Return to Lab Intro | go_back Return to Lab 2: Verifying Hardware with System Console | printtopic Show All Steps

  1. Create a directory called "qsys_headers" where we can store the system header files that we are about to create.
    [BareMetal_fft]$ mkdir qsys_headers
    [BareMetal_fft]$

  2. Run the "sopc-create-header-files" utility to generate the header files.
    [BareMetal_fft]$ sopc-create-header-files ../../soc_system.sopcinfo --output-dir qsys_headers
    swinfo2header: Creating macro file 'qsys_headers/soc_system.h' for SOPC Builder system 'soc_system'
    swinfo2header: Creating macro file 'qsys_headers/axi_bridge_for_acp_128_0.h' for module 'axi_bridge_for_acp_128_0'
    swinfo2header: Creating macro file 'qsys_headers/fft_ddr_bridge.h' for module 'fft_ddr_bridge'
    swinfo2header: Creating macro file 'qsys_headers/fft_sub.h' for module 'fft_sub'
    swinfo2header: Creating macro file 'qsys_headers/fft_sub_DDR.h' for module 'fft_sub_DDR'
    swinfo2header: Creating macro file 'qsys_headers/fft_sub_mm_bridge_0.h' for module 'fft_sub_mm_bridge_0'
    swinfo2header: Creating macro file 'qsys_headers/fft_sub_sgdma_from_fft.h' for module 'fft_sub_sgdma_from_fft'
    swinfo2header: Creating macro file 'qsys_headers/fft_sub_sgdma_to_fft.h' for module 'fft_sub_sgdma_to_fft'
    swinfo2header: Creating macro file 'qsys_headers/fpga_only_master.h' for module 'fpga_only_master'
    swinfo2header: Creating macro file 'qsys_headers/hps_0.h' for module 'hps_0'
    swinfo2header: Creating macro file 'qsys_headers/hps_0_bridges.h' for module 'hps_0_bridges'
    swinfo2header: Creating macro file 'qsys_headers/hps_0_arm_a9_0.h' for module 'hps_0_arm_a9_0'
    swinfo2header: Creating macro file 'qsys_headers/hps_0_arm_a9_1.h' for module 'hps_0_arm_a9_1'
    swinfo2header: Creating macro file 'qsys_headers/lw_mm_bridge.h' for module 'lw_mm_bridge'
    swinfo2header: Creating macro file 'qsys_headers/memcpy_msgdma_mm_read.h' for module 'memcpy_msgdma', master group 'memcpy_msgdma_mm_read'
    swinfo2header: Creating macro file 'qsys_headers/memcpy_msgdma_mm_write.h' for module 'memcpy_msgdma', master group 'memcpy_msgdma_mm_write'
    [BareMetal_fft]$

  3. Review the files generated in the "qsys_headers folder". The utility generates a variety of header files, each defining the memory map from the perspective of a particular master since within a Qsys system each master can have it's own unique view of it's memory map. The files used in the bare metal app fft.c are listed below.
    Generated File Function
    soc_system.h The memory map as viewed by FPGA masters, in this case the mSGDMA master.
    hps_0.h The memory map of FPGA slaves as viewed by the hard ARM processors.
  4. Compile the application with the provided Makefile:
    [BareMetal_fft]$ make
    [BareMetal_fft]$

  5. This will create a file called "fft.bin" which is the program that we now want to run.
    [BareMetal_fft]$ ls
    alt_types.h                  fft.axf.map      fft.c    hello.o  Makefile
    cycloneV-dk-ram-modified.ld  fft.axf.objdump  fft.o    io.c     msgdma
    fft.axf                      fft.bin          hello.c  io.o     qsys_headers
    [BareMetal_fft]$

  6. Now you need to copy the "fft.bin" file onto the FAT partition of the SD card that you are using to boot your target development board for the workshop series lab work. There are two ways to do this:
    1. If your target board is already powered up, booted into linux, and connected to your host development machine with the USB OTG cable, you should be able to mount the FAT partition of the SD card as a removeable media device that is available to your host development machine. See this page for more information on that flow, Interacting with the USB gadget mass storage interface.

    2. Otherwise, you can remove your SD card from the target development board and insert it into your host development machine using whatever SD card adapter you used to program the SD card image initially.

    • Once you have your SD card FAT partition mounted on your host development machine, just copy the "fft.bin" file over to it. Place it at the top level of the FAT partition, as a peer to the "boot.script" file that is already there, like this:
      [BareMetal_fft]$ cp fft.bin /<path-to-SD card-FAT-partition>/
      [BareMetal_fft]$ ls /<path-to-SD card-FAT-partition>/
      ALTERA_AV_SOC                DE0_NANO_SOC      rootfs.img
      ALTERA_CV_SOC                fft.bin           u-boot.scr
      ARROW_SOCKIT                 hdl_src           WS1-IntroToSoC
      board_info                   ip                WS2-IntroToLinux
      boot.script                  MACNICA_HELIO_14  WS3-DevelopingDrivers
      CRITICALLINK_MITYSOM_DEVKIT  patches           zImage.socfpga-3.10-ltsi
      [BareMetal_fft]$

    • If needed, re-install your SD card into your target development board.

  7. Now boot your target development board into u-boot:
    1. Plug the SD card you just programmed with the "fft.bin" application into the development target board.

    2. Connect the development target board to your development host machine with the USB serial cable.

    3. Start an appropriate terminal emulation program on your development host. See the " USB serial console interface" page for advice on this.

    4. Power on your development target board, or press the cold reset button if it is already on.

    5. You should see the Preloader and u-boot startup messages scroll into the terminal emulator display, u-boot will pause for 5 seconds to allow you to type "stop" to stop the boot process in u-boot. When you see that prompt type "stop". If you don't get u-boot to stop successfully and it proceeds into the linux kernel, press the cold reset button or power cycle the board and try again.

  8. Now at the u-boot prompt, the first thing we need to do is program the FPGA with its RBF file.
    1. List the files on the FAT partition to see the top level directory structure:
      SOCFPGA_CYCLONE5 # fatls mmc 0:1
                  altera_av_soc/
                  altera_cv_soc/
                  arrow_sockit/
                  board_info/
           1956   boot.script
                  criticallink_mitysom_devkit/
                  de0_nano_soc/
                  hdl_src/
                  ip/
                  macnica_helio_14/
                  patches/
       23581507   rootfs.img
           2028   u-boot.scr
                  ws1-introtosoc/
                  ws2-introtolinux/
                  ws3-developingdrivers/
        3570344   zimage.socfpga-3.10-ltsi
          51936   fft.bin
      
      5 file(s), 13 dir(s)
      
      SOCFPGA_CYCLONE5 #

    2. You should see the board specific directory listed for you target development board, list that directory:
      NOTE: we will use the de0_nano_soc for this example
      SOCFPGA_CYCLONE5 # fatls mmc 0:1 /de0_nano_soc
                  ./
                  ../
         241460   u-boot.img
           1316   de0_nano_soc.qpf
        3306479   soc_system.sopcinfo
          21978   soc_system.dtb
                  hps_isw_handoff/
                  output_files/
          70613   soc_system.qsys
          42597   soc_system.dts
          41347   de0_nano_soc.qsf
         262144   preloader-mkpimage.bin
      
      8 file(s), 4 dir(s)
      
      SOCFPGA_CYCLONE5 #

    3. You should see the "output_files" directory in your board specific directory, list that directory:
      SOCFPGA_CYCLONE5 # fatls mmc 0:1 /de0_nano_soc/output_files
                  ./
                  ../
        2089112   de0_nano_soc.rbf
        4794559   de0_nano_soc.sof
          13488   de0_nano_soc.jdi
      
      3 file(s), 2 dir(s)
      
      SOCFPGA_CYCLONE5 #

    4. You should see the board specific RBF file listed in the "output_files" directory, you need to load that file into memory, like this:
      SOCFPGA_CYCLONE5 # fatload mmc 0:1 ${fpgadata} /de0_nano_soc/output_files/de0_nano_soc.rbf
      reading /de0_nano_soc/output_files/de0_nano_soc.rbf
      2089112 bytes read in 122 ms (16.3 MiB/s)
      SOCFPGA_CYCLONE5 #

    5. You program the FPGA with that RBF data, like this:
      SOCFPGA_CYCLONE5 # fpga load 0 ${fpgadata} ${filesize}
      SOCFPGA_CYCLONE5 # 

  9. With the FPGA programmed successfully, you can now configure the HPS-to-FPGA bridges, like this:
    SOCFPGA_CYCLONE5 # run bridge_enable_handoff
    ## Starting application at 0x3FF79550 ...
    ## Application terminated, rc = 0x0
    SOCFPGA_CYCLONE5 #

  10. And now you can load the bare metal application into memory from the FAT partition, like this:
    SOCFPGA_CYCLONE5 # fatload mmc 0:1 0x00100040 fft.bin
    reading fft.bin
    51936 bytes read in 13 ms (3.8 MiB/s)
    SOCFPGA_CYCLONE5 #

  11. And now you can run the bare metal program, like this:
    SOCFPGA_CYCLONE5 # go 0x00100040
    ## Starting application at 0x00100040 ...
    Hello World!
    
    Hello from SoC FPGA to everyone!
    This program was called with "0".
     **** square wave ****
    signal 0 32767
    signal 1 32767
    signal 2 32767
    ---cut---

On your terminal emulator display you should see the bare metal program print the input samples that it sends into the FFT hardware and the real and imaginary results that it retrieves from the FFT hardware. The default is 128 samples but you can recompile the bare metal program and change the value to any power of 2 less than or equal to 4096. To do this, edit the source file "fft.c", on line 69 change the DATA_LENGTH macro.
// *****************        feel free to change this value  ****************
// 128 256 512 1024 2048 4096
#define DATA_LENGTH 128

In the "hello.c" source file you can change the output from square wave to sine wave by changing the argument passed to "fft_main()" from 0 to 1.
int main(void)
{
    printf("Hello World!\r\n");
    fft_main(0);

    printf("all done\r\n");
    return 0;
}

Automate the bare metal application startup with Lab 1 Preloader

Now that you have successfully built the bare metal application and used u-boot to manually start the application running on your target development board, lets try to automate the boot sequence so that the bare metal application just runs after we power up the target development board. There are a number of ways that we might accomplish this, from where things stand now, you could modify the u-boot "boot.script" to essentially add the commands that we manually typed into the u-boot console above, and then you can wrap that new script with the "mkimage" utility and create your own new "u-boot.scr" which the current u-boot programmed on your SD card could then load and execute to invoke the bare metal application. That would be a fine example, but its not that different from what we already did manually, and it does rely on some big resources to be loaded on the SD card, like a u-boot image with FAT support for instance. Feel free to give this a try on your own if you like. The approach that we are going to take in this lab is to merge the bare metal application that we just created with the Preloader that was created in Lab 1, which requires nothing more than the Preloader to get us going. So the boot ROM will load the Preloader and execute it, and then the Preloader will load the bare metal application and execute it.

The process for this is going to be very similar to the process that we used in Lab 1, however, first thing we need to do is prepare the "fft.bin" application for the Lab 1 Preloader to load properly. This requires us to wrap the "fft.bin" image with a u-boot "mkimage" wrapper which we do like this:

[BareMetal_fft]$ mkimage -A arm -O u-boot -T firmware -C none -a 0x00100040 -e 0x00100040 -n 'bare metal app' -d fft.bin fft.bin.img
Image Name:   bare metal app
Created:      Sun Jun 21 16:22:18 2015
Image Type:   ARM U-Boot Firmware (uncompressed)
Data Size:    51936 Bytes = 50.72 kB = 0.05 MB
Load Address: 00100040
Entry Point:  00100040
[BareMetal_fft]$

That creates a uImage file that we named "fft.bin.img", which we will need to program into the 0xA2 partition along with the Lab 1 Preloader.

On a Linux host.

Using the "dd" method.

Insert your SD card into your development host machine using whatever SD card adapter you used to program the SD card image initially.

We need to know what block device our SD card is referenced with on our host, we can determine this by looking in the "/sys/block" directory. In our case we have used a USB SD card adapter and this is the only USB block device that we have attached to our system, so if we run the following command we can easily isolate the device we are interested in:
[BareMetal_fft]$ ls -l /sys/block/ | grep "usb"
lrwxrwxrwx 1 root root 0 Jun 20 10:30 sdb -> ../devices/pci0000:00/0000:00:1a.0/usb3/3-1/3-1.2/3-1.2:1.0/host9/target9:0:0/9:0:0:0/block/sdb
[BareMetal_fft]$

From the above command we see that the device appears to "sdb". We can also verify that in the "/proc/parittions" file, like this:
[BareMetal_fft]$ cat /proc/partitions
major minor  #blocks  name

...cut...
   8       16    3887104 sdb
   8       17     262144 sdb1
   8       18     260096 sdb2
   8       19       1024 sdb3
[BareMetal_fft]$

If you are not using a USB SD card adapter but have an internal SD card reader in your development host, then you may need to search for block devices installed on something other than USB. A common SD card reader device type is "mmc", so if you perform the above commands but search for "mmc" instead of "usb" then your output may look like this instead.
[BareMetal_fft]$ ls -l /sys/block | grep "mmc"
lrwxrwxrwx 1 root root 0 Jun 23 00:48 mmcblk0 -> ../devices/pci0000:00/0000:00:1c.0/0000:02:00.0/mmc_host/mmc0/mmc0:0007/block/mmcblk0
[BareMetal_fft]$ cat /proc/partitions
major minor  #blocks  name

...cut...
 179        0    3887104 mmcblk0
 179        1     262144 mmcblk0p1
 179        2     260096 mmcblk0p2
 179        3       1024 mmcblk0p3
[BareMetal_fft]$

For the purposes of the rest of this description, we will stick with the USB block device example and use "/dev/sdb" as our block device in the following commands.

Now that we know which block device we want to program, we need to determine which partition we need to program. The partition that stores the preloader is the 0xA2 partition on the SD card, we can determine which partition that is by running "fdisK" like this:
[BareMetal_fft]$ sudo fdisk -l /dev/sdb

Disk /dev/sdb: 3.7 GiB, 3980394496 bytes, 7774208 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x06bfaed7

Device     Boot  Start     End Sectors  Size Id Type
/dev/sdb1         4096  528383  524288  256M  b W95 FAT32
/dev/sdb2       528384 1048575  520192  254M 83 Linux
/dev/sdb3         2048    4095    2048    1M a2 unknown

Partition table entries are not in disk order.
[BareMetal_fft]$

From the above command we see that the 0xA2 partition is associated with "/dev/sdb3", so that is the partition that we will "dd" the Lab 1 preloader into.
CAUTION: be absolutely certain that you have the correct device, as "dd" to the wrong device can destroy your host system.
NOTE: replace the XXX below with your block partition reference.
[BareMetal_fft]$ sudo dd if=../spl_bsp/preloader-mkpimage.bin of=/dev/XXX
512+0 records in
512+0 records out
262144 bytes (262 kB) copied, 0.205229 s, 1.3 MB/s
[BareMetal_fft]$

Now that we have the Lab 1 Preloader programmed, we need add the "fft.bin.img" file to the 0xA2 partition. The Lab 1 Preloader was configured with the default settings that cause it to look for the next image to load out of the 0xA2 partition at offset 0x00040000. We do this like this:
NOTE: replace the XXX below with your block partition reference.
[BareMetal_fft]$ printf "%d\n" 0x40000
262144
[BareMetal_fft]$ sudo dd if=fft.bin.img of=/dev/XXX bs=262144 seek=1
0+1 records in
0+1 records out
52000 bytes (52 kB) copied, 0.00836857 s, 6.2 MB/s
[BareMetal_fft]$

After we have programmed the Lab 1 Preloader and bare metal application onto our SD card we can run "sync" to ensure that all the IO is complete and then we can remove the SD card from our development host machine.
[spl_bsp]$ sync
[spl_bsp]$

Using the "alt-boot-disk-util" method.

If you are uncomfortable using the "dd" command to program the Lab 1 Preloader and bare metal application as shown above, or if you would like a safer mechanism to accomplish this, Altera provides a utility called "alt-boot-disk-util" in the SoC EDS tools installation that attempts to provide this functionality for you. So if you follow the procedure above to determine the block device that your SD card is represented with on your development host, you can use "alt-boot-disk-util" to program your Lab 1 Preloader and bare metal application. "alt-boot-disk-util" will inspect the device that you pass to it to ensure that it appears to be the block device with a 0xA2 partition on it and then program the Lab 1 Preloader image and the bare metal application image into the proper locations. You invoke it like this:
[BareMetal_fft]$ sudo <path-to-soceds-installation>/host_tools/altera/diskutils/alt-boot-disk-util -p ../spl_bsp/preloader-mkpimage.bin -b fft.bin.img -a write /dev/sdb
Altera Boot Disk Utility
Copyright (C) 1991-2014 Altera Corporation

Altera Boot Disk Utility was successful.
[BareMetal_fft]$

On a Windows host.

Using the "alt-boot-disk-util" method.

Insert your SD card into your development host machine using whatever SD card adapter you used to program the SD card image initially.

Determine the drive letter that the Windows environment represents the SD card image with on your host. "alt-boot-disk-util" will inspect the device that you pass to it to ensure that it appears to be the block device with a 0xA2 partition on it and then program the new Preloader image into the proper location. You invoke it like this:
[BareMetal_fft]$ alt-boot-disk-util -p ../spl_bsp/preloader-mkpimage.bin -b fft.bin.img -a write <drive-letter>
Altera Boot Disk Utility
Copyright (C) 1991-2014 Altera Corporation

Altera Boot Disk Utility was successful.
[BareMetal_fft]$

Running the Lab 1 Preloader with bare metal application

home Jump to Workshop Main Page | go_start Return to Lab Intro | go_back Return to Lab 2: Verifying Hardware with System Console | printtopic Show All Steps

To run the Lab 1 Preloader and bare metal application on your development target.
  1. Plug the SD card you just programmed with the Lab 1 Preloader and bare metal application into the development target board.
  2. Connect the development target board to your development host machine with the USB serial cable.
  3. Start an appropriate terminal emulation program on your development host. See the " USB serial console interface" page for advice on this.
  4. Power on your development target board.

What you should see in your terminal emulator program once you power up your develpment target board is the Lab 1 Preloader and the bare metal application that you just built and programmed onto the SD card recycling about every second. Why is this happening? The default settings for the Lab 1 Preloader are configured for the Preloader to load the next software stage from the 0xA2 partition at offset 0x40000. Now that you have programmed a valid uImage file into that location with "fft.bin.img" the Lab 1 Preloader loads that program and runs it. Now the default Lab 1 Preloader settings also instruct the Preloader to leave the watchdog timer active as it jumps into the next software stage. Our bare metal application does nothing to interact with the watchdog timer. Eventually the system watchdog timer expires and produces a warm reset event to the HPS system and the whole process repeats.

Look a little closer at what's happening and you'll notice that the bare metal application is not really running to completion either. It prints out the "Hello World" statements and then stops just as it begins writing the FFT input samples into the FPGA. Why does this happen? Well the preloader has done nothing to program the FPGA fabric, nor take the HPS to FPGA bridges out of reset, and our bare metal application does not do any of this either. So when our bare metal application begins to interact with the FPGA it fails. At a hardware level this interaction causes data abort responses back to the processor which generates data abort exceptions for which our bare metal application has no handlers, so it basically crashes into a while(1) infinite loop.

So we have successfully demonstrated that we can start our bare metal application directly from the boot flash using the Preloader, but we have also illustrated some of the other details that we would need to consider and deal with if we were actually interested in deploying a viable program like this.

Restoring the original Preloader.

home Jump to Workshop Main Page | go_start Return to Lab Intro | go_back Return to Lab 2: Verifying Hardware with System Console | printtopic Show All Steps

Let's restore the SD card to it's previous state.
  1. Power off and remove the SD card from your development target.

  2. Insert the SD card back into your development host.

  3. Mount the FAT partition from the SD card.

  4. Locate the original Preloader image "preloader-mkpimage.bin" in your board specific directory.
    (Here we use the DE0_NANO_SOC as an example)
    ws1 lab1 orig preloader.png

  5. Now program the original Preloader back onto your SD card using one of the methods that was described above when proramming the new Preloader to the SD card.
    (Something like this)
    [BareMetal_fft]$ alt-boot-disk-util -p <path-to-original-Preloader>/preloader-mkpimage.bin -a write /dev/sdb
    Altera Boot Disk Utility
    Copyright (C) 1991-2014 Altera Corporation
    
    Altera Boot Disk Utility was successful.
    [BareMetal_fft]$

  6. After reprogramming the original Preloader, remove the SD card from your development host machine, plug it back into your development target and verify that your target boots all the way into linux again.

go_forward Proceed to Lab 4: Linux FFT Application

© 1999-2024 RocketBoards.org by the contributing authors. All material on this collaboration platform is the property of the contributing authors.

Privacy Policy - Terms Of Use

This website is using cookies. More info. That's Fine