A software O-RAN radio unit (O-RU) runs the radio-unit half of the O-RAN 7.2x fronthaul — the low
physical layer (FFT/IFFT, cyclic prefix, PRACH extraction) — as a program on a server, and uses a
USRP software-defined radio as its RF front end. COSMOS runs two: ProtO-RU, which pairs with the
OCUDU (srsRAN) O-DU, and nr-oru, which ships with Duranta (OAI) and pairs with the Duranta O-DU.
Both work end to end with a commercial Quectel modem: ProtO-RU on an N310 on sb1, nr-oru on a B210
on osc.
Because both ends of the fronthaul are software, both ends report counters — on-time, early and late
windows, sequence errors, lower-PHY real-time failures — that a commercial O-RU never exposes. That
makes the soft O-RUs the place to learn what a healthy 7.2 link looks like before driving a vendor
radio.
Other O-RUs: fleet overview · Benetel RAN650 · LiteOn FlexFi · Foxconn RPQN-7801 · Software O-RUs
| ProtO-RU (OCUDU) | nr-oru (Duranta) |
|
|---|---|---|
| What it is | ProtO-RU (NUS CIR), a Category-A O-RU built on the OCUDU/srsRAN code base; binary ru_emulator |
OAI's soft O-RU, part of the Duranta tree; binary nr-oru |
| Version on COSMOS | ProtO-RU branch dev @ a3ba4b9 |
Duranta tag 2026.w37 + the COSMOS nr-oru fix set |
| O-DU it pairs with | OCUDU gnb (ru_ofh), dev @ 36128c56ef + T1a/Ta4 range patch to 5000 µs |
Duranta nr-softmodem (fhi_72, Intel xran on DPDK) |
| Image | ocudu.ndz (both hosts; ProtO-RU binaries from the 2026-09-15 build) |
duranta.ndz (both hosts); duranta-20260917 carries the fix set |
| Verified radio | USRP N310 sdr1-s1-lg1.sb1 (10.39.2.1, XG FPGA, 2× 10 GbE) |
USRP B210 serial 3080C7F, USB 3.0 on srv20.osc |
| Other radios | B210 on osc ran end to end but degrades under load (see Performance); upstream also supports X410 | N310 on sb1: not verified end to end |
| Band / carrier | n41, DL ARFCN 519000, SSB ARFCN 516030 | n78, 3550.08 MHz, SSB ARFCN 636672, PointA 636060 |
| Bandwidth / PRB / SCS | 40 MHz, 30 kHz SCS, 61.44 Msps | 51 PRB / 20 MHz, 30 kHz SCS, 30.72 Msps |
| Duplex / TDD | TDD DDDSU, 2.5 ms; S slot 10 DL / 2 UL symbols | TDD DDDSU, 2.5 ms; S slot 6 DL / 0 UL symbols |
| Antennas | 1T1R (2×2 possible at 20 MHz, see Tuning) | 1T1R |
| TX / RX gain | tx_gain 37, rx_gain 40 (N310 dB), with ProtO-RU's gain_backoff: 12 |
OAI_ORU_TX_GAIN=10 (79.75 dB actual), OAI_ORU_RX_GAIN=114 (70 dB actual) |
| Fronthaul transport | raw AF_PACKET socket on the physical function DATA1a, both ends; VLAN 4 tagged in software; promiscuous |
DPDK on two E810 virtual functions (VFs) per host, vfio-pci, VLAN 4 as the VF port VLAN |
| Compression | BFP 9 (block floating point) for DL, UL and PRACH; static headers | none — uncompressed 16-bit IQ only |
| M-Plane | none | none |
| Timing | PTP G.8275.1 from the fabric boundary clock on both hosts (LLS-C3); phc2sys -w |
PTP G.8275.1 on both hosts; phc2sys stepped once, then slewed |
| Windows | O-DU T1a_cp 2250–2435 µs, T1a_up 2050–2215 µs, Ta4 800–1480 µs | O-DU T1a_cp_dl 285–470, T1a_cp_ul 285–429, T1a_up 300–450, Ta4 900–950 µs; O-RU T2a_cp 285–1200, T2a_up 300–1200 µs |
| PRACH | index 159 (short format B4, the only one ProtO-RU supports), eAxC 4 | index 157 (the only one nr-oru supports), prach_eaxc_offset = 1 |
| Addresses | each host's own DATA1a MAC (read with protoru-render mac) |
O-DU VFs 00:11:22:33:64:68/69, O-RU VFs 00:11:22:33:64:66/67 |
| PCI on COSMOS | 3 | 3 (cell id 3) |
| Power control | the USRP's own node (omf tell -a on -t sdr1-s1-lg1 on console.sb1) |
the B210 is USB-powered from the O-RU server |
| Status | works — registration, PDU session, data (2026-09-15, load-back re-checked the same day) | works — registration, PDU session, data (2026-09-16) |
A soft O-RU has no stored state of its own: everything is in a configuration file on the O-RU
host, read at start-up. There is no M-Plane, no golden state to restore and no cold-cycle rule for
the O-RU itself — restarting the program resets it. The O-DU and O-RU files must agree on the
carrier, PRACH, TDD pattern, fronthaul MACs, compression and timing windows; each section below
shows the pair.
Both soft O-RUs need the same host preparation as an O-DU on both servers: per-host CPU isolation
(cosmos-ofh-isolation --apply and a reboot, once after every omf load), PTP locked to the fabric,
NIC interrupts moved off the isolated cores, and, for nr-oru, SR-IOV VFs with hugepages. Each item is
explained on Real-time host preparation.
Keep the soft O-RU host and its USRP in the same domain. A 30.72 or 61.44 Msps IQ stream across the
osc↔sb1 data gateway contends with other users' USRP streams.
The radios themselves are managed as described on SDR hardware: the N310
has its own power target and management address; the B210 must link at USB 3.0
(speed=5000Mbps in sysfs, no Operating over USB 2 from UHD) — a host power cycle does not change the
link speed, reseating or replacing the cable does.
| Parameter | ProtO-RU (OCUDU) | nr-oru (Duranta) |
|---|---|---|
| Band | n41 | n78 |
| DL ARFCN / centre | 519000 | carrier 3550080 kHz |
| SSB ARFCN | 516030 | 636672 |
| PointA | derived by OCUDU | 636060 |
| Bandwidth | 40 MHz | 51 PRB (20 MHz) |
| Sample rate | 61.44 Msps (N310 master clock 122.88 MHz) | 30.72 Msps (B210 master clock 30.72 MHz) |
| TDD | period 5 slots: 3 DL, S (10 DL / 2 UL symbols), 1 UL | period 5 slots: 3 DL, S (6 DL / 0 UL symbols), 1 UL |
| PRACH | index 159, root 1, zero-correlation zone 0, frequency start 0 | index 157, root 1, zero-correlation zone 0, frequency start 0 |
| PCI / TAC / PLMN | 3 / 7 / 00101 | 3 / 1 / 00101 |
| Gains | tx_gain 37, rx_gain 40, gain_backoff 12 |
OAI_ORU_TX_GAIN=10, OAI_ORU_RX_GAIN=114 |
| UE (verified) | Quectel RM520N-GL on sdr1-in3.sb1, RSRP −97 dBm, SINR 18 dB |
Quectel RM520N-GL on node1.osc, RSRP −91 dBm, SINR 18 dB |
The nr-oru cell is 51 PRB because OAI's B200/B210 driver cannot run the 106-PRB / 61.44 Msps carrier
of the upstream reference.
| Item | ProtO-RU (OCUDU) | nr-oru (Duranta) |
|---|---|---|
| DL / UL eAxC | dl_port_id: [0], ul_port_id: [0] (both ends) |
0 (single antenna) |
| PRACH eAxC | prach_port_id: [4] (both ends; [4, 5] for 2×2) |
O-RU prach_eaxc_offset = 1; O-DU prach_config = { kbar = 4; }, O-RU prach_kbar = 4 |
| Compression | BFP 9 for DL, UL and PRACH; enable_ul/dl_static_compr_hdr: true on the O-DU, is_ul/dl_static_compr_hdr: true on the O-RU |
O-DU iq_width = 16, iq_width_prach = 16, comp_hdr_type = "dynamic"; O-RU comp_type = "none" |
| VLAN / PCP | VLAN 4 tagged in software on both ends; PCP not set | VLAN 4 as each VF's port VLAN (ip link set <PF> vf N mac … vlan 4 spoofchk off trust on); PCP not set |
| O-DU MAC as the O-RU expects it | the O-DU host's DATA1a MAC (du_mac_addr in the O-RU file) |
00:11:22:33:64:68, 00:11:22:33:64:69 (du_mac_addr in the O-RU file) |
| O-RU MAC | the O-RU host's DATA1a MAC |
00:11:22:33:64:66, 00:11:22:33:64:67 |
| DL windows | O-DU T1a_cp_dl 2250–2435, T1a_up 2050–2215 µs; O-RU T2a_cp_dl 2235–2435, T2a_up 2015–2215 µs | O-DU T1a_cp_dl 285–470, T1a_up 300–450 µs; O-RU T2a_cp 285–1200, T2a_up 300–1200 µs |
| UL windows | O-DU T1a_cp_ul 2250–2435, Ta4 800–1480 µs; O-RU T2a_cp_ul 2235–2435, Ta3 800–1200 µs | O-DU T1a_cp_ul 285–429, Ta4 900–950 µs |
| MTU | 9000 on DATA1a (host prep) |
O-DU mtu = 9600, O-RU mtu = 9216 |
A software O-RU needs far more lead time than a hardware one. ProtO-RU asks for C-plane up to 2435 µs
ahead of air time so it can run the low PHY and hand samples to UHD; stock OCUDU caps T1a and Ta4 at
1960 µs, and the ocudu.ndz build raises the limit to 5000 µs. nr-oru's receive window is 285–1200 µs;
T1a values tuned for a routed N310 (up to 2400 µs) put every DL packet outside it.
Where it lives. On the ocudu.ndz image: ru_emulator at /usr/local/bin/ru_emulator (source
/opt/proto-ru), the O-DU at /opt/ocudu/build/apps/gnb/gnb, and the COSMOS renderer, launcher and
host-prep scripts in /root/proto-ru/ (protoru-render.py, protoru-run.sh, protoru-host-prep.sh;
repository copy configs/protoru/). ProtO-RU's own guides are in /opt/proto-ru/proto-ru/
(ARCHITECTURE.md, TIME_SYNC.md, TROUBLESHOOTING.md, KNOWN_ISSUES.md).
How the configs are made. Nothing host-specific is written by hand. protoru-render.py reads the
fronthaul MAC from sysfs and the real-time cores from the host's isolated set on the fronthaul NIC's
NUMA node, then writes the O-DU file (du) or the O-RU file (ru). Core rules: on the O-DU,
ru_cpus = the first two isolated cores, OFH txrx_cpus = the next four, main pool = every other CPU
except 0; on the O-RU, timing_cpus = the first isolated core, ofh_cpus = the next three (ProtO-RU
needs at least three per cell), sdr.ru_cpus = the next three. The defaults are the verified sb1 cell;
--bw, --band, --arfcn, --layers, --device-args, --tx-gain, --rx-gain and --clock override
them.
The O-RU file — the renderer's output for the verified sb1 run (O-RU host srv2-lg1, isolated CPUs
7,9,11,13,15,17,19,21,23, MACs from that run), written to /root/proto-ru/ru.yml:
# ProtO-RU (NUS-CIR dev) soft O-RU - rendered by protoru-render.py on srv2-lg1
# fronthaul DATA1a (50:7c:6f:6c:76:38) <- O-DU 50:7c:6f:6c:76:9c; USRP type=n3xx,addr=10.39.2.1,master_clock_rate=122.88e6,recv_frame_size=7900,send_frame_size=7900; isolated RT cores [7, 9, 11, 13, 15, 17, 19, 21, 23]
log:
filename: /tmp/proto-ru.log
level: info
ru_emu:
timing_cpus: [7]
cells:
- network_interface: DATA1a
ru_mac_addr: 50:7c:6f:6c:76:38
du_mac_addr: 50:7c:6f:6c:76:9c
vlan_tag: 4 # tagged in software; the PF has no port VLAN
enable_promiscuous: true
bandwidth: 40
common_scs: 30
dl_arfcn: 519000
band: 41
dl_port_id: [0]
ul_port_id: [0]
prach_port_id: [4]
prach_format: short
compr_method_dl: bfp
compr_bitwidth_dl: 9
compr_method_ul: bfp
compr_bitwidth_ul: 9
compr_method_prach: bfp
compr_bitwidth_prach: 9
is_ul_static_compr_hdr: true
is_dl_static_compr_hdr: true
iq_scaling: 1.0
t2a_max_cp_dl: 2435
t2a_min_cp_dl: 2235
t2a_max_cp_ul: 2435 # the OCUDU value (2835 is for OAI)
t2a_min_cp_ul: 2235
t2a_max_up: 2215
t2a_min_up: 2015
ta3_max_up: 1200
ta3_min_up: 800
ofh_cpus: [9, 11, 13] # >= 3 per cell, or ProtO-RU's OFH threads share and go LATE
sdr:
device_driver: uhd
device_args: type=n3xx,addr=10.39.2.1,master_clock_rate=122.88e6,recv_frame_size=7900,send_frame_size=7900
srate: 61.44
tx_gain: 37
rx_gain: 40
gain_backoff: 12
enable_clipping: true
power_ceiling: -0.1
clock_source: internal
sync_source: internal
max_proc_delay: 2
execution_profile: auto
ru_cpus: [15, 17, 19]
pinning_policy: mask
The O-DU file — the renderer's output for the same run (O-DU host srv1-lg1, 24 CPUs, same
isolated set), written to /root/proto-ru/du.yml:
# OCUDU O-DU for a ProtO-RU soft O-RU - rendered by protoru-render.py on srv1-lg1
# fronthaul DATA1a (50:7c:6f:6c:76:9c) -> O-RU 50:7c:6f:6c:76:38; isolated RT cores [7, 9, 11, 13, 15, 17, 19, 21, 23]
cu_cp:
amf:
addrs: 127.0.0.5 # the ocudu image's local Open5GS
port: 38412
bind_addrs: 127.0.0.1
supported_tracking_areas:
- tac: 7
plmn_list:
- plmn: "00101"
tai_slice_support_list:
- sst: 1
ru_ofh:
ru_bandwidth_MHz: 40
t1a_max_cp_dl: 2435 # ProtO-RU profile; > 1960 needs the COSMOS OCUDU build
t1a_min_cp_dl: 2250
t1a_max_cp_ul: 2435
t1a_min_cp_ul: 2250
t1a_max_up: 2215
t1a_min_up: 2050
ta4_max: 1480
ta4_min: 800
is_prach_cp_enabled: true
ignore_ecpri_payload_size: true
compr_method_ul: bfp
compr_bitwidth_ul: 9
compr_method_dl: bfp
compr_bitwidth_dl: 9
compr_method_prach: bfp
compr_bitwidth_prach: 9
enable_ul_static_compr_hdr: true
enable_dl_static_compr_hdr: true
iq_scaling: 16
cells:
- network_interface: DATA1a
ru_mac_addr: 50:7c:6f:6c:76:38
du_mac_addr: 50:7c:6f:6c:76:9c
vlan_tag_cp: 4
vlan_tag_up: 4
enable_promiscuous: true # srsRAN's AF_PACKET socket receives nothing otherwise
dl_port_id: [0]
ul_port_id: [0]
prach_port_id: [4]
cell_cfg:
dl_arfcn: 519000
band: 41
channel_bandwidth_MHz: 40
common_scs: 30
plmn: "00101"
tac: 7
pci: 3
nof_antennas_dl: 1
nof_antennas_ul: 1
prach:
prach_config_index: 159 # short format B4: the only PRACH ProtO-RU supports
prach_root_sequence_index: 1
zero_correlation_zone: 0
prach_frequency_start: 0
tdd_ul_dl_cfg:
dl_ul_tx_period: 5
nof_dl_slots: 3
nof_dl_symbols: 10
nof_ul_slots: 1
nof_ul_symbols: 2
expert_phy:
allow_request_on_empty_uplink_slot: true
max_proc_delay: 10
pusch_dec_max_iterations: 15
metrics: # the honest fronthaul counter (ether_rx rx_bytes)
enable_log: true
layers:
enable_ru: true
enable_sched: true
periodicity:
du_report_period: 1000
log:
filename: /tmp/ocudu-protoru-du.log
all_level: warning
ofh_level: info
expert_execution:
threads:
main_pool:
nof_threads: 16
affinities:
main_pool_cpus: 1,2,3,4,5,6,8,10,12,14,16,18,19,20,21,22,23
ofh:
txrx_cpus: ["11,13,15,17"]
cell_affinities:
- ru_cpus: 7,9
On another host the MACs and CPU lists differ; render the pair on the hosts you use.
Launch. On both hosts protoru-host-prep.sh DATA1a (performance governor, deep C-states off,
DATA1a promiscuous with MTU 9000, IRQs off the isolated cores, rmem_max/wmem_max 32 MB for UHD,
PTP step-then-slew). Then exchange MACs, start the O-RU first and the O-DU second:
ssh root@srv1-lg1 'protoru-render mac' # the O-DU MAC
ssh root@srv2-lg1 'protoru-render mac' # the O-RU MAC
ssh root@srv2-lg1 'bash /root/proto-ru/protoru-run.sh ru <O-DU MAC>'
ssh root@srv1-lg1 'bash /root/proto-ru/protoru-run.sh du <O-RU MAC>'
protoru-run.sh ru routes the USRP address over DATA2a (the E810 on the same NUMA node as the
isolated cores), renders ru.yml and starts ru_emulator; protoru-run.sh du starts Open5GS,
provisions the COSMOS SIM catalogue, renders du.yml and starts gnb. Both launch under
taskset -c 0-$(($(nproc --all)-1)) with stdin held open: isolcpus removes the isolated cores from
the default mask, and srsRAN rejects configured cores outside the process mask. The full procedure is
the OCUDU with ProtO-RU tutorial.
Health. O-RU: the per-second RX table in /root/proto-ru/ru-stdout.log (RX_ON_TIME carries the
traffic, RX_LATE and RX_SEQ_ERR stay 0). O-DU: OFH sector#0 metrics in
/tmp/ocudu-protoru-du.log with rx_on_time equal to rx_total.
Where it lives. On the duranta.ndz image: nr-oru and nr-softmodem in
/opt/duranta/cmake_targets/ran_build/build; bring-up scripts /root/du-oru-bringup.sh (O-DU) and
/root/oru-bringup.sh (O-RU). The verified config pair is configs/nroru-b210/gnb-b210oru.conf and
configs/nroru-b210/ru-b210.conf in the duranta-cosmos repository, copied to /root/oru/ on the
O-DU and O-RU hosts. The bring-up rewrites the AMF address in the O-DU file at launch, so that line
differs between hosts.
The COSMOS nr-oru fix set (configs/nroru-b210/patches/nr-oru-fixes-2026-09-16.diff, carried
by duranta-20260917): the O-RU drains the USRP's start-up backlog before anchoring its frame counter;
it drops pre-anchor DL symbols (otherwise the DL reorder ring deadlocks at start); the UHD driver gets a
get_timestamp hook, enabled only when OAI_ORU_DEV_TS is set, so the monolithic USRP gNB is
unaffected; the RX gain is applied from OAI_ORU_RX_GAIN (upstream nr-oru leaves it at 0 dB);
and the packet-processor statistics print every 256 frames instead of only at exit.
The O-RU file (ru-b210.conf, exact):
# Duranta nr-oru soft O-RU on a USRP B210 (srv20.osc) -- 51 PRB / 20 MHz n78 @ 30.72 Msps, O-RAN 7.2 to the srv19 O-DU (pair: gnb-b210oru.conf).
# From upstream ru.sa.band77.106prb.fhi72.1x1-oru.conf (tag 2026.w36). NOTE: core arrays must be libconfig LISTS "( )"; w36 reads "mu" (the shipped file says numerology).
# SPDX-License-Identifier: LicenseRef-CSSL-1.0
ORUs = (
{
tx_bw = [51];
rx_bw = [51];
carrier_tx = [3550080];
carrier_rx = [3550080];
prach_config_index = 157;
prach_msg1_start = 0;
tdd_period = 5;
num_dl_slots = 3;
num_dl_symbols = 6;
num_ul_slots = 1;
num_ul_symbols = 0;
numerology = 1;
tx_core = 9;
fronthaul = {
dpdk_devices = ("0000:12:01.0", "0000:12:01.1"); # one VF can be used as well
rx_core = 5;
north_cores = (4);
south_core = 7;
ul_worker_cores = (6, 8);
du_mac_addr = ("00:11:22:33:64:68", "00:11:22:33:64:69");
mtu = 9216;
T2a_up = (300, 1200);
T2a_cp = (285, 1200);
prach_eaxc_offset = 1;
prach_kbar = 4;
extra_eal_args = ("--file-prefix", "ru");
comp_type = "none"; # options: "none", "bfp", "blkscale", "ulaw"
}
});
RUs = (
{
local_rf = "no";
nb_tx = 1;
nb_rx = 1;
att_tx = 0;
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 75;
sf_extension = 0;
eNB_instances = [0];
sl_ahead = 5;
tr_preference = "raw_if4p5"; # important: activate FHI7.2
sdr_addrs = "type=b200,master_clock_rate=30.72e6,num_send_frames=256,num_recv_frames=256";
do_precoding = 0; # needs to match O-RU configuration
# PRACH configuration
num_tp_cores = 2;
tp_cores = [10,11];
});
log_config :
{
global_log_level = "info";
hw_log_level = "info";
phy_log_level = "info";
};
The O-DU file (gnb-b210oru.conf, exact as committed; the AMF address is rewritten at launch):
# Duranta O-DU (srv19.osc) for the nr-oru soft O-RU on a B210 (srv20.osc): 51 PRB / 20 MHz band n78, PCI 3 / cell-id 3.
# Derived from upstream gnb.sa.band77.106prb.fhi72.1x1-oru.conf (tag 2026.w36) + the 51-PRB band-78 cell of
# gnb.sa.band78.51PRB.bladerf2.0xa4.conf. Pair file: ru-b210.conf. AMF IP is rewritten at launch (patch-amf.sh).
Active_gNBs = ( "gNB-OAI");
Asn1_verbosity = "none";
gNBs =
(
{
gNB_ID = 0xe00;
gNB_name = "gNB-OAI";
tracking_area_code = 1;
plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) });
nr_cellid = 3L;
pdsch_AntennaPorts_N1 = 1;
maxMIMO_layers = 1;
min_rxtxtime = 6;
servingCellConfigCommon = (
{
physCellId = 3;
absoluteFrequencySSB = 636672;
dl_frequencyBand = 78;
dl_absoluteFrequencyPointA = 636060;
dl_offstToCarrier = 0;
dl_subcarrierSpacing = 1;
dl_carrierBandwidth = 51;
initialDLBWPlocationAndBandwidth = 13750;
initialDLBWPsubcarrierSpacing = 1;
initialDLBWPcontrolResourceSetZero = 11;
initialDLBWPsearchSpaceZero = 0;
ul_frequencyBand = 78;
ul_offstToCarrier = 0;
ul_subcarrierSpacing = 1;
ul_carrierBandwidth = 51;
pMax = 23;
initialULBWPlocationAndBandwidth = 13750;
initialULBWPsubcarrierSpacing = 1;
prach_ConfigurationIndex = 157;
prach_msg1_FDM = 0;
prach_msg1_FrequencyStart = 0;
zeroCorrelationZoneConfig = 0;
preambleReceivedTargetPower = -90;
preambleTransMax = 7;
powerRampingStep = 2;
ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 15;
ra_ContentionResolutionTimer = 7;
rsrp_ThresholdSSB = 19;
prach_RootSequenceIndex_PR = 2;
prach_RootSequenceIndex = 1;
msg1_SubcarrierSpacing = 1,
restrictedSetConfig = 0,
msg3_DeltaPreamble = 2;
p0_NominalWithGrant = -80; # soft O-RU on an N310: +20 dB UL target (UE underestimates PL: ssPBCH_BlockPower 0)
pucchGroupHopping = 0;
hoppingId = 0;
p0_nominal = -96;
ssb_PositionsInBurst_Bitmap = 0x1;
ssb_periodicityServingCell = 2;
dmrs_TypeA_Position = 0;
subcarrierSpacing = 1;
referenceSubcarrierSpacing = 1;
dl_UL_TransmissionPeriodicity = 5;
nrofDownlinkSlots = 3;
nrofDownlinkSymbols = 6;
nrofUplinkSlots = 1;
nrofUplinkSymbols = 0;
ssPBCH_BlockPower = 0;
}
);
SCTP :
{
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
amf_ip_address = ({ ipv4 = "192.168.70.133"; });
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.129";
GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.129";
GNB_PORT_FOR_NGU = 2152;
};
}
);
MACRLCs = (
{
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 200;
pucch_TargetSNRx10 = 200;
pusch_FailureThres = 100;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 110;
pucch0_dtx_threshold = 80;
pusch_dtx_threshold = -100;
tx_amp_backoff_dB = 36;
L1_rx_thread_core = 8;
L1_tx_thread_core = 10;
phase_compensation = 0; # needs to match O-RU configuration
}
);
RUs = (
{
local_rf = "no";
nb_tx = 1;
nb_rx = 1;
att_tx = 0;
att_rx = 0;
bands = [78];
max_pdschReferenceSignalPower = -27;
max_rxgain = 75;
sf_extension = 0;
eNB_instances = [0];
sl_ahead = 5;
tr_preference = "raw_if4p5"; # important: activate FHI7.2
do_precoding = 0; # needs to match O-RU configuration
ru_thread_core = 6;
}
);
security = {
ciphering_algorithms = ( "nea0" );
integrity_algorithms = ( "nia2", "nia0" );
drb_ciphering = "yes";
drb_integrity = "no";
};
log_config :
{
global_log_level = "info";
hw_log_level = "info";
phy_log_level = "info";
mac_log_level = "info";
rlc_log_level = "info";
pdcp_log_level = "info";
rrc_log_level = "info";
ngap_log_level = "info";
f1ap_log_level = "info";
};
fhi_72 = {
dpdk_devices = ("0000:12:01.0", "0000:12:01.1"); # srv19 DATA1a (E810) VF0/VF1, vfio-pci, MAC 00:11:22:33:64:68/69
dpdk_iova_mode = "VA";
system_core = 3;
io_core = 5;
worker_cores = (7);
ru_addr = ("00:11:22:33:64:66", "00:11:22:33:64:67"); # srv20 nr-oru VF0/VF1
mtu = 9600;
fh_config = ({
T1a_cp_dl = (285, 470); # upstream nr-oru reference (gnb.band77.mu1.106rb.fhi.1x1.conf)
T1a_cp_ul = (285, 429); # the N310-over-routed-path values (1800/2400) put every DL packet outside the O-RU window
T1a_up = (300, 450);
Ta4 = (900, 950); # upstream reference value
ru_config = {
comp_hdr_type = "dynamic";
iq_width = 16; # upstream doc 7.5: nr-oru supports UNCOMPRESSED U-plane only; the O-DU must match
iq_width_prach = 16;
};
prach_config = {
kbar = 4;
};
});
};
Settings that decide whether this pair works:
nr-oru supports only uncompressed 16-bit U-plane (upstreamdoc/ORAN_FHI7.2_ORU_Tutorial.md §7.5). With BFP-9 on the O-DU, the O-DU decodes the O-RU'sOAI_ORU_TX_GAIN is an attenuation from the B210's 89.75 dB maximum (10 → 79.75 dB).OAI_ORU_RX_GAIN passes through a ~44 dB offset (50 → 6 dB, 84 → 40 dB, 114 → 70 dB). CheckActual RX gain in /root/nr-oru.log.tx_amp_backoff_dB = 36 and phase_compensation = 0 on the O-DU matter only to a time-domain radio( … ), not arrays [ … ].Launch. O-DU first (it starts the core, transmits DL C/U-plane and waits for uplink), then the
O-RU with the B210 file and gains — the O-RU script's defaults are the N310 file and N310 gains:
ssh root@<o-du-node> 'bash /root/du-oru-bringup.sh'
ssh root@<o-ru-node> 'CONF=/root/oru/ru-b210.conf OAI_ORU_TX_GAIN=10 OAI_ORU_RX_GAIN=114 bash /root/oru-bringup.sh'
Both scripts create two VFs on DATA1a, set the VF MACs after the vfio-pci bind (the bind wipes
them), pin NIC IRQs off the isolated cores, set hugepages and start PTP. oru-bringup.sh launches
nr-oru with OAI_ORU_DEV_TS=1 and checks that uplink U-plane flows within 60 s. The full procedure
is the Duranta soft O-RU tutorial.
Health. O-DU /tmp/du_oru.log: [o-du 0][rx … pps …] non-zero and all six o_du0 error counters
at 0. O-RU /root/nr-oru.log: Total U-Plane Packets sent increasing, timing-error and
Application Too Slow counters flat after start-up (a few thousand at start are the pre-anchor
backlog), OVERALL STATUS: PASS.
configs/nroru-b210/ru-n310.conf drives the sb1 N310 from the O-RU host. It differs from
ru-b210.conf in the radio line, the receive windows and num_ul_symbols:
num_ul_symbols = 4;
…
T2a_up = (300, 4000); # accept DL up to 4 ms early (N310 TX lead)
T2a_cp = (285, 4000);
…
comp_type = "bfp"; # options: "none", "bfp", "blkscale", "ulaw"
…
sdr_addrs = "addr=10.39.2.1,master_clock_rate=122.88e6,recv_frame_size=7900,send_frame_size=7900,num_recv_frames=64,num_send_frames=256";
The committed file still has comp_type = "bfp"; set it to "none" and keep the O-DU at
iq_width = 16, because the compression rule applies to every nr-oru radio. On the N310 the gain
knobs mean something else: OAI_ORU_RX_GAIN is the USRP RX gain in dB (40), OAI_ORU_TX_GAIN an
attenuation from the N310's 65 dB maximum. ru-n310-srv1-lg1.conf is the same file with the PCI
addresses and cores of srv1-lg1.sb1 (VFs 0000:86:01.0/.1). This combination has not completed a
registration with the uncompressed settings.
| Setting | ProtO-RU (OCUDU) | nr-oru (Duranta) |
Why |
|---|---|---|---|
| Compression | BFP 9 | none (16-bit) | nr-oru's packet processor implements only uncompressed U-plane |
| O-DU T1a lead | up to 2435 µs | up to 470 µs | ProtO-RU's software low PHY + UHD path needs the lead; nr-oru publishes a 1200 µs window |
| PRACH | index 159 (B4), eAxC 4 | index 157, prach_eaxc_offset = 1 |
each O-RU supports one PRACH format |
| Transport | raw socket on the PF, software VLAN | DPDK VFs, port VLAN | the two stacks' OFH implementations |
| Carrier | n41, 40 MHz, 61.44 Msps | n78, 51 PRB, 30.72 Msps | a B210 under OAI cannot run 61.44 Msps; the N310 can |
| Configs | rendered per host by protoru-render.py |
hand-written pair, PCI addresses and cores fixed to the osc hosts | — |
| TDD special slot | 10 DL / 2 UL symbols | 6 DL / 0 UL symbols | set independently for each rig |
| Gain knobs | real dB values, plus a fixed 12 dB gain_backoff |
environment variables with offsets (TX = attenuation from max) | — |
iperf3, receiver line. 1×1 on both rigs, so these are far below a 4T4R commercial O-RU.
| Soft O-RU | Radio | Cell | O-DU and O-RU CPU platform | Logical CPUs | DL / UL (Mbit/s) | RSRP / SINR | Date | Image |
|---|---|---|---|---|---|---|---|---|
| ProtO-RU (OCUDU, Open5GS) | USRP N310, tx_gain 37 |
n41 40 MHz 1×1 | 2× Intel Xeon Gold 6226, 24 cores | 24 | 88–103 / 38–39 | −97 dBm / 18 dB | 2026-09-15 | ocudu-20260915 (4 runs) |
| ProtO-RU (OCUDU, Open5GS) | USRP N310, tx_gain 37 |
n41 40 MHz 1×1 | 2× Intel Xeon Gold 6226, 24 cores | 24 | 88–101 / 38–39 | — | 2026-09-15 | ocudu-20260915, loaded back from the saved image |
| ProtO-RU (OCUDU, Open5GS) | USRP N310, tx_gain 25 |
n41 40 MHz 1×1 | 2× Intel Xeon Gold 6226, 24 cores | 24 | 38 / 39 | −112 dBm / 4 dB | 2026-09-15 | pre-bake build |
| ProtO-RU (OCUDU, Open5GS), 2×2 | USRP N310 | n41 40 MHz 2×2 | 2× Intel Xeon Gold 6226, 24 cores | 24 | 45.6 DL | — | 2026-09-15 | pre-bake build |
| ProtO-RU (OCUDU) | USRP B210 (USB 3) | n78 40 MHz 1×1 at 3420.48 MHz | 2× Intel Xeon Gold 6126, 24 cores | 48 | 14.9 / 17.5, then 16.1 / 8.5, then 1.5 / 0 | −93 to −98 dBm / 17–22 dB | 2026-09-14 | none (ProtO-RU dev @ a3ba4b9 in Docker, O-DU built from the ProtO-RU tree) |
nr-oru (Duranta, OAI CN5G) |
USRP B210 (USB 3), TX 10 / RX 114 | n78 51 PRB 1×1 | 2× Intel Xeon Gold 6126, 24 cores | 48 | 30.7–34.3 / 7.3 | −91 dBm / 18 dB | 2026-09-16 | duranta-20260916 + the nr-oru fix set |
On the ProtO-RU rig the uplink is bounded by the TDD pattern: one UL slot plus two symbols in every five.
Real-time failure in lower PHY, hundreds of times) and DL falls to 45.6 Mbit/s against 103 for 1×1. Stay 1×1--bw 20 --layers 2, which also sets prach_port_id: [4, 5]).ru_cpus have not been tried.Downlink data late at about 80/sexceeded maximum number of timed out receive calls; throughputsrate 23.04, half the USB load), largernum_recv_frames/num_send_frames, more ru_cpus, a larger sdr.max_proc_delay with a matchingKNOWN_ISSUES.md lists internal-clock drift).gain_backoff: 12, so the direct-USRP OCUDU tutorial'stx_gain 25 radiates 12 dB less here: RSRP −112, SINR 4, DL 38. tx_gain 37 restores RSRP −97 /nr-oru bandwidth. 51 PRB is the B210 limit under OAI. A 106-PRB cell needs 61.44 Msps and anr-oru on the N310. Not verified end to end (see above). With BFP and N310-era windows it| Symptom | Cause | Fix |
|---|---|---|
nr-oru: Msg3 never decodes (RA failed at state WAIT_Msg3) while PRACH and RAR work |
the O-DU compresses (BFP-9); nr-oru does uncompressed only |
O-DU iq_width/iq_width_prach 16, O-RU comp_type = "none" |
nr-oru: cell not on air, hundreds of thousands of U-Plane Timing Early Errors |
the O-DU's T1a lead is outside the O-RU's T2a window | the upstream reference windows (above) |
nr-oru: UE sees the cell at RSRP ≈ −111, SINR ≈ 3 |
OAI_ORU_TX_GAIN raised instead of lowered — it is an attenuation |
10 on the B210 |
nr-oru: PRACH weak, prach_I0 = 0.0 dB, raw RX rms ≈ 1 LSB |
RX gain never applied (0 dB) | an nr-oru with the COSMOS fix set, OAI_ORU_RX_GAIN 114 (B210) / 40 (N310) |
nr-oru: South Write Coalescing Stats: calls=0, Application Too Slow climbing |
DL reorder deadlock at start | an nr-oru with the COSMOS fix set |
nr-oru: missed UL symbol 100 frames apart, UL pending queue full |
frame counter anchored on a stale sample (USRP start-up backlog) | an nr-oru with the COSMOS fix set |
nr-oru: Assertion (num_dpdk_devices > 0 …) at start |
launched with the wrong -O file |
CONF=/root/oru/ru-b210.conf (the script's default is the N310 file) |
UHD Operating over USB 2 → ERROR_CODE_OVERFLOW (B210) |
B210 linked at USB 2.0 | reseat or replace the USB 3 cable |
O-DU (either stack) receives nothing, rx 0 |
nr-oru: VF MAC lost at the vfio-pci bind; ProtO-RU/OCUDU: NIC not promiscuous |
set the MAC after the bind (the bring-up does); enable_promiscuous: true + ip link set DATA1a promisc on |
OCUDU gnb: Invalid CPU core selected |
launched under a mask without the isolated cores (nproc counts only the caller's mask) |
taskset -c 0-$(($(nproc --all)-1)) |
OCUDU gnb rejects t1a_max_cp_dl: 2435 |
stock OCUDU caps T1a/Ta4 at 1960 µs | the ocudu.ndz build (5000 µs) |
RU number of PRACH ports=1 must be equal or greater than the number of reception antennas=2 |
2×2 needs one PRACH eAxC per antenna | prach_port_id: [4, 5] on both sides |
UHD: The recv buffer could not be resized sufficiently |
socket buffer ceiling too low for 10 GbE streaming | net.core.rmem_max/wmem_max 33554432 (ProtO-RU host prep does it) |
N310: IOError: socket closed at start |
the radio is still claimed by the previous session, or its data route uses the 1 GbE control path | wait for claimed: False; route the radio over the 10 GbE data NIC |
| Other users' USRP streams underflow when the O-RU starts | the IQ stream crosses the osc↔sb1 gateway | keep the O-RU host and its USRP in one domain |
| Quectel camps but never registers after many attach cycles | the modem has marked PLMN 00101 forbidden (EF_FPLMN) | clear EF_FPLMN (AT+CRSM=214,28539,0,0,12,"FFFFFFFFFFFFFFFFFFFFFFFF"), see COTS UE: the Quectel modem |
Upstream
dev)ru_ofh): https://docs.srsran.com/projects/project/en/latest/On this wiki
COSMOS files (duranta-cosmos repository): configs/protoru/ (renderer, launcher, host prep),
configs/nroru-b210/ (config pair, N310 variants, bring-up scripts, patches/, README runbook).
Author(s): COSMOS team · Last verified: 2026-09-15 (ProtO-RU + N310, sb1), 2026-09-16 (nr-oru + B210, osc) · page written 2026-09-25 · Tags: oran, o-ru, 7.2, fronthaul, soft-oru, protoru, nr-oru, ocudu, duranta, usrp, b210, n310, hardware