Compilation Pipeline

How to run a program on a FPGA

To incorporate a hex file into an HDL design for an open-source RISC-V softcore processor on an FPGA like the ARTY A7 without writing custom HDL code, and to convert a hex file into a format suitable for HDL integration, you can leverage existing tools, scripts, and pre-built infrastructure provided by FPGA toolchains or open-source projects. Below, I’ll outline the ways to achieve this with minimal manual HDL coding, focusing on automated or semi-automated methods to incorporate the hex file and convert it for HDL use. I’ll also revisit the "Hello World" program context to ensure clarity, assuming you’re using an open-source RISC-V softcore like PicoRV32.


Context Recap: Hello World Program

You’re working with a C program like this:

#include <stdio.h>
int main() {
    printf("Hello, World!\n");
    while (1);
    return 0;
}

This is compiled into an ELF file (hello.elf) using the RISC-V toolchain, then converted to a Verilog-compatible hex file (hello.hex) with:

riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostartfiles -T linker.ld -o hello.elf hello.c
riscv64-unknown-elf-objcopy -O verilog hello.elf hello.hex

The goal is to incorporate hello.hex into the RISC-V softcore’s memory without writing custom HDL and to understand how to convert the hex file for HDL use.


Ways to Incorporate the Hex File Without Writing HDL

The following methods allow you to integrate the hex file into your RISC-V softcore’s memory with minimal or no manual HDL coding, leveraging existing tools or frameworks:

1. Using FPGA Vendor Tools (Xilinx Vivado IP Integrator)

Xilinx Vivado, the primary tool for the ARTY A7, provides graphical interfaces and IP cores that can automate memory initialization without writing Verilog or VHDL.

  • How It Works:

    • Vivado’s IP Integrator lets you create a block design with a pre-configured memory IP (e.g., Block Memory Generator) that can be initialized with a hex file.

    • You integrate the RISC-V softcore (e.g., PicoRV32) with this memory IP, and Vivado handles the memory interface.

  • Steps:

    1. Open Vivado: Start a new project for the ARTY A7 (XC7A35TICSG324-1).

    2. Create a Block Design:

      • In Vivado, go to “Create Block Design” and add the PicoRV32 core (import its Verilog files as an IP).

    3. Add Block Memory Generator:

      • From the IP Catalog, add the “Block Memory Generator” IP.

      • Configure it as a single-port or dual-port RAM, sized to fit your program (e.g., 4KB for hello.hex).

    4. Initialize with Hex File:

      • In the Block Memory Generator settings, select “Load Init File” and choose hello.hex.

      • Vivado converts the hex file into a .coe (Coefficient) or .mif (Memory Initialization File) format internally.

    5. Connect to Softcore:

      • Connect the memory IP’s address, data, and control lines to PicoRV32’s memory interface using Vivado’s auto-connect feature.

    6. Synthesize and Program:

      • Generate the bitstream and program the ARTY A7 via USB.

      • The FPGA loads the softcore with hello.hex in memory, ready to execute.

  • Advantages:

    • No manual HDL coding required.

    • Vivado’s GUI simplifies integration.

    • Supports both block RAM and external DDR3 (with additional IP like MIG).

  • Tools Needed:

    • Xilinx Vivado (free WebPACK edition for ARTY A7).

    • Pre-compiled hello.hex.

  • Execution:

    • On FPGA power-up, PicoRV32 fetches instructions from the initialized block RAM, runs the program, and outputs “Hello, World!” via UART (assuming UART is configured).

2. Using Open-Source RISC-V Project Templates

Many open-source RISC-V softcore projects (e.g., PicoRV32, VexRiscv) include scripts or templates that automate hex file integration.

  • How It Works:

    • Projects like PicoRV32 provide example designs with pre-built memory modules and scripts to load hex files.

    • These scripts convert the hex file and integrate it into the HDL during synthesis.

  • Example: PicoRV32:

    • The PicoRV32 repository includes a firmware directory with a Makefile that automates hex file integration.

    • Steps:

      1. Clone PicoRV32:

        git clone https://github.com/cliffordwolf/picorv32
        cd picorv32/firmware
      2. Replace Firmware:

        • Copy hello.hex to the firmware directory, replacing the default firmware file (e.g., firmware.hex).

      3. Run the Makefile:

        • The Makefile typically includes rules to incorporate firmware.hex into the Verilog memory module.

        • Example command:

          make firmware.hex
        • This updates the memory initialization in the HDL (e.g., via $readmemh).

      4. Synthesize:

        • Use the provided Vivado project or TCL script (e.g., make synth in PicoRV32) to build the bitstream.

      5. Program the FPGA:

        • Load the bitstream onto the ARTY A7.

    • Outcome: The softcore runs hello.hex, printing “Hello, World!” to UART.

  • Advantages:

    • Leverages pre-existing project infrastructure.

    • Minimal configuration needed.

    • Community-tested for ARTY A7.

  • Tools Needed:

    • PicoRV32 repository.

    • Vivado.

    • RISC-V toolchain for generating hello.hex.

3. Using Pre-Built Bootloaders

Instead of embedding the hex file, you can use an existing bootloader to load the program at runtime, avoiding HDL modifications entirely.

  • How It Works:

    • A bootloader (e.g., from SiFive Freedom E SDK) is pre-embedded in the softcore’s memory.

    • It loads hello.elf or hello.bin via UART or another interface, then jumps to the program’s entry point.

  • Example Bootloaders:

    1. SiFive Freedom E SDK Bootloader:

      • Source: GitHub (sifive/freedom-e-sdk).

      • Steps:

        • Clone the SDK: git clone https://github.com/sifive/freedom-e-sdk.

        • Select a bootloader example (e.g., software/bootloader).

        • Compile the bootloader for RV32I:

          make PROGRAM=bootloader BOARD=freedom-e310-arty
        • This generates bootloader.hex, which you embed in the HDL using Vivado’s Block Memory Generator (as above).

        • Program the FPGA with the bitstream containing the bootloader.

        • Send hello.elf over UART:

          riscv64-unknown-elf-objcopy -O binary hello.elf hello.bin
          cat hello.bin > /dev/ttyUSB0
        • The bootloader loads the program into DDR3 (e.g., 0x80000000) and executes it.

    2. OpenSBI:

      • Source: GitHub (riscv-software-src/opensbi).

      • Use: A lightweight firmware for RISC-V that can act as a bootloader.

      • Steps:

        • Build OpenSBI for your softcore (e.g., PLATFORM=generic).

        • Embed the resulting fw_jump.bin into the HDL memory.

        • Load hello.elf via UART or JTAG using OpenSBI’s mechanisms.

  • Advantages:

    • No need to modify HDL after embedding the bootloader.

    • Flexible for updating programs without re-synthesis.

  • Tools Needed:

    • SiFive SDK or OpenSBI.

    • Serial terminal (e.g., minicom).

    • RISC-V toolchain.

  • Execution:

    • The bootloader runs on FPGA reset, loads hello.bin, and the softcore outputs “Hello, World!” via UART.

4. Using OpenOCD for Direct Loading

OpenOCD can load the ELF file directly into memory via JTAG, bypassing HDL modifications.

  • How It Works:

    • OpenOCD communicates with the RISC-V softcore over JTAG, writing hello.elf to memory and setting the program counter.

  • Steps:

    1. Set Up OpenOCD:

      • Create a configuration file (arty_a7.cfg):

        interface ftdi
        ftdi_device_desc "Digilent Adept USB Device"
        ftdi_vid_pid 0x0403 0x6010
        adapter_khz 10000
        transport select jtag
        set _CHIPNAME riscv
        jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x0e20a0dd
        target create $_CHIPNAME.cpu riscv -chain-position $_CHIPNAME
    2. Load the Program:

      openocd -f arty_a7.cfg -c "init; reset halt; load_image hello.elf; resume; exit"
      • This loads hello.elf into memory (e.g., 0x00000000 or 0x80000000) and starts execution.

  • Advantages:

    • No HDL changes needed.

    • Ideal for development and testing.

  • Execution:

    • The softcore runs the program, outputting “Hello, World!” to UART.


Converting Hex to HDL-Compatible Formats

The hex file (hello.hex) is already in a Verilog-compatible format after objcopy -O verilog, but some tools require other formats for HDL integration. Here’s how to convert it:

  1. To COE (Coefficient) File for Vivado:

    • Vivado’s Block Memory Generator often uses .coe files.

    • Use a script to convert hello.hex to .coe:

      # hex2coe.py
      with open("hello.hex", "r") as hex_file, open("hello.coe", "w") as coe_file:
          coe_file.write("memory_initialization_radix=16;\nmemory_initialization_vector=\n")
          data = [line.strip() for line in hex_file if line.strip()]
          coe_file.write(",\n".join(data) + ";\n")
    • Run: python hex2coe.py.

    • Load hello.coe in Vivado’s Block Memory Generator.

  2. To MIF (Memory Initialization File):

    • Some FPGA tools (e.g., Intel Quartus) use .mif files.

    • Use a tool like srec_cat (from SRecord package):

      srec_cat hello.hex -verilog -o hello.mif -mif
  3. Direct Use in Verilog:

    • hello.hex is already usable with $readmemh in Verilog, as shown earlier.

    • No conversion is needed if your softcore’s HDL uses $readmemh.


Execution and Result

  • Using Vivado or PicoRV32 Template:

    • The hex file is embedded in block RAM during synthesis.

    • On FPGA power-up, PicoRV32 runs hello.elf, outputting “Hello, World!” to a UART terminal (e.g., minicom at /dev/ttyUSB0, 115200 baud).

  • Using Bootloader:

    • The bootloader (e.g., SiFive’s) loads hello.bin via UART, then jumps to it.

    • Result: “Hello, World!” in the terminal.

  • Using OpenOCD:

    • OpenOCD loads hello.elf directly, and the program runs, showing the same output.

Terminal Output:

Hello, World!

Last updated