Skip to main content

Connect a Radio

The radio behind a cell is declared on the NRCell: the backend and its fields, the node and the radio on it, the clock source. What each backend makes Racora generate is How Radios Are Driven; every field, with its default and range, is in the NRCell API.

Declare the Backend​

Set spec.radioBackend.ruType and the matching block; only that block is read.

A USRP B210 over UHD:

radioBackend:
ruType: uhd
uhd:
deviceArgs: "type=b200,serial=35912B3,num_recv_frames=64,num_send_frames=64"
srate: 23.04 # MHz; carries a 10 or 20 MHz cell
otwFormat: sc12 # halves USB bandwidth on a B210
txGain: 80 # dB
rxGain: 40 # dB
clockSource: gpsdo # internal (default) | external | gpsdo

deviceArgs names the radio (the serial matters on a node with several) and deepens the USB transfer queues a B210 needs at this rate; ruType: uhd alone is a valid declaration for a lone B210 on a lone radio node.

A virtual radio with a UE simulator you deploy:

radioBackend:
ruType: zmq
zmq:
txPort: "tcp://0.0.0.0:2000" # where the DU transmits; a Service exposes 2000
rxPort: "tcp://<ue-simulator>.user-equipment.svc.cluster.local:2001" # where it receives

Point rxPort at your simulator's Service; the default host is in the user-equipment namespace (the controller's ueNamespace value).

No radio at all, what the example cell uses:

radioBackend:
ruType: dummy

An O-RU over Open Fronthaul is the ofh backend, set up in An O-RU over Open Fronthaul.

Pick the Node and the Radio​

Three things decide placement, all on the NRCell:

  1. ruType: uhd adds the racora.io/rf-ready=true node selector, ruType: ofh the racora.io/fronthaul-ready=true one; virtual cells get none. What a node must be to carry that label is the Requirements page and the node contract; how a node gets there is Add a Radio Node.
  2. duAssignment.nodeName adds a kubernetes.io/hostname selector for that node, merged with rf-ready, so the scheduler still checks that the node is RF-ready and has a free radio.
  3. radioBackend.uhd.deviceArgs picks the radio on the node: type=b200 takes whichever USRP the device plugin attached; serial=… makes it a specific one, which matters as soon as a node holds two.

Put the radio on USB 3; at 23.04 MS/s a B210 on USB 2 has not got the bandwidth. After a DU crash the radio can be left with a stale USB claim: delete the DU pod to clear it.

Set the Clock Source and Check GPS Lock​

For a single cell leave clockSource: internal (the default). For cells between which UEs hand over, set clockSource: gpsdo on every cell (a GPSDO on every radio; external works the same way with a shared reference) and wait for GPS lock before you expect mobility.

With clockSource: gpsdo and no lock, the DU does not start: it logs Could not lock reference GPS time source and restarts until the antenna sees enough satellites. To check lock, read the radio's GPS sensors on the radio node itself (the provisioner installs UHD there) while the DU is waiting between restarts and so does not hold the radio:

uhd_usrp_probe --args "type=b200,serial=<serial>" --sensor /mboards/0/sensors/gps_locked
uhd_usrp_probe --args "type=b200,serial=<serial>" --sensor /mboards/0/sensors/gps_servo

gps_locked reads true once the GPSDO has lock; gps_servo is the GPSDO's own status line. For a single cell, switching the cell to internal brings it up without GPS.

An O-RU over Open Fronthaul​

An O-RAN split 7.2 O-RU hangs off a fronthaul port of a racora.io/fronthaul-ready node (Add a Radio Node); the cell names that node in duAssignment.nodeName. The ofh block carries what is particular to this radio, and a radio profile what its model fixes. A 4T2R O-RU on the n78 carrier at 3750 MHz:

spec:
dlArfcn: 650000 # 3750 MHz, used for both directions (TDD)
band: 78
channelBandwidthMHz: 10
commonScs: 30
duAssignment:
nodeName: <node cabled to the O-RU>
radioBackend:
ruType: ofh
ofh:
profileRef: oru-profile
ruMacAddr: "<the O-RU's fronthaul MAC>"
vlanTagCp: 5 # one VLAN for C- and U-plane
vlanTagUp: 5
dlPortId: [0, 1, 2, 3] # one eAxC id per antenna: 4 DL
ulPortId: [0, 1] # 2 UL
prachPortId: [4, 5]
txGain: 20 # dB, the O-RU's carrier gain

The profile is a ConfigMap next to the cell, its profile.yaml the same keys; take the values from the radio's vendor documentation and its M-plane delay profile:

apiVersion: v1
kind: ConfigMap
metadata:
name: oru-profile
namespace: racora-system
data:
profile.yaml: |
t1aMaxCpDl: <µs> # the DU's transmission and reception windows
t1aMinCpDl: <µs>
ta4Max: <µs>
ta4Min: <µs>
isPrachCpEnabled: true
enableDlStaticComprHdr: false # what the radio expects on the wire
enableUlStaticComprHdr: false
iqScaling: <scaling>
mplane:
compressionType: STATIC
tddCarrierBinding: true

The node's fronthaul interface, the DU's MAC address and the F1-U address are filled in on the node when the DU starts; set ofh.networkInterface only to use another port than the one node provisioning selected.

Let the DU pod hold the M-plane session. Put the O-RU's NETCONF account in a Secret in the DU namespace and name its endpoint on the cell; the pod's mplane sidecar then provisions the O-RU and starts the DU only once its carriers are active:

kubectl -n distributed-unit create secret generic oru-account \
--from-literal=username=<account> --from-file=password=<file holding the password>
ofh:
mplane:
netconfHost: <the O-RU's management address> # or callHome: true
credentialsSecret: oru-account

kubectl -n racora-system get nrcell <cell> then shows the turn-up in RadioReady and DUReady; the sidecar's log (kubectl -n distributed-unit logs <du pod> -c mplane) is the M-plane conversation in short.

To take the cell down, delete it: the pod goes, and with it the DU and then the M-plane session. To restart it, delete its DU pod (kubectl -n distributed-unit delete pod -l app=du-<cell>): the replacement provisions the O-RU again before its DU starts, which is also what the controller does by itself when the DU container restarts. Keep one M-plane client per O-RU: while the cell holds the session, run no other client against the O-RU.

Or provision the O-RU yourself, before the cell exists. Without an endpoint the O-RU must already carry the cell's configuration, with its carriers active and its sync state LOCKED, when the DU starts. Render its provisioning file from the manifests with the controller's own code:

{ cat oru-profile.yaml; echo ---; cat cell.yaml; } | \
kubectl -n racora-system exec -i deploy/racora-controller -- \
python -m racora_controller.ofh_config > ru.yml

The --- keeps the two manifests separate YAML documents; concatenated without it, a file that does not start with its own --- merges into the one before it.

Replace @FRONTHAUL_MAC@ in ru.yml with the MAC address of the node's fronthaul port, provision the O-RU with an M-plane client, for example the OCUDU O1 adapter (--profile ru --ru_supervise --ru_provision_config ru.yml), and apply the cell once the carriers are READY. The controller writes the same file into the cell's ConfigMap (ru.yml in nrcell-du-<cell>-config). Take it down in the reverse order: delete the cell, then end the M-plane session; to restart the cell, take it down and bring it up again.

The Node Must Reach the O-RU​

The DU of a cell with an M-plane address requests one healthy O-RU (racora.io/oru) from the node, as the fronthaul agent advertises it, so it only schedules where the O-RU answers. Before applying the cell, or when its pod stays Pending, look at what the node sees:

kubectl describe node <node> | grep -A1 'racora.io/oru' # Capacity: the O-RUs named; Allocatable: the ones answering
kubectl -n racora-system logs ds/fronthaul-agent # the reason, one line per change

The agent also hands the O-RU its address: a request from the cell's ruMacAddr gets netconfHost, so that address must be an IPv4 address in the fronthaul port's subnet, and nothing else on the segment may serve DHCP. Allocatable 0 with the O-RU powered and no DHCP line in the agent's log means it never asked, or asked from another MAC (an UnknownORU Event on the node names it); netconf-closed after a DHCP line is an O-RU still booting. The agent, the condition RadioReady=False/Unreachable and what the controller does when a running cell loses its O-RU are on The Fronthaul Agent.

Tune the Physical Layer​

Beyond the radio block, spec.ssb, pdcch, pdsch, pusch and prach are passed through verbatim into the DU's cell configuration, so any physical-layer parameter the shipped gNB accepts can be set per cell. Their meaning is the stack's, documented at docs.ocudu.org; the API server does not validate them.