CUBB 64TR Scenario: Analysis on Data and Dimension of DU-RU Fronthaul Interface

Hi,
I am analyzing the data volume of the DU-RU fronthaul interface under the 64TR scenario. As I understand it, the DU first performs SRS channel estimation and BFW calculation. Then ULBFW is transmitted from DU to RU. The RU applies spatial filtering to raw received data using ULBFW, and finally sends the filtered data back to DU as the input of PUSCH. Could you verify whether this comprehension is correct?

Regarding data dimensions: The nLayers of ULBFW is 8, corresponding to 1 layer per UE. However, the dimension of received PUSCH input DataRx generated in MATLAB simulation is {16, 14, 3276}. I think the first 8 dimensions stand for valid ZF outputs, while the latter 8 represent residual original signals. Only the first 8 dimensions are actually processed by cuPHY with the configuration nRxAnt = nUplinkStreams = 8. Is this interpretation accurate?

Furthermore, after spatial filtering at RU, is the data dimension transmitted to DU fixed at 16 in practical deployment, or dynamically adjusted according to nLayers? Relevant code shows a fixed 16-dimension transmission, which leads to redundant data delivery and seems inconsistent with standard theoretical expectations.

There are three questions to confirm:
1.Whether the described uplink signal processing flow between DU and RU is correct
2.The composition and processing dimension of 16-dimensional PUSCH data
3.The dimension of IQ data transmitted from RU to DU

In addition, the mentioned TVs are as follows:
“TVnr_ULMIX_6621_PUSCH_gNB_CUPHY_s0p7.h5”
“TVnr_ULMIX_21523_SRS_gNB_CUPHY_s3p23.h5”
“TVnr_9355_BFW_gNB_CUPHY_s0.h5”

Thank you!

Hi @fltang ,

1.Whether the described uplink signal processing flow between DU and RU is correct

Your described uplink processing flow is correct. Please see for details:

  1. SRS Channel Estimation (DU): During SRS slots, the RU sends raw 64-antenna IQ data to the DU (fronthaul in 64-port mode). The DU runs channel estimation via detSrs_alt() and stores the per-PRG channel matrices in srsState for use in subsequent slots.
  2. BFW Computation (DU): The DU computes Zero-Forcing beamforming weights from the SRS channel estimates: W = inv(H'H) * H'. This is done either widebanded (genie-channel path) or per-PRG (SRS-estimated path) depending on configuration.
  3. ULBFW Transmitted DU→RU: in architectural intent. In a real O-RAN 7.2x deployment, these BFW weights are sent to the RU via the C-Plane fronthaul. In the MATLAB simulation, this explicit message exchange is abstracted — the weights are applied inline within ulRxBf.m — but the mathematical operation is identical.
  4. RU Applies Spatial Filtering:The RU multiplies the raw 64-antenna received signal by the BFW weights, producing spatially filtered output streams (one stream per scheduled UE layer). In simulation, ulRxBf.m writes ZF outputs for 8 UEs to port indices 1–8 of the 64-column buffer.
  5. Filtered Data Sent to DU as PUSCH Input: The filtered output — fixed at 16 streams (NUM_RX_PORT=16) regardless of how many UEs are scheduled — is transmitted to the DU over the fronthaul U-Plane. The DU’s PUSCH decoder (detPusch) receives a DataRx tensor of shape {16, 14, 3276} and processes it. This fixed-16 dimensionality is confirmed by both the C++ driver (phypusch_aggr.cpp, slot_command.hpp) and the test vector (TVnr_ULMIX_6621_gNB_FAPI_s0.h5).

One important clarification: SRS and PUSCH slots use different fronthaul port counts (64 for SRS, 16 for PUSCH). The switch from 64→16 ports is the direct consequence of the RU-side spatial filtering: BFW compresses 64 physical antennas down to 16 fronthaul streams. The MATLAB simulation models this compression via the Nant_gNB/CellConfigPorts swap in testCompGenTV_ulmix.m and the truncation in ulRxBf.m.

2.The composition and processing dimension of 16-dimensional PUSCH data

The 16-dimensional PUSCH DataRx has a heterogeneous composition, and your interpretation is correct:

ulRxBf.m operates on a 64-column buffer initialized from the 64-antenna received signal. It writes ZF outputs only to portIdx = 1:8 (the PUSCH-allocated port indices for 8 UEs × 1 layer). Columns 9–64 are never overwritten and remain as raw antenna data. The buffer is then truncated to CellConfigPorts=16, leaving columns 9–16 as raw antenna 9–16 signals.

Processing dimension: Although the DataRx tensor has shape {3276, 14, 16} (nRxAnt=16), the effective processing dimension for PUSCH equalization is nUplinkStreams=8. The DMRS port bitmask in PDUs 17–24 (one bit per UE, bits 0–7) directs the equalizer to columns 0–7. Columns 8–15 carry no PUSCH-relevant information and are not used. Therefore nRxAnt ≠ nUplinkStreams (16 ≠ 8) in this configuration.

Important caveat: This structure is a simulation artifact, not a real-hardware behavior. The TODO comment in testCompGenTV_ulmix.m flags this explicitly: the single Xtf buffer is shared between SRS (needs 64 columns) and PUSCH (needs 16) as a workaround. In real O-RAN 7.2x deployment, the RU sends only the 16 beamformed fronthaul streams to the DU — it does not bundle raw antenna signals alongside them. The 16 streams from the RU would all be valid beamformed outputs in actual hardware.

3.The dimension of IQ data transmitted from RU to DU

The fronthaul IQ dimension transmitted from RU to DU is fixed per cell type, not dynamically adjusted by nLayers.

For PUSCH slots in a 64TR cell: it is always 16 ports.
The DataRx tensor has shape {3276, 14, 16} every slot, regardless of how many UEs are scheduled or how many layers each UE uses. This is enforced by:

  • NUM_RX_PORT=16 compile-time constant in slot_command.hpp
  • pusch_ap_dim = getMaxUlAntennaPorts() = 16 set once from cell config at startup in phypusch_aggr.cpp
  • Xtf1(:, 1:SimCtrl.CellConfigPorts) unconditional truncation in ulRxBf.m (CellConfigPorts=16)

For SRS slots in a 64TR cell: always 64 ports.
SRS requires the full 64-antenna raw IQ for channel estimation. Its DataRx tensor has shape {3276, 4, 64}, confirmed by physrs_aggr.cpp and TVnr_ULMIX_21523_gNB_FAPI_s3.h5. The two slot types operate at entirely different fronthaul dimensions.

nUplinkStreams (=8) ≠ fronthaul dimension (=16). The 8 co-scheduled UEs × 1 layer produce nUplinkStreams=8 , but the fronthaul always sends 16 ports. cuPHY selects the 8 active ports via the DMRS port bitmask in each PDU (bits 0–7 in TV 6621). The remaining 8 ports are allocated but carry no useful PUSCH data for this UE group.

Let me know if you have any further questions.

Thanks a lot for your explanation! I have one more question. In real-world deployments, how are the 16 beamforming output streams implemented? By transmitting two UE groups?

No, the 16 ports are not split between two UEG groups (8+8). Multiple UEGs on different PRBs each independently use the same port columns (e.g., ports 0–7 for a group of 8 single-layer UEs), differentiated by frequency not by port range. The code confirms this: pTDataRx[cellPrmDynIdx] is a single cell-level tensor shared by all UEGs, with no port offset accumulated across groups. The comment in phypusch_aggr.cpp:110 names it explicitly: “groups FDM-partition PRBs, total is constant.”

The 16 ports represent the maximum number of simultaneous spatial streams per PRG region. The RU applies different BFW weights at different PRB ranges (one set per UEG’s allocation), but the beamformed output always lands on the same 16 fronthaul ports. In a typical 8-UE-per-group scenario, only 8 of the 16 ports carry active beamformed streams at any given frequency location; the remaining 8 are unused there.