Here's a work-in-progress Apptainer .def file.
It successfully:
- executes in sw/examples: "make all"
- runs the individual simulations (ghdl is included)
- generates PDF and HTML.
It has lots of debug comments in it, etc. But I wanted to provide it to you in case you found it useful.
I also do NOT claim expertise with Containers, so there may be some significant refactoring that should be done.
But, it does have the advantage of working :)
Bootstrap: docker
From: ubuntu:22.04
################################################################################
# map from system to container during build process
%files
# . /opt
# /path/on/host/data.txt /opt/data.txt
# /path/on/host/config_folder /etc/config
################################################################################
%setup
################################################################################
%post
# create dummy file to get rid of warning about it not existing
touch /etc/localtime
apt-get update
# RESOLVE CLOCK WARNING
apt-get install -y locales
locale-gen en_US.UTF-8
#
# Tools required to compile C
#
apt-get install -y dialog
apt-get install -y make
apt-get install -y less
apt-get install -y vim-gtk3
apt-get install -y build-essential
apt-get install -y apt-utils
apt-get install -y wget
#
# So docs dir can be processed: doxygen, pdf
#
apt-get install -y asciidoctor
apt-get install -y doxygen
apt-get install -y graphicsmagick libgraphicsmagick1-dev
#
# GHDL simulator
#
apt-get install -y ghdl gtkwave
# install python so pip can be used to install wavedrom.
apt-get install -y python3 python3-pip
apt-get update
apt upgrade -y
#######################################
# install ascii doctor. Has to be done
# within ruby.
gem install asciidoctor-diagram
gem install asciidoctor-pdf
#######################################
# install wavedrom, which should be installed in /opt
# requires python
# pip install wavedrom
# at this point, in directory "/opt".
####################
# download and install nvm.
# -q0- : cant find docs on it.
# downloads and runs install.sh
# side effects:
# RHEL9:
# creates ~/.nvm
# creates ~/.wget-hsts
#
rm -rf /root/.nvm
rm -rf /root/.wget-hsts
# setting PROFILE is supposed to cause nvm to not modify your ~/.bashrc, but
# didnt have any effect.
# export PROFILE=/opt/nvm_profile.sh
# in reviewing the nvm_install.sh, it looks like
# XDG_CONFIG_HOME sets alternate to # ~/.nvm
# NVM_DIR replace whatever XDG_CONFIG_HOME or ~/.nvm are set to
#
export NVM_DIR=/opt/nvm_install_dir
mkdir -p $NVM_DIR
# --hsts-file=/path/to/your/custom_hsts_file
wget -qO- --hsts-file=/opt/wget-hsts https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash > nvm_install.txt
# wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh > nvm_install.sh
# nvm gets installed in /root/.nvm
# nvm source is in /root/.bashrc
export NVM_DIR="/opt/nvm_install_dir"
ls -la $NVM_DIR
#
# load the nvm scripts so it is on the search path
#
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
####################
# install node version 20.x
# instructions from : https://linuxize.com/post/how-to-install-node-js-on-ubuntu-22-04/
# installs node version 24.15.0 (as of May 11,2026)
nvm install --lts
nvm use --lts
# returns 24.15.0
node --version
####################
# npm is used to install xpm.
# check npm version; returns 11.12.1
npm --version
#
####################
# install xpm, which is used to install riscv compiler
npm install --location=global xpm@latest
#
# report xpm version (0.23.2 or higher)
#
xpm --version
####################
# install riscv compiler
#
# installs into n3_dir/root/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/15.2.0-1.1/.content/bin/riscv-none-elf-gcc
# use XPACKS_STORE_FOLDER to change location
xpm install --global @xpack-dev-tools/riscv-none-elf-gcc@15.2.0-1.1 > gcc_install.log
################################################################################
%environment
export NVM_DIR="/opt/n3_dir/opt/nvm_install_dir"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
export GCC_ROOT="/opt/n3_dir/root/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/15.2.0-1.1/.content"
export PATH="$GCC_ROOT/bin:$PATH"
export C_INCLUDE_PATH=$GCC_ROOT/lib/gcc/riscv-none-elf/15.2.0/include:$C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=$GCC_ROOT/lib/gcc/riscv-none-elf/15.2.0/include:$CPLUS_INCLUDE_PATH
export NEORV32_HOME=/opt/neorv32
echo "ENV_REPORT_START"
echo $PATH
echo $GCC_ROOT
echo $NEORV32_HOME
echo "ENV_REPORT_END"
################################################################################
%runscript
echo "RUNSCRIPT_START"
echo $PATH
echo $GCC_ROOT
echo $NEORV32_HOME
echo "RUNSCRIPT_UPDATE_PATH"
export NVM_DIR="/opt/n3_dir/opt/nvm_install_dir"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
# export GCC_ROOT="/opt/n3_dir/root/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/15.2.0-1.1/.content"
export GCC_ROOT="/root/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/15.2.0-1.1/.content"
export PATH="$GCC_ROOT/bin:$PATH"
export C_INCLUDE_PATH=$GCC_ROOT/lib/gcc/riscv-none-elf/15.2.0/include:$C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=$GCC_ROOT/lib/gcc/riscv-none-elf/15.2.0/include:$CPLUS_INCLUDE_PATH
# PATH returns:
# /opt/n3_dir/root/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/15.2.0-1.1/.content/bin
# /opt/n3_dir/root/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/15.2.0-1.1/.content/bin
# /usr/local/sbin
# /usr/local/bin
# /usr/sbin
# /usr/bin
# /sbin
# /bin
export NEORV32_HOME=/opt/neorv32
echo "PATH = " $PATH
echo "GCC_ROOT = " $GCC_ROOT
echo "C_INCLUDE_PATH = " $C_INCLUDE_PATH
echo "CPLUS_INCLUDE_PATH = " $CPLUS_INCLUDE_PATH
echo "GCC_LOCATIONS_START"
# cd /
# find . -name riscv-none-elf-gcc
/root/.local/xPacks/@xpack-dev-tools/riscv-none-elf-gcc/15.2.0-1.1/.content/bin/riscv-none-elf-gcc --version
echo "GCC_LOCATIONS_FINISH"
#
# add GCC to search path
# test search path
#
echo "RUNSCRIPT_GCC_PATH"
which riscv-none-elf-gcc
echo "RUNSCRIPT_GCC_VERSION"
riscv-none-elf-gcc --version
echo "RUNSCRIPT_FINISH"
#########################################
echo "===== SLASH"
ls /
echo "===== OPT"
ls /opt
echo "===== ROOT"
ls -a /root
echo "===== HOME"
cd ~
echo "HOME_DIR IS"
pwd
ls -a
# install ascii doctor. Has to be done
# within ruby.
# INSTALL: /usr/bin/asciidoctor
gem install asciidoctor-diagram
#
# install wavedrom, which should be installed in /opt
# install ascii doctor. Has to be done
# within ruby.
gem install asciidoctor-diagram
#
# install wavedrom, which should be installed in /opt
pip install wavedrom
pip show wavedrom
# INSTALL: /usr/local/lib/python3.10/dist-packages
# INSTALL: /usr/local/bin/wavedrompy
########################################
npm install -g wavedrom-cli
# INSTALL: /opt/n3_dir/opt/nvm_install_dir/versions/node/v24.16.0/bin/wavedrom-cli
#######################################
# generate documentation
#######################################
#
cd /opt/neorv32/docs
make
make html > /opt/doc_html_make.log 2>&1
make pdf > /opt/doc_pdf_make.log 2>&1
#######################################
# check toolchain
#######################################
cd /opt/neorv32/sw/example
make check > /opt/sw_example_check.log 2>&1
#
#######################################
# generate compiled VHDL code for each example
#######################################
#
cd /opt/neorv32/sw/example
make all > /opt/sw_example_all.log 2>&1
#
# simulation in GHDL runs really slow, because they use real UART
# so the commented-out line should be copy/pasted and run interactively.
#
# for demo_gpio, the UART transmits:
### ../sim/sim_uart_rx.vhd:67:13:@637135ns:(report note): tb.uart0_rx: (13)
### ../sim/sim_uart_rx.vhd:67:13:@1210095ns:(report note): tb.uart0_rx: (10)
### ../sim/sim_uart_rx.vhd:69:13:@1783055ns:(report note): tb.uart0_rx: <
### ../sim/sim_uart_rx.vhd:69:13:@2356015ns:(report note): tb.uart0_rx: <
### ../sim/sim_uart_rx.vhd:69:13:@2928975ns:(report note): tb.uart0_rx:
### ../sim/sim_uart_rx.vhd:69:13:@3501935ns:(report note): tb.uart0_rx: N
### ../sim/sim_uart_rx.vhd:69:13:@4074895ns:(report note): tb.uart0_rx: E
### ../sim/sim_uart_rx.vhd:69:13:@4647855ns:(report note): tb.uart0_rx: O
### ../sim/sim_uart_rx.vhd:69:13:@5220815ns:(report note): tb.uart0_rx: R
### ../sim/sim_uart_rx.vhd:69:13:@5793775ns:(report note): tb.uart0_rx: V
### ../sim/sim_uart_rx.vhd:69:13:@6366735ns:(report note): tb.uart0_rx: 3
### ../sim/sim_uart_rx.vhd:69:13:@6939695ns:(report note): tb.uart0_rx: 2
### ../sim/sim_uart_rx.vhd:69:13:@7512655ns:(report note): tb.uart0_rx:
### ../sim/sim_uart_rx.vhd:69:13:@8085615ns:(report note): tb.uart0_rx: G
### ../sim/sim_uart_rx.vhd:69:13:@8658575ns:(report note): tb.uart0_rx: P
### ../sim/sim_uart_rx.vhd:69:13:@9231535ns:(report note): tb.uart0_rx: I
### ../sim/sim_uart_rx.vhd:69:13:@9804495ns:(report note): tb.uart0_rx: O
### /usr/bin/ghdl-mcode:info: simulation stopped by --stop-time @10ms
# cd /opt/neorv32/sw/example/demo_gpio
# make sim > /opt/sw_example_demo_gpio.log 2>&1
########################################
# run default hello_world example
if false; then
cd /opt/neorv32/sw/example/hello_world
make USER_FLAGS+=-DUART0_SIM_MODE clean install sim > /opt/sw_example_hello_world.log 2>&1
fi
if false; then
cd /opt/neorv32/sw/example/demo_spi
make USER_FLAGS+=-DUART0_SIM_MODE clean install sim > /opt/sw_example_demo_spi.log 2>&1
fi
# cd to /opt, so always finish in a predictable location
cd /opt
Here's a work-in-progress Apptainer .def file.
It successfully:
It has lots of debug comments in it, etc. But I wanted to provide it to you in case you found it useful.
I also do NOT claim expertise with Containers, so there may be some significant refactoring that should be done.
But, it does have the advantage of working :)