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:
Open Vivado: Start a new project for the ARTY A7 (XC7A35TICSG324-1).
Create a Block Design:
In Vivado, go to “Create Block Design” and add the PicoRV32 core (import its Verilog files as an IP).
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
).
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.
Connect to Softcore:
Connect the memory IP’s address, data, and control lines to PicoRV32’s memory interface using Vivado’s auto-connect feature.
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:
Clone PicoRV32:
git clone https://github.com/cliffordwolf/picorv32 cd picorv32/firmware
Replace Firmware:
Copy
hello.hex
to thefirmware
directory, replacing the default firmware file (e.g.,firmware.hex
).
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
).
Synthesize:
Use the provided Vivado project or TCL script (e.g.,
make synth
in PicoRV32) to build the bitstream.
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
orhello.bin
via UART or another interface, then jumps to the program’s entry point.
Example Bootloaders:
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.
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:
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
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
or0x80000000
) 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:
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.
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
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