Examples
This section provides worked examples showing the complete HALO workflow from model definition through application integration.
System Architecture Overview
The HALO framework follows a layered architecture where multiple cores/components running on different platforms communicate through well-defined interfaces and protocols.

Example: RISC-V AMP System with Linux, FreeRTOS, Zephyr, and Bare-metal cores
This image shows a real-world RISC-V system architecture:
- Hart4 (Yellow): Linux system with kernel and userspace application
- Hart3, Hart2, Hart1 (Blue): Real-time U74 cores running bare-metal firmware, FreeRTOS, and Zephyr
- Hart0 (Green): S7 core running boot coordinator/bootloader (not involved in application communication)
- Shared Services: Common shared memory, peripherals, and UART console accessible to all cores
HALO configures communication channels between the application cores (Hart4, Hart3, Hart2, Hart1) and generates platform-specific code for each.
Example 1: Step-by-Step Workflow
Step 1: Define Your System Model (HADL)
Create a system.adl file describing all components and connections aligned with the RISC-V AMP architecture:
HALOFramework {
components {
Hart4 : Component { Type: Application, Platform: Linux, Function: GeneralProcessing }
Hart3 : Component { Type: Application, Platform: BareMetal, Function: RealTimeProcessing }
Hart2 : Component { Type: Application, Platform: FreeRTOS, Function: RealTimeProcessing }
Hart1 : Component { Type: Application, Platform: Zephyr, Function: RealTimeProcessing }
}
connections {
connection LinuxToBare {
from Hart4 to Hart3
interface: ControlInterface
profile: sharedMemoryProfile1
}
connection LinuxToFreeRTOS {
from Hart4 to Hart2
interface: SensorInterface
profile: sharedMemoryProfile1
}
connection LinuxToZephyr {
from Hart4 to Hart1
interface: StatusInterface
profile: sharedMemoryProfile1
}
connection BareToRTOS {
from Hart3 to Hart2
interface: DataInterface
profile: sharedMemoryProfile2
}
connection FreeRTOSToZephyr {
from Hart2 to Hart1
interface: CoordinationInterface
profile: sharedMemoryProfile2
}
}
}
Step 2: Define Data Types (IDDL)
Create halo_interface_data.iddl with reusable data structures:
dataStructures {
SensorData {
integer sensorId
unsigned int timestamp
byte payload[256]
}
ControlCommand {
byte commandCode
integer parameter1
integer parameter2
}
SystemStatus {
byte statusCode
integer cpuLoad
integer memoryUsed
}
}
Step 3: Define Interfaces (IDL)
Create halo_interfaces.idl specifying what data flows in each interface:
include halo_interface_data.iddl
interface ControlInterface {
access {
read {
ControlCommand
}
write {
SystemStatus
}
}
integrity {
crc32 {
ControlCommand
}
}
}
interface SensorInterface {
access {
read {
SensorData
}
write {
SystemStatus
}
}
integrity {
crc32 {
SensorData
}
}
}
interface StatusInterface {
access {
read {
SystemStatus
}
write {
SystemStatus
}
}
}
interface DataInterface {
access {
read {
SensorData
ControlCommand
}
write {
SensorData
}
}
integrity {
crc16 {
SensorData
}
}
}
interface CoordinationInterface {
access {
read {
ControlCommand
}
write {
ControlCommand
}
}
}
Step 4: Define Hardware Mapping Profiles (HMML)
Create halo_hml_profiles.hmml if you don’t want to use a built-in HALO profile SharedMemory; If you want to use SharedMemory (–include-stdlib-profiles) you can override its parameters but cannot redefine it:
Profiles {
<your shared memory profile>
}
Step 5: Instantiate Profiles (HML)
Create halo_hml_instances.hml mapping profiles to connections, for this example we will use built-in HALO profile SharedMemory but override some attributes.
Profiles {
SharedMemory sharedMemoryProfile1 {
memSize: 64MB
baseAddress: 0x80000000
}
SharedMemory sharedMemoryProfile2 {
memSize: 32MB
baseAddress: 0x84000000
}
}
built-in HALO profile SharedMemory:
SharedMemory {
memSize: 64MB
baseAddress: 0x80000000
cacheable: True
policy: WriteBack
coherence: Software
syncType: AcquireRelease
permissions: RW
priority: High
syncBlocking: True
syncPrimitive: mutex
}
Step 6: Compose the Model
Run the HALO composer to validate and unify all inputs. We add –include-stdlib-profiles as we are using built-in HALO profile SharedMemory
halo compose \
--hadls-root ./path_to_hadls \
--output-dir ./halo_out \
--include-stdlib-profiles
Optional: extend model value domains with user types.
Example user_types.json:
{
"componentType": ["MyType", "MyType2"],
"componentPlatform": ["MyPlatform", "MyPlatform2"],
"componentFunction": ["MyFunction", "MyFunction2"]
}
Supported commands:
python -m halo compose --hadls-root path\to\hadls --output-dir path\to\out --user-types path\to\user_types.json
python -m halo compose --from-xmi path\to\model.uml --output-dir path\to\out --user-types path\to\user_types.json
You can use this to add custom component type/platform/function values into the unified model without changing HALO source code.
This produces:
halo_unified_model.json- unified system model- UML/XMI exports for review
- Analysis reports validating consistency
Step 7: Generate Code
Before generating code, ensure you have platform and protocol generators available.
For protocol generator:
-
Use HALO’s built-in standard library profiles (SharedMemory, Blackboard, EventChannel), compose with
--include-stdlib-profilesflag (as shown in Step 6) for built-in profiles - Create custom protocol generators, check how to create protocol generator
- Install them via pip so HALO discovers them automatically
For platform generator:
-
Create custom platform generators for platform-specific code generation, check how to create platform generator
-
Install them via pip so HALO discovers them automatically
For this example, we proceed with built-in profiles. Generate platform and protocol-specific implementations:
halo generate \
--composer-output-dir ./halo_out \
--output-dir ./generated
This produces platform-specific packages:
Hart4_linux/- Linux platform code (Hart4)Hart2_freertos/- FreeRTOS platform code (Hart2)Hart1_zephyr/- Zephyr platform code (Hart1)Hart3_baremetal/- Bare-metal platform code (Hart3)portable/- Common portable layer
For application integration with the generated code, see the Integration guide.
Creating a Custom Platform Generator
Platform generators emit platform-specific initialization and integration code.
Step 1: Scaffold the Platform Generator
halo create-generator --generator-type platform --no-interactive \
--name my_custom_platform \
--description "My Custom Platform Generator" \
--author "Your Name" \
--email "you@example.com" \
--platform my_custom_platform \
--output-dir ./generators
Step 2: Examine the Generator Structure
halo-generator-my_custom_platform/
├── pyproject.toml
├── src/
│ └── halo_gen_my_custom_platform/
│ ├── __init__.py
│ ├── config.py # Discovery metadata
│ ├── render.py # Generation logic
│ └── templates/
│ ├── platform_init.h.j2
│ ├── platform_init.c.j2
│ └── platform_hal.h.j2
Step 3: Implement config.py
Tell HALO how to discover your generator:
"""Configuration for the My Custom Platform generator."""
from pathlib import Path
def get_platform_config():
"""Return platform generator configuration for HALO discovery."""
module_dir = Path(__file__).parent
render_module = module_dir / "render.py"
return {
"name": "my_custom_platform",
"supported_protocols": ["sharedmemory", "blackboard", "eventchannel"],
"module": str(render_module),
"description": "My Custom Platform Generator",
}
Step 4: Implement render.py
Generate the platform-specific code:
"""Rendering engine for My Custom Platform."""
def render(unified_model, output_dir, platform_name):
"""
Generate platform initialization and HAL code.
Args:
unified_model: dict with components, connections, interfaces
output_dir: Path to write generated files
platform_name: str identifying this platform (e.g., "my_custom_platform")
"""
# Filter model for this platform's components
my_components = [c for c in unified_model['components']
if c['platform'] == platform_name]
# Get connections involving these components
my_connections = [conn for conn in unified_model['connections']
if any(c in conn['components'] for c in my_components)]
# Generate platform initialization code
init_code = generate_init_code(my_components, my_connections)
(output_dir / "platform_init.c").write_text(init_code)
# Generate HAL header
hal_header = generate_hal_header(my_components, unified_model['interfaces'])
(output_dir / "platform_hal.h").write_text(hal_header)
Step 5: Create Jinja2 Templates
Template: templates/platform_init.c.j2
#include "platform_init.h"
#include "halo_api.h"
/**
* Initialize {{ platform }} platform
* Sets up communication channels for {{ components|length }} component(s)
*/
void platform_{{ platform }}_init(void) {
// Setup platform-specific hardware (GPIO, timers, interrupts)
setup_hardware();
// Initialize shared memory regions
{% for conn in connections %}
if (conn.profile == "sharedMemory") {
setup_shared_memory(0x{{ conn.baseAddress }}, {{ conn.memSize }});
}
{% endfor %}
// Initialize DMA channels
{% for conn in connections %}
if (conn.profile == "DMA") {
setup_dma_channel({{ conn.controller }}, {{ conn.channel }});
}
{% endfor %}
// Call HALO init
halo_core_init_{{ platform }}();
}
Step 6: Install and Use Your Generator
cd generators/halo-generator-my_custom_platform
pip install -e .
Now generate code for your platform:
halo generate --platform my_custom_platform --composer-output-dir ./halo_out
Creating a Custom Protocol Generator
Protocol generators emit transport-specific code for communication patterns.
Step 1: Scaffold the Protocol Generator
halo create-generator --generator-type protocol --no-interactive \
--name my_custom_protocol \
--description "My Custom Protocol Generator" \
--author "Your Name" \
--email "you@example.com" \
--platform my_custom_protocol \
--supported-platforms linux,freertos,zephyr \
--output-dir ./generators
Step 2: Implement Protocol Logic (render.py)
"""Rendering engine for My Custom Protocol."""
def render(unified_model, output_dir, protocol_name):
"""Generate protocol-specific transport code."""
# Find all connections using this protocol
my_connections = [c for c in unified_model['connections']
if c['profile'] == protocol_name]
for conn in my_connections:
# Generate send/receive functions for each connection
sender_code = generate_send_code(conn)
receiver_code = generate_recv_code(conn)
output_file = output_dir / f"{protocol_name}_{conn['name']}.c"
output_file.write_text(sender_code + receiver_code)
Step 3: Create Protocol Header Template
Template: templates/protocol.h.j2
#ifndef {{ protocol|upper }}_H
#define {{ protocol|upper }}_H
#include "halo_types.h"
#include "halo_structs.h"
/**
* {{ protocol }} Protocol - Custom Transport Layer
* Supports {{ connections|length }} connection(s)
*/
{% for conn in connections %}
// Connection: {{ conn.name }}
// From: {{ conn.from }} To: {{ conn.to }}
// Interface: {{ conn.interface }}
typedef struct {
halo_u32_t seq_num;
halo_u64_t timestamp;
halo_u32_t crc;
} {{ protocol }}_{{ conn.name }}_header_t;
int {{ protocol }}_send_{{ conn.name }}(const void *data, halo_size_t len);
int {{ protocol }}_recv_{{ conn.name }}(void *data, halo_size_t *len);
{% endfor %}
#endif /* {{ protocol|upper }}_H */
#include "halo_api.h"
#include "halo_structs.h"
#include "Linux_init.h"
Step 2: Initialize HALO at Startup
int main(int argc, char *argv[]) {
// Initialize HALO communication layer
halo_core1_init_Linux();
printf("HALO initialized on Linux Core1\n");
// Your application code here
while (1) {
process_sensor_data();
send_commands();
usleep(10000); // 10ms
}
return 0;
}
Step 3: Send Data Using HALO API
#include "halo_api.h"
void send_sensor_data(uint32_t sensor_id, uint8_t *payload) {
SharedData data;
data.sensorId = sensor_id;
data.timestamp = get_current_time();
memcpy(data.payload, payload, sizeof(data.payload));
// Send to FreeRTOS Core2 via LinuxToRTOS connection
int result = halo_send_SharedData_LinuxToRTOS(&data);
if (result == HALO_OK) {
printf("Data sent successfully\n");
} else {
printf("Send failed with error code: %d\n", result);
}
}
Step 4: Receive Data Using HALO API
void receive_system_status(void) {
SystemStatus status;
int result;
// Try to receive from PL (Hardware Accelerator)
result = halo_recv_SystemStatus_PLToLinux(&status);
if (result == HALO_OK) {
printf("Status received:\n");
printf(" Code: 0x%02X\n", status.statusCode);
printf(" CPU Load: %d%%\n", status.cpuLoad);
printf(" Memory Used: %d bytes\n", status.memoryUsed);
} else if (result == HALO_NODATA) {
// No data available yet
printf("No data available\n");
} else {
printf("Receive error: %d\n", result);
}
}
Step 5: Complete Application Example
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "halo_api.h"
#include "Linux_init.h"
typedef struct {
uint32_t sensor_count;
uint32_t frame_number;
} AppState;
void app_init(AppState *state) {
state->sensor_count = 0;
state->frame_number = 0;
// Initialize HALO
halo_core1_init_Linux();
printf("Application initialized\n");
}
void app_process_cycle(AppState *state) {
ControlCommand cmd;
SystemStatus status;
// Receive commands from FreeRTOS
if (halo_recv_ControlCommand_RTOSToLinux(&cmd) == HALO_OK) {
printf("Command received: 0x%02X\n", cmd.commandCode);
state->sensor_count++;
}
// Receive status from accelerator
if (halo_recv_SystemStatus_PLToLinux(&status) == HALO_OK) {
printf("System status: CPU=%d%%, Mem=%d\n",
status.cpuLoad, status.memoryUsed);
}
// Send sensor data periodically
if ((state->frame_number % 10) == 0) {
SensorData data = {
.sensorId = 1,
.timestamp = state->frame_number,
};
strcpy((char*)data.payload, "Sensor reading from Core1");
int result = halo_send_SensorData_LinuxToRTOS(&data);
if (result != HALO_OK) {
printf("Send error: %d\n", result);
}
}
state->frame_number++;
}
int main(int argc, char *argv[]) {
AppState state;
app_init(&state);
// Main application loop
for (int i = 0; i < 1000; i++) {
app_process_cycle(&state);
usleep(10000); // 10ms per cycle
}
printf("Application complete: %u frames, %u sensors\n",
state.frame_number, state.sensor_count);
return 0;
}
Error Handling
The HALO API returns status codes:
typedef enum {
HALO_OK = 0, // Operation successful
HALO_INVALID = -1, // Invalid parameters
HALO_NODATA = -2, // No data available
HALO_OVERFLOW = -3, // Buffer overflow
HALO_UNDERFLOW = -4, // Buffer underflow
HALO_CRC_ERROR = -5, // CRC check failed
HALO_TIMEOUT = -6, // Operation timeout
} halo_status_t;
Always check return codes:
int result = halo_send_SensorData_LinuxToRTOS(&data);
switch (result) {
case HALO_OK:
printf("Success\n");
break;
case HALO_CRC_ERROR:
printf("Data integrity check failed\n");
break;
case HALO_OVERFLOW:
printf("Send buffer full\n");
break;
default:
printf("Unknown error: %d\n", result);
}
Summary
The complete workflow is:
- Model: Write ADL, IDL, IDDL, HMML/HML files describing your system
- Compose: Run
halo composeto validate and unify the model - Generate: Run
halo generateto produce platform/protocol code - Integrate: Link generated code into your applications
- Initialize: Call platform init functions at startup
- Communicate: Use generated send/receive APIs in your code
- Deploy: Run on all target platforms
For platform or protocol customization, create custom generators using the scaffolding tool and register them via pip entry points.