2. eXtending software
If X-HEEP is vendorized into your project, you can still have software source files in any directory of your top-level repository and build them using X-HEEP’s compilation flow.
Repository structure
The following is an example repository folder structure focusing on extending the software flow.
BASE
├── sw
│ ├── applications
│ │ └── your_app
│ │ ├── main.c
│ │ ├── your_app.c
│ │ ├── your_app.h
│ │ └── ...
│ ├── build -> ../hw/vendor/esl_epfl_x_heep/sw/build
│ ├── device -> ../hw/vendor/esl_epfl_x_heep/sw/device
│ ├── linker -> ../hw/vendor/esl_epfl_x_heep/sw/linker
│ └── external
│ ├── drivers
│ │ └── your_copro
│ │ ├── your_copro.c
│ │ ├── your_copro.h
│ │ └── your_copro_defs.h -> ../../../../hw/vendor/your_copro/sw/your_copro_defs.h
│ └── extensions
│ │ └── your_copro_x_heep.h
│ └── lib
│ └── crt
│ └── external_crt0.S
├── hw
│ └── vendor
│ ├── your_copro
│ ├── esl_epfl_x_heep.vendor.hjson
│ └── esl_epfl_x_heep
│ ├── hw
│ ├── sw
│ │ ├── applications
│ │ ├── build
│ │ ├── device
│ │ └── ...
│ ├── Makefile
│ ├── external.mk
│ └── ...
├── Makefile
├── util
│ └── vendor.py
└── ...
Where BASE is your repository’s base directory, esl_epfl_x_heep is the vendorized X-HEEP repository and your_app is the name of the application you intend to build.
The /sw/ folder
The BASE/sw/ folder must comply with X-HEEP’s repository structure and therefore include an applications, build, device and linker folder.
It is not compulsory for it to be on the BASE directory, although this is the default structure that X-HEEP’s Makefiles will assume if no other path is specified through the SOURCE variable. If you plan to store source files in a different location that the one proposed, just call make making the SOURCE path explicit.
make app PROJECT=your_app SOURCE=<path_to_your_sw_relative_to_x_heep_sw>
Consider that, inside this sw folder, the same structure than the one proposed is required.
Inside the applications folder different projects can be stored (still respecting the name_of_project/main.c structure of X-HEEP).
The build, device and linker should be linked with the vendorized folders inside X-HEEP.
In this example that is done from the BASE directory as follows:
ln -s ../hw/vendor/esl_epfl_x_heep/sw/build sw/build
ln -s ../hw/vendor/esl_epfl_x_heep/sw/device sw/device
ln -s ../hw/vendor/esl_epfl_x_heep/sw/linker sw/linker
The /sw/applications folder
Inside the sw/applications/ folder you may have different applications that can be built separately. Each application is a directory named after your application, containing one and only one main.c file which is built during the compilation process. The folder can contain other source or header files (of any name but main.c).
The /sw/external folder
In the (optional) external folder you can add whatever is necessary for software to work with your coprocessor/accelerator. This might include:
Sources and header files.
Soft links to folders or files.
A
lib/crt/directory with anexteral_crt0.Sfile (will be included insideBASE/sw/device/lib/crt/crt0.S).
The external folder or any of its subdirectories cannot contain neither a device nor an applications folder as it would collide with the respective folders inside BASE/sw/. It should also not contain a main.c file.
Embedding binary data into X-HEEP firmware
The util/c_gen.py script is a versatile utility for converting binary files and NumPy arrays into C header files. This is particularly useful for embedding data directly into the X-HEEP firmware, such as the Firmware for your programmable accelerator or some Python-generated golden results for your application.
The utility can be used in two ways: as a straightforward command-line tool for simple conversions, or as a Python module for more complex and customized header generation.
It can add a zero-filled region as a prefix or a suffix to the binary code. This could be useful to reserve memory space like a stack or runtime parameter for an accelerator.
Command-Line Usage
For quick conversion of a single binary file (e.g., a compiled firmware blob), the command-line interface is ideal.
python util/c_gen.py <header_file> <bin_file> [--prefix-pad <bytes>] [--prefix-pad=<bytes>] [--suffix-pad <bytes>] [--suffix-pad=<bytes>] [--static] [--attribute <attr>] [--attribute=<attr>] [<src_file> ...]
Argument/Option |
Description |
|---|---|
|
Output header path (for example, |
|
Input binary file to be converted. |
|
Number of zero bytes prepended before the binary payload. |
|
Number of zero bytes appended after the binary payload. |
|
Emits arrays with |
|
Adds a C attribute to generated arrays (repeatable). |
|
Optional C/C++ source files. Every line starting with |
--prefix-pad and --suffix-pad accept decimal or base-prefixed values (for example, 32, 0x20) and must be non-negative.
Example:
python c_gen.py sw/external/lib/driver/accel/firmware.h whatever/firmware.bin \
--prefix-pad 16 \
--suffix-pad 16 \
--static \
--attribute 'section(".data_interleaved")' \
whatever/main.c
This generates a header with:
FIRMWARE_SIZEaccounting for explicit prefix/suffix padding and 32-bit alignment.A
static uint32_t __attribute__((section(".data_interleaved"))) firmware[]array.Any
#definedirectives found infirmware_defs.c.
Programmatic Usage (as a Python Module)
For more advanced use cases, you can import the CFileGen class into your Python scripts. This approach is highly recommended for test-bench generation and complex data embedding, as it provides much greater flexibility:
Multiple data sources: add binaries, input/output matrices, and code arrays into one header.
Binary padding:
add_binary(name, file, prefix_pad=..., suffix_pad=...)to reserve bytes around firmware blobs.Storage class control:
set_storage_class("static")(or another class string) for all generated arrays.Custom attributes:
add_attribute(...)to attach GCC/Clang attributes to all arrays.Macro generation: use
add_macro,add_macro_hex,add_macro_raw, oradd_macros_from_source.NumPy conversion: converts
int8/int16/int32anduint8/uint16/uint32arrays to C arrays with hexadecimal values.Automatic size macros: emits
_SIZE,_ROWS, and_COLSfor input/output matrices.
Example:
import numpy as np
from c_gen import CFileGen
test_vectors = np.array([
[10, -20, 30],
[40, -50, 60],
[70, 80, -90],
], dtype=np.int16)
header_gen = CFileGen()
header_gen.set_storage_class("static")
header_gen.add_attribute('section(".data_interleaved")')
header_gen.add_macro("accel_test_id", 7, "Regression ID")
header_gen.add_binary("accelerator_fw", "build/accelerator.bin", prefix_pad=16, suffix_pad=16)
header_gen.add_input_matrix("input_vectors", test_vectors)
header_gen.write_header("generated_tests", "accelerator_test.h")
Excerpt of generated header:
#ifndef ACCELERATOR_TEST_H_
#define ACCELERATOR_TEST_H_
#include <stdint.h>
// Macros
// ------
#define ACCEL_TEST_ID 7 // Regression ID
// Binary size
// -----------
#define ACCELERATOR_FW_SIZE 4128
// Input matrix size
#define INPUT_VECTORS_SIZE 18
#define INPUT_VECTORS_ROWS 3
#define INPUT_VECTORS_COLS 3
// Binary files
// ------------
static uint32_t __attribute__((section(".data_interleaved"))) accelerator_fw[] = {
0x00000000,
// ...
};
// Input matrices
// --------------
static int16_t __attribute__((section(".data_interleaved"))) input_vectors [] = {
0x000a, 0xffec, 0x001e,
0x0028, 0xffce, 0x003c,
0x0046, 0x0050, 0xffa6
};
#endif // ACCELERATOR_TEST_H_
The BASE/Makefile
The BASE/Makefile is your own custom Makefile. You can use it as a bridge to access the Makefile from X-HEEP. To do so, it MUST include the external.mk AFTER all your custom rules.
Example of BASE/Makefile
MAKE = make
.PHONY: custom
custom:
@echo Nothing is executed from X-HEEP, as custom is not a target inside X-HEEP.
app:
@echo This target will do something and then call the one inside X-HEEP.
$(MAKE) -f $(XHEEP_MAKE) $(MAKECMDGOALS) PROJECT=hello_world SOURCE=.
verilator-build:
@echo You will not access the verilator-build target from X-HEEP.
export HEEP_DIR = hw/vendor/esl_epfl_x_heep/
XHEEP_MAKE = $(HEEP_DIR)/external.mk
include $(XHEEP_MAKE)
The
customrule will not use the X-HEEP Makefile in any way. Make the target a prerequisite of.PHONYto prevent X-HEEP’s Makefile from attempting to run a non-existent target.The
apprule will perform actions before calling X-HEEP Makefile’sapprule. In this case, the project and where the source files are to be extracted from is being specified. TheSOURCE=.argument will set X-HEEP’s ownsw/folder as the directory from which to fetch source files. This is an example of building inner sources from an external directory.The
verilator-buildrule will override the X-HEEP Makefile’s one.Any other target will be passed straight to X-HEEP’s Makefile. For example
make mcu-gen CPU=cv32e40px
If you plan to vendorize X-HEEP in a different directory than the one proposed, just update:
export HEEP_DIR = <path_to_x_heep_relative_to_this_directory>
Extending the mcu-gen configuration
In your own Makefile, you can also extend the mcu-gen target to generate a custom MCU configuration for your application, including any .tpl files you might want to add. To do so, you can add an mcu-gen target in your Makefile like the following:
Example of Makefile extending mcu-gen
# Global configuration
ROOT_DIR := $(realpath .)
# X-HEEP configuration
XHEEP_DIR := $(ROOT_DIR)/hw/vendor/x-heep
X_HEEP_CFG := $(ROOT_DIR)/config/python_unsupported.hjson
PYTHON_CHEEP_CFG := $(ROOT_DIR)/config/cheep_configs.py
PAD_CFG ?= $(ROOT_DIR)/config/cheep_pads.py
# CHEEP templated files
# Collects all .tpl files in the project excluding certain directories
CHEEP_GEN_TPLS := $(shell find . \( -path './hw/vendor' -o -path './sw/vendor' -o -path './sw/linker' \) -prune -o -name '*.tpl' -print)
# Prefix the paths of the .tpl files with the relative path to X-HEEP to make them accessible from the mcu-gen target in X-HEEP's Makefile
# The HEEP_REL_PATH is defined in the `external.mk` makefile
EXTERNAL_MCU_GEN_TEMPLATES = $(addprefix $(HEEP_REL_PATH)/,$(CHEEP_GEN_TPLS))
## Generate X-HEEP MCU system files
.PHONY: mcu-gen
mcu-gen:
$(MAKE) -f $(XHEEP_MAKE) mcu-gen \
X_HEEP_CFG=$(X_HEEP_CFG) \
PYTHON_X_HEEP_CFG=$(PYTHON_CHEEP_CFG) \
PADS_CFG=$(PAD_CFG) \
EXTERNAL_DOMAINS=$(EXTERNAL_DOMAINS) \
EXTERNAL_MCU_GEN_TEMPLATES="$(EXTERNAL_MCU_GEN_TEMPLATES)"
@echo "✅ DONE! X-HEEP MCU and CHEEP generated successfully"
Excluding files from compilation
If you have files that need to be excluded from the GCC compilation flow, you can add them to a directory containing the keyword exclude, and/or rename the file to include the keyword exclude.
In the following example, the files marked with ✅ will be compiled, and the ones marked with ❌ will not.
BASE
├── sw
│ ├── applications
│ │ └── your_app
│ │ ├── ✅ main.c
│ │ ├── ✅ your_app.c
│ │ ├── your_app.h
│ │ ├── ❌ my_kernel_exclude.c
│ │ ├── my_kernel.h
│ │ └── exclude_files
│ │ └── ❌ kernel_asm.S
Makefile help
If you want that the commands make or make help show the help for your external Makefile, add the following lines before the first include directive or target.
Addition to print the target's help
# HEEP_DIR might already be defined, you may want to move it to the top
export HEEP_DIR = hw/vendor/esl_epfl_x_heep/
# Get the path of this Makefile to pass to the Makefile help generator
MKFILE_PATH = $(shell dirname "$(realpath $(firstword $(MAKEFILE_LIST)))")
export FILE_FOR_HELP = $(MKFILE_PATH)/Makefile
## Call the help generator. Calling simply
## $ make
## or
## $ make help
## Will print the help of this project.
## With the parameter WHICH you can select to print
## either the help of X-HEEP (WHICH=xheep)
## or both this project's and X-HEEP's (WHICH=all)
help:
ifndef WHICH
${HEEP_DIR}/util/MakefileHelp
else ifeq ($(filter $(WHICH),xheep x-heep),)
${HEEP_DIR}/util/MakefileHelp
$(MAKE) -C $(HEEP_DIR) help
else
$(MAKE) -C $(HEEP_DIR) help
endif
Remember to add double hashes
##on any comment you want printed on the help. Use## @section SectionNameto divide the documentation in sections