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:
- 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.
- 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.
- 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.
- 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.
- 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.