The Build System
Gundolf Kiefer, 2025
**************************************************
This chapter is under construction!
**************************************************
Overview
The PicoNut build system is based on GNU make and provides the following features:
support for hardware and (RISC-V) software
support for hardware simulation and synthesis
support for alternative synthesis toolchains
support for internal builds and externally built systems by means of a build context
support for automated verification, CI/CD
support for integrated documentation (
helptarget)modular, only necessary parts rebuilt
highly parallel operation, dependency-driven for fast build cycles
prepared for automatic rule checking, e.g. to identify installation conflicts, missing verify targets
Improvements over the previous build system (in use until 09/2025) include:
Support for external modules and fast builds using prebuilt libraries
Cleaned up directory structure
Avoidance of unnecessary dependencies and duplicated code
Naming conventions: Prefix PN_/pn_ for anything related to PicoNut
fewer make targets
fewer make variables to learn, explicit rules
brief outputs
fewer .mk files
configuration: fewer & clearly documented locations
Sanity: Explicit check for anything leading to non-obvious errors
General Concepts
Modules
The PicoNut project is divided into modules. A module is a unit with its own Makefile that can be built by itself.
A module may be internal as part of the PicoNut repository or external as part of a user project which uses PicoNut. External modules are not part of the PicoNut project.
Each module is identified by a unique identifier, which is available as a variable PN_MODULE_ID in any PicoNut Makfile. For internal modules, the ID is equal to the relative path inside the PicoNut source tree (PN_SOURCE_DIR). For external modules, the ID may be provided by the user or be set automatically to the absolute path inside the user’s file system (without a leading “/”).
The term module name (variable PN_MODULE_NAME) reflects the last component of the module ID, which is also the exact name of the containing directory.
Modules may have submodules. These are modules with their own Makefile. Submodules may be subcomponents (e.g. the ALU of a CPU), alternatives (e.g. Nucleus variants as submodules as in hw/cpu/nucleus or hw/cpu/membrana ) or have any other meaning at the responsibilty of the base module developer. In any case, submodules are reflected by sudirectories with the same name as the respective submodule.
Subdirectories can also be freely used at the responsibilty of the module developer. It is not required that subdirectories represent submodules.
Target Technologies (Techs)
Hardware modules may be built for simulation or for synthesis for a certain FPGA or ASIC technology. These different general targets are referred to as target technologies or - shortened - techs (build argument TECHS). Presently, the following techs are defined:
sim: Simulationsyn: Synthesis for various physical (FPGA) technologies
In the future, more techs may be added.
Directories
The build system operates relies on a clearly defined directory structure and trees as defined by the following subsections.
The Source Tree (PN_SOURCE_DIR)
This is the directory tree obtained after cloning the PicoNut repository, and it contains all source files.
The source tree is to be considered read-only, in particular, no build artefacts are allowed to be written to it. Exceptions must have a good reason and must be documented. Otherwise, writing into the source tree is considered to be a bug.
The subdirectory build is reserved, since some users may want to use $(PN_SOURCE_DIR)/build as the build tree directory (see below).
Note: The presence of a source tree is optional. In particular, externally built systems may use an installation directory instead.
The Build Tree (PN_BUILD_DIR)
The build tree contains all files (final or temporary) generated during the build process.
For each module, the build system assigns a unique, module-specific subdirectory to the module by means of the variable PN_MODULE_BUILD_DIR. Any outputs must be directed to PN_MODULE_BUILD_DIR or a module-specific subdirectory.
A module can use its build directory freely and, for example, create and use subdirectories as adequate. The only restriction to this is that the subdirectories must not have the same name as eventual submodules.
If the module has submodules, the build directory of each submodule will be a $(PN_MODULE_BUILD_DIR)/<submodule name>. This may be used by the main module, e.g. to access header files or object files of a submodule directly without the necessity to export these files globally.
The module build directory is specific to a certain context (e.g. system or global, see below). Hence, builds for different systems with potentially different configurations do not interfere with each other.
The Installation Tree (PN_INSTALLED_DIR)
The installation directory is the place where pre-compiled libraries or pre-synthesized modules together with the exported header files are placed.
Purpose of a PicoNut installation is to allow building systems without a PicoNut source tree (external builds). In addition, builds of internal systems can be accelerated with the help of an installation.
Note: The existence of an installation directory is optional.
The location is given by the user by the environment variable PICONUT. Inside the build system, the installation directory must be accessed by the variable PN_INSTALLED_DIR, which is a normalized absolute path to the same directory. If no installation exists, PN_INSTALLED_DIR is empty.
Recommended locations are:
PICONUT = ~/.piconutfor a user-specific installationPICONUT = /opt/piconutfor a computer-wide installation
The System Directory (PN_SYSTEM_DIR)
This is the root of a system project using PicoNut. It must contain the following files:
Makefile: A PicoNut-compliant Makfile for the whole system.piconut-config.mk: System-specific configuration
The final outputs of the build process (target stage) are then written into:
piconut/: All PicoNut hardware and software outputs.
The Stage Tree (PN_STAGE_DIR)
The stage tree is a directory inside the build tree or the system directory with the same structure as an installation tree, which is used to collect all exported outputs during a recursive multi-module build process.
All install-* targets defined in Makefiles actually write into the stage tree. Only the main install target actually writes into a real installation directory as specified by PREFIX. And it does so by updating the stage tree and then copying to $(PREFIX)
For global builds (without a system context), the stage directory is contained in the build tree ($(PN_BUILD_DIR/pub). For system context, the stage directory is (PN_SYSTEM_DIR)/piconut.
Build Context
The build system may be used for very different use cases:
developing systems using PicoNut
developing PicoNut components as a PicoNut developer
developing on the PicoNut infrastructure as a PicoNut core developer
Systems may have custom configurations. PicoNut developers typically work inside the PicoNut source tree. System developers may not.
The build context refers to the system currently built for, or it may be global, if not system is defined.
The build system provides a separation of build contexts by means context-specific module build directories (PN_MODULE_BUILD_DIR).
Also, the behaviour of some targets may depend on the context in order to facilitate the typical use cases. For example, for systems, the stage directory is set to the system’s piconut directory to have all outputs immediately available for the system.
Layers and Dependencies
To keep the growing ecosystem around the PicoNut project maintainable, strict modularity with clearly-defined dependencies between modules must be maintained.
Each module is assigned to one of the following layers:
Layers
The PicoNut ecosystem is divided into layers, and any module is assigned to a specific layers. The following subsection describe the layers informally.
Specific information on rules and Makefile considerations are given in the section on writing Makefiles.
Layer 0: Configuration
This layer comprises all auto-generated headers containing configuration parameter definitions. The configuration is updated automatically by means of make targets from layer 1. Normal modules (layer 2 and 3) do not need to take care of it.
Note: The outputs contain variables to identify the version. Whenever the version changes, make config-update should be executed.
Layer 1: Common + Tools
These are header files or elementary software libraries commonly used in any other modules. Common modules ensure that all build tools (from tools) are available and that #include <piconut.h> works properly.
Common code is located inside hw/common, sw/common and
tools of the PicoNut source tree.
Layer 2: Regular Components
This layer comprises all regular components, typically hardware modules together with their software drivers. This includes:
hw/cpu(the CPU with all Nucleus and Membrana variants)hw/peripherals(hardware models + software drivers)GUI or other simulator components
Layer 3: Systems and Software
Modules at this layer may be:
a) Systems b) Software: libraries, applications, operating systems
Both systems and software applications may also be external, i.e. not part of the PicoNut source tree.
In general, layer-3 modules (systems or software) never access other components directly through the source tree or by targets in their Makefiles. Instead, they use PicoNut modules via an installation in a wider sense. This may either be a global installation ($PICONUT) or system-specific installation maintained inside $(PN_SYSTEM_DIR)/piconut.
Dependency Rules
Dependencies are allowed as specified by the following rules:
A modules may depend on its own submodules. The build system ensures that submodules are staged before the module itself is built.
A module may depend on any modules of any lower layer. The build system can be used to ensure that modules of layers 0 through 2 are prebuilt and staged before the module’s submodules and the module itself are built. The section on writing Makefiles details on how to specify such dependencies for a module.
Systems, software libraries and software applications located in layer 3 may depend arbitrarily on software libraries or applications. The developer of a new or changed module is responsible to maintain the buildability of the whole project for all possible configurations. In particular, circular dependencies must be avoided. In general, dependencies between software modules should be kept minimal. In the future, a layer concept for the software infrastructure itself may be defined.
Besides the cases mentioned so far, a module must not depend on any other modules.
Sub-targets for a certain technology (
TECHS) must not depend on sub-targets for other technologies - neither of the same module nor of any (legally) dependent module. In particular, synthesis techs require certain, sometimes complex (vendor-specific) tools. This rule is to avoid dependencies on such tool suites if a user does not really want to use it.Software modules of layer 3 are not allowed to use hardware-specific configuration variables (
PN_CFG_*, layer 0). Such software must be portable. Any hardware-specific software must be encapsulated in a driver (layer 2) or a common module (layer 1).
Examples
(Hardware) Components like CPU components or peripheral hardware including their software drivers are assigned to layer 2. They may use anything from
hw/commonorsw/common(layer 1).Systems (layer 3) may use any hardware components of layer 2. Their software may be part of the system itself, but may also be a standard application located in
sw/applications. Hence, the system may depend on software libraries and applications.Software projects (layer 3) may use a smaller or larger set of software libraries. Hence, irregular dependencies are allowed for hardware-independent software.
For common modules (layer 0) special rules apply. They comprise only a small part of the code base, and their build times and memory footprints should be kept minimal. Developer working on such modules should carefully inform themselves about the rules that apply. Also, they should be aware that even small changes may quickly break the whole build system.
Using the Build System
Overview
Each module directory contains a Makefile capable of building, verifying and installing the module (and its submodules). They may also implement additional, module-specific targets.
The set of targets and command-line variables can be viewed by running
$ make help
in the module source directory.
When is it necessary to clean?
The built system automatically detects changes in pure source code files and rebuilts parts as necessary. However, the following changes generally require a manual cleanup (make clean) of the relevant module(s):
Makefile is modified, e.g. new/removed source files
other meta files are changed
config/build parameters are changed (exception:
PN_CFG_*)command line parameters (e.g.
DEBUG) of the make invocation are changed
Writing Makefiles
This section describes the general structure of a module Makefile, variables, rules and targets provided by the build system and the targets that may or must be defined by the Makefile.
Starting Point for New Modules
In many cases, an existing Makefile can just be reused and adapted for a new module. The following modules have well-documented Makefiles that can be used as templates to start with:
hw/peripherals/uartfor a peripheral modulehw/cpufor a hardware module with submoduleshw/cpu/membrana/membrana_hwfor a hardware module with parts written in Verilogsw/applications/hello_piconutfor a software module
General Structure of a Makefile
Each Makefile has the following general structure:
<PicoNut header>
# Rules for tech A (e.g. simulation) ...
...
<compile, link, archive or synthesis rules>
# Rules for tech B (e.g. synthesis, optional) ...
...
<compile, link, archive or synthesis rules>
# Rules for software A (optional) ...
...
<compile, link, archive or synthesis rules>
# Global targets ...
<global targets>
The following subsections give details on the parts mentioned above.
Build Configuration
Refers to: <PicoNut header>
The header section …
may set some variables that define general properties of the module (the pre-configuration),
then includes the PicoNut build system,
then sets some general variables and rules affecting the build system in general (the post-configuration)
Inclusion of the PicoNut build system happens with a single line like:
include ../../piconut.mk (relative path to piconut.mk in the source root directory)
for internal modules. External modules not part of the PicoNut repository include the file by:
include $(PICONUT)/piconut.mk
where PICONUT is an environment variable pointing to the PicoNut installation.
In the pre-configuration section, the following optional variables may be set:
PN_SUBMODULES: list of relative paths to submodules. These will be built by the build system before their respective parent modules. Submodules are contained in subdirectories of the module. However, not every subdirectory must represent a submodule.PN_BUILD_COMMON: build the common and config part first. This defaults to 1, which is adequate for all layer-2 modules as well as internal systems and software of layer 3. Common modules must set it to 0. External modules should set it to 0 to not depend on the PicoNut source tree. (Default: 1)PN_BUILD_HW: If set (= 1), automatic rules for building hardware are activated. (Default: 1)PN_BUILD_SW: If set (= 1), automatic rules for building software are activated. (Default: 1)
By default, C++ source files (.cpp) are assumed to by hardware (SystemC) code, and C sources are treated as software (for RISC-V). Software modules using C++ may set PN_BUILD_HW = 0 to clarify that C++ sources are not meant to be build for the host system.
In the post-configuration section, the automatic build-prepare: target may be supplied with additional dependencies or a recipe to be executed before the build process starts. This is used to declare dependencies on modules at lower layers.
Also, any user-specific variables may be set in the post-configuration section.
Layer-Specific Options
**************************************************
This section is under construction!
**************************************************
TBD: Introduce PN_BUILD_COMMON and the default setting towards layer 2-
Any changes to the configuration or a Makefile requires a cleanup before the next build (
make clean)
Layer 0: Configuration
This layer comprises all auto-generated headers containing configuration parameter definitions. The configuration is updated automatically by means of make targets from layer 1. Normal modules (layer 2 and 3) do not need to take care of it.
Internal target(s):
config-build(equivalent toconfig-installtoPN_STAGE_DIR)config-install
Outputs:
(hw|sw)/include/piconut-config.h
Depends on (as available):
$(PN_SOURCE_DIR)/piconut-config.mk$(PN_INSTALLED_DIR)/piconut-config.mk$(PN_SYSTEM_DIR)/piconut-config.mk
Note: The outputs contain variables to identify the version. Whenever the version changes, make config-update should be executed.
Layer 1: Common + Tools
These are header files or elementary software libraries commonly used in any other modules. Common modules ensure that all build tools (from tools) are available outside and that #include <piconut.h> works properly.
Common code must be located inside
hw/common,sw/commonandtools,
respectively. For common code, the following rules apply:
#include <piconut.h>is not allowed. Instead,#include "[<relpath>/]piconut_base.h"may be used to have the basic subset of definitions and the configuration variables available.It is strongly discouraged to use configutation variables to avoid frequent rebuilds
A layer-1 module (common + tools) should add the following rule to its Makefile to ensure that layer 0 is up-to-date in the current build tree:
build-prepare: prepare-config
Internal target(s) (for developers):
common-buildcommon-install
Layer 2: Regular Components
This layer comprises all regular components, typically hardware modules together with their software drivers. This includes:
hw/cpu(the CPU with all Nucleus and Membrana variants)hw/peripherals(hardware models + software drivers)GUI or other simulator components
A layer-2 module (PicoNut component) should add the following rule to its Makefile to ensure that layers 0 and 1 are up-to-date in the current build tree:
build-prepare: prepare-common
This line should be added to any Makefile of a layer-2 module, including submodules. The allows to (re)build submodules independently.
To avoid that the common parts are built multiple times during recursive make invocations (i.e. for submodules), the above rule only performs a build in the top-most recursion layer ($(MAKELEVEL) == 0. If this does not work properly, for example, if the PicoNut build system is invoked as a recursive build from a third-party Makefile, the option PN_BUILD_COMMON=1 may be added to the command line.
Layer 3: Systems and Software
Modules at this layer may be:
a) Systems
- may have own configuration
- have their own tree inside PN_BUILD_DIR
- target ‘install’ installs into the system directory by default: PREFIX ?= $(PN_SYSTEM_DIR)/piconut
b) Software: libraries, applications, operating systems - must not depend on a system - may use drivers from layer 2 (components) - must not access configuration variables (may be accessed by driver functions) - may have irregular dependencies on other libraries (or apps): these may be expressed by make rules
Both systems and software applications may also be external, i.e. not part of the PicoNut source tree.
In general, layer-3 modules (systems or software) never access other components directly through the source tree. Instead, they are accessed via an installation, either globally ($PICONUT) or maintained inside the system directory ($(PN_SYSTEM_DIR)/piconut).
For a system or software, there are multiple options, depending on the setup of the system/software developer:
a) PicoNut installation exists, no non-default compile-time options selected
This is a typical case for external development of a system or software, which is not part of the PicoNut repository, and the developer only uses PicoNut.
In this case, no rule needs be added to the Makefile, but the developer is responsible for providing a PicoNut installation.
b) No PicoNut installation exists or non-default compile-time options are set
In this case, all or a subset of PicoNut modules are built together with the system in its own build tree. To this end, the following rule must be added to the respective system Makefile:
build-prepare:
$(PN_PREBUILD) MODULES=<required modules>
If no installation exists, all modules used by the system must be added to the MODULES argument.
Note: The user is responsible to carefully select all components used by the system and affected by the custom configuration. Missing out modules may lead to hard to detect problems.
If the system has submodules, the above lines should be added to all (sub)modules, where the MODULES arguments only cover those modules directly required by the respective (sub)module
c) Software Applications or Libraries
To allow local builds, software modules may add a rule to require the common modules:
build-prepare: prepare-common
$(PN_PREBUILD) MODULES=<required modules>
Automatic Recipes for Compilation and Linking
Refers to: <compile, link, archive or synthesis rules>
For Hardware (simulation)
For compiling hardware source files, the following rule is provided by the build system:
C++:
%.cpp->$(PN_MODULE_BUILD_DIR)/sim/%.o
Input files are detected by their suffix. Outputs are automatically written into $(PN_MODULE_BUILD_DIR)/sim. The list of all object files (for linking/archiving) can be obtained from the list of source by an assignment like:
MODULE_OBJ := $(MODULE_SRC:%.cpp=$(PN_MODULE_BUILD_DIR)/sim/%.o)
Build dependencies are maintained automatically in .d files that must be included by a line like:
-include $(MODULE_OBJ:%.o=%.d)
For linking hardware object files to an executable, automatic recipes are defined for the following suffixes:
Testbench executable:
$(PN_MODULE_BUILD_DIR)/sim/%_tbSimulator:
$(PN_MODULE_BUILD_DIR)/sim/%_sim
To activate the recipe, a rule declaring the underlying object files must be declared, for example:
$(PN_MODULE_BUILD_DIR)/sim/mymodule_tb: $(MYMODULE_OBJ)
Similarly, an automatic recipe exists for building an archive /library) based on the following name template:
Archive:
$(PN_MODULE_BUILD_DIR)/sim/%.a
Usage example:
$(PN_MODULE_BUILD_DIR)/sim/libmymodule.a: $(MYMODULE_OBJ)
Custom flags for compilation or linking can be added by:
PN_HW_CFLAGS += <user compiler flags>
PN_HW_LDFLAGS += <user linker flags>
In general, all search paths are set by the build system, and the same holds for optimization options (based on the global DEBUG parameter). Hence, custom flags should be avoided. However, used libraries must always be declared explicitly by extending PN_HW_LDFLAGS.
Note: All libraries used by the a module - including those generated by submodules - must be declared explicitly by extending PN_HW_LDFLAGS. For example (in module hw/cpu):
PN_HW_LDFLAGS += -lnucleus_ref -lmembrana_soft
For Hardware (synthesis)
TBD
For Software (RISC-V Cross-Compilation)
For compiling software (RISC-V) source files, the following rules are provided by the build system:
C:
%.cpp->$(PN_MODULE_BUILD_DIR)/%.oAssembler:
$.S->$(PN_MODULE_BUILD_DIR)/%.o
Input files are detected by their suffix. Outputs are automatically written into $(PN_MODULE_BUILD_DIR). The list of all object files (for linking/archiving) can be obtained from the list of source by an assignment like:
MODULE_OBJ := $(MODULE_SRC:%.cpp=$(PN_MODULE_BUILD_DIR)/%.o)
Build dependencies are maintained automatically in .d files that must be included by a line like:
-include $(MODULE_OBJ:%.o=%.d)
For linking object files to an executable, an automatic recipe is defined for the .elf suffix:
RISC-V executable:
$(PN_MODULE_BUILD_DIR)/%.elf
To activate the recipe, a rule declaring the underlying object files must be declared, for example:
$(PN_MODULE_BUILD_DIR)/myapp.elf: $(MYAPP_OBJ)
Similarly, an automatic recipe exists for building an archive /library) based on the following name template:
Archive:
$(PN_MODULE_BUILD_DIR)/%.a
Usage example:
$(PN_MODULE_BUILD_DIR)/libmyapp.a: $(MYAPP_OBJ)
Custom flags for compilation or linking can be added by:
PN_SW_CFLAGS += <user compiler flags>
PN_SW_LDFLAGS += <user linker flags>
In general, all search paths are set by the build system, and the same holds for optimization options (based on the global DEBUG parameter). Hence, custom flags should be avoided. However, used libraries must always be declared explicitly by extending PN_SW_LDFLAGS.
Note: All libraries used by the a module - including those generated by submodules - must be declared explicitly by extending PN_SW_LDFLAGS.
Search Order
The automatic recipes make sure that header files, libraries and synthesis artefacts are searched for in the following location in this order of precedence:
PN_BUILD_DIR/pubPN_SYSTEM_DIRPN_INSTALLED_DIR
Inside each of these locations, the following sub-paths are used:
hw/include: headers for hardware moduleshw/lib: libraries for hardware simulationhw/syn: synthesis artefacts for hardware modulessw/include: headers for software (RISC-V) modulessw/lib: libraries for software (RISC-V) modulessw/lib: software binaries (for the RISC-V architecture)
Global Module Targets
Refers to: <global targets>
The following targets may be provided by the Makefile. They will be picked up and used by the main targets build, stage, verify and install, which are defined and implemented in the build system.
Unless specified otherwise, the targets are optional. If missing, they do not produce any outputs.
build-all: Build everything which is not dependent on a certain TECHS argument, e.g. RISC-V software.build-sim: Build all artefacts for simulation, typically the library and testbench executable.build-syn: Build all synthesis artefacts.verify-sim/verify-sim/verify-syn: Verify the outputs of the respectivebuild-*target.install-all: Stage everything which does not depend on a certain TECHS argument. In the case of hardware, header files should be installed here.install-sim: Stage all simulation artefacts.install-syn: Stage all synthesis artefacts.
Installation Macros
Inside the install-* recipes (see previous section), the following commands must be used. It is not allowed to write into the target directory in another way. They make sure that the files are installed into the right directory with the correct permissions:
$(PN_INSTALL_HW_INCLUDE) <file(s)>: Install hardware header(s)$(PN_INSTALL_HW_LIB) <file(s)>: Install hardware simulation library/libraries$(PN_INSTALL_HW_BIN) <file(s)>: Install a simulator$(PN_INSTALL_HW_SYN) <file(s)>: Install a synthesis artefact$(PN_INSTALL_SW_INCLUDE) <file(s)>: Install software headers$(PN_INSTALL_SW_LIB) <file(s)>: Install a software library/libraries$(PN_INSTALL_SW_BIN) <file(s)>: Install a software binary$(PN_INSTALL_TOOL_BIN) <file(s)>: Install a tool (fromtools/*only)$(PN_INSTALL_TOOL_LIB) <file(s)>: Install a tool library (fromtools/*only)
Variables
**************************************************
This section is under construction!
**************************************************
Module Properties
$(PN_MODULE_BUILD_DIR): Build directory for the current module. All generated files must go there!$(PN_MODULE_ID): Module ID. For an internal module, this is the relative path below$(PN_SOURCE_DIR). For external module, this is the absolute path in the file system without a leading/.$(PN_MODULE_NAME): Last path component of$(PN_MODULE_ID)
Directories
$(PN_SOURCE_DIR): Source directory root (or empty, if not defined)$(PN_BUILD_DIR): Build directory root$(PN_SYSTEM_DIR): Root of System directory (or empty, if no system context is used)$(PN_INSTALLED_DIR): Root of an existing PicoNut installation (or empty, if not available)
TBD
How the build system works
Automatic rules
build,verifyandinstallautomatically call the same target for their direct submodules first.Only
builddepends on targets of layers <= 1. Installation and verification is controlled by the main Makefile.verify-(sim|syn)andinstall-(sim|syn)automatically depend on their respectivebuild-*targets, but not on anything of a lower layer.
Manual rules
To ensure that all modules of lower layers are built correctly, manual dependencies for build-prepare as described in the section on dependency layers.
Internal notes (TBD)
build-prepare: Phony target, by default empty, all dependencies are resolved before any of the modules’sbuild-submodulesandbuild-<tech>targets are invokedcomponents (layer 2) have dependency
build-prepare: build-common
Layer build preparation:
How to prepare PN_BUILD_DIR for a build process in layer n.
layer 0: (nothing to do)
layer 1: depend on
prepare-config(==config)layer 2: build and export layer 1 by recursive make invocations, target:
prepare-common->build-preparedepends onbuild-common, which does it alllayer 3: in MAKELEVEL == 0: build parts of layer 2 (system/software makefile must name the modules explicitly)
-> PN_REBUILD_MODULES
Configuration parameters
**************************************************
This section is under construction!
**************************************************
General considerations
All configuration parameters should be visible in hardware and software.
A configuration parameter always relates to
a) a module (identified by its relative directory path)
b) a group of modules, e.g. all Nuclei or all Membranas (-> identified by their common containing directory, e.g.
hw/cpus/nuclei)c) a system (e.g. memory layout, clock frequency)
Configuration parameters are typically set in (just) three places:
a) inside the respective module itself (= default for common use)
b) in the build system (= default for common use)
c) inside the system for system-secific settings
Configuration parameters mainly control the hardware. Software may just want to read/know them, mainly for compile-time optimizations/variations.
As far as possible, hardware parameters should remain variable at runtime (simulation) or synthesis time (synthesis).
As far as possible, hardware parameters should be accessible to software at runtime, e.g. by means of CSRs (CPU) or version/feature registers (peripherals).
Rules and conventions
a) Module-Level
Module-related configuration parameters are defined and documented in the respective module sources, typically inside C/C++ header files (.h). The documentation should be done via Doxygen as a section “Configuration Options” inside the module documentation.
All configuration variables must adhere to the naming conventions (see section on naming conventions, i.e. start with
PN_CFG_).All definitions inside the module must allow them to be predefined by enclosing them with
#ifndef PN_CFG_...and#endif.Before using any configuration file in C/C++ code, the PicoNut header file must be included:
#include <piconut.h>
Notes:
The way to define module-specific parameters ist not language-independent. In the future, this may be overcome by introducing an optional, per-module
piconut-config.mkfile.
b) Global configuration
Configuration parameters not related to a particular module (typically system-level parameters) are defined and documented with the build system. At present, this happens in the file
$PN_SOURCE_DIR/piconut-config.mk.A global C header file containing all parameters set at installation time is auto-generated and included by
#include <piconut.h>from any hardware or software source file.Header/package files for other languages (e.g. VHDL) may follow in the future. They will also be auto-generated from
$PN_SOURCE_DIR/piconut-config.mk.
c) System configuration
Any configuration parameter may also be set/modified in a system project using the file
PN_SYSTEM_DIR/piconut-config.mk. A standard makefile rule for systems will generate a C/C++ header insidepiconut/.The system makefile is responsible for (re-)building any PicoNut modules with system-specific parameters that do not match the global defaults. Such modules must be built and installed as follows:
$ make build PN_SYSTEM=<my system>$ make install PN_SYSTEM=<my system> PREFIX=<my system>/piconut
Naming conventions
All configuration parameters start with
PN_CFG_.PN_<name>_: Each module must have a unique name and name its parameters according to it.PN_CFG_CPU_: Parameters related to a PicoNut corePN_CFG_SYS_: System parameters (e.g. memory layout, system clock frequency)
Synthesis
**************************************************
This section is under construction!
**************************************************
Possible synthesis outputs & installation exports
<module>.sv: RTL SystemVerilog, hollow (without submodules, file may include .v/.sv files of submodules)<module>.v: RTL Verilog, solid (includes complete subtree)<module>.<board>.json/<module>.<board>.v<module>.<board>.dcp: Gate-level, technology-mapped netlist (board-specific)<module>.<board>.bit/<module>.dfu: Bitfile for FPGA programming, module is a system
NOTE: At present, the target hardware and technology is defined by PN_BOARD only. There are no variables to specify the device family, device or toolchain. All the three of them are implicitely defined by PN_BOARD.
Phony targets
**The following targets are provided by the build system. **
build-rtl: Synthesize SystemC code (ICSC), translate everything into clean Verilog or VHDLbuild-netlist: Run RTL synthesize to obtain a gate-level netlistboard-specific: depends on PN_CFG_BOARD
build-layout: Run place & route and, in case of FPGA technology, build a bitfilesystem-specific: depends on PN_BOARD_CONTRAINTS
board-specific: depends on PN_BOARD
The following targets are recognized by the build system and should be defined in modules.
build-syn: Build all synthesis targets
Types of modules
Simple Module
no exchangable submodules
not reused in presynthesized way
Recommended Makefile section:
build-syn: (nothing)
verify-syn: build-netlist
run testbench
install-syn: (build-syn)
headers
Reusable Module
no exchangable submodules
reusable in presynthesized way
e.g.: nucleus, membrana, peripheral
Recommended Makefile section:
build-syn: build-netlist
verify-syn: build-netlist
run testbench
install-syn: (build-syn)
headers
.v . .(v|json|dcp)
Super-Module / Hollow Module
with exchangable submodules (“blackbox”)
not reused in presynthesized way
e.g. cpu, system
Recommended Makefile section:
build-syn: build-rtl
verify-syn: build-netlist
run testbench with a default set of submodules
install-syn: (build-syn)
headers
.sv for the default config: .v
. .(v|json|dcp)
Naming and Coding Conventions
**************************************************
This section is under construction!
**************************************************
All symbols that may go into
PN_INSTALLED_DIRmust be prefixed byPN_orpn_. Legal exceptions are:purely internal symbols (e.g. internal modules of a Nucleus)
Makefile symbols that directly affect the build process and nothing else, i.e. are not visible and used in the source code: PREFIX, VERBOSE, CHECK (but not PN_DEBUG, which is visible in the source code)
…
Directory layouts
**************************************************
This section is under construction!
**************************************************
The Source Tree (PN_SOURCE_DIR)
The Build Tree (PN_BUILD_DIR)
General Considerations
The build directory
PN_BUILD_DIRreflects the directory hierarchy of the source tree (PN_SOURCE_DIR).All build artefacts must go into
PN_MODULE_BUILD_DIR. The module is responsible and free to use this directory as needed.The subdirectory
pub/is reserved; build artefacts of submodules are “installed” here during the build process. The internal organization ofpub/is the same as inPN_INSTALLED_DIR(OBSOLETE) The following directory names are reserved, they contain installation artefacts of the
sw/commonandhw/commonmodules:sw/include,sw/libhw/include,hw/lib, (hw/syn)
The Installation Tree (PN_INSTALLED_DIR)
General Considerations
The directory
PN_INSTALLED_DIRis expected to contain all build artefacts used by a) other modules, b) (external) systems or c) software applications.The build system never writes to
PN_INSTALLED_DIR. The installation target for “install” targets is given by commonly used variablePREFIX.The directory structure is flat compared to the source tree to simplify search paths for libraries and headers. All modules contained in the PicoNut source tree are responsible to avoid naming conflicts.
The same layout is used for a global installation and per-system modules. The system project directory
piconut/can be regarded as an overlay of the installation directory.All modules of the PicoNut repo except systems must be buildable without an installed directory and without a system. Systems may rely on an existing install dir.
Layout
VERSION # Version string
sw/
include/ # RISC-V headers (.h) (flat directory hierarchy)
lib/ # RISC-V libraries (.a) (flat directory hierarchy)
bin/ # RISC-V binaries
hw/
include/ # SystemC headers (.h, .H) for hardware modules (flat hierarchy)
<module>/ # headers specific to a certain module (e.g. peripheral or Nucleus/Membrana variant)
<peripheral>/ # headers specific to a certain peripheral
<peripheral>.h # main header to use a certain peripheral
lib/ # Hardware simulation libraries (.a) (flat hierarchy)
syn/ # Hardware synthesis artefacts (.v, .vhdl, ...) (flat hierarchy)
Possible types:
.sv: System verilog: current module without submodules (typically generated by ICSC)
.v: Verilog modules (manually written or generated by ICSC
...
bin/ # Tools
src/ # exact copy of the source tree from which this was installed (without .git* and other metafiles); TBD: Headers may primarily be found here then instead of sw/include or hw/include -> remove them here or in */include?.
The System Directory (PN_SYSTEM_DIR)
piconut/
# - Same sub-directories as $PN_INSTALL_DIR without 'src/'.
# - Directory can and should be removed by "clean" target of the user's project
# - Modules with non-default parameters must be built and installed using
# PREFIX=$PN_SYSTEM_DIR/piconut
# - The makefile (piconut.mk) makes sure that headers and
# libraries are first searched in the system directory, then in $PICONUT.
piconut-config.mk
# system-specific configuration
Further Information
For the case that some information is missing or appears to be outdated, more and up-to-date information can be found in
the sample Makefiles mentioned in the section on writing new Makefiles,
as comments in the file
piconut.mk.