PHY RAN App#

Integration application bridging FAPI (MAC-PHY) and Fronthaul (RU) interfaces.

Overview#

The PHY RAN App integrates FAPI message processing with Fronthaul communication and executes the PUSCH uplink receiver pipeline:

  • FAPI Integration - Receives messages via NVIPC from MAC layer

  • Message Adapter - Converts FAPI messages to PUSCH processing requests

  • Fronthaul Integration - C-Plane (DPDK) and U-Plane (DOCA) processing

  • PUSCH RX Pipeline Execution - Runs PUSCH uplink receiver processing

  • Slot Indication - Periodic trigger coordinating slot-based processing

System Architecture#

The integration test orchestrates three independent processes that communicate to form a complete 5G RAN stack:

PHY RAN App System Architecture (Light)
PHY RAN App System Architecture (Dark)

Process Interactions#

Direction

Communication Details

testMACphy_ran_app

Sends FAPI control messages (UL_TTI_REQUEST, SLOT_RESPONSE).

phy_ran_apptestMAC

Sends FAPI indications (SLOT, CRC, measurements).

phy_ran_appRU Emulator

Sends O-RAN C-Plane control messages via DPDK (Ethernet).

RU Emulatorphy_ran_app

Transmits O-RAN U-Plane I/Q samples via DOCA (NIC).

Key Components#

1. FapiRxHandler#

Location: ran/runtime/phy_ran_app/lib/include/phy_ran_app/fapi_rx_handler.hpp

The central component managing FAPI message processing:

  • Owns the nvIPC endpoint (shared memory transport)

  • Owns a Sample5GPipeline instance (Message Adapter)

  • Receives FAPI messages from testMAC

  • Processes messages through Sample5GPipeline’s state machine

  • Provides interfaces for slot indication, slot info, and pipeline execution

2. TX Thread: Slot Indication Timed Trigger#

Purpose: Periodically sends SLOT.indication FAPI messages to testMAC

Key Details:

  • Runs on dedicated RT core with 500μs period

  • Created using TimedTrigger with make_slot_indication_func()

  • Waits for cells to be configured/started before beginning

  • Triggers at SFN boundary with GPS-based timing calculation

3. RX Thread: FAPI Message Reception#

Purpose: Continuously polls nvIPC and processes incoming FAPI messages from testMAC

Key Details:

  • Runs on dedicated RT core as a single continuous task

  • Non-blocking polling with 100μs sleep when idle

  • Forwards messages to Sample5GPipeline for state machine processing

  • Important messages: UL_TTI_REQUEST, SLOT_RESPONSE

FAPI State Machine#

Owner: Sample5GPipeline (inside FapiRxHandler)

Tracks the FAPI protocol state for each cell and slot combination:

  • States: IDLE, UL_TTI_RECEIVED, SLOT_RESPONSE_RECEIVED, GRAPH_SCHEDULED

  • Transitions: * UL_TTI_REQUEST -> IDLE to UL_TTI_RECEIVED * SLOT_RESPONSE -> UL_TTI_RECEIVED to SLOT_RESPONSE_RECEIVED * Schedule uplink graph -> SLOT_RESPONSE_RECEIVED to GRAPH_SCHEDULED * Graph completes -> GRAPH_SCHEDULED to IDLE

Critical Logic: When the state machine transitions to SLOT_RESPONSE_RECEIVED, it invokes the GraphScheduleCallback, which triggers the uplink processing graph.

Initialization Phase#

The application initialization follows these steps:

  1. Argument Parsing & Logging Setup: Parse CLI args and setup logging.

  2. Signal Handlers Setup: Register SIGINT/SIGTERM handlers.

  3. Fronthaul Initialization: Load YAML config, init DPDK/DOCA, calc timing params.

  4. Create Task Schedulers: Setup uplink (3 RT workers) and RX (1 RT worker) schedulers.

  5. Create nvIPC Endpoint & FapiRxHandler: Setup shared memory transport and message adapter.

  6. Build Uplink Processing Graph: Create and connect C-Plane, U-Plane, and PUSCH tasks.

  7. Schedule RX Task: Start FAPI RX polling on dedicated core.

  8. Create Slot Indication Trigger: Setup 500us periodic trigger.

  9. Wait for Cells to Start: Poll until testMAC sends START.request.

  10. Start Slot Indication Trigger: Begin slot processing at SFN boundary.

Runtime Execution Flow#

Overview#

Once initialized, the application operates with three concurrent threads and one periodic trigger:

  • TX Thread (Trigger): Sends SLOT.indication every 500us.

  • RX Thread (Polling): Polls nvIPC, processes FAPI messages, updates state machine.

  • Uplink Workers (3 RT Cores): Executes C-Plane -> U-Plane -> PUSCH RX graph on-demand.

Detailed Sequence Diagram#

PHY RAN App Sequence Diagram

Flow for Single Slot Processing#

Time
  │
  │  (Slot N begins)
  │
  ├──> [TX Thread] Send SLOT.indication for Slot N
  │
  │  (testMAC processes scheduling)
  │
  ├──> [RX Thread] Receive UL_TTI_REQUEST for Slot N+4
  │       └─> FAPI State: IDLE -> UL_TTI_RECEIVED
  │
  ├──> [RX Thread] Receive SLOT_RESPONSE for Slot N+4
  │       └─> FAPI State: UL_TTI_RECEIVED -> SLOT_RESPONSE_RECEIVED
  │       └─> Trigger Uplink Graph for Slot N+4
  │
  │  (Uplink graph executes on dedicated workers)
  │
  ├──> [Network Core 1] C-Plane Processing
  │       └─> Convert FAPI messages to O-RAN C-Plane
  │       └─> DPDK TX to RU Emulator
  │       └─> RU prepares to send UL data
  │
  ├──> [Network Core 2] U-Plane Processing
  │       └─> DOCA NIC receives I/Q samples from RU
  │       └─> CUDA kernel reorders samples to device buffer
  │
  ├──> [Compute Core] PUSCH RX Processing
  │       └─> TensorRT pipeline executes with I/Q buffer
  │       └─> Generate CRC and measurements
  │       └─> Send results to testMAC via nvIPC
  │
  │  (Graph completes)
  │
  ├──> [FAPI State] GRAPH_SCHEDULED -> IDLE
  │
  │  (Ready for next slot)
  v
Time

Threading Model#

The application uses dedicated RT-priority cores for deterministic execution:

Thread/Task

Core

Priority

Category

Purpose

Slot Indication Trigger

--slot-indication-core

95

N/A

Send SLOT.indication every 500μs

FAPI RX Task

--rx-core

95

Default

Poll nvIPC, process FAPI messages

C-Plane Task

--cplane-core

95

Network

Convert FAPI to O-RAN, DPDK TX

U-Plane Task

--uplane-core

95

Network

DOCA RX, CUDA reordering

PUSCH RX Task

--pusch-core

95

Compute

TensorRT pipeline execution

All RT threads run at priority 95 with SCHED_FIFO scheduling policy.

API Reference#

using ran::phy_ran_app::GraphScheduleCallback = std::function<void(ran::fapi::SlotInfo slot)>#

Callback invoked when uplink graph should be scheduled

Called by Sample5GPipeline after all cells have sent SLOT_RESPONSE for a slot. Passes the slot number for which the graph should execute. Used in Phase 1 for graph scheduling.

Param slot:

[in] Slot number (0-19) for which to schedule the graph

using ran::phy_ran_app::PrintAndValidate = fluent::NamedType<bool, struct PrintAndValidateTag>#

Strong type for validation flag to avoid ambiguous boolean parameters.

constexpr std::uint32_t ran::phy_ran_app::DEFAULT_RX_CORE = 6#

Default core for FAPI RX thread (polls nvIPC)

constexpr std::uint32_t ran::phy_ran_app::DEFAULT_SLOT_INDICATION_CORE = 7#

Default core for slot indication trigger thread.

constexpr std::uint32_t ran::phy_ran_app::DEFAULT_CPLANE_CORE = 8#

Default core for C-Plane processing task.

constexpr std::uint32_t ran::phy_ran_app::DEFAULT_UPLANE_CORE = 9#

Default core for U-Plane processing task.

constexpr std::uint32_t ran::phy_ran_app::DEFAULT_PUSCH_CORE = 10#

Default core for PUSCH RX processing task.

constexpr std::uint32_t ran::phy_ran_app::DEFAULT_SLOT_AHEAD = 1#

Default slots to process ahead for C-Plane transmission (PHY timing chain)

constexpr std::uint32_t ran::phy_ran_app::DEFAULT_EXPECTED_CELLS = 1#

Default expected number of cells (limited by TensorRT engine)

ran::phy_ran_app::DECLARE_LOG_COMPONENT(
PhyRanApp,
App,
Config,
FapiRx,
CPlane,
UPlane,
MessageAdapter,
PuschRx,
SlotIndication,
Stats,
)#

Statistics and reporting.

PHY RAN App logging component categories

Usage:

RT_LOGC_INFO(ran::phy_ran_app::PhyRanApp::App, "Application started");
RT_LOGC_DEBUG(ran::phy_ran_app::PhyRanApp::FapiRx, "Received message");

std::function<void()> ran::phy_ran_app::make_process_cplane_func(
ran::fronthaul::Fronthaul &fronthaul,
ran::message_adapter::IFapiSlotInfoProvider &slot_info_provider,
std::atomic<ran::fapi::SlotInfo> &slot_info,
std::chrono::nanoseconds t0,
std::chrono::nanoseconds tai_offset,
)#

Create C-Plane processing task function.

Processes C-Plane messages and transmits to RU via DPDK. Uses IFapiSlotInfoProvider to access slot info and accumulated FAPI messages. Absolute slot is obtained from slot_info_provider (monotonic counter).

Warning

LIFETIME SAFETY: Captures fronthaul, slot_info_provider, and slot_info by reference. Caller must ensure objects outlive returned function.

Parameters:
  • fronthaul[in] Fronthaul instance reference

  • slot_info_provider[in] Slot info provider for slot state and accumulated FAPI messages

  • slot_info[inout] Atomic slot info for current slot timing

  • t0[in] Time for SFN 0, subframe 0, slot 0

  • tai_offset[in] TAI offset

Returns:

Function to process C-Plane for a slot

std::function<void()> ran::phy_ran_app::make_process_uplane_func(
ran::fronthaul::Fronthaul &fronthaul,
ran::message_adapter::IFapiSlotInfoProvider &slot_info_provider,
std::atomic<ran::fapi::SlotInfo> &slot_info,
)#

Create U-Plane processing task function.

Processes U-Plane packets received via DOCA and orders them. Uses IFapiSlotInfoProvider to check for UL data and get current slot timing. Absolute slot is obtained from slot_info_provider (monotonic counter).

Warning

LIFETIME SAFETY: Captures fronthaul, slot_info_provider, and slot_info by reference. Caller must ensure objects outlive returned function.

Parameters:
  • fronthaul[in] Fronthaul instance reference

  • slot_info_provider[in] Slot info provider for slot state and accumulated FAPI messages

  • slot_info[inout] Atomic slot info for current slot timing

Returns:

Function to process U-Plane per slot

std::function<void()> ran::phy_ran_app::make_process_pusch_func(
ran::message_adapter::IPipelineExecutor &pipeline_executor,
std::atomic<ran::fapi::SlotInfo> &slot_info,
)#

Create PUSCH RX processing task function.

Executes PUSCH pipeline for the current slot via IPipelineExecutor interface. Phase 1: Invokes pipeline execution after U-Plane processing.

Warning

LIFETIME SAFETY: Captures pipeline_executor and slot_info by reference. Caller must ensure objects outlive returned function.

Parameters:
  • pipeline_executor[in] Pipeline executor for launching PUSCH pipelines

  • slot_info[inout] Atomic slot info for getting current slot

Returns:

Function to process PUSCH RX per slot

std::function<void()> ran::phy_ran_app::make_slot_indication_func(
ran::message_adapter::ISlotIndicationSender &slot_sender,
const ran::phy_ran_app::FapiRxHandler &fapi_rx_handler,
std::atomic_bool &running,
)#

Create slot indication function for timed trigger.

Creates a function that sends SLOT.indication messages to all running cells via the ISlotIndicationSender interface. Handles shutdown condition:

  • All cells stopped during runtime

Note

main() guarantees cells are running before trigger starts, so this function only needs to detect runtime shutdown (cells stopping after startup).

Warning

LIFETIME SAFETY: Captures slot_sender, fapi_rx_handler, and running by reference. Caller must ensure objects outlive returned function.

Parameters:
  • slot_sender[in] Interface for sending slot indications

  • fapi_rx_handler[in] Reference to FapiRxHandler (for cell count)

  • running[inout] Atomic flag to signal shutdown

Returns:

Function to be called periodically by TimedTrigger

void ran::phy_ran_app::setup_logging()#

Setup logging for the application.

Configures quill logging and registers all component categories. Adapted from fronthaul_app_utils.cpp::setup_logging().

tl::expected<AppArguments, std::string> ran::phy_ran_app::parse_arguments(
int argc,
char **argv,
)#

Parse command-line arguments.

Uses CLI11 to parse arguments with validation. Adapted from fronthaul_app_utils.cpp::parse_arguments().

Parameters:
  • argc[in] Argument count

  • argv[in] Argument vector

Returns:

Parsed arguments or error message

std::string ran::phy_ran_app::create_nvipc_config()#

Create NVIPC configuration YAML string.

Generates NVIPC configuration for PHY side (primary). Adapted from fapi_sample_utils.cpp::create_nvipc_config().

Returns:

NVIPC configuration as YAML string

static framework::net::EnvConfig ran::phy_ran_app::create_network_config(
const std::string &nic_pcie_addr,
std::uint32_t gpu_device_id,
std::uint32_t dpdk_core,
std::uint32_t mtu_size,
)#

Create network environment configuration for DPDK and DOCA.

Helper function adapted from fronthaul_app_utils.cpp::create_network_config().

Parameters:
  • nic_pcie_addr[in] NIC PCIe address

  • gpu_device_id[in] GPU device ID

  • dpdk_core[in] DPDK lcore

  • mtu_size[in] MTU size for mempool

Returns:

Network environment configuration

tl::expected<ran::fronthaul::FronthaulConfig, std::string> ran::phy_ran_app::create_fronthaul_config(
const std::string &yaml_config_path,
const std::string &nic_pcie_addr,
std::uint32_t gpu_device_id,
std::uint32_t dpdk_core,
std::uint32_t slot_ahead,
)#

Create Fronthaul configuration from YAML file.

Parses Fronthaul YAML configuration and creates FronthaulConfig structure. Adapted from fronthaul_app_utils.cpp::create_fronthaul_config_from_yaml().

Parameters:
  • yaml_config_path[in] Path to Fronthaul YAML configuration file

  • nic_pcie_addr[in] NIC PCIe address for DPDK

  • gpu_device_id[in] GPU device ID

  • dpdk_core[in] DPDK lcore for C-Plane processing

  • slot_ahead[in] Number of slots ahead for C-Plane transmission

Returns:

FronthaulConfig if successful, error message on failure

TimingResult ran::phy_ran_app::calculate_timing_parameters(
const framework::task::StartTimeParams &start_params,
std::uint32_t slot_ahead,
std::uint64_t slot_period_ns,
)#

Calculate timing parameters for C-Plane transmission and trigger start.

Computes GPS-based timing for slot indication trigger and C-Plane transmission. Duplicated from fronthaul_app_utils.cpp::calculate_timing_parameters().

Parameters:
  • start_params[in] Start time parameters (current time, period, GPS alpha/beta)

  • slot_ahead[in] Number of slots to process ahead for C-Plane

  • slot_period_ns[in] Slot period in nanoseconds

Returns:

Timing parameters including start time, t0, and TAI offset

bool ran::phy_ran_app::print_and_validate_stats(
const ran::message_adapter::PhyStats &stats,
const PrintAndValidate fail_on_errors,
)#

Print runtime statistics and optionally validate (fail on errors)

Always prints per-cell and total CRC failure counts using RT_LOGC macros. When validation is enabled, returns false if any CRC failures detected.

Parameters:
  • stats[in] The runtime statistics to print and validate

  • fail_on_errors[in] Whether to fail (return false) on detection of errors

Returns:

true if validation passes or is disabled, false if validation enabled and errors detected

struct AppArguments#
#include <phy_ran_app_utils.hpp>

Command-line arguments for phy_ran_app (based on fronthaul_app pattern + FAPI extensions)

Public Members

std::string nic_pcie_addr#

DU NIC PCIe address.

std::string config_file_path#

Path to YAML configuration file.

std::uint32_t rx_core = {DEFAULT_RX_CORE}#

FAPI RX thread core (polls nvIPC)

std::uint32_t slot_indication_core = {DEFAULT_SLOT_INDICATION_CORE}#

Slot indication trigger core.

std::uint32_t cplane_core = {DEFAULT_CPLANE_CORE}#

C-Plane processing core.

std::uint32_t uplane_core = {DEFAULT_UPLANE_CORE}#

U-Plane processing core.

std::uint32_t pusch_core = {DEFAULT_PUSCH_CORE}#

PUSCH RX processing core.

std::optional<std::size_t> num_slots#

Number of slots (unlimited if nullopt)

std::uint32_t slot_ahead = {DEFAULT_SLOT_AHEAD}#

Slots to process ahead.

std::uint32_t gpu_device_id = {0}#

GPU device ID.

std::uint32_t expected_cells = {DEFAULT_EXPECTED_CELLS}#

Expected cells (FAPI-specific)

bool validate = {false}#

Validate runtime statistics and fail on errors.

class FapiRxHandler#
#include <fapi_rx_handler.hpp>

FAPI RX message handler

Owns the nvIPC endpoint and manages a single Sample5GPipeline instance for processing FAPI messages. Replaces FapiState in the phy_ran_app architecture.

Responsibilities:

  • Own nvIPC endpoint lifecycle

  • Receive messages from nvIPC in polling loop

  • Forward messages to Sample5GPipeline for processing

  • Provide interface access for slot indication and message capture

Thread-safety: Not thread-safe. Should be called from single FAPI RX thread. However, interface accessors (get_slot_indication_sender, get_slot_info_provider, get_pipeline_executor) return pointers that may be used from different threads if the underlying implementation is thread-safe.

Public Types

using InitParams = ran::fapi::FapiState::InitParams#

Initialization parameters type

Reuses FapiState::InitParams for compatibility with existing nvIPC configuration infrastructure.

Public Functions

explicit FapiRxHandler(
const InitParams &params,
GraphScheduleCallback on_graph_schedule,
framework::pipeline::IPipelineOutputProvider &output_provider,
)#

Construct FapiRxHandler

Creates nvIPC endpoint and Sample5GPipeline with graph schedule callback.

Parameters:
  • params[in] Initialization parameters

  • on_graph_schedule[in] Callback for scheduling uplink graph

  • output_provider[in] Reference to pipeline output provider interface (non-owning)

Throws:

std::runtime_error – if nvIPC creation fails

~FapiRxHandler()#

Destructor

Cleans up Sample5GPipeline and closes nvIPC endpoint.

FapiRxHandler(const FapiRxHandler&) = delete#

Copy constructor (deleted - owns nvIPC endpoint)

FapiRxHandler &operator=(const FapiRxHandler&) = delete#

Assignment operator (deleted - owns nvIPC endpoint)

FapiRxHandler(FapiRxHandler&&) = delete#

Move constructor (deleted - owns nvIPC endpoint)

FapiRxHandler &operator=(FapiRxHandler&&) = delete#

Move assignment operator (deleted - owns nvIPC endpoint)

void receive_and_process_messages(const std::atomic<bool> &running)#

Receive and process FAPI messages

Polling loop with non-blocking receive. Sleeps 100µs when no message is available. Runs until running flag is cleared.

This method should be called from the FAPI RX task in the task graph.

Parameters:

running[in] Atomic flag to control loop execution

ran::message_adapter::ISlotIndicationSender *get_slot_indication_sender(
)#

Get slot indication sender interface

Returns pointer to ISlotIndicationSender interface for use by the slot indication timer. This design avoids the code smell of having FapiRxHandler (RX concern) directly exposing send methods (TX concern).

Returns:

Pointer to ISlotIndicationSender, or nullptr if not available

ran::message_adapter::IFapiSlotInfoProvider *get_slot_info_provider(
)#

Get slot info provider for C-plane task (Phase 1)

Returns pointer to IFapiSlotInfoProvider interface for use by the C-plane processing task to access current slot info and accumulated UL-TTI messages.

Returns:

Pointer to IFapiSlotInfoProvider interface, or nullptr if not available

ran::message_adapter::IPipelineExecutor *get_pipeline_executor()#

Get pipeline executor for PUSCH task (Phase 1)

Returns pointer to IPipelineExecutor interface for use by the PUSCH RX processing task to launch pipeline execution for a given slot.

Returns:

Pointer to IPipelineExecutor interface, or nullptr if not available

std::size_t get_num_cells_running() const#

Get number of currently running cells

Thread-safe: Delegates to Sample5GPipeline which uses atomic operations.

Returns:

Count of cells in RUNNING state, or 0 if pipeline not initialized

const ran::message_adapter::PhyStats &get_stats() const#

Get snapshot of pipeline PHY statistics.

Thread-safe method to retrieve current statistics from the underlying pipeline.

Returns:

Snapshot of current pipeline statistics

struct TimingResult#
#include <phy_ran_app_utils.hpp>

Timing parameters result.

Public Members

std::uint64_t start_time_ns = {}#

Start time in nanoseconds.

std::chrono::nanoseconds t0 = {}#

Time for SFN 0, subframe 0, slot 0.

std::chrono::nanoseconds tai_offset = {}#

TAI offset.