If I understand you correctly, you want one HAL interface that stays the same but has two different “backends” that implement the HAL for either a simulator or the real hardware. There’s a few ways you can do this, depending on how significant the changes are. In any case, Alire’s crate configuration variables provide a nice mechanism to do this, and this is what I have used to configure my runtimes.
With Alire’s crate configuration variables, your HAL crate could define something like this in its alire.toml file:
[configuration.variables]
Target = { type = "Enum", values = ["Simulator", "Hardware"], default = "Hardware }
This will declare a variable that can be configured for either the Simulator or the real target Hardware. Users of your HAL crate can then set this variable in their alire.toml (assuming your HAL crate is called my_hal for example:
[configuration.values]
my_hal.Target = "Simulator"
At build time, Alire will generate an Ada spec (.ads), GPR file (.gpr), and C header file (.h) that contain the configuration values. For example, a file called my_hal_config.ads will be generated.
You can then import these generated sources to configure your source code in various ways, depending on how significant the differences are between the simulator and real hardware. Here’s a use cases:
- If you just need to choose a different value between the hardware and simulator, then you can choose the value at compile time based on the. For example, to choose
1 for the simulator and 2 for the real hardware:
with My_Hal_Config; use My_Hal_Config;
package Example is
My_Constant : constant := (if My_Hal_Config.Target = Simulator
then 1
else 2);
end Example;
- If you need certain code in a function or procedure to be executed only on the simulator, then you can use an
if statement on a constant boolean for this:
with My_Hal_Config; use My_Hal_Config;
procedure Example is
Is_Simulator : constant Boolean := My_Hal_Config.Target = Simulator;
begin
-- A procedure call that is executed for both the sim and the real hardware
Common_Code;
-- Code that is only executed on the simulator
if Is_Simulator then
Do_Something_For_Simulator;
end if;
end Example;
- If your implementations differ wildly between the two targets, then you can choose different source files at build time. To do this, you can create 2 different source subdirectories: e.g. one directory called
Simulator and one directory called Hardware. You can then have two implementations of the same source file (e.g. hal.adb), one in each directory. Then, in your GPR file you can choose which sources you want to use by setting Source_Dirs. For example:
with "my_hal_config.gpr";
project My_HAL is
for Source_Dirs use
-- directory containing sources common to both impls
("src/common",
-- directory containing the sources for a specific target
"src/" & My_Hal_Config.Target);
end My_HAL;
I’ve used all three of these approaches in the RP2350 runtimes. I’ve used #1 for setting clock configuration constants, #2 for omitting some multicore-only code for when the runtime is configured for a single core, and #3 for choosing different versions of a source file (for package Ada.Interrupts.Names) depending on single/multicore configuration.