Author SHA1 Message Date
maki 67717ed5c1 update 2026-09-19 23:28:08 +09:00
maki c50fe1af53 feat(nr): implement rank-8 PDSCH 2026-09-15 00:36:01 +09:00
francescomani 04ee4ea8e5 restructuring of UE DLSCH code to handle 2 codewords
even if 2 codewords are currently not supported and not tested, the way UE handled the possibility of having 2 codewords was messy, in between support and non-support
the intention of this MR is to cleanup the code more than extend the support

Signed-off-by: Francesco Mani <email@francescomani.it>
2026-09-14 22:56:26 +09:00
makiandClaude Opus 4.7 08349f246b RC SM: fix heap corruption on UE teardown after rrcRelease event
When xApp subscribes to RC SM Style 1 (Message Copy), the gNB's
signal_rrc_msg() path on rrcRelease handed an async indication to the
E2 agent thread, then the calling code proceeded to UE-context
teardown while the encoder was still walking the indication structure.
Three issues observed in production:

1. malloc_or_fail() leaves union/pointer fields in rc_ind_data and its
   sub-structures uninitialized; the encoder dereferences whatever
   garbage was in the unused union member and corrupts the heap.
   Fix: switch malloc_or_fail(sizeof(...)) to
   calloc_or_fail(1, sizeof(...)) in fill_rrc_msg_copy, fill_ue_id,
   and the rc_ind_data_t allocations.

2. The async indication path runs concurrently with UE-context
   removal. Add rc_teardown_lock/rc_teardown_unlock helpers (public
   wrappers around rc_mutex) so the RRC layer can serialize teardown
   against any in-flight signal_rrc_msg() critical section.

3. Wrap rrc_gNB_remove_ue_context() in rrc_gNB.c with
   rc_teardown_lock/unlock under E2_AGENT.

With these three fixes plus the deployment-side MALLOC_CHECK_=3
backstop, a 20-minute challenging-schedule run completes with 22
RRC_RELEASE events processed cleanly and zero crashes (vs. SIGSEGV
within 10-30 minutes previously).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 18:55:45 +09:00
Jaroslava Fiedlerova bfb177c38e Merge branch 'integration_2026_w17' into 'develop'
Integration `2026.w17`

* !4054 Minor fixes to gNB/UE behavior in RFsim
* !4062 Minor fixes/cleanup in NGAP lib
* !4036 Fix CSI-RS estimation
* !4055 fix (RRC): do not trigger N2 HO when no active PDU sessions are present
* !3988 asn1c inside local tree
* !3762 L1 gNB type0 PDSCH
* !4069 ctest: fix tests when compiling with sanitizers, ignore build directories, fix spsc_q build
* !4067 Small compilation fixes
* !3444 7.2 FHI with XRAN K release
* !4061 T-Tracer & Data Recording v1.1: UL PHY trace modularization and recording enhancements
* !3975 ZMQ radio
* Increase PRACH queue capacity from 8 to 16
* !4059 CI: Various CI adjustments

Closes #1074 and #1073

See merge request oai/openairinterface5g!4063
2026-04-24 20:23:02 +00:00
Jaroslava Fiedlerova 434cd8ca3d Merge remote-tracking branch 'origin/ci-adjustments' into integration_2026_w17 (!4059)
CI: Various CI adjustments

- RAN-SA-FHI72-CN5G: activate/inactive carriers on VVDN RU - to check if
  it improves reliability and stability of the pipeline
- RAN-SA-FHI72-CN5G: update CN with new IP range - avoid IP address
  overlap with CN used in RAN-SA-AERIAL-XXX pipelines
- RAN-SA-B200-Module-SABOX-Container: test with deltaMCS in SC-FDMA test
  case
- RAN-NSA-B200-Module-LTEBOX-Container: adjust PRACH DTX threshold to
  reduce number of false RA attempts on eNB
- remove retx check in RAN-SA-Multi-Antenna-CN5G and RAN-SA-AERIAL-CN5G -
  to avoid false CI failure caused by unsuccessful retx check of
  interfering UE
- adjust RAN-SA-Multi-Antenna-CN5G and RAN-SA-AERIAL-CN5G configs to
  improve UL throughput
2026-04-24 20:46:46 +02:00
Jaroslava Fiedlerova 38167e50f8 CI: Adjust config file in UL heavy test of SA-AERIAL-CN5G
Manual testing showed that increasing pusch_TargetSNRx10 by 3 dB improves
uplink throughput in UL-heavy test cases of SA-AERIAL-CN5G
2026-04-24 17:18:28 +02:00
Jaroslava Fiedlerova d1129d1f90 CI: Reduce RX gain in 100 MHz test of SA-Multi-Antenna-CN5G
Manual testing showed that lowering the RX gain reduces UL
retransmissions and improves overall UL throughput in the 100 MHz 2x2
test case of the SA-Multi-Antenna CN5G pipeline.
2026-04-24 17:18:28 +02:00
Jaroslava Fiedlerova 6a6f200790 CI: Change OC CN for RAN-SA-FHI72-CN5G 2026-04-24 17:18:28 +02:00
Jaroslava Fiedlerova 1c26f372aa CI: Disable machine performance tuning in RAN-SA-FHI72-CN5G
Performance tuning is done automatically after the reboot
2026-04-24 17:18:28 +02:00
Jaroslava Fiedlerova 0da440017e CI: Test deltaMCS power control mode in SA-B200-Module-SABOX-Container 2026-04-24 17:18:28 +02:00
Jaroslava Fiedlerova e0a2b4837c CI: Configuration file adjustment for NSA-B200-Module-LTEBOX-Container
Try to reduce number of false RA attempts on eNB
2026-04-24 17:18:28 +02:00
Jaroslava Fiedlerova 11dc86f59c CI: Add scripts to (in)activate carriers on VVDN
Add vvdn-activate-carriers.sh to verify RU PTP synchronization and
configuration. Activates carriers on VVDN RU by editing sysrepo running
datastore (INACTIVE->ACTIVE).

Add vvdn-inactive-carriers.sh to reverse the operation, setting all
carriers back to INACTIVE via sysrepocfg.
2026-04-24 17:18:28 +02:00
Jaroslava Fiedlerova 547f683ebe CI: Remove retx check from OTA setups with Quectel
Retx check frequently fails in OTA pipelines, particularly for interfering
UEs attempting to connect to the cell. This leads to job failures in OTA
pipelines.

Remove the retx check from  Multi-Antenna and Aerial pipelines to improve
CI stability and avoid false failures. In these pipelines, iperf test
already validates correct funtionality, making the retx check redundant.
2026-04-17 14:09:22 +02:00
102 changed files with 7077 additions and 1679 deletions
+2
View File
@@ -978,6 +978,8 @@ set(PHY_SRC_UE
${OPENAIR1_DIR}/PHY/nr_phy_common/src/nr_phy_common_srs.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_scrambling.c
${OPENAIR1_DIR}/PHY/NR_REFSIG/refsig.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_state.c
${OPENAIR1_DIR}/PHY/NR_TRANSPORT/nr_antenna_control.c
)
set(PHY_NR_SRC
+4 -4
View File
@@ -79,11 +79,11 @@ oc-cn5g:
oc-cn5g-20897:
Host: cacofonix
NetworkScript: echo "inet 172.21.6.105"
NetworkScript: echo "inet 172.21.6.115"
RunIperf3Server: False
Deploy: "! scripts/oc-cn5g-deploy.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72"
Undeploy: "! scripts/oc-cn5g-undeploy.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72"
LogCollect: "! scripts/oc-cn5g-logcollect.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72 %%log_dir%%"
Deploy: "! scripts/oc-cn5g-deploy.sh /opt/oai-cn5g-fed-develop-2026-apr oaicicd-core-for-fhi72"
Undeploy: "! scripts/oc-cn5g-undeploy.sh /opt/oai-cn5g-fed-develop-2026-apr oaicicd-core-for-fhi72"
LogCollect: "! scripts/oc-cn5g-logcollect.sh /opt/oai-cn5g-fed-develop-2026-apr oaicicd-core-for-fhi72 %%log_dir%%"
oc-cn5g-00103-ho:
Host: groot
@@ -215,6 +215,7 @@ L1s =
(
{
num_cc = 1;
prach_dtx_threshold = 200;
tr_n_preference = "local_mac";
}
);
@@ -186,10 +186,10 @@ MACRLCs = (
local_s_portd = 50011; // vnf p7 port [!]
tr_s_preference = "aerial";
tr_n_preference = "local_RRC";
pucch_RSSI_Threshold = -270;
pusch_RSSI_Threshold = -260;
pusch_TargetSNRx10 = 260; # 150;
pucch_TargetSNRx10 = 200; #200;
pucch_RSSI_Threshold = -270;
pusch_RSSI_Threshold = -260;
pusch_TargetSNRx10 = 290;
pucch_TargetSNRx10 = 200;
dl_max_mcs = 28;
ul_max_mcs = 28;
ul_min_mcs = 9;
@@ -161,7 +161,7 @@ gNBs =
////////// AMF parameters:
amf_ip_address = ({ ipv4 = "172.21.6.103"; });
amf_ip_address = ({ ipv4 = "172.21.6.113"; });
NETWORK_INTERFACES :
{
@@ -197,7 +197,7 @@ RUs = (
nb_tx = 2;
nb_rx = 2;
att_tx = 0;
att_rx = 0;
att_rx = 6;
bands = [77];
max_pdschReferenceSignalPower = -27;
max_rxgain = 71;
@@ -18,7 +18,7 @@ gNBs =
nr_cellid = 12345678L;
////////// Physical parameters:
use_deltaMCS = 1;
servingCellConfigCommon = (
{
@@ -180,7 +180,7 @@ MACRLCs = (
{
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 200;
pusch_TargetSNRx10 = 100;
pucch_TargetSNRx10 = 200;
ul_prbblack_SNR_threshold = 10;
}
+48
View File
@@ -0,0 +1,48 @@
#!/bin/sh
# SPDX-License-Identifier: MIT
set -e
RU_IP="10.10.0.111"
RU_HOST="root@10.10.0.111"
ssh_exec() {
ssh $RU_HOST $@ </dev/null
}
echo "→ Checking for PTP fluctuations in the last 60 minutes..."
logs=$(journalctl -u ptp4l.service --since "60 minutes ago")
check_ptp=$(echo "$logs" | awk '
/rms/ {
rms=$8
if (rms > 100) {
print "✗ PTP FLUCTUATION DETECTED → " $0
exit 0
}
}
')
if [ -n "$check_ptp" ]; then
echo "$check_ptp"
else
echo "✓ No PTP fluctuations detected."
fi
echo "→ Checking if VVDN LPRU is PTP synchronized (timeout 1 min)..."
if ssh_exec 'timeout 1m sh -c "tail -F /var/log/synctimingptp2.log | grep -m 1 -F ,\ synchronized"'; then
echo "✓ VVDN LPRU synchronized"
else
echo "✗ VVDN LPRU not synchronized"
fi
echo "→ Checking TX/RX carrier configuration..."
if ssh_exec 'sysrepocfg -X -d running -f xml | grep -q "<active>INACTIVE</active>"'; then
echo "→ Some carriers are not active, activating via sysrepocfg..."
ssh_exec 'sysrepocfg -X -d running -f xml | sed "s/<active>INACTIVE<\/active>/<active>ACTIVE<\/active>/g" | sysrepocfg -I -d running -f xml'
echo "✓ All TX/RX carriers activated."
else
echo "✓ All TX/RX carriers already activated."
fi
exit 0
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
# SPDX-License-Identifier: MIT
set -e
RU_HOST="root@10.10.0.111"
ssh_exec() {
ssh $RU_HOST $@ </dev/null
}
echo "→ Inactivating TX/RX carriers on VVDN LPRU..."
ssh_exec 'sysrepocfg -X -d running -f xml | sed "s/ACTIVE/INACTIVE/g" | sysrepocfg -I -d running -f xml'
echo "✓ TX/RX carriers inactivated."
@@ -122,7 +122,7 @@
<node>gracehopper1-oai</node>
<yaml_path>ci-scripts/yaml_files/sa_gnb_aerial</yaml_path>
<analysis>
<services>oai-gnb-aerial=RetxCheck=20,100,100,100 oai-gnb-aerial=EndsWithBye</services>
<services>oai-gnb-aerial=EndsWithBye</services>
</analysis>
</testCase>
@@ -111,7 +111,7 @@
<node>gracehopper1-oai</node>
<yaml_path>ci-scripts/yaml_files/sa_gnb_aerial_ul</yaml_path>
<analysis>
<services>oai-gnb-aerial=RetxCheck=20,100,100,100 oai-gnb-aerial=EndsWithBye</services>
<services>oai-gnb-aerial=EndsWithBye</services>
</analysis>
</testCase>
@@ -12,6 +12,13 @@
<script>yaml_files/sa_fhi_7.2_vvdn_gnb/setup_config.sh</script>
</testCase>
<testCase>
<class>Custom_Script</class>
<desc>Activate carriers on VVDN RU</desc>
<node>cacofonix</node>
<script>scripts/vvdn-activate-carriers.sh</script>
</testCase>
<testCase>
<class>Pull_Local_Registry</class>
<desc>Pull Images from Local Registry</desc>
@@ -134,9 +141,9 @@
<testCase>
<class>Custom_Script</class>
<always_exec>true</always_exec>
<desc>Set CPU to idle state, set kernel parameters to default values</desc>
<desc>Inactivate carriers on VVDN RU</desc>
<node>cacofonix</node>
<script>yaml_files/sa_fhi_7.2_vvdn_gnb/setup_cleanup.sh</script>
<script>scripts/vvdn-inactivate-carriers.sh</script>
</testCase>
</testCaseList>
@@ -104,7 +104,7 @@
<node>matix</node>
<yaml_path>ci-scripts/yaml_files/5g_sa_n310_2x2_100MHz</yaml_path>
<analysis>
<services>oai-gnb=RetxCheck=dl=10,100,100,100;ul=25,100,100,100 oai-gnb=EndsWithBye</services>
<services>oai-gnb=EndsWithBye</services>
</analysis>
</testCase>
@@ -104,7 +104,7 @@
<node>matix</node>
<yaml_path>ci-scripts/yaml_files/5g_sa_n310_2x2_60MHz</yaml_path>
<analysis>
<services>oai-gnb=RetxCheck=dl=15,100,100,100;ul=20,100,100,100 oai-gnb=EndsWithBye</services>
<services>oai-gnb=EndsWithBye</services>
</analysis>
</testCase>
@@ -104,7 +104,7 @@
<node>matix</node>
<yaml_path>ci-scripts/yaml_files/5g_sa_n310_4x4_60MHz</yaml_path>
<analysis>
<services>oai-vnf=RetxCheck=10,100,100,100 oai-vnf=EndsWithBye oai-pnf=EndsWithBye</services>
<services>oai-vnf=EndsWithBye oai-pnf=EndsWithBye</services>
</analysis>
</testCase>
@@ -1,7 +0,0 @@
# SPDX-License-Identifier: MIT
set -e
sudo cpupower idle-set -E > /dev/null
sudo sysctl kernel.sched_rt_runtime_us=950000
sudo sysctl kernel.timer_migration=1
exit 0
@@ -17,7 +17,7 @@ services:
devices:
- /dev/bus/usb/:/dev/bus/usb/
volumes:
- ../../conf_files/gnb.sa.band78.106prb.usrpb200.sc-fdma.conf:/opt/oai-gnb/etc/gnb.conf
- ../../conf_files/gnb.sa.band78.106prb.usrpb200.sc-fdma-deltaMCS.conf:/opt/oai-gnb/etc/gnb.conf
# for performance reasons, we use host mode: in bridge mode, we have
# unacceptable DL TCP performance. However, the whole point of
# containerization is to not be in host mode, so update this to macvlan
+189
View File
@@ -13,10 +13,199 @@
#include "nr_common.h"
#include <limits.h>
#include <math.h>
#include <string.h>
#define C_SRS_NUMBER (64)
#define B_SRS_NUMBER (4)
void nr_pdsch_rank_capacity_scores(const float eigenvalues[NR_MAX_NB_LAYERS],
uint8_t max_rank,
float noise_power,
float scores[NR_MAX_NB_LAYERS])
{
AssertFatal(max_rank >= 1 && max_rank <= NR_MAX_NB_LAYERS,
"Invalid maximum PDSCH rank %u\n",
max_rank);
const float finite_noise = isfinite(noise_power) && noise_power > 0.0f ? noise_power : 1.0f;
for (int rank = 1; rank <= NR_MAX_NB_LAYERS; rank++) {
scores[rank - 1] = 0.0f;
if (rank > max_rank)
continue;
for (int layer = 0; layer < rank; layer++) {
const float eigenvalue = isfinite(eigenvalues[layer]) && eigenvalues[layer] > 0.0f
? eigenvalues[layer]
: 0.0f;
scores[rank - 1] += log2f(1.0f + eigenvalue / (rank * finite_noise));
}
}
}
uint8_t nr_pdsch_select_rank(const float scores[NR_MAX_NB_LAYERS],
uint8_t max_rank,
float relative_margin)
{
AssertFatal(max_rank >= 1 && max_rank <= NR_MAX_NB_LAYERS,
"Invalid maximum PDSCH rank %u\n",
max_rank);
const float margin = isfinite(relative_margin) && relative_margin > 0.0f ? relative_margin : 0.0f;
uint8_t best_rank = 1;
float best_score = isfinite(scores[0]) ? scores[0] : 0.0f;
for (uint8_t rank = 2; rank <= max_rank; rank++) {
const float candidate = isfinite(scores[rank - 1]) ? scores[rank - 1] : 0.0f;
if (candidate > best_score * (1.0f + margin)) {
best_rank = rank;
best_score = candidate;
}
}
return best_rank;
}
uint8_t nr_type1_sp_4x1_i11_count(uint8_t layers)
{
switch (layers) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
return 16;
case 7:
case 8:
return 8;
default:
return 0;
}
}
uint8_t nr_type1_sp_4x1_i13_count(uint8_t layers)
{
switch (layers) {
case 1:
case 5:
case 6:
case 7:
case 8:
return 1;
case 2:
return 4;
case 3:
case 4:
return 3;
default:
return 0;
}
}
uint8_t nr_type1_sp_4x1_i2_count(uint8_t layers)
{
if (layers == 1)
return 4;
return layers >= 2 && layers <= 8 ? 2 : 0;
}
bool nr_type1_sp_4x1_weights(uint8_t layers,
uint8_t i11,
uint8_t i13,
uint8_t i2,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS])
{
memset(weights, 0, NR_MAX_NB_LAYERS * NR_MAX_CSI_PORTS * sizeof(**weights));
const uint8_t i11_count = nr_type1_sp_4x1_i11_count(layers);
const uint8_t i13_count = nr_type1_sp_4x1_i13_count(layers);
const uint8_t i2_count = nr_type1_sp_4x1_i2_count(layers);
if (i11_count == 0 || i11 >= i11_count || i13 >= i13_count || i2 >= i2_count)
return false;
/* TS 38.214 Tables 5.2.2.2.1-5--12, specialized to the one-dimensional
* N1=4 panel. O1=4, so adjacent orthogonal DFT beams are four indices
* apart. Values 0..3 below encode 1, -1, phi, and -phi respectively. */
uint8_t beam_offset[NR_MAX_NB_LAYERS] = {0};
uint8_t polarization[NR_MAX_NB_LAYERS] = {0};
switch (layers) {
case 1:
polarization[0] = 2;
break;
case 2:
beam_offset[1] = 4 * i13;
polarization[0] = 2;
polarization[1] = 3;
break;
case 3:
case 4: {
const uint8_t offset = 4 * (i13 + 1);
beam_offset[1] = offset;
beam_offset[3] = offset;
polarization[0] = 2;
polarization[1] = 2;
polarization[2] = 3;
polarization[3] = 3;
break;
}
case 5: {
const uint8_t offsets[] = {0, 0, 4, 4, 8};
const uint8_t polarizations[] = {2, 3, 0, 1, 0};
memcpy(beam_offset, offsets, sizeof(offsets));
memcpy(polarization, polarizations, sizeof(polarizations));
break;
}
case 6: {
const uint8_t offsets[] = {0, 0, 4, 4, 8, 8};
const uint8_t polarizations[] = {2, 3, 2, 3, 0, 1};
memcpy(beam_offset, offsets, sizeof(offsets));
memcpy(polarization, polarizations, sizeof(polarizations));
break;
}
case 7: {
const uint8_t offsets[] = {0, 0, 4, 8, 8, 12, 12};
const uint8_t polarizations[] = {2, 3, 2, 0, 1, 0, 1};
memcpy(beam_offset, offsets, sizeof(offsets));
memcpy(polarization, polarizations, sizeof(polarizations));
break;
}
case 8: {
const uint8_t offsets[] = {0, 0, 4, 4, 8, 8, 12, 12};
const uint8_t polarizations[] = {2, 3, 2, 3, 0, 1, 0, 1};
memcpy(beam_offset, offsets, sizeof(offsets));
memcpy(polarization, polarizations, sizeof(polarizations));
break;
}
}
const float scale = 1.0f / sqrtf((float)layers);
const float phi_re = cosf((float)M_PI * i2 / 2.0f);
const float phi_im = sinf((float)M_PI * i2 / 2.0f);
for (int layer = 0; layer < layers; layer++) {
const int beam = i11 + beam_offset[layer];
float pol_re = 1.0f;
float pol_im = 0.0f;
switch (polarization[layer]) {
case 1:
pol_re = -1.0f;
break;
case 2:
pol_re = phi_re;
pol_im = phi_im;
break;
case 3:
pol_re = -phi_re;
pol_im = -phi_im;
break;
}
for (int element = 0; element < 4; element++) {
const float angle = 2.0f * (float)M_PI * element * beam / 16.0f;
const float beam_re = cosf(angle);
const float beam_im = sinf(angle);
weights[layer][element].r = lrintf(SHRT_MAX * scale * beam_re);
weights[layer][element].i = lrintf(SHRT_MAX * scale * beam_im);
weights[layer][element + 4].r = lrintf(SHRT_MAX * scale * (pol_re * beam_re - pol_im * beam_im));
weights[layer][element + 4].i = lrintf(SHRT_MAX * scale * (pol_re * beam_im + pol_im * beam_re));
}
}
return true;
}
/* TS 38.211 Table 6.4.1.4.3-1: SRS bandwidth configuration */
static const unsigned short srs_bandwidth_config[C_SRS_NUMBER][B_SRS_NUMBER][2] = {
/* B_SRS = 0 B_SRS = 1 B_SRS = 2 B_SRS = 3 */
+78 -1
View File
@@ -9,6 +9,7 @@
#ifndef __COMMON_UTILS_NR_NR_COMMON__H__
#define __COMMON_UTILS_NR_NR_COMMON__H__
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "assertions.h"
@@ -71,9 +72,85 @@ static inline const char *rnti_types(nr_rnti_type_t rr)
#define MAX_GSCN_BAND 620 // n78 has the highest GSCN range of 619
#define NR_SYMBOLS_PER_SLOT 14
#define NR_SYMBOLS_PER_SLOT_EXTENDED_CP 12
#define NR_MAX_NB_LAYERS 4 // 8
#define NR_MAX_NB_LAYERS 8
#define NR_MAX_NB_CODEWORDS 2
#define NR_MAX_CSI_PORTS 12
/* TS 38.211 Table 7.3.1.3-1: PDSCH uses one codeword up to rank four and
* two codewords from rank five onward. Keep this split in one shared helper
* so MAC and PHY cannot silently disagree about the 4+4 rank-eight mapping. */
static inline uint8_t nr_pdsch_num_codewords(uint8_t layers)
{
AssertFatal(layers >= 1 && layers <= NR_MAX_NB_LAYERS, "Invalid PDSCH layer count %u\n", layers);
return layers > 4 ? 2 : 1;
}
static inline uint8_t nr_pdsch_layers_per_codeword(uint8_t layers, uint8_t codeword)
{
const uint8_t n_codewords = nr_pdsch_num_codewords(layers);
AssertFatal(codeword < n_codewords, "Invalid codeword %u for %u PDSCH layers\n", codeword, layers);
if (n_codewords == 1)
return layers;
return layers / 2 + (codeword == 1 ? layers % 2 : 0);
}
/* A two-TB PDSCH has two separate HARQ-ACK decisions even though both TBs
* share one HARQ process. ACKs for padding-only replacement codewords must
* not clear the other TB's pending retransmission. */
static inline uint8_t nr_pdsch_pending_after_ack(uint8_t pending_mask,
uint8_t active_mask,
uint8_t ack_mask)
{
AssertFatal(active_mask == 1 || active_mask == 3, "Invalid active codeword mask %u\n", active_mask);
const uint8_t pending = pending_mask ? pending_mask : active_mask;
return pending & ~(ack_mask & active_mask);
}
/** Compute equal-power spatial-capacity scores for candidate PDSCH ranks.
*
* eigenvalues contains the descending eigenvalues of H^H H. The score for
* rank r is sum_i log2(1 + eigenvalue_i / (r * noise_power)): total TX power
* is held constant and divided equally among the r layers. Scores estimate
* the useful spatial multiplexing opportunity; MCS, coding, and scheduling
* overhead are intentionally left to MAC.
*/
void nr_pdsch_rank_capacity_scores(const float eigenvalues[NR_MAX_NB_LAYERS],
uint8_t max_rank,
float noise_power,
float scores[NR_MAX_NB_LAYERS]);
/** Select the lowest rank that is materially better than all lower ranks.
* relative_margin is expressed as a fraction (0.05 means a five-percent
* required improvement), which avoids switching rank for negligible model
* gains. Returns a one-based rank.
*/
uint8_t nr_pdsch_select_rank(const float scores[NR_MAX_NB_LAYERS],
uint8_t max_rank,
float relative_margin);
/** Number of valid i_1_1 values for the rank-1--8 Type-I single-panel
* codebooks with (N1,N2,O1,O2)=(4,1,4,1). This is the eight-port geometry
* used by the experimental rank-8 branch. Returns zero for invalid ranks.
*/
uint8_t nr_type1_sp_4x1_i11_count(uint8_t layers);
/** Number of valid i_1_3 and i_2 values in the same codebook. */
uint8_t nr_type1_sp_4x1_i13_count(uint8_t layers);
uint8_t nr_type1_sp_4x1_i2_count(uint8_t layers);
/** Build one TS 38.214 Type-I single-panel rank-1--8 precoder for the
* eight-port (N1,N2,O1,O2)=(4,1,4,1), codebookMode=1 geometry.
*
* weights is indexed [layer][antenna port]. Coefficients use OAI's existing
* Q15 codebook convention (1/sqrt(layers) scaling); unused entries are zero.
* Returns false when layers, i11, i13, or i2 is outside this codebook.
*/
bool nr_type1_sp_4x1_weights(uint8_t layers,
uint8_t i11,
uint8_t i13,
uint8_t i2,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS]);
// Since the IQ samples are represented by SQ15 R+I (see https://en.wikipedia.org/wiki/Q_(number_format)) we need to compensate when
// calcualting signal energy. Instead of shifting each sample right by 15, we can normalize the result in dB scale once its
// calcualted. Signal energy is calculated using RMS^2, where each sample is squared before taking the average of the sum, therefore
+3
View File
@@ -5,3 +5,6 @@ target_link_libraries(test_nr_common PRIVATE nr_common GTest::gtest minimal_lib)
add_dependencies(tests test_nr_common)
add_test(NAME test_nr_common
COMMAND ./test_nr_common)
add_executable(nr_rank_selector_benchmark nr_rank_selector_benchmark.cpp)
target_link_libraries(nr_rank_selector_benchmark PRIVATE nr_common minimal_lib)
@@ -0,0 +1,63 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <algorithm>
#include <cmath>
#include <cstdio>
extern "C" {
#include "nr_common.h"
}
struct rank_scenario_t {
const char *name;
float eigenvalues[NR_MAX_NB_LAYERS];
float noise_power;
};
static uint8_t legacy_rank(const float eigenvalues[NR_MAX_NB_LAYERS])
{
uint8_t rank = 1;
for (int layer = 1; layer < NR_MAX_NB_LAYERS; layer++) {
if (eigenvalues[layer] <= 0.0f
|| 10.0f * std::log10(eigenvalues[0] / eigenvalues[layer]) >= 8.0f)
break;
rank++;
}
return rank;
}
int main()
{
const rank_scenario_t scenarios[] = {
{"dominant_one", {100.0f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f}, 1.0f},
{"four_modes", {100.0f, 80.0f, 60.0f, 40.0f, 0.01f, 0.01f, 0.01f, 0.01f}, 1.0f},
{"eight_modes", {100.0f, 95.0f, 90.0f, 85.0f, 80.0f, 75.0f, 70.0f, 65.0f}, 1.0f},
{"balanced_low_snr", {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}, 10.0f},
{"legacy_false_high_rank", {1.0f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f, 0.16f}, 10.0f},
{"weak_eighth", {100.0f, 90.0f, 80.0f, 70.0f, 60.0f, 50.0f, 40.0f, 5.0f}, 1.0f},
};
std::puts("scenario,policy,selected_rank,selected_capacity_bps_hz");
for (const rank_scenario_t &scenario : scenarios) {
float scores[NR_MAX_NB_LAYERS] = {};
nr_pdsch_rank_capacity_scores(scenario.eigenvalues,
NR_MAX_NB_LAYERS,
scenario.noise_power,
scores);
const uint8_t capacity_rank = nr_pdsch_select_rank(scores, NR_MAX_NB_LAYERS, 0.05f);
const uint8_t legacy = legacy_rank(scenario.eigenvalues);
const uint8_t policies[] = {capacity_rank, legacy, 1, 4, 8};
const char *policy_names[] = {"capacity", "legacy", "fixed1", "fixed4", "fixed8"};
for (int policy = 0; policy < 5; policy++) {
const uint8_t rank = std::min<uint8_t>(policies[policy], NR_MAX_NB_LAYERS);
std::printf("%s,%s,%u,%.6f\n",
scenario.name,
policy_names[policy],
rank,
scores[rank - 1]);
}
}
return 0;
}
+124
View File
@@ -2,6 +2,7 @@
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#include <climits>
#include <gtest/gtest.h>
extern "C" {
#include "nr_common.h"
@@ -21,6 +22,129 @@ TEST(nr_common, nr_timer) {
EXPECT_TRUE(nr_timer_expired(&timer));
}
TEST(nr_common, pdsch_codeword_layer_split)
{
constexpr uint8_t expected_codewords[] = {1, 1, 1, 1, 2, 2, 2, 2};
constexpr uint8_t expected_cw0_layers[] = {1, 2, 3, 4, 2, 3, 3, 4};
constexpr uint8_t expected_cw1_layers[] = {0, 0, 0, 0, 3, 3, 4, 4};
for (uint8_t layers = 1; layers <= NR_MAX_NB_LAYERS; layers++) {
const int index = layers - 1;
EXPECT_EQ(nr_pdsch_num_codewords(layers), expected_codewords[index]);
EXPECT_EQ(nr_pdsch_layers_per_codeword(layers, 0), expected_cw0_layers[index]);
if (expected_codewords[index] == 2) {
EXPECT_EQ(nr_pdsch_layers_per_codeword(layers, 1), expected_cw1_layers[index]);
}
}
}
TEST(nr_common, pdsch_two_codeword_ack_tracks_only_pending_transport_blocks)
{
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 3, 0), 3); // both NACK or DTX
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 3, 1), 2); // CW0 ACK, CW1 NACK
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 3, 2), 1); // CW0 NACK, CW1 ACK
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 3, 3), 0); // both ACK
EXPECT_EQ(nr_pdsch_pending_after_ack(2, 3, 1), 2); // padding CW0 ACK is irrelevant
EXPECT_EQ(nr_pdsch_pending_after_ack(2, 3, 2), 0); // pending CW1 ACK finishes
EXPECT_EQ(nr_pdsch_pending_after_ack(1, 3, 2), 1); // padding CW1 ACK is irrelevant
EXPECT_EQ(nr_pdsch_pending_after_ack(1, 3, 1), 0); // pending CW0 ACK finishes
EXPECT_EQ(nr_pdsch_pending_after_ack(0, 1, 1), 0); // one-codeword legacy HARQ
}
TEST(nr_common, pdsch_rank_capacity_prefers_independent_modes)
{
float eigenvalues[NR_MAX_NB_LAYERS];
for (float &eigenvalue : eigenvalues)
eigenvalue = 100.0f;
float scores[NR_MAX_NB_LAYERS] = {};
nr_pdsch_rank_capacity_scores(eigenvalues, NR_MAX_NB_LAYERS, 1.0f, scores);
for (int rank = 1; rank < NR_MAX_NB_LAYERS; rank++)
EXPECT_GT(scores[rank], scores[rank - 1]);
EXPECT_EQ(nr_pdsch_select_rank(scores, NR_MAX_NB_LAYERS, 0.05f), 8);
}
TEST(nr_common, pdsch_rank_capacity_rejects_weak_modes)
{
float eigenvalues[NR_MAX_NB_LAYERS] = {100.0f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f};
float scores[NR_MAX_NB_LAYERS] = {};
nr_pdsch_rank_capacity_scores(eigenvalues, NR_MAX_NB_LAYERS, 1.0f, scores);
EXPECT_EQ(nr_pdsch_select_rank(scores, NR_MAX_NB_LAYERS, 0.05f), 1);
}
TEST(nr_common, pdsch_rank_capacity_respects_rank_and_gain_margin)
{
const float scores[NR_MAX_NB_LAYERS] = {10.0f, 10.4f, 10.6f, 20.0f, 30.0f, 40.0f, 50.0f, 60.0f};
EXPECT_EQ(nr_pdsch_select_rank(scores, 3, 0.05f), 3);
EXPECT_EQ(nr_pdsch_select_rank(scores, 2, 0.05f), 1);
}
TEST(nr_common, type1_sp_4x1_parameter_ranges)
{
constexpr uint8_t expected_i11[] = {16, 16, 16, 16, 16, 16, 8, 8};
constexpr uint8_t expected_i13[] = {1, 4, 3, 3, 1, 1, 1, 1};
constexpr uint8_t expected_i2[] = {4, 2, 2, 2, 2, 2, 2, 2};
for (uint8_t layers = 1; layers <= NR_MAX_NB_LAYERS; layers++) {
EXPECT_EQ(nr_type1_sp_4x1_i11_count(layers), expected_i11[layers - 1]);
EXPECT_EQ(nr_type1_sp_4x1_i13_count(layers), expected_i13[layers - 1]);
EXPECT_EQ(nr_type1_sp_4x1_i2_count(layers), expected_i2[layers - 1]);
}
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
EXPECT_FALSE(nr_type1_sp_4x1_weights(0, 0, 0, 0, weights));
EXPECT_FALSE(nr_type1_sp_4x1_weights(8, 8, 0, 0, weights));
EXPECT_FALSE(nr_type1_sp_4x1_weights(8, 0, 1, 0, weights));
EXPECT_FALSE(nr_type1_sp_4x1_weights(8, 0, 0, 2, weights));
}
TEST(nr_common, type1_sp_4x1_precoders_are_orthogonal)
{
for (uint8_t layers = 1; layers <= NR_MAX_NB_LAYERS; layers++) {
const uint8_t i11_count = nr_type1_sp_4x1_i11_count(layers);
for (uint8_t i11 = 0; i11 < i11_count; i11++) {
for (uint8_t i13 = 0; i13 < nr_type1_sp_4x1_i13_count(layers); i13++) {
for (uint8_t i2 = 0; i2 < nr_type1_sp_4x1_i2_count(layers); i2++) {
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
ASSERT_TRUE(nr_type1_sp_4x1_weights(layers, i11, i13, i2, weights));
const int64_t expected_norm = 8LL * SHRT_MAX * SHRT_MAX / layers;
const int64_t tolerance = expected_norm / 1000;
for (int a = 0; a < layers; a++) {
int64_t diagonal = 0;
for (int port = 0; port < 8; port++)
diagonal += (int64_t)weights[a][port].r * weights[a][port].r
+ (int64_t)weights[a][port].i * weights[a][port].i;
EXPECT_NEAR((double)diagonal, (double)expected_norm, (double)tolerance);
for (int b = a + 1; b < layers; b++) {
int64_t inner_re = 0;
int64_t inner_im = 0;
for (int port = 0; port < 8; port++) {
inner_re += (int64_t)weights[a][port].r * weights[b][port].r
+ (int64_t)weights[a][port].i * weights[b][port].i;
inner_im += (int64_t)weights[a][port].r * weights[b][port].i
- (int64_t)weights[a][port].i * weights[b][port].r;
}
EXPECT_NEAR((double)inner_re, 0.0, (double)tolerance);
EXPECT_NEAR((double)inner_im, 0.0, (double)tolerance);
}
}
}
}
}
}
}
TEST(nr_common, type1_sp_4x1_i2_rotates_cross_polarization)
{
c16_t i2_zero[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
c16_t i2_one[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
ASSERT_TRUE(nr_type1_sp_4x1_weights(8, 0, 0, 0, i2_zero));
ASSERT_TRUE(nr_type1_sp_4x1_weights(8, 0, 0, 1, i2_one));
EXPECT_EQ(i2_zero[0][4].i, 0);
EXPECT_EQ(i2_one[0][4].r, 0);
EXPECT_EQ(i2_zero[0][4].r, i2_one[0][4].i);
}
int main(int argc, char **argv)
{
+67
View File
@@ -0,0 +1,67 @@
FROM ran-base:latest AS ran-build
ARG BUILD_OPTION
RUN rm -Rf /oai-ran
WORKDIR /oai-ran
COPY . .
# build all targets so final targets can be created afterwards
RUN /bin/sh oaienv && \
cd cmake_targets && \
./build_oai --ninja \
--gNB --RU --nrUE -w SIMU -w USRP --build-lib telnetsrv \
--build-e2 --cmake-opt -DKPM_VERSION=KPM_V3_00 --cmake-opt -DE2AP_VERSION=E2AP_V3 \
--cmake-opt -DCMAKE_C_FLAGS="-Werror" --cmake-opt -DCMAKE_CXX_FLAGS="-Werror" $BUILD_OPTION && \
echo "---- ldd on executables ----" && \
ldd ran_build/build/*softmodem* ran_build/build/nr-cuup ran_build/build/oairu && \
echo "---- ldd on shared libraries ----" && \
ldd ran_build/build/*.so
RUN set -eux; \
apt-get update; \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl wget iproute2 iputils-ping dnsutils jq \
iperf iperf3 ffmpeg gdb\
nghttp2-client || true; \
if ! command -v h2load >/dev/null 2>&1; then \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nghttp2 || true; \
fi; \
wget -O /usr/local/bin/sipp https://github.com/SIPp/sipp/releases/download/v3.7.7/sipp && chmod +x /usr/local/bin/sipp; \
rm -rf /var/lib/apt/lists/*
# FlexRIC libraries (built by --build-e2) - only copy service model libraries (*_sm.so).
# The source tree can carry a pre-existing FlexRIC build directory whose object-file
# timestamps are newer than freshly copied headers. Force the MAC service model and
# xApp to rebuild so mac_ind_msg_t has one ABI in the gNB, plugin, and xApp. Install
# plugins only after that rebuild.
RUN mkdir -p /usr/local/etc/flexric /usr/local/lib/flexric && \
cp /oai-ran/openair2/E2AP/flexric/flexric.conf /usr/local/etc/flexric/ 2>/dev/null || true && \
cd /oai-ran/openair2/E2AP/flexric/build && \
sed -i 's|/home/[^/]*/openairinterface5g|/oai-ran|g' CMakeCache.txt && \
find . -name 'Makefile' -o -name '*.cmake' | xargs sed -i 's|/home/[^/]*/openairinterface5g|/oai-ran|g' 2>/dev/null || true && \
find src/sm/mac_sm/CMakeFiles/mac_sm.dir src/sm/mac_sm/CMakeFiles/mac_sm_static.dir \
-type f \( -name '*.o' -o -name '*.o.d' \) -delete && \
rm -f src/sm/mac_sm/libmac_sm.so src/sm/mac_sm/libmac_sm_static.a \
examples/xApp/c/monitor/CMakeFiles/xapp_nakaolab.dir/xapp_nakaolab.c.o \
examples/xApp/c/monitor/CMakeFiles/xapp_nakaolab.dir/xapp_nakaolab.c.o.d && \
cmake --build . --target mac_sm mac_sm_static xapp_nakaolab -j2 && \
find /oai-ran/openair2/E2AP/flexric/build -name "*_sm.so" -exec cp {} /usr/local/lib/flexric/ \; && \
echo "/usr/local/lib/flexric" > /etc/ld.so.conf.d/flexric.conf && ldconfig && \
ls -la /usr/local/lib/flexric/
# Install netcat for antenna control testing + paramiko for M-Plane NETCONF server
RUN apt-get update && apt-get install -y --no-install-recommends \
netcat-openbsd python3-pip \
&& pip3 install --break-system-packages paramiko \
&& rm -rf /var/lib/apt/lists/*
# O-RU M-Plane NETCONF server (o-ran-uplane-conf antenna mask control)
# Listens on port 831, translates NETCONF edit-config to internal antenna control
COPY mplane/mplane_server.py /opt/mplane/mplane_server.py
COPY mplane/wg4-capability.json /opt/mplane/wg4-capability.json
COPY mplane/gnb-mplane-entrypoint.sh /opt/mplane/gnb-mplane-entrypoint.sh
RUN chmod +x /opt/mplane/gnb-mplane-entrypoint.sh
EXPOSE 831
ENTRYPOINT ["/bin/bash"]
+10
View File
@@ -24,6 +24,8 @@
#include "NR_PHY_INTERFACE/NR_IF_Module.h"
#include "PHY/INIT/nr_phy_init.h"
#include "PHY/MODULATION/nr_modulation.h"
#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
#include "PHY/NR_TRANSPORT/nr_antenna_control.h"
#include "PHY/NR_TRANSPORT/nr_transport_proto.h"
#include "PHY/TOOLS/tools_defs.h"
#include "PHY/defs_RU.h"
@@ -372,6 +374,14 @@ void init_eNB_afterRU(void)
*/
//init_precoding_weights(RC.gNB[inst]);
init_gNB_Tpool(inst);
// antenna control
if (nr_antenna_control_start_test_server(&gNB->antenna_state, 9999))
LOG_I(PHY, "[ANTENNA_CTRL] Test control server started on port 9999\n");
if (inst < RC.nb_nr_macrlc_inst && RC.nrmac && RC.nrmac[inst]) {
RC.nrmac[inst]->antenna_state = &gNB->antenna_state;
LOG_I(PHY, "[ANTENNA_STATE] Connected MAC scheduler to PHY antenna state for dynamic layer adaptation\n");
}
}
}
+2 -2
View File
@@ -302,8 +302,8 @@ void nrue_init_openair0(void)
cfg->ru_id = ru_id;
cfg->num_rb_dl = frame_parms->N_RB_DL;
cfg->tx_num_channels = min(4, frame_parms->nb_antennas_tx);
cfg->rx_num_channels = min(4, frame_parms->nb_antennas_rx);
cfg->tx_num_channels = min(MAX_ANT, frame_parms->nb_antennas_tx);
cfg->rx_num_channels = min(MAX_ANT, frame_parms->nb_antennas_rx);
LOG_I(PHY,
"HW: Configuring ru_id %d, sample_rate %f, tx/rx num_channels %d/%d, duplex_mode %s\n",
+5 -4
View File
@@ -558,8 +558,8 @@ static int UE_dl_preprocessing(PHY_VARS_NR_UE *UE,
if (phy_data->dlsch[0].active
&& (phy_data->dlsch[0].rnti_type == TYPE_C_RNTI_ || phy_data->dlsch[0].rnti_type == TYPE_RA_RNTI_)) {
// indicate to tx thread to wait for DLSCH decoding
if (phy_data->dlsch[0].dlsch_config.k1_feedback) { // if feedback is 0 there is no HARQ associated with this DLSCH
const int ack_nack_slot = (proc->nr_slot_rx + phy_data->dlsch[0].dlsch_config.k1_feedback) % fp->slots_per_frame;
if (phy_data->dlsch_config.k1_feedback) { // if feedback is 0 there is no HARQ associated with this DLSCH
const int ack_nack_slot = (proc->nr_slot_rx + phy_data->dlsch_config.k1_feedback) % fp->slots_per_frame;
tx_wait_for_dlsch[ack_nack_slot]++;
}
}
@@ -763,8 +763,9 @@ void *UE_thread(void *arg)
if (get_softmodem_params()->sync_ref && UE->sl_mode == 2) {
UE->is_synchronized = 1;
} else {
//warm up the RF board
} else if (!IS_SOFTMODEM_RFSIM) {
// Warm up physical RF hardware. RFsim has no RF board to settle, and
// discarding 100 simulated frames only delays initial synchronization.
openair0_timestamp_t tmp;
for (int i = 0; i < 50; i++)
readFrame(UE, &tmp, duration_rx_to_tx, true);
+6 -1
View File
@@ -246,6 +246,12 @@ int main(int argc, char **argv)
softmodem_verify_mode(get_softmodem_params());
const char *cpu_meas_env = getenv("OAI_CPU_MEAS");
if (cpu_meas_env != NULL && strcmp(cpu_meas_env, "1") == 0) {
cpumeas(CPUMEAS_ENABLE);
LOG_I(UTIL, "UE PHY CPU measurements enabled by OAI_CPU_MEAS=1\n");
}
#if T_TRACER
T_Config_Init();
#endif
@@ -490,4 +496,3 @@ int main(int argc, char **argv)
printf("Bye.\n");
return 0;
}
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# gNB entrypoint with M-Plane NETCONF server
#
# Starts the M-Plane server alongside the gNB process.
# The M-Plane server acts as an O-RU NETCONF endpoint,
# translating o-ran-uplane-conf edit-config to internal antenna control.
#
# Usage: gnb-mplane-entrypoint.sh <gnb-command-args...>
# Example: gnb-mplane-entrypoint.sh /oai-ran/.../nr-softmodem -O /opt/.../gnb.conf --rfsim
set -e
echo "[gnb-mplane] Starting M-Plane NETCONF server (port ${MPLANE_PORT:-831})..."
python3 /opt/mplane/mplane_server.py &
MPLANE_PID=$!
# Give the M-Plane server a moment to start
sleep 1
if ! kill -0 $MPLANE_PID 2>/dev/null; then
echo "[gnb-mplane] WARNING: M-Plane server failed to start, continuing without it"
fi
# Run the gNB command (passed as arguments)
echo "[gnb-mplane] Starting gNB: $@"
exec "$@"
+622
View File
@@ -0,0 +1,622 @@
#!/usr/bin/env python3
"""
O-RU M-Plane NETCONF Server for gNB Antenna Control
Lightweight NETCONF/SSH server implementing the O-RAN WG4 M-Plane interface
for antenna mask configuration (o-ran-uplane-conf.yang).
The gNB acts as an O-RU for antenna control purposes:
- Advertises mplane-supported-trx-control-masks (capability report)
- Accepts tx-array-antenna-mask-config via NETCONF edit-config
- Translates received mask to internal antenna control (localhost:9999)
- Sends mplane-trx-control-ant-mask-update notification (logged)
Protocol:
SSH transport (paramiko) -> NETCONF base:1.0 framing -> XML RPC processing
Architecture:
o1-adapter (O-DU) --NETCONF:831--> mplane_server.py (O-RU) --localhost:9999--> nr_antenna_control.c
"""
import base64
import json
import logging
import os
import re
import signal
import socket
import sys
import threading
import time
import xml.etree.ElementTree as ET
import paramiko
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
MPLANE_PORT = 831
MPLANE_USER = MPLANE_PASS = "netconf"
GNB_CTRL_HOST = "127.0.0.1"
GNB_CTRL_PORT = 9999
WG4_CAPABILITY_FILE = "/opt/mplane/wg4-capability.json"
SSH_HOST_KEY = "/opt/mplane/ssh_host_rsa_key"
NS_NC = "urn:ietf:params:xml:ns:netconf:base:1.0"
NS_UPLANE = "urn:o-ran:uplane-conf:1.0"
NETCONF_END = "]]>]]>" # https://www.rfc-editor.org/rfc/rfc4742 NETCONF base:1.0 message delimiter
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
stream=sys.stdout,
)
log = logging.getLogger("mplane-server")
# WG4 Capability (O-RU supported masks)
def b64_to_mask(b64_str):
return int.from_bytes(base64.b64decode(b64_str), byteorder="big")
def mask_to_b64(mask_int):
byte_len = max(1, (mask_int.bit_length() + 7) // 8)
return base64.b64encode(mask_int.to_bytes(byte_len, byteorder="big")).decode()
class Capability:
"""O-RU capability report: tx-arrays with supported masks."""
def __init__(self):
self.tx_arrays = {} # name -> {"masks": [{index, b64, int}], ...}
self.current_config = {} # array-name -> {"index": i, "mask_int": m}
def load(self, path):
if not (path and os.path.exists(path)):
# Fallback: generate for 4 antennas
masks = []
for n in range(4, 0, -1):
m = (1 << n) - 1
masks.append({"index": 4 - n, "b64": mask_to_b64(m), "int": m})
self.tx_arrays["tx-array-0"] = {"masks": masks, "columns": 4, "layers": 4}
self.current_config["tx-array-0"] = {"index": 0, "mask_int": 0x0F}
log.info("Generated fallback capability (4 antennas)")
return
with open(path) as f:
data = json.load(f) # returns a dict
for arr in data.get("tx-arrays", []): # blank list for fallback
name = arr["name"]
cap = arr.get("mplane-trx-control-txarr-capability-info", {})
masks = []
for m in cap.get("mplane-supported-trx-control-masks", []):
masks.append({
"index": m["index"],
"b64": m["antenna-mask"],
"int": b64_to_mask(m["antenna-mask"]),
})
self.tx_arrays[name] = {
"masks": masks,
"columns": arr.get("number-of-columns", 4),
"layers": arr.get("number-of-array-layers", 4),
}
# Default: all active (index 0)
if masks:
self.current_config[name] = {
"index": masks[0]["index"],
"mask_int": masks[0]["int"],
}
log.info("Loaded capability: %d tx-arrays from %s", len(self.tx_arrays), path)
def resolve_index(self, array_name, index):
arr = self.tx_arrays.get(array_name)
if not arr:
return None, f"unknown tx-array '{array_name}'"
for m in arr["masks"]:
if m["index"] == index:
return m, ""
return None, f"index {index} not in supported masks"
def resolve_mask(self, array_name, mask_int):
arr = self.tx_arrays.get(array_name)
if not arr:
return None, f"unknown tx-array '{array_name}'"
for m in arr["masks"]:
if m["int"] == mask_int:
return m, ""
return None, f"mask 0x{mask_int:02X} not in supported masks"
def get_capability_xml(self):
"""Generate o-ran-uplane-conf operational data XML for <get> response."""
parts = [f'<user-plane-configuration xmlns="{NS_UPLANE}">']
for name, arr in self.tx_arrays.items():
parts.append(f"<tx-arrays><name>{name}</name>")
parts.append(f"<number-of-columns>{arr['columns']}</number-of-columns>")
parts.append(f"<number-of-array-layers>{arr['layers']}</number-of-array-layers>")
parts.append("<mplane-trx-control-txarr-capability-info>")
for m in arr["masks"]:
parts.append("<mplane-supported-trx-control-masks>")
parts.append(f"<index>{m['index']}</index>")
parts.append(f"<antenna-mask>{m['b64']}</antenna-mask>")
parts.append("</mplane-supported-trx-control-masks>")
parts.append("</mplane-trx-control-txarr-capability-info>")
parts.append("</tx-arrays>")
# Current config
for aname, cfg in self.current_config.items():
parts.append("<tx-array-antenna-mask-config>")
parts.append(f"<array-name>{aname}</array-name>")
parts.append(f"<antenna-bitmask-index>{cfg['index']}</antenna-bitmask-index>")
parts.append("</tx-array-antenna-mask-config>")
parts.append("</user-plane-configuration>")
return "".join(parts)
# ---------------------------------------------------------------------------
# gNB internal antenna control client (localhost:9999)
# ---------------------------------------------------------------------------
class GnbCtrlClient:
"""Connects to the gNB internal test socket to apply antenna masks."""
def __init__(self, host, port):
self.host = host
self.port = port
self.sock = None
def connect(self):
for attempt in range(30):
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(5.0)
self.sock.connect((self.host, self.port))
self._recv() # consume welcome
log.info("Connected to gNB control at %s:%d", self.host, self.port)
return True
except (socket.error, ConnectionRefusedError) as e:
log.debug("gNB ctrl connect attempt %d/30: %s", attempt + 1, e)
if self.sock:
self.sock.close()
self.sock = None
time.sleep(2)
log.warning("Cannot connect to gNB control at %s:%d", self.host, self.port)
return False
def send_command(self, cmd):
if not self.sock:
if not self.connect():
return "ERROR: not connected"
try:
self.sock.sendall((cmd + "\n").encode())
time.sleep(0.1)
return self._recv()
except (socket.error, BrokenPipeError):
log.warning("gNB ctrl connection lost, reconnecting...")
self.sock = None
if not self.connect():
return "ERROR: reconnect failed"
self.sock.sendall((cmd + "\n").encode())
time.sleep(0.1)
return self._recv()
def _recv(self):
self.sock.settimeout(0.5)
data = b""
try:
while True:
chunk = self.sock.recv(4096)
if not chunk:
break
data += chunk
except socket.timeout:
pass
return data.decode("utf-8", errors="ignore")
def close(self):
if self.sock:
try:
self.sock.sendall(b"QUIT\n")
except Exception:
pass
self.sock.close()
self.sock = None
# ---------------------------------------------------------------------------
# NETCONF Session Handler
# ---------------------------------------------------------------------------
class NetconfSession:
"""Handle a single NETCONF session on an SSH channel."""
def __init__(self, channel, capability, gnb_ctrl):
self.channel = channel
self.cap = capability
self.gnb = gnb_ctrl
self.session_id = 1
self.running = True
def run(self):
try:
self._send_hello()
hello_msg = self._recv_message()
if hello_msg is None:
log.warning("Client did not send hello")
return
log.info("NETCONF hello exchange complete")
while self.running:
msg = self._recv_message()
if msg is None:
break
self._process_rpc(msg)
except Exception as e:
log.exception("Session error: %s", e)
finally:
try:
self.channel.close()
except Exception:
pass
log.info("NETCONF session closed")
def _send_hello(self):
hello = (
'<?xml version="1.0" encoding="UTF-8"?>'
f'<hello xmlns="{NS_NC}">'
"<capabilities>"
"<capability>urn:ietf:params:netconf:base:1.0</capability>"
"<capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>"
"</capabilities>"
f"<session-id>{self.session_id}</session-id>"
"</hello>"
)
self._send_message(hello)
def _send_message(self, xml_str):
data = (xml_str + NETCONF_END).encode("utf-8")
self.channel.sendall(data)
def _recv_message(self):
buf = b""
while self.running:
try:
self.channel.settimeout(300)
chunk = self.channel.recv(65536)
if not chunk:
return None
buf += chunk
text = buf.decode("utf-8", errors="ignore")
if NETCONF_END in text:
return text.split(NETCONF_END)[0].strip()
except socket.timeout:
continue
except Exception:
return None
return None
def _process_rpc(self, xml_str):
# Strip XML declaration if present
xml_str = re.sub(r"<\?xml[^?]*\?>", "", xml_str).strip()
try:
root = ET.fromstring(xml_str)
except ET.ParseError as e:
log.warning("Malformed RPC: %s", e)
self._send_error("0", "malformed-message", "rpc")
return
msg_id = root.get("message-id", "0")
# Find the operation element
for child in root:
local_tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag
if local_tag == "edit-config":
self._handle_edit_config(msg_id, child)
elif local_tag == "get":
self._handle_get(msg_id, child)
elif local_tag == "get-config":
self._handle_get_config(msg_id, child)
elif local_tag == "close-session":
self._send_ok(msg_id)
self.running = False
elif local_tag == "lock" or local_tag == "unlock":
self._send_ok(msg_id)
else:
self._send_error(msg_id, "operation-not-supported", "protocol")
return # Only process first operation
def _handle_edit_config(self, msg_id, edit_elem):
"""Handle edit-config for tx-array-antenna-mask-config."""
# Find <config> element
config_elem = None
for child in edit_elem:
local = child.tag.split("}")[-1] if "}" in child.tag else child.tag
if local == "config":
config_elem = child
break
if config_elem is None:
self._send_error(msg_id, "missing-element", "protocol",
"Missing <config> element")
return
# Find tx-array-antenna-mask-config under user-plane-configuration
mask_config = None
for elem in config_elem.iter():
local = elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
if local == "tx-array-antenna-mask-config":
mask_config = elem
break
if mask_config is None:
# No antenna mask config in this edit - accept silently
self._send_ok(msg_id)
return
# Extract array-name, antenna-bitmask-index or antenna-bitmask
array_name = None
bitmask_index = None
bitmask_b64 = None
for child in mask_config:
local = child.tag.split("}")[-1] if "}" in child.tag else child.tag
if local == "array-name":
array_name = child.text.strip() if child.text else None
elif local == "antenna-bitmask-index":
bitmask_index = int(child.text.strip()) if child.text else None
elif local == "antenna-bitmask":
bitmask_b64 = child.text.strip() if child.text else None
if not array_name:
self._send_error(msg_id, "missing-element", "application",
"Missing array-name in tx-array-antenna-mask-config")
return
# Resolve the mask
resolved = None
if bitmask_index is not None:
resolved, err = self.cap.resolve_index(array_name, bitmask_index)
if not resolved:
self._send_error(msg_id, "invalid-value", "application", err)
return
log.info("M-Plane edit-config: array=%s, antenna-bitmask-index=%d",
array_name, bitmask_index)
elif bitmask_b64 is not None:
mask_int = b64_to_mask(bitmask_b64)
resolved, err = self.cap.resolve_mask(array_name, mask_int)
if not resolved:
self._send_error(msg_id, "invalid-value", "application", err)
return
log.info("M-Plane edit-config: array=%s, antenna-bitmask=%s (0x%02X)",
array_name, bitmask_b64, mask_int)
else:
self._send_error(msg_id, "missing-element", "application",
"Need antenna-bitmask or antenna-bitmask-index")
return
# Apply to gNB via internal control socket and verify it succeeded.
# The gNB returns "OK: TX mask 0xAA -> 0xBB" on success; the socket
# wrapper returns "ERROR: ..." on transport failure and the gNB itself
# returns "ERROR: ..." for invalid input. Returning <ok/> when the
# underlying control failed would let the M-Plane state and the gNB
# state diverge.
mask_int = resolved["int"]
resp = self.gnb.send_command(f"SET TX MASK 0x{mask_int:02X}")
resp_clean = (resp or "").strip()
if not resp_clean.upper().startswith("OK"):
log.warning("gNB rejected mask 0x%02X: %r", mask_int, resp_clean)
self._send_error(
msg_id,
"operation-failed",
"application",
f"gNB control did not confirm mask change: {resp_clean or 'no response'}",
)
return
log.info("Applied mask 0x%02X to gNB: %s", mask_int, resp_clean)
# Update current config only after the gNB confirmed.
self.cap.current_config[array_name] = {
"index": resolved["index"],
"mask_int": mask_int,
}
# Note: the production O1 path used by this paper runs through
# docker/o1-adapter/o1_antenna_bridge.py; this legacy gNB-side
# M-Plane server keeps the same OK-required check for parity.
# Log WG4 mplane-trx-control-ant-mask-update notification
log.info("WG4 mplane-trx-control-ant-mask-update: "
"array-name='%s', array-type=TX-ARRAY, "
"updated-antenna-mask-index=%d, updated-antenna-mask=%s",
array_name, resolved["index"], resolved["b64"])
self._send_ok(msg_id)
def _handle_get(self, msg_id, get_elem):
"""Handle <get> - return operational data (capability + current config)."""
data_xml = self.cap.get_capability_xml()
reply = (
f'<rpc-reply xmlns="{NS_NC}" message-id="{msg_id}">'
f"<data>{data_xml}</data>"
"</rpc-reply>"
)
self._send_message(reply)
def _handle_get_config(self, msg_id, get_elem):
"""Handle <get-config> - return running config (current mask config)."""
parts = [f'<user-plane-configuration xmlns="{NS_UPLANE}">']
for aname, cfg in self.cap.current_config.items():
parts.append("<tx-array-antenna-mask-config>")
parts.append(f"<array-name>{aname}</array-name>")
parts.append(f"<antenna-bitmask-index>{cfg['index']}</antenna-bitmask-index>")
parts.append("</tx-array-antenna-mask-config>")
parts.append("</user-plane-configuration>")
reply = (
f'<rpc-reply xmlns="{NS_NC}" message-id="{msg_id}">'
f'<data>{"".join(parts)}</data>'
"</rpc-reply>"
)
self._send_message(reply)
def _send_ok(self, msg_id):
reply = (
f'<rpc-reply xmlns="{NS_NC}" message-id="{msg_id}">'
"<ok/>"
"</rpc-reply>"
)
self._send_message(reply)
def _send_error(self, msg_id, error_tag, error_type, error_message=""):
msg_part = ""
if error_message:
msg_part = f"<error-message>{error_message}</error-message>"
reply = (
f'<rpc-reply xmlns="{NS_NC}" message-id="{msg_id}">'
"<rpc-error>"
f"<error-type>{error_type}</error-type>"
f"<error-tag>{error_tag}</error-tag>"
"<error-severity>error</error-severity>"
f"{msg_part}"
"</rpc-error>"
"</rpc-reply>"
)
self._send_message(reply)
# ---------------------------------------------------------------------------
# SSH Server Interface
# ---------------------------------------------------------------------------
class MplaneSSHServer(paramiko.ServerInterface):
def __init__(self):
self.subsystem_event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == "session":
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def get_allowed_auths(self, username):
return "password"
def check_auth_password(self, username, password):
if username == MPLANE_USER and password == MPLANE_PASS:
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def check_channel_subsystem_request(self, channel, name):
if name == "netconf":
self.subsystem_event.set()
return True
return False
# ---------------------------------------------------------------------------
# Main Server
# ---------------------------------------------------------------------------
def handle_connection(client_sock, addr, host_key, capability, gnb_ctrl):
"""Handle a single SSH/NETCONF client connection."""
log.info("SSH connection from %s:%d", addr[0], addr[1])
transport = None
try:
transport = paramiko.Transport(client_sock)
transport.add_server_key(host_key)
server = MplaneSSHServer()
transport.start_server(server=server)
channel = transport.accept(30)
if channel is None:
log.warning("No channel opened by client")
return
# Wait for NETCONF subsystem request
if not server.subsystem_event.wait(10):
log.warning("Client did not request netconf subsystem")
channel.close()
return
session = NetconfSession(channel, capability, gnb_ctrl)
session.run()
except paramiko.SSHException as e:
log.warning("SSH error from %s: %s", addr[0], e)
except Exception as e:
log.exception("Connection error from %s: %s", addr[0], e)
finally:
if transport:
try:
transport.close()
except Exception:
pass
def main():
log.info("O-RU M-Plane NETCONF server starting")
log.info(" Port: %d", MPLANE_PORT)
log.info(" gNB control: %s:%d (internal)", GNB_CTRL_HOST, GNB_CTRL_PORT)
# Load/generate SSH host key
if os.path.exists(SSH_HOST_KEY):
host_key = paramiko.RSAKey.from_private_key_file(SSH_HOST_KEY)
log.info("Loaded SSH host key from %s", SSH_HOST_KEY)
else:
host_key = paramiko.RSAKey.generate(2048)
os.makedirs(os.path.dirname(SSH_HOST_KEY), exist_ok=True)
host_key.write_private_key_file(SSH_HOST_KEY)
log.info("Generated SSH host key at %s", SSH_HOST_KEY)
# Load WG4 capability
capability = Capability()
capability.load(WG4_CAPABILITY_FILE)
for name, arr in capability.tx_arrays.items():
log.info(" tx-array '%s': %d supported masks", name, len(arr["masks"]))
for m in arr["masks"]:
log.info(" index=%d: %s (0x%02X, %d active)",
m["index"], m["b64"], m["int"], bin(m["int"]).count("1"))
# Connect to gNB internal control
gnb_ctrl = GnbCtrlClient(GNB_CTRL_HOST, GNB_CTRL_PORT)
gnb_ctrl.connect()
# Create server socket
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(("0.0.0.0", MPLANE_PORT))
server_sock.listen(5)
server_sock.settimeout(1.0)
shutdown = threading.Event()
def handle_signal(signum, frame):
log.info("Shutdown signal received")
shutdown.set()
signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
log.info("M-Plane NETCONF server ready on port %d", MPLANE_PORT)
while not shutdown.is_set():
try:
client_sock, addr = server_sock.accept()
t = threading.Thread(
target=handle_connection,
args=(client_sock, addr, host_key, capability, gnb_ctrl),
daemon=True,
)
t.start()
except socket.timeout:
continue
except Exception as e:
if not shutdown.is_set():
log.exception("Accept error: %s", e)
log.info("Shutting down...")
server_sock.close()
gnb_ctrl.close()
if __name__ == "__main__":
main()
+17
View File
@@ -0,0 +1,17 @@
{
"tx-arrays": [
{
"name": "tx-array-0",
"number-of-columns": 4,
"number-of-array-layers": 4,
"mplane-trx-control-txarr-capability-info": {
"mplane-supported-trx-control-masks": [
{"index": 0, "antenna-mask": "Dw=="},
{"index": 1, "antenna-mask": "Bw=="},
{"index": 2, "antenna-mask": "Aw=="},
{"index": 3, "antenna-mask": "AQ=="}
]
}
}
]
}
@@ -118,6 +118,7 @@ typedef struct {
typedef struct {
uint8_t harq_pid;
uint8_t cw_idx;
uint8_t ack_nack;
uint32_t pdu_length;
uint8_t* pdu;
@@ -462,10 +463,26 @@ typedef struct {
uint8_t power_control_offset; // Ratio of PDSCH EPRE to NZP CSI-RSEPRE [3GPP TS 38.214, sec 5.2.2.3.1], Value: 0->23 representing -8 to 15 dB in 1dB steps; 255: L1 is configured with ProfileSSS
uint8_t power_control_offset_ss; // Ratio of NZP CSI-RS EPRE to SSB/PBCH block EPRE [3GPP TS 38.214, sec 5.2.2.3.1], Values: 0: -3dB; 1: 0dB; 2: 3dB; 3: 6dB; 255: L1 is configured with ProfileSSS
uint8_t measurement_bitmap; // bit 0 RSRP, bit 1 RI, bit 2 LI, bit 3 PMI, bit 4 CQI, bit 5 i1
// Internal UE MAC-to-PHY context for Type-I single-panel PMI search. Zero
// means that no associated codebook report was found for this CSI-RS.
uint8_t codebook_n1;
uint8_t codebook_n2;
uint8_t codebook_mode;
} fapi_nr_dl_config_csirs_pdu_rel15_t;
typedef enum{vrb_to_prb_mapping_non_interleaved = 0, vrb_to_prb_mapping_interleaved = 1} vrb_to_prb_mapping_t;
typedef struct {
uint8_t mcs;
bool new_data_indicator;
uint8_t rv;
uint16_t targetCodeRate;
uint8_t qamModOrder;
uint32_t TBS;
uint8_t ldpcBaseGraph;
uint8_t Nl;
} fapi_nr_dl_cw_info_t;
typedef struct {
uint16_t BWPSize;
uint16_t BWPStart;
@@ -482,15 +499,8 @@ typedef struct {
uint8_t prb_bundling_size_ind;
uint8_t rate_matching_ind;
uint8_t zp_csi_rs_trigger;
uint8_t mcs;
bool new_data_indicator;
uint8_t rv;
uint16_t targetCodeRate;
uint8_t qamModOrder;
uint32_t TBS;
uint8_t tb2_mcs;
bool tb2_new_data_indicator;
uint8_t tb2_rv;
uint8_t n_codewords;
fapi_nr_dl_cw_info_t cw_info[2];
uint8_t harq_process_nbr;
vrb_to_prb_mapping_t vrb_to_prb_mapping;
uint8_t dai;
@@ -523,7 +533,6 @@ typedef struct {
uint16_t dlDataScramblingId;
uint16_t pduBitmap;
uint32_t k1_feedback;
uint8_t ldpcBaseGraph;
uint8_t numCsiRsForRateMatching;
fapi_nr_dl_config_csirs_pdu_rel15_t csiRsForRateMatching[NFAPI_MAX_NUM_CSI_RATEMATCH];
} fapi_nr_dl_config_dlsch_pdu_rel15_t;
@@ -744,4 +753,3 @@ typedef struct {
} fapi_nr_config_request_t;
#endif
@@ -246,10 +246,31 @@ void nr_polar_info_bit_pattern(uint8_t *ibp,
}
if (n_PC > 0) {
AssertFatal(n_pc_wm == 0, "Q_PC_N computation for n_pc_wm = %i is not implemented yet!\n", n_pc_wm);
AssertFatal(n_pc_wm <= 1, "n_pc_wm = %i is not valid (only 0 and 1 allowed)\n", n_pc_wm);
// As per 5.3.1.2 of 38.212, denote the set of bit indices for parity check bits as Q_PC_N
// A number of (n_PC - n_pc_wm) parity check bits are placed in the (n_PC - n_pc_wm) least reliable bit indices
for (int n = 0; n < n_PC - n_pc_wm; n++) {
Q_PC_N[n] = Q_I_N[n];
}
// A number of n_pc_wm other parity check bits are placed in the bit indices of minimum row weight of most reliable bit indices
if (n_pc_wm == 1) {
// tilde{Q_I}^N = K most reliable indices of bar{Q_I}^N = Q_I_N[n_PC .. K+n_PC-1].
// Find minimum row weight over this range; on ties, keep the most reliable
// (later position, since Q_I_N is ascending by reliability).
int min_weight = INT_MAX;
int best_pos = -1;
for (int n = n_PC; n <= (K + n_PC) - 1; n++) {
const int w = __builtin_popcount((unsigned)Q_I_N[n]);
if (w <= min_weight) {
min_weight = w;
best_pos = n;
}
}
AssertFatal(best_pos >= 0, "No candidate found in tilde{Q_I}^N for n_pc_wm = 1 (K=%u, n_PC=%u)", K, n_PC);
Q_PC_N[n_PC - 1] = Q_I_N[best_pos];
}
}
// Q_F_N = Q_0_N-1 \ Q_I_N
@@ -266,7 +287,7 @@ void nr_polar_info_bit_pattern(uint8_t *ibp,
}
}
// Information and Parity Chack Bit Pattern
// Information and Parity Check Bit Pattern
for (int n = 0; n <= N - 1; n++) {
ibp[n] = 0;
for (int m = 0; m <= (K + n_PC) - 1; m++) {
@@ -277,7 +298,7 @@ void nr_polar_info_bit_pattern(uint8_t *ibp,
}
pcbp[n] = 0;
for (int m = 0; m < n_PC - n_pc_wm; m++) {
for (int m = 0; m < n_PC; m++) {
if (n == Q_PC_N[m]) {
pcbp[n] = 1;
break;
+3
View File
@@ -143,6 +143,9 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
init_DLSCH_struct(gNB);
nr_init_antenna_state(&gNB->antenna_state, Ptx, Prx, "tx-array-0", "rx-array-0");
LOG_I(PHY, "[ANTENNA_STATE] Initialized with %d TX, %d RX antennas\n", Ptx, Prx);
/* Do NOT allocate per-antenna rxdataF: the gNB gets a pointer to the
* RU to copy/recover freq-domain memory from there */
common_vars->rxdataF = (c16_t ***)malloc16(common_vars->num_beams_period * sizeof(c16_t**));
+7 -3
View File
@@ -160,8 +160,12 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB)
LOG_I(PHY, "Initializing UE vars for gNB TXant %u, UE RXant %u\n", fp->nb_antennas_tx, fp->nb_antennas_rx);
phy_init_nr_top(ue);
// many memory allocation sizes are hard coded
AssertFatal( fp->nb_antennas_rx <= 4, "hard coded allocation for ue_common_vars->dl_ch_estimates[gNB_id]" );
// NR UE receive buffers are dimensioned dynamically. Keep the explicit
// platform bound so an invalid RF configuration fails before allocation.
AssertFatal(fp->nb_antennas_rx > 0 && fp->nb_antennas_rx <= MAX_ANT,
"UE RX antenna count %u exceeds platform maximum %u",
fp->nb_antennas_rx,
MAX_ANT);
AssertFatal( nb_connected_gNB <= NUMBER_OF_CONNECTED_gNB_MAX, "n_connected_gNB is too large" );
// init phy_vars_ue
@@ -170,7 +174,7 @@ int init_nr_ue_signal(PHY_VARS_NR_UE *ue, int nb_connected_gNB)
ue->measurements.ssb_sinr_dB[i] = INT_MIN;
}
for (int i = 0; i < 4; i++) {
for (int i = 0; i < fp->nb_antennas_rx; i++) {
ue->rx_gain_max[i] = 135;
ue->rx_gain_med[i] = 128;
ue->rx_gain_byp[i] = 120;
+1
View File
@@ -10,6 +10,7 @@
#include "PHY/LTE_TRANSPORT/transport_eNB.h"
#include "modulation_eNB.h"
#include "common/utils/LOG/vcd_signal_dumper.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
int beam_precoding(int32_t **txdataF,
int32_t **txdataF_BF,
+32
View File
@@ -4,6 +4,8 @@
#include "nr_modulation.h"
#include "openair1/PHY/TOOLS/tools_defs.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#include "common/utils/LOG/log.h"
void nr_beam_precoding(c16_t **txdataF,
c16_t **txdataF_BF,
@@ -25,3 +27,33 @@ void nr_beam_precoding(c16_t **txdataF,
15);
}
}
void nr_beam_precoding_with_muting(c16_t **txdataF,
c16_t **txdataF_BF,
NR_DL_FRAME_PARMS *frame_parms,
int32_t ***beam_weights,
int symbol,
int aa,
int nb_antenna_ports,
int offset,
const nr_antenna_state_t *ant_state)
{
// Clear output buffer first
memset(&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size], 0,
sizeof(c16_t) * frame_parms->ofdm_symbol_size);
// O-RAN energy saving: skip precoding if antenna is muted
// This implements transmission blanking per WG4 spec
if (ant_state != NULL && !nr_is_tx_antenna_active(ant_state, aa)) {
LOG_D(PHY, "[ANTENNA_MUTE] Antenna %d muted, blanking transmission for symbol %d\n",
aa, symbol);
return;
}
// Normal precoding for active antennas
for (int p = 0; p < nb_antenna_ports; p++) {
multadd_cpx_vector((c16_t *)&txdataF[p][(symbol * frame_parms->ofdm_symbol_size) + offset],
(c16_t *)beam_weights[p][aa],
(c16_t *)&txdataF_BF[aa][symbol * frame_parms->ofdm_symbol_size],
frame_parms->ofdm_symbol_size,
15);
}
}
+41 -57
View File
@@ -216,15 +216,19 @@ void nr_modulation(const uint32_t *in, uint32_t length, uint16_t mod_order, int1
}
void nr_layer_mapping(int nbCodes,
int encoded_len,
c16_t mod_symbs[nbCodes][encoded_len],
const c16_t *const mod_symbs[NR_MAX_NB_CODEWORDS],
const uint32_t n_symbs_per_codeword[NR_MAX_NB_CODEWORDS],
uint8_t n_layers,
int layerSz,
uint32_t n_symbs,
c16_t tx_layers[][layerSz])
{
const uint32_t n_symbs = n_symbs_per_codeword[0];
LOG_D(PHY, "Doing layer mapping for %d layers, %d symbols\n", n_layers, n_symbs);
c16_t *mod = mod_symbs[0];
const c16_t *mod = mod_symbs[0];
AssertFatal(nbCodes == nr_pdsch_num_codewords(n_layers),
"Layer mapping got %d codewords for %u layers\n",
nbCodes,
n_layers);
switch (n_layers) {
case 1:
memcpy(tx_layers[0], mod, n_symbs * sizeof(**mod_symbs));
@@ -472,58 +476,26 @@ void nr_layer_mapping(int nbCodes,
case 5:
case 6:
case 7:
case 8:
/*
// Layer 0,1
for (int i = 0; i < n_symbs; i += 2) {
const int txIdx = i / 2;
tx_layer[0][txIdx] = mod_symbs[0][i];
tx_layer[1][txIdx] = mod_symbs[0][i + 1];
case 8: {
int first_layer = 0;
for (int q = 0; q < nbCodes; q++) {
const int cw_layers = nr_pdsch_layers_per_codeword(n_layers, q);
AssertFatal(n_symbs_per_codeword[q] % cw_layers == 0,
"Codeword %d has %u symbols, not divisible by %d layers\n",
q,
n_symbs_per_codeword[q],
cw_layers);
const uint32_t symbols_per_layer = n_symbs_per_codeword[q] / cw_layers;
AssertFatal(symbols_per_layer <= layerSz,
"Layer buffer too small: need %u, have %d symbols\n",
symbols_per_layer,
layerSz);
for (uint32_t i = 0; i < symbols_per_layer; i++)
for (int l = 0; l < cw_layers; l++)
tx_layers[first_layer + l][i] = mod_symbs[q][i * cw_layers + l];
first_layer += cw_layers;
}
// layers 2,3,4
else
for (int i = 0; i < n_symbs; i += 3) {
const int txIdx = i / 3;
tx_layer[2][txIdx] = mod_symbs[1][i + 2];
tx_layer[3][txIdx] = mod_symbs[1][i + 3];
tx_layer[4][txIdx] = mod_symbs[1][i + 4];
}
break;
case 6:
for (int q=0; q<2; q++)
for (int i = 0; i < n_symbs; i += 3) {
const int txIdx = i / 3;
tx_layer[0][txIdx] = mod_symbs[q][i + layer];
tx_layer[1][txIdx] = mod_symbs[q][i + layer];
tx_layer[2][txIdx] = mod_symbs[q][i + layer];
tx_layer[3][txIdx] = mod_symbs[q][i + layer];
tx_layer[4][txIdx] = mod_symbs[q][i + layer];
tx_layer[5][txIdx] = mod_symbs[q][i + layer];
}
break;
case 7:
if (layer < 3)
for (int i = 0; i < n_symbs; i += 3) {
const int txIdx = i / 3;
tx_layer[txIdx] = mod_symbs[1][i + layer];
}
else
for (int i = 0; i < n_symbs; i += 4) {
const int txIdx = i / 4;
tx_layer[txIdx] = mod_symbs[0][i + layer];
}
break;
case 8:
for (int q=0; q<2; q++)
for (int i = 0; i < n_symbs; i += 4) {
const int txIdx = i / 4;
tx_layer[txIdx] = mod_symbs[q][i + layer];
}
break;
*/
} break;
default:
AssertFatal(0, "Invalid number of layers %d\n", n_layers);
}
@@ -687,7 +659,7 @@ c16_t nr_layer_precoder(int sz, c16_t datatx_F_precoding[][sz], const char *prec
c16_t nr_layer_precoder_cm(int n_layers,
int symSz,
c16_t datatx_F_precoding[n_layers][symSz],
const c16_t datatx_F_precoding[n_layers][symSz],
int ap,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
int offset)
@@ -819,7 +791,19 @@ void nr_layer_precoder_simd(const int n_layers,
{
// For x86, use 256 SIMD for every 8 RE and 128 SIMD for last 4 RE
// For aarch64, use 128 SIMD for every 4 RE
AssertFatal(n_layers > 0 && n_layers <= 4, "Shouldn't get here, n_layers %d\n", n_layers);
AssertFatal(n_layers > 0 && n_layers <= NR_MAX_NB_LAYERS, "Invalid PDSCH layer count %d\n", n_layers);
/* The hand-vectorized kernels below remain the fast path for ranks 1--4.
* Rank 5--8 is uncommon and uses the same full complex matrix operation via
* the scalar reference kernel. Unlike antenna fan-out, this combines every
* distinct layer with its own precoder weight and is therefore a genuine
* high-rank precoder. */
if (n_layers > 4) {
for (int re = 0; re < re_cnt; re++)
txdataF_precoded[sc_offset + re] =
nr_layer_precoder_cm(n_layers, symSz, txdataF_res_mapped, ant, weights, sc_offset + re);
return;
}
// 512/256 SIMD: Do 16/8 RE in one iteration, 3 iterations for 2 RB
c16_t *beginning = txdataF_precoded + sc_offset;
+31 -4
View File
@@ -7,6 +7,7 @@
#include <stdint.h>
#include "PHY/defs_nr_common.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#define DMRS_MOD_ORDER 2
/*Precoding matices: W[pmi][antenna_port][layer]*/
@@ -36,11 +37,10 @@ void nr_modulation(const uint32_t *in,
*/
void nr_layer_mapping(int nbCodes,
int encoded_len,
c16_t mod_symbs[nbCodes][encoded_len],
const c16_t *const mod_symbs[NR_MAX_NB_CODEWORDS],
const uint32_t n_symbs_per_codeword[NR_MAX_NB_CODEWORDS],
uint8_t n_layers,
int layerSz,
uint32_t n_symbs,
c16_t tx_layers[][layerSz]);
/*! \brief Perform NR layer mapping. TS 38.211 V15.4.0 subclause 7.3.1.3
@@ -84,6 +84,33 @@ void nr_beam_precoding(c16_t **txdataF,
int nb_antenna_ports,
int offset);
/*! \brief NR beam precoding with O-RAN compliant antenna muting
*
* Extends nr_beam_precoding to support antenna muting for energy saving
* per O-RAN WG4 MPLANE-TRX-CONTROL specification (o-ran-uplane-conf.yang).
* When an antenna is muted, transmission is blanked (output zeroed).
*
* @param[in] txdataF Input frequency-domain data per antenna port
* @param[out] txdataF_BF Output beamformed data per physical antenna
* @param[in] frame_parms Frame parameters
* @param[in] beam_weights Beamforming weights
* @param[in] symbol Current symbol number
* @param[in] aa Physical antenna index
* @param[in] nb_antenna_ports Number of antenna ports
* @param[in] offset Frequency offset
* @param[in] ant_state Antenna state for muting control (NULL = all active)
*/
void nr_beam_precoding_with_muting(c16_t **txdataF,
c16_t **txdataF_BF,
NR_DL_FRAME_PARMS *frame_parms,
int32_t ***beam_weights,
int symbol,
int aa,
int nb_antenna_ports,
int offset,
const nr_antenna_state_t *ant_state);
void apply_nr_rotation_TX(const NR_DL_FRAME_PARMS *fp,
c16_t *txdataF,
bool is_flat_buff,
@@ -124,7 +151,7 @@ c16_t nr_layer_precoder(int sz, c16_t datatx_F_precoding[][sz], const char *prec
c16_t nr_layer_precoder_cm(int n_layers,
int symSz,
c16_t datatx_F_precoding[n_layers][symSz],
const c16_t datatx_F_precoding[n_layers][symSz],
int ap,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
int offset);
@@ -16,13 +16,14 @@ extern "C" {
#include "openair1/PHY/TOOLS/tools_defs.h" // c16_t
#include "nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h" // fapi_nr_dl_config_dci_dl_pdu_rel15_t
#include "common/utils/LOG/log.h"
//#include "openair1/PHY/MODULATION/nr_modulation.h" // nr_layer_precoder_simd_[cm, simd]
// Forward declare the function but hide the Variable-Length Array (VLA) feature from C not available in C++
c16_t nr_layer_precoder_cm(int n_layers,
int symbol_size,
c16_t (*dataF_in)[/*symbol_size*/], // c16_t dataF_in[n_layers][symbol_size]
const c16_t (*dataF_in)[/*symbol_size*/], // c16_t dataF_in[n_layers][symbol_size]
int ant,
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS],
int offset);
@@ -37,6 +38,14 @@ void nr_layer_precoder_simd(int n_layers,
int re_cnt,
c16_t *dataF_out);
// Forward declare the layer mapper while hiding its C VLA dimension.
void nr_layer_mapping(int nb_codewords,
const c16_t *const mod_symbols[2],
const uint32_t n_symbols_per_codeword[2],
uint8_t n_layers,
int layer_size,
c16_t (*tx_layers)[/*layer_size*/]);
#ifdef __cplusplus
}
#endif
@@ -185,11 +194,50 @@ TEST(NrLayerPrecoderTest, Compare_CM_SIMD)
}
}
TEST(NrLayerMappingTest, Rank8UsesTwoCodewordsAcrossEightDistinctLayers)
{
constexpr int n_codewords = 2;
constexpr int n_layers = 8;
constexpr int layers_per_codeword = 4;
constexpr int symbols_per_layer = 7;
constexpr int symbols_per_codeword = layers_per_codeword * symbols_per_layer;
std::vector<c16_t> cw0(symbols_per_codeword);
std::vector<c16_t> cw1(symbols_per_codeword);
for (int i = 0; i < symbols_per_codeword; ++i) {
cw0[i] = {static_cast<int16_t>(100 + i), static_cast<int16_t>(-100 - i)};
cw1[i] = {static_cast<int16_t>(1000 + i), static_cast<int16_t>(-1000 - i)};
}
const c16_t *mod_symbols[2] = {cw0.data(), cw1.data()};
const uint32_t n_symbols_per_codeword[2] = {symbols_per_codeword, symbols_per_codeword};
std::vector<c16_t> layer_storage(n_layers * symbols_per_layer, c16_t{SHRT_MIN, SHRT_MIN});
c16_t(*tx_layers)[symbols_per_layer] = reinterpret_cast<c16_t(*)[symbols_per_layer]>(layer_storage.data());
nr_layer_mapping(n_codewords,
mod_symbols,
n_symbols_per_codeword,
n_layers,
symbols_per_layer,
tx_layers);
for (int re = 0; re < symbols_per_layer; ++re) {
for (int layer = 0; layer < n_layers; ++layer) {
const int cw = layer / layers_per_codeword;
const int cw_layer = layer % layers_per_codeword;
const c16_t expected = mod_symbols[cw][re * layers_per_codeword + cw_layer];
EXPECT_EQ(tx_layers[layer][re].r, expected.r) << "real mismatch at layer " << layer << ", RE " << re;
EXPECT_EQ(tx_layers[layer][re].i, expected.i) << "imag mismatch at layer " << layer << ", RE " << re;
}
}
}
int main(int argc, char **argv)
{
// Initialize random seed
srand(time(0));
logInit();
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
@@ -0,0 +1,735 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
/*! \file PHY/NR_TRANSPORT/nr_antenna_control.c
* \brief Antenna control interface implementation with O-RAN M-Plane support
* \author OpenAirInterface
* \date 2025
* \version 2.0
*/
#include "nr_antenna_control.h"
#include "common/utils/LOG/log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
// Try to include M-Plane header if available
#ifdef OAI_MPLANE
#include "radio/fhi_72/mplane/antenna-control.h"
#include "radio/fhi_72/mplane/ru-mplane-api.h"
#endif
// Global control state
static struct {
pthread_t thread;
int server_fd;
nr_antenna_state_t *ant_state;
volatile bool running;
int port;
nr_antenna_ctrl_backend_t backend;
int64_t tx_lock_until_ms[NR_MAX_ANTENNAS];
pthread_mutex_t command_lock;
pthread_mutex_t clients_lock;
pthread_cond_t clients_done;
unsigned int client_count;
#ifdef OAI_MPLANE
ru_session_t *ru_session;
#else
void *ru_session;
#endif
} g_ctrl = {0};
static bool valid_tsm_duration(int seconds)
{
return seconds == 0 || seconds == 15 || seconds == 30 ||
seconds == 45 || seconds == 60;
}
static int64_t monotonic_ms(void)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000;
}
static int64_t tx_lock_remaining_ms(int ant_id)
{
if (ant_id < 0 || ant_id >= NR_MAX_ANTENNAS)
return 0;
int64_t remaining = g_ctrl.tx_lock_until_ms[ant_id] - monotonic_ms();
if (remaining <= 0) {
g_ctrl.tx_lock_until_ms[ant_id] = 0;
return 0;
}
return remaining;
}
static void set_tx_lock(int ant_id, int seconds)
{
if (ant_id < 0 || ant_id >= NR_MAX_ANTENNAS)
return;
g_ctrl.tx_lock_until_ms[ant_id] =
seconds > 0 ? monotonic_ms() + (int64_t)seconds * 1000 : 0;
}
static void clear_all_tx_locks(void)
{
memset(g_ctrl.tx_lock_until_ms, 0, sizeof(g_ctrl.tx_lock_until_ms));
}
// ============================================================================
// Test Socket Server Implementation (Development/Testing Mode)
// ============================================================================
// Process a single command and generate response
static void process_command(const char *cmd, char *response, size_t resp_size)
{
char cmd_upper[256];
strncpy(cmd_upper, cmd, sizeof(cmd_upper) - 1);
cmd_upper[sizeof(cmd_upper) - 1] = '\0';
// Convert to uppercase for case-insensitive matching
for (char *p = cmd_upper; *p; p++) {
if (*p >= 'a' && *p <= 'z') *p -= 32;
}
// Trim whitespace
char *p = cmd_upper;
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
char *end = p + strlen(p) - 1;
while (end > p && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = '\0';
if (strncmp(p, "MUTE TX ", 8) == 0) {
int ant_id = -1;
int duration = 0;
int fields = sscanf(p + 8, "%d %d", &ant_id, &duration);
if (fields < 1 || ant_id < 0 || ant_id >= g_ctrl.ant_state->num_tx_antennas) {
snprintf(response, resp_size, "ERROR: Invalid TX antenna id\n");
return;
}
if (fields >= 2 && !valid_tsm_duration(duration)) {
snprintf(response, resp_size,
"ERROR: Unsupported TSM duration %d; use 15, 30, 45, or 60 seconds\n",
duration);
return;
}
nr_mute_tx_antenna(g_ctrl.ant_state, ant_id);
set_tx_lock(ant_id, duration);
snprintf(response, resp_size, "OK: TX antenna %d muted (TSM=%ds)\n",
ant_id, duration);
}
else if (strncmp(p, "UNMUTE TX ", 10) == 0) {
int ant_id = -1;
char option[16] = {0};
int fields = sscanf(p + 10, "%d %15s", &ant_id, option);
if (fields < 1 || ant_id < 0 || ant_id >= g_ctrl.ant_state->num_tx_antennas) {
snprintf(response, resp_size, "ERROR: Invalid TX antenna id\n");
return;
}
bool force = fields >= 2 && strcmp(option, "FORCE") == 0;
int64_t remaining = tx_lock_remaining_ms(ant_id);
if (remaining > 0 && !force) {
snprintf(response, resp_size,
"ERROR: TX antenna %d locked for %ld more ms\n",
ant_id, (long)remaining);
return;
}
set_tx_lock(ant_id, 0);
nr_unmute_tx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: TX antenna %d unmuted%s\n",
ant_id, force ? " (emergency override)" : "");
}
else if (strncmp(p, "MUTE RX ", 8) == 0) {
int ant_id = atoi(p + 8);
nr_mute_rx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: RX antenna %d muted\n", ant_id);
}
else if (strncmp(p, "UNMUTE RX ", 10) == 0) {
int ant_id = atoi(p + 10);
nr_unmute_rx_antenna(g_ctrl.ant_state, ant_id);
snprintf(response, resp_size, "OK: RX antenna %d unmuted\n", ant_id);
}
else if (strncmp(p, "SET TX MASK ", 12) == 0) {
unsigned int mask;
int duration = 0;
char option[16] = {0};
int fields = sscanf(p + 12, "%x %d %15s", &mask, &duration, option);
if (fields < 1) {
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
return;
}
if (fields >= 2 && !valid_tsm_duration(duration)) {
snprintf(response, resp_size,
"ERROR: Unsupported TSM duration %d; use 15, 30, 45, or 60 seconds\n",
duration);
return;
}
bool force = fields >= 3 && strcmp(option, "FORCE") == 0;
uint8_t valid_mask = (1u << g_ctrl.ant_state->num_tx_antennas) - 1u;
uint8_t requested = (uint8_t)mask & valid_mask;
uint8_t old_requested = nr_get_requested_tx_antenna_bitmask(g_ctrl.ant_state);
uint8_t waking = requested & ~old_requested;
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
if ((waking & (1u << ant_id)) && tx_lock_remaining_ms(ant_id) > 0 && !force) {
snprintf(response, resp_size,
"ERROR: TX antenna %d is still TSM-locked\n", ant_id);
return;
}
}
nr_request_tx_antenna_bitmask(g_ctrl.ant_state, requested);
uint8_t muted = old_requested & ~requested;
for (int ant_id = 0; ant_id < g_ctrl.ant_state->num_tx_antennas; ++ant_id) {
uint8_t bit = 1u << ant_id;
if (muted & bit)
set_tx_lock(ant_id, duration);
if (waking & bit)
set_tx_lock(ant_id, 0);
}
bool pending = nr_get_pending_tx_antenna_bitmask(g_ctrl.ant_state, NULL);
uint8_t active = nr_get_tx_antenna_bitmask(g_ctrl.ant_state);
snprintf(response, resp_size,
"OK: TX mask 0x%02X -> 0x%02X (active=0x%02X%s, TSM=%ds%s)\n",
old_requested, requested, active,
pending ? ", HARQ drain pending" : "",
duration, force ? ", emergency override" : "");
}
else if (strncmp(p, "SET RX MASK ", 12) == 0) {
unsigned int mask;
if (sscanf(p + 12, "%x", &mask) == 1 || sscanf(p + 12, "0X%x", &mask) == 1) {
uint8_t old = nr_set_rx_antenna_bitmask(g_ctrl.ant_state, (uint8_t)mask);
snprintf(response, resp_size, "OK: RX mask 0x%02X -> 0x%02X\n", old, (uint8_t)mask);
} else {
snprintf(response, resp_size, "ERROR: Invalid mask format. Use hex (e.g., 0F or 0x0F)\n");
}
}
else if (strcmp(p, "STATUS") == 0) {
bool in_recovery = nr_is_in_recovery_mode(g_ctrl.ant_state);
uint8_t requested_mask = nr_get_requested_tx_antenna_bitmask(g_ctrl.ant_state);
bool pending = nr_get_pending_tx_antenna_bitmask(g_ctrl.ant_state, NULL);
snprintf(response, resp_size,
"=== Antenna Status ===\n"
"TX Array: %s\n"
" Antennas: %d configured, %d active\n"
" Bitmask: 0x%02X\n"
" Requested bitmask: 0x%02X\n"
" HARQ drain pending: %s\n"
" Antenna 0 protected: %s\n"
"RX Array: %s\n"
" Antennas: %d configured, %d active\n"
" Bitmask: 0x%02X\n"
"Recovery Mode: %s\n"
"Backend: %s\n",
g_ctrl.ant_state->tx_array_name,
g_ctrl.ant_state->num_tx_antennas,
nr_get_active_tx_antenna_count(g_ctrl.ant_state),
nr_get_tx_antenna_bitmask(g_ctrl.ant_state),
requested_mask,
pending ? "YES" : "NO",
g_ctrl.ant_state->config.protect_antenna0 ? "YES" : "NO",
g_ctrl.ant_state->rx_array_name,
g_ctrl.ant_state->num_rx_antennas,
nr_get_active_rx_antenna_count(g_ctrl.ant_state),
nr_get_rx_antenna_bitmask(g_ctrl.ant_state),
in_recovery ? "ACTIVE (use identity matrix)" : "OFF",
g_ctrl.backend == ANTENNA_CTRL_BACKEND_MPLANE ? "O-RAN M-Plane NETCONF" :
g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET ? "Test Socket (NOT O-RAN)" :
"None");
size_t used = strlen(response);
if (used < resp_size) {
used += snprintf(response + used, resp_size - used, "TSM locks:");
for (int ant_id = 0;
ant_id < g_ctrl.ant_state->num_tx_antennas && used < resp_size;
++ant_id) {
int64_t remaining = tx_lock_remaining_ms(ant_id);
if (remaining > 0)
used += snprintf(response + used, resp_size - used,
" TX%d=%lds", ant_id, (long)((remaining + 999) / 1000));
}
if (used < resp_size)
snprintf(response + used, resp_size - used, "\n");
}
}
else if (strcmp(p, "STATS") == 0) {
uint64_t tx_events, rx_events, muted_slots;
nr_get_antenna_stats(g_ctrl.ant_state, &tx_events, &rx_events, &muted_slots);
snprintf(response, resp_size,
"=== Energy Saving Statistics ===\n"
"TX mute events: %lu\n"
"RX mute events: %lu\n"
"Total muted slots: %lu\n",
tx_events, rx_events, muted_slots);
}
else if (strcmp(p, "RESET STATS") == 0) {
nr_reset_antenna_stats(g_ctrl.ant_state);
snprintf(response, resp_size, "OK: Statistics reset\n");
}
else if (strcmp(p, "RESET") == 0 || strcmp(p, "UNMUTE ALL") == 0) {
// Reset all antennas to active state with recovery mode
clear_all_tx_locks();
nr_unmute_all_tx_antennas(g_ctrl.ant_state);
nr_unmute_all_rx_antennas(g_ctrl.ant_state);
snprintf(response, resp_size,
"OK: All antennas unmuted (recovery mode: %u ms)\n"
" TX mask: 0x%02X (%d antennas)\n"
" RX mask: 0x%02X (%d antennas)\n",
g_ctrl.ant_state->recovery.recovery_duration_ms,
nr_get_tx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_tx_antennas,
nr_get_rx_antenna_bitmask(g_ctrl.ant_state), g_ctrl.ant_state->num_rx_antennas);
}
else if (strcmp(p, "PROTECT ANT0 ON") == 0) {
nr_set_antenna0_protection(g_ctrl.ant_state, true);
snprintf(response, resp_size, "OK: Antenna 0 protection enabled (SSB)\n");
}
else if (strcmp(p, "PROTECT ANT0 OFF") == 0) {
nr_set_antenna0_protection(g_ctrl.ant_state, false);
snprintf(response, resp_size, "OK: Antenna 0 protection disabled (WARNING: may cause UE sync loss)\n");
}
else if (strcmp(p, "SYNC") == 0) {
// Synchronize to RU (only works with M-Plane backend)
if (nr_antenna_control_sync_to_ru(g_ctrl.ant_state)) {
snprintf(response, resp_size, "OK: Synchronized to RU\n");
} else {
snprintf(response, resp_size, "ERROR: Sync failed (M-Plane backend required)\n");
}
}
else if (strcmp(p, "HELP") == 0) {
snprintf(response, resp_size,
"=== Antenna Control ===\n"
"MUTE TX <id> [15|30|45|60] - Mute TX antenna with optional TSM lock\n"
"UNMUTE TX <id> [FORCE] - Unmute TX antenna\n"
"MUTE/UNMUTE RX <id> - Change RX antenna state\n"
"SET TX MASK <hex> [duration] [FORCE] - Set TX mask and TSM lock\n"
"SET RX MASK <hex> - Set RX bitmask\n"
"STATUS - Show state\n"
"STATS - Show statistics\n"
"RESET - Unmute all\n"
"PROTECT ANT0 ON/OFF\n"
"HELP/QUIT\n");
}
else if (strcmp(p, "QUIT") == 0 || strcmp(p, "EXIT") == 0) {
snprintf(response, resp_size, "BYE\n");
}
else if (strlen(p) == 0) {
response[0] = '\0'; // Empty command, no response
}
else {
snprintf(response, resp_size, "ERROR: Unknown command '%s'. Type HELP for usage.\n", cmd);
}
}
static void send_client_prompt(int client_fd)
{
const char prompt[] = "> ";
if (send(client_fd, prompt, sizeof(prompt) - 1, MSG_NOSIGNAL) < 0)
LOG_D(PHY, "[ANTENNA_CTRL] Failed to send client prompt: %s\n", strerror(errno));
}
// Handle a single client connection
static void handle_client(int client_fd)
{
char buffer[256];
char response[1024];
// Send welcome message with warning about non-compliance
const char *welcome =
"=== OAI Antenna Control Test Server ===\n"
"WARNING: This is a TEST interface, NOT O-RAN compliant!\n"
"For production, use NETCONF via radio/fhi_72/mplane/\n"
"Type HELP for commands.\n\n";
send(client_fd, welcome, strlen(welcome), MSG_NOSIGNAL);
send_client_prompt(client_fd);
while (g_ctrl.running) {
// Receive command
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
continue;
if (n <= 0)
break;
buffer[n] = '\0';
// Serialize state-changing commands while allowing multiple connected
// clients (for example the persistent O1 bridge and a diagnostic client).
pthread_mutex_lock(&g_ctrl.command_lock);
process_command(buffer, response, sizeof(response));
pthread_mutex_unlock(&g_ctrl.command_lock);
// Send response
if (strlen(response) > 0) {
send(client_fd, response, strlen(response), MSG_NOSIGNAL);
}
// Check for quit
if (strstr(buffer, "QUIT") || strstr(buffer, "quit") ||
strstr(buffer, "EXIT") || strstr(buffer, "exit")) {
break;
}
send_client_prompt(client_fd);
}
close(client_fd);
LOG_I(PHY, "[ANTENNA_CTRL] Client disconnected\n");
}
static void *client_thread(void *arg)
{
const int client_fd = (int)(intptr_t)arg;
handle_client(client_fd);
pthread_mutex_lock(&g_ctrl.clients_lock);
AssertFatal(g_ctrl.client_count > 0, "Antenna-control client count underflow\n");
g_ctrl.client_count--;
pthread_cond_broadcast(&g_ctrl.clients_done);
pthread_mutex_unlock(&g_ctrl.clients_lock);
return NULL;
}
// Server thread function
static void *server_thread(void *arg)
{
(void)arg;
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
LOG_I(PHY, "[ANTENNA_CTRL] Test server listening on port %d\n", g_ctrl.port);
while (g_ctrl.running) {
// Accept connection with timeout
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(g_ctrl.server_fd, &read_fds);
struct timeval tv = {.tv_sec = 1, .tv_usec = 0};
int ret = select(g_ctrl.server_fd + 1, &read_fds, NULL, NULL, &tv);
if (ret <= 0) continue;
int client_fd = accept(g_ctrl.server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0) continue;
LOG_I(PHY, "[ANTENNA_CTRL] Client connected from %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// Bound recv() so shutdown can wait for detached client workers without
// leaving a thread blocked forever on an idle persistent connection.
struct timeval client_timeout = {.tv_sec = 1, .tv_usec = 0};
setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &client_timeout, sizeof(client_timeout));
pthread_mutex_lock(&g_ctrl.clients_lock);
g_ctrl.client_count++;
pthread_mutex_unlock(&g_ctrl.clients_lock);
pthread_t client;
if (pthread_create(&client, NULL, client_thread, (void *)(intptr_t)client_fd) != 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create client thread\n");
close(client_fd);
pthread_mutex_lock(&g_ctrl.clients_lock);
g_ctrl.client_count--;
pthread_cond_broadcast(&g_ctrl.clients_done);
pthread_mutex_unlock(&g_ctrl.clients_lock);
continue;
}
pthread_detach(client);
}
return NULL;
}
// ============================================================================
// Public API Implementation
// ============================================================================
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state, void *ru_session)
{
#ifdef OAI_MPLANE
if (g_ctrl.running) {
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
return false;
}
if (ant_state == NULL || ru_session == NULL) {
LOG_E(PHY, "[ANTENNA_CTRL] Invalid parameters\n");
return false;
}
g_ctrl.ant_state = ant_state;
g_ctrl.ru_session = (ru_session_t *)ru_session;
g_ctrl.backend = ANTENNA_CTRL_BACKEND_MPLANE;
g_ctrl.running = true;
// Check if RU supports TRX control feature
if (!mplane_supports_trx_control(g_ctrl.ru_session)) {
LOG_W(PHY, "[ANTENNA_CTRL] RU does not support MPLANE-TRX-CONTROL feature\n");
LOG_W(PHY, "[ANTENNA_CTRL] Antenna muting may not work on this RU\n");
}
LOG_I(PHY, "[ANTENNA_CTRL] Initialized with O-RAN M-Plane NETCONF backend\n");
LOG_I(PHY, "[ANTENNA_CTRL] TX array: %s, RX array: %s\n",
ant_state->tx_array_name, ant_state->rx_array_name);
return true;
#else
LOG_E(PHY, "[ANTENNA_CTRL] M-Plane support not compiled in (OAI_MPLANE not defined)\n");
LOG_E(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_start_test_server() for testing\n");
return false;
#endif
}
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port)
{
if (g_ctrl.running) {
LOG_W(PHY, "[ANTENNA_CTRL] Control interface already active\n");
return false;
}
if (ant_state == NULL) {
LOG_E(PHY, "[ANTENNA_CTRL] Antenna state is NULL\n");
return false;
}
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
LOG_W(PHY, "[ANTENNA_CTRL] Starting TEST socket server - NOT O-RAN COMPLIANT!\n");
LOG_W(PHY, "[ANTENNA_CTRL] For production O-RAN compliance, use M-Plane NETCONF\n");
LOG_W(PHY, "[ANTENNA_CTRL] See: radio/fhi_72/mplane/antenna-control-mplane.h\n");
LOG_W(PHY, "[ANTENNA_CTRL] ======================================================\n");
g_ctrl.ant_state = ant_state;
g_ctrl.port = (port > 0) ? port : NR_ANTENNA_CONTROL_TEST_PORT;
g_ctrl.backend = ANTENNA_CTRL_BACKEND_TEST_SOCKET;
pthread_mutex_init(&g_ctrl.command_lock, NULL);
pthread_mutex_init(&g_ctrl.clients_lock, NULL);
pthread_cond_init(&g_ctrl.clients_done, NULL);
g_ctrl.client_count = 0;
// Create socket
g_ctrl.server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (g_ctrl.server_fd < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create socket: %s\n", strerror(errno));
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
return false;
}
// Allow address reuse
int opt = 1;
setsockopt(g_ctrl.server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Bind
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_ANY),
.sin_port = htons(g_ctrl.port)
};
if (bind(g_ctrl.server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to bind port %d: %s\n", g_ctrl.port, strerror(errno));
close(g_ctrl.server_fd);
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
return false;
}
// Listen
if (listen(g_ctrl.server_fd, 1) < 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to listen: %s\n", strerror(errno));
close(g_ctrl.server_fd);
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
return false;
}
// Start server thread
g_ctrl.running = true;
if (pthread_create(&g_ctrl.thread, NULL, server_thread, NULL) != 0) {
LOG_E(PHY, "[ANTENNA_CTRL] Failed to create server thread\n");
g_ctrl.running = false;
close(g_ctrl.server_fd);
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
return false;
}
LOG_I(PHY, "[ANTENNA_CTRL] Test server started on port %d\n", g_ctrl.port);
LOG_I(PHY, "[ANTENNA_CTRL] Connect with: nc localhost %d\n", g_ctrl.port);
return true;
}
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port)
{
LOG_W(PHY, "[ANTENNA_CTRL] nr_antenna_control_start() is DEPRECATED\n");
LOG_W(PHY, "[ANTENNA_CTRL] Use nr_antenna_control_init_mplane() for O-RAN compliance\n");
LOG_W(PHY, "[ANTENNA_CTRL] or nr_antenna_control_start_test_server() for testing\n");
return nr_antenna_control_start_test_server(ant_state, port);
}
void nr_antenna_control_stop(void)
{
if (!g_ctrl.running) return;
g_ctrl.running = false;
if (g_ctrl.backend == ANTENNA_CTRL_BACKEND_TEST_SOCKET) {
// Close server socket to unblock accept()
if (g_ctrl.server_fd >= 0) {
close(g_ctrl.server_fd);
g_ctrl.server_fd = -1;
}
// Wait for thread
pthread_join(g_ctrl.thread, NULL);
// Client workers wake from recv() at least once per second and observe
// running=false. Keep ant_state valid until every worker has exited.
pthread_mutex_lock(&g_ctrl.clients_lock);
while (g_ctrl.client_count > 0)
pthread_cond_wait(&g_ctrl.clients_done, &g_ctrl.clients_lock);
pthread_mutex_unlock(&g_ctrl.clients_lock);
pthread_cond_destroy(&g_ctrl.clients_done);
pthread_mutex_destroy(&g_ctrl.clients_lock);
pthread_mutex_destroy(&g_ctrl.command_lock);
}
g_ctrl.backend = ANTENNA_CTRL_BACKEND_NONE;
g_ctrl.ant_state = NULL;
g_ctrl.ru_session = NULL;
LOG_I(PHY, "[ANTENNA_CTRL] Control interface stopped\n");
}
bool nr_antenna_control_is_running(void)
{
return g_ctrl.running;
}
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void)
{
return g_ctrl.backend;
}
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state)
{
#ifdef OAI_MPLANE
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync to RU\n");
return false;
}
if (ant_state == NULL) {
ant_state = g_ctrl.ant_state;
}
if (ant_state == NULL) {
return false;
}
// Send TX antenna mask to RU
antenna_ctrl_result_t result;
result = mplane_set_tx_antenna_mask(g_ctrl.ru_session,
ant_state->tx_array_name,
nr_get_tx_antenna_bitmask(ant_state),
ant_state->num_tx_antennas);
if (result != ANTENNA_CTRL_SUCCESS) {
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync TX mask: %s\n",
antenna_ctrl_result_str(result));
return false;
}
// Send RX antenna mask to RU
result = mplane_set_rx_antenna_mask(g_ctrl.ru_session,
ant_state->rx_array_name,
nr_get_rx_antenna_bitmask(ant_state),
ant_state->num_rx_antennas);
if (result != ANTENNA_CTRL_SUCCESS) {
LOG_W(PHY, "[ANTENNA_CTRL] Failed to sync RX mask: %s\n",
antenna_ctrl_result_str(result));
return false;
}
LOG_I(PHY, "[ANTENNA_CTRL] Successfully synchronized antenna masks to RU\n");
return true;
#else
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
return false;
#endif
}
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state)
{
#ifdef OAI_MPLANE
if (g_ctrl.backend != ANTENNA_CTRL_BACKEND_MPLANE || g_ctrl.ru_session == NULL) {
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane backend not active, cannot sync from RU\n");
return false;
}
if (ant_state == NULL) {
ant_state = g_ctrl.ant_state;
}
if (ant_state == NULL) {
return false;
}
uint8_t tx_mask, rx_mask;
antenna_ctrl_result_t result;
// Get TX antenna mask from RU
result = mplane_get_tx_antenna_mask(g_ctrl.ru_session,
ant_state->tx_array_name,
&tx_mask);
if (result == ANTENNA_CTRL_SUCCESS) {
nr_set_tx_antenna_bitmask(ant_state, tx_mask);
}
// Get RX antenna mask from RU
result = mplane_get_rx_antenna_mask(g_ctrl.ru_session,
ant_state->rx_array_name,
&rx_mask);
if (result == ANTENNA_CTRL_SUCCESS) {
nr_set_rx_antenna_bitmask(ant_state, rx_mask);
}
LOG_I(PHY, "[ANTENNA_CTRL] Synchronized antenna masks from RU: TX=0x%02X, RX=0x%02X\n",
tx_mask, rx_mask);
return true;
#else
LOG_W(PHY, "[ANTENNA_CTRL] M-Plane not compiled in\n");
return false;
#endif
}
@@ -0,0 +1,123 @@
#ifndef NR_ANTENNA_CONTROL_H
#define NR_ANTENNA_CONTROL_H
#include "nr_antenna_state.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/// Default port for the test socket server (development mode only)
#define NR_ANTENNA_CONTROL_TEST_PORT 9999
/**
* @brief Antenna control backend type
*/
typedef enum {
ANTENNA_CTRL_BACKEND_NONE = 0, ///< No external control
ANTENNA_CTRL_BACKEND_MPLANE, ///< O-RAN M-Plane NETCONF (production)
ANTENNA_CTRL_BACKEND_TEST_SOCKET ///< Test socket interface (development)
} nr_antenna_ctrl_backend_t;
/**
* @brief Initialize antenna control with O-RAN M-Plane NETCONF backend
*
* This is the O-RAN compliant production interface. It connects the
* antenna state to a real NETCONF session to an O-RU.
*
* When antenna masks are changed via nr_set_tx_antenna_bitmask() or
* similar functions, the changes are automatically propagated to the
* RU via NETCONF edit-config operations.
*
* @param ant_state Pointer to the antenna state to control
* @param ru_session Active M-Plane NETCONF session (from radio/fhi_72/mplane/)
* @return true if initialization successful
*/
bool nr_antenna_control_init_mplane(nr_antenna_state_t *ant_state,
void *ru_session);
/**
* @brief Start the test socket server (development mode only)
*
* Creates a simple TCP server for testing antenna control without
* NETCONF infrastructure. This is NOT O-RAN compliant.
*
* Commands supported (for backwards compatibility):
* MUTE TX <ant_id> [15|30|45|60] - Mute TX antenna with optional TSM lock
* UNMUTE TX <ant_id> [FORCE] - Unmute TX antenna; FORCE breaks lock
* MUTE RX <ant_id> - Mute RX antenna
* UNMUTE RX <ant_id> - Unmute RX antenna
* SET TX MASK <hex> [duration] [FORCE] - Set TX mask and per-chain TSM lock
* SET RX MASK <hex> - Set RX antenna bitmask
* STATUS - Get current antenna state
* STATS - Get energy saving statistics
* RESET STATS - Reset statistics
* QUIT - Close connection
*
* @param ant_state Pointer to the antenna state to control
* @param port TCP port to listen on (use 0 for default)
* @return true if server started successfully
*
* @note This function logs a warning that it's not O-RAN compliant
*/
bool nr_antenna_control_start_test_server(nr_antenna_state_t *ant_state, int port);
/**
* @brief Legacy function - start antenna control server
*
* @deprecated Use nr_antenna_control_init_mplane() for O-RAN compliance
* or nr_antenna_control_start_test_server() for testing.
*
* This function starts the test socket server for backwards compatibility.
* It logs a deprecation warning.
*/
bool nr_antenna_control_start(nr_antenna_state_t *ant_state, int port);
/**
* @brief Stop the antenna control interface
*
* Stops the test socket server if running.
* For M-Plane backend, this disconnects from NETCONF notifications.
*/
void nr_antenna_control_stop(void);
/**
* @brief Check if antenna control is active
* @return true if control interface is running
*/
bool nr_antenna_control_is_running(void);
/**
* @brief Get current antenna control backend type
* @return Backend type currently in use
*/
nr_antenna_ctrl_backend_t nr_antenna_control_get_backend(void);
/**
* @brief Synchronize local antenna state to RU via M-Plane NETCONF
*
* Sends the current antenna bitmask configuration to the RU.
* Only applicable when using the M-Plane NETCONF backend.
*
* @param ant_state Pointer to the antenna state
* @return true if synchronization successful or not applicable
*/
bool nr_antenna_control_sync_to_ru(nr_antenna_state_t *ant_state);
/**
* @brief Synchronize RU antenna state to local via M-Plane NETCONF
*
* Reads the current antenna bitmask from the RU and updates local state.
* Only applicable when using the M-Plane NETCONF backend.
*
* @param ant_state Pointer to the antenna state
* @return true if synchronization successful or not applicable
*/
bool nr_antenna_control_sync_from_ru(nr_antenna_state_t *ant_state);
#ifdef __cplusplus
}
#endif
#endif /* NR_ANTENNA_CONTROL_H */
@@ -0,0 +1,437 @@
#include "nr_antenna_state.h"
#include "common/utils/LOG/log.h"
#include <string.h>
#include <time.h>
// Helper to count set bits in a byte
static inline int popcount8(uint8_t x) {
int count = 0;
while (x) {
count += x & 1;
x >>= 1;
}
return count;
}
// Get current timestamp in microseconds
static inline uint64_t get_timestamp_us(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
}
void nr_init_antenna_state(nr_antenna_state_t *state,
uint8_t num_tx,
uint8_t num_rx,
const char *tx_array_name,
const char *rx_array_name)
{
if (state == NULL) return;
memset(state, 0, sizeof(nr_antenna_state_t));
state->num_tx_antennas = (num_tx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_tx;
state->num_rx_antennas = (num_rx > NR_MAX_ANTENNAS) ? NR_MAX_ANTENNAS : num_rx;
// Initialize with all configured antennas active
// Create bitmask with num_tx/num_rx lowest bits set
state->tx_antenna_bitmask = (1 << state->num_tx_antennas) - 1;
state->requested_tx_antenna_bitmask = state->tx_antenna_bitmask;
state->tx_mask_update_pending = false;
state->rx_antenna_bitmask = (1 << state->num_rx_antennas) - 1;
// Set O-RAN array names
if (tx_array_name) {
strncpy(state->tx_array_name, tx_array_name, sizeof(state->tx_array_name) - 1);
} else {
strncpy(state->tx_array_name, "tx-array-0", sizeof(state->tx_array_name) - 1);
}
if (rx_array_name) {
strncpy(state->rx_array_name, rx_array_name, sizeof(state->rx_array_name) - 1);
} else {
strncpy(state->rx_array_name, "rx-array-0", sizeof(state->rx_array_name) - 1);
}
pthread_mutex_init(&state->lock, NULL);
state->stats.last_update_timestamp = get_timestamp_us();
// Initialize recovery mode
state->recovery.in_recovery = false;
state->recovery.recovery_start_ts = 0;
state->recovery.recovery_duration_ms = 500; // 500ms default recovery time
// Protect antenna 0 by default (needed for SSB/PSS/SSS)
state->config.protect_antenna0 = true;
LOG_I(PHY, "[ANTENNA_STATE] Initialized: TX antennas=%d (mask=0x%02X, array=%s), "
"RX antennas=%d (mask=0x%02X, array=%s), antenna0_protected=%d\n",
state->num_tx_antennas, state->tx_antenna_bitmask, state->tx_array_name,
state->num_rx_antennas, state->rx_antenna_bitmask, state->rx_array_name,
state->config.protect_antenna0);
}
void nr_free_antenna_state(nr_antenna_state_t *state)
{
if (state == NULL) return;
pthread_mutex_destroy(&state->lock);
}
static uint8_t valid_tx_mask(const nr_antenna_state_t *state)
{
return (uint8_t)((1u << state->num_tx_antennas) - 1u);
}
/* Caller holds state->lock. */
static void set_tx_antenna_bitmask_locked(nr_antenna_state_t *state, uint8_t bitmask)
{
const uint8_t old_bitmask = state->tx_antenna_bitmask;
state->tx_antenna_bitmask = bitmask & valid_tx_mask(state);
if (state->tx_antenna_bitmask == old_bitmask)
return;
state->stats.tx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] TX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
old_bitmask,
state->tx_antenna_bitmask,
state->tx_array_name,
popcount8(state->tx_antenna_bitmask),
state->num_tx_antennas);
}
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{
if (state == NULL) return 0;
pthread_mutex_lock(&state->lock);
const uint8_t old_bitmask = state->tx_antenna_bitmask;
state->requested_tx_antenna_bitmask = bitmask & valid_tx_mask(state);
state->tx_mask_update_pending = false;
set_tx_antenna_bitmask_locked(state, state->requested_tx_antenna_bitmask);
pthread_mutex_unlock(&state->lock);
return old_bitmask;
}
uint8_t nr_request_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{
if (state == NULL) return 0;
pthread_mutex_lock(&state->lock);
const uint8_t old_requested = state->requested_tx_antenna_bitmask;
const uint8_t requested = bitmask & valid_tx_mask(state);
state->requested_tx_antenna_bitmask = requested;
/* Adding chains cannot invalidate an in-flight PDSCH. Removing a chain can,
* so leave the physical mask unchanged until MAC has drained HARQ processes
* whose original layer count exceeds the new target. */
if ((state->tx_antenna_bitmask & (uint8_t)~requested) == 0) {
state->tx_mask_update_pending = false;
set_tx_antenna_bitmask_locked(state, requested);
} else {
state->tx_mask_update_pending = true;
LOG_I(PHY,
"[ANTENNA_STATE] TX downshift requested: active=0x%02X target=0x%02X; waiting for DL HARQ drain\n",
state->tx_antenna_bitmask,
requested);
}
pthread_mutex_unlock(&state->lock);
return old_requested;
}
uint8_t nr_get_requested_tx_antenna_bitmask(const nr_antenna_state_t *state)
{
if (state == NULL) return 0xFF;
pthread_mutex_lock((pthread_mutex_t *)&state->lock);
const uint8_t requested = state->requested_tx_antenna_bitmask;
pthread_mutex_unlock((pthread_mutex_t *)&state->lock);
return requested;
}
bool nr_get_pending_tx_antenna_bitmask(const nr_antenna_state_t *state, uint8_t *bitmask)
{
if (state == NULL) return false;
pthread_mutex_lock((pthread_mutex_t *)&state->lock);
const bool pending = state->tx_mask_update_pending;
if (pending && bitmask != NULL)
*bitmask = state->requested_tx_antenna_bitmask;
pthread_mutex_unlock((pthread_mutex_t *)&state->lock);
return pending;
}
bool nr_apply_requested_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t expected_bitmask)
{
if (state == NULL) return false;
pthread_mutex_lock(&state->lock);
const bool matches = state->tx_mask_update_pending
&& state->requested_tx_antenna_bitmask == expected_bitmask;
if (matches) {
set_tx_antenna_bitmask_locked(state, expected_bitmask);
state->tx_mask_update_pending = false;
}
pthread_mutex_unlock(&state->lock);
return matches;
}
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask)
{
if (state == NULL) return 0;
pthread_mutex_lock(&state->lock);
uint8_t old_bitmask = state->rx_antenna_bitmask;
// Mask to only valid antenna bits
uint8_t valid_mask = (1 << state->num_rx_antennas) - 1;
state->rx_antenna_bitmask = bitmask & valid_mask;
// Update statistics
if (state->rx_antenna_bitmask != old_bitmask) {
state->stats.rx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] RX bitmask changed: 0x%02X -> 0x%02X (array=%s, active=%d/%d)\n",
old_bitmask, state->rx_antenna_bitmask, state->rx_array_name,
popcount8(state->rx_antenna_bitmask), state->num_rx_antennas);
}
pthread_mutex_unlock(&state->lock);
return old_bitmask;
}
uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state)
{
if (state == NULL) return 0xFF;
pthread_mutex_lock((pthread_mutex_t*)&state->lock);
uint8_t m = state->tx_antenna_bitmask;
pthread_mutex_unlock((pthread_mutex_t*)&state->lock);
return m;
}
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state)
{
if (state == NULL) return 0xFF;
pthread_mutex_lock((pthread_mutex_t*)&state->lock);
uint8_t m = state->rx_antenna_bitmask;
pthread_mutex_unlock((pthread_mutex_t*)&state->lock);
return m;
}
bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_tx_antennas) return false;
return (state->tx_antenna_bitmask & (1 << ant_id)) != 0;
}
bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_rx_antennas) return false;
return (state->rx_antenna_bitmask & (1 << ant_id)) != 0;
}
int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state)
{
if (state == NULL) return 0;
return popcount8(state->tx_antenna_bitmask);
}
int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state)
{
if (state == NULL) return 0;
return popcount8(state->rx_antenna_bitmask);
}
void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_tx_antennas) return;
// Protect antenna 0 if configured (needed for SSB/PSS/SSS transmission)
if (ant_id == 0 && state->config.protect_antenna0) {
LOG_W(PHY, "[ANTENNA_STATE] Cannot mute TX antenna 0: protected for SSB transmission\n");
LOG_W(PHY, "[ANTENNA_STATE] Use 'SET TX MASK' to override protection if needed\n");
return;
}
uint8_t new_mask = nr_get_requested_tx_antenna_bitmask(state) & ~(1 << ant_id);
nr_request_tx_antenna_bitmask(state, new_mask);
}
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_tx_antennas) return;
uint8_t new_mask = nr_get_requested_tx_antenna_bitmask(state) | (1 << ant_id);
nr_request_tx_antenna_bitmask(state, new_mask);
}
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_rx_antennas) return;
uint8_t new_mask = state->rx_antenna_bitmask & ~(1 << ant_id);
nr_set_rx_antenna_bitmask(state, new_mask);
}
void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id)
{
if (state == NULL || ant_id >= state->num_rx_antennas) return;
uint8_t new_mask = state->rx_antenna_bitmask | (1 << ant_id);
nr_set_rx_antenna_bitmask(state, new_mask);
}
int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers)
{
if (state == NULL)
return requested_layers;
/* Cap new transmissions to the target immediately. During a downshift the
* physical mask can remain wider for a few HARQ rounds, which is safe; the
* reverse ordering would erase layers from buffered retransmissions. */
const int target = popcount8(nr_get_requested_tx_antenna_bitmask(state));
if (target > 0 && target < requested_layers)
return target;
return requested_layers;
}
void nr_log_antenna_state(const nr_antenna_state_t *state)
{
if (state == NULL) {
LOG_I(PHY, "[ANTENNA_STATE] State is NULL\n");
return;
}
LOG_I(PHY, "[ANTENNA_STATE] === Antenna State Report ===\n");
LOG_I(PHY, "[ANTENNA_STATE] TX Array: %s\n", state->tx_array_name);
LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_tx_antennas);
LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->tx_antenna_bitmask);
LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_tx_antenna_count(state));
LOG_I(PHY, "[ANTENNA_STATE] Individual: ");
for (int i = 0; i < state->num_tx_antennas; i++) {
LOG_I(PHY, "TX%d=%s ", i, nr_is_tx_antenna_active(state, i) ? "ON" : "OFF");
}
LOG_I(PHY, "\n");
LOG_I(PHY, "[ANTENNA_STATE] RX Array: %s\n", state->rx_array_name);
LOG_I(PHY, "[ANTENNA_STATE] Configured: %d antennas\n", state->num_rx_antennas);
LOG_I(PHY, "[ANTENNA_STATE] Bitmask: 0x%02X\n", state->rx_antenna_bitmask);
LOG_I(PHY, "[ANTENNA_STATE] Active: %d antennas\n", nr_get_active_rx_antenna_count(state));
LOG_I(PHY, "[ANTENNA_STATE] Statistics:\n");
LOG_I(PHY, "[ANTENNA_STATE] TX mute events: %lu\n", state->stats.tx_mute_events);
LOG_I(PHY, "[ANTENNA_STATE] RX mute events: %lu\n", state->stats.rx_mute_events);
LOG_I(PHY, "[ANTENNA_STATE] Total muted slots: %lu\n", state->stats.total_muted_slots);
LOG_I(PHY, "[ANTENNA_STATE] =============================\n");
}
void nr_get_antenna_stats(const nr_antenna_state_t *state,
uint64_t *tx_mute_events,
uint64_t *rx_mute_events,
uint64_t *total_muted_slots)
{
if (state == NULL) return;
if (tx_mute_events) *tx_mute_events = state->stats.tx_mute_events;
if (rx_mute_events) *rx_mute_events = state->stats.rx_mute_events;
if (total_muted_slots) *total_muted_slots = state->stats.total_muted_slots;
}
void nr_reset_antenna_stats(nr_antenna_state_t *state)
{
if (state == NULL) return;
pthread_mutex_lock(&state->lock);
state->stats.tx_mute_events = 0;
state->stats.rx_mute_events = 0;
state->stats.total_muted_slots = 0;
state->stats.energy_saved_estimate = 0;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] Statistics reset\n");
pthread_mutex_unlock(&state->lock);
}
void nr_unmute_all_tx_antennas(nr_antenna_state_t *state)
{
if (state == NULL) return;
pthread_mutex_lock(&state->lock);
uint8_t old_mask = state->tx_antenna_bitmask;
uint8_t all_on = (1 << state->num_tx_antennas) - 1;
state->requested_tx_antenna_bitmask = all_on;
state->tx_mask_update_pending = false;
state->tx_antenna_bitmask = all_on;
// Enter recovery mode if we were muted
if (old_mask != all_on) {
state->recovery.in_recovery = true;
state->recovery.recovery_start_ts = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] Entering recovery mode for %u ms after unmuting TX antennas\n",
state->recovery.recovery_duration_ms);
}
state->stats.tx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] All TX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n",
old_mask, all_on, state->tx_array_name);
pthread_mutex_unlock(&state->lock);
}
void nr_unmute_all_rx_antennas(nr_antenna_state_t *state)
{
if (state == NULL) return;
pthread_mutex_lock(&state->lock);
uint8_t old_mask = state->rx_antenna_bitmask;
uint8_t all_on = (1 << state->num_rx_antennas) - 1;
state->rx_antenna_bitmask = all_on;
state->stats.rx_mute_events++;
state->stats.last_update_timestamp = get_timestamp_us();
LOG_I(PHY, "[ANTENNA_STATE] All RX antennas unmuted: 0x%02X -> 0x%02X (array=%s)\n",
old_mask, all_on, state->rx_array_name);
pthread_mutex_unlock(&state->lock);
}
bool nr_is_in_recovery_mode(const nr_antenna_state_t *state)
{
if (state == NULL) return false;
if (!state->recovery.in_recovery) return false;
// Check if recovery period has elapsed
uint64_t now = get_timestamp_us();
uint64_t elapsed_us = now - state->recovery.recovery_start_ts;
uint64_t duration_us = (uint64_t)state->recovery.recovery_duration_ms * 1000ULL;
if (elapsed_us >= duration_us) {
// Recovery complete - cast away const for state update
nr_antenna_state_t *s = (nr_antenna_state_t *)state;
s->recovery.in_recovery = false;
LOG_I(PHY, "[ANTENNA_STATE] Recovery mode complete after %lu ms\n", elapsed_us / 1000);
return false;
}
return true;
}
void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect)
{
if (state == NULL) return;
pthread_mutex_lock(&state->lock);
state->config.protect_antenna0 = protect;
LOG_I(PHY, "[ANTENNA_STATE] Antenna 0 protection %s\n", protect ? "enabled" : "disabled");
pthread_mutex_unlock(&state->lock);
}
@@ -0,0 +1,285 @@
#ifndef NR_ANTENNA_STATE_H
#define NR_ANTENNA_STATE_H
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
#ifdef __cplusplus
extern "C" {
#endif
/// Maximum number of physical antennas supported
#define NR_MAX_ANTENNAS 8
/// O-RAN compliant antenna state for TX/RX arrays
/// Mirrors o-ran-uplane-conf.yang: tx-array-antenna-mask-config
typedef struct {
/// Antenna bitmask per O-RAN WG4 spec: bit N = 1 means antenna N is active
/// Corresponds to o-ran-uplane-conf:antenna-bitmask
uint8_t tx_antenna_bitmask;
uint8_t rx_antenna_bitmask;
/// Target mask used for new scheduling while an unsafe TX downshift drains.
uint8_t requested_tx_antenna_bitmask;
/// True when requested_tx_antenna_bitmask removes physical TX chains that
/// cannot be muted until incompatible in-flight DL HARQ processes complete.
bool tx_mask_update_pending;
/// Total number of configured antennas
uint8_t num_tx_antennas;
uint8_t num_rx_antennas;
/// Thread safety for runtime updates
pthread_mutex_t lock;
/// Energy saving statistics (for research/monitoring)
struct {
uint64_t tx_mute_events; ///< Number of TX mute operations
uint64_t rx_mute_events; ///< Number of RX mute operations
uint64_t total_muted_slots; ///< Total slots with at least one antenna muted
uint64_t energy_saved_estimate; ///< Estimated energy saved (arbitrary units)
uint64_t last_update_timestamp; ///< Last update time (for O1 notifications)
} stats;
/// O-RAN compliance: array name for NETCONF identification
char tx_array_name[64];
char rx_array_name[64];
/// Recovery mode: after unmuting, allow CSI to update before using full capacity
struct {
bool in_recovery; ///< Currently in recovery mode
uint64_t recovery_start_ts; ///< Timestamp when recovery started (us)
uint32_t recovery_duration_ms; ///< Recovery duration in milliseconds (default 500ms)
} recovery;
/// Configuration flags
struct {
bool protect_antenna0; ///< Never mute antenna 0 (needed for SSB)
} config;
} nr_antenna_state_t;
/**
* @brief Initialize antenna state with all antennas active
* @param state Pointer to antenna state structure
* @param num_tx Number of TX antennas
* @param num_rx Number of RX antennas
* @param tx_array_name O-RAN array name for TX (e.g., "tx-array-0")
* @param rx_array_name O-RAN array name for RX (e.g., "rx-array-0")
*/
void nr_init_antenna_state(nr_antenna_state_t *state,
uint8_t num_tx,
uint8_t num_rx,
const char *tx_array_name,
const char *rx_array_name);
/**
* @brief Free antenna state resources
* @param state Pointer to antenna state structure
*/
void nr_free_antenna_state(nr_antenna_state_t *state);
/**
* @brief Set TX antenna bitmask (O-RAN O1 interface equivalent)
*
* This function implements the O-RAN WG4 MPLANE-TRX-CONTROL feature.
* Corresponds to NETCONF edit-config on tx-array-antenna-mask-config.
*
* @param state Pointer to antenna state structure
* @param bitmask Antenna bitmask (bit N = 1 means antenna N active)
* @return Previous bitmask value
*/
uint8_t nr_set_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
/**
* @brief Request a TX mask, staging physical downshifts until MAC HARQ is safe
*
* New scheduling immediately observes the requested mask. Pure upshifts are
* applied physically at once; a request that removes active TX chains remains
* pending until the MAC scheduler explicitly commits it.
*
* @param state Pointer to antenna state structure
* @param bitmask Requested antenna bitmask
* @return Previous requested bitmask
*/
uint8_t nr_request_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
/**
* @brief Read the requested TX mask
* @return Requested bitmask, or 0xFF when state is NULL
*/
uint8_t nr_get_requested_tx_antenna_bitmask(const nr_antenna_state_t *state);
/**
* @brief Atomically snapshot a pending TX-mask request
* @param state Pointer to antenna state structure
* @param bitmask Output requested mask when pending
* @return true when a physical mask update is pending
*/
bool nr_get_pending_tx_antenna_bitmask(const nr_antenna_state_t *state, uint8_t *bitmask);
/**
* @brief Commit a previously checked pending TX mask
*
* The compare against expected_bitmask prevents a concurrent control request
* from being applied using a stale HARQ-safety decision.
*
* @param state Pointer to antenna state structure
* @param expected_bitmask Mask for which the scheduler completed its check
* @return true when that exact pending request was committed
*/
bool nr_apply_requested_tx_antenna_bitmask(nr_antenna_state_t *state, uint8_t expected_bitmask);
/**
* @brief Set RX antenna bitmask (O-RAN O1 interface equivalent)
* @param state Pointer to antenna state structure
* @param bitmask Antenna bitmask (bit N = 1 means antenna N active)
* @return Previous bitmask value
*/
uint8_t nr_set_rx_antenna_bitmask(nr_antenna_state_t *state, uint8_t bitmask);
/**
* @brief Get current TX antenna bitmask
* @param state Pointer to antenna state structure
* @return Current TX antenna bitmask
*/
uint8_t nr_get_tx_antenna_bitmask(const nr_antenna_state_t *state);
/**
* @brief Get current RX antenna bitmask
* @param state Pointer to antenna state structure
* @return Current RX antenna bitmask
*/
uint8_t nr_get_rx_antenna_bitmask(const nr_antenna_state_t *state);
/**
* @brief Check if a specific TX antenna is active
* @param state Pointer to antenna state structure
* @param ant_id Antenna index (0-based)
* @return true if antenna is active, false if muted
*/
bool nr_is_tx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Check if a specific RX antenna is active
* @param state Pointer to antenna state structure
* @param ant_id Antenna index (0-based)
* @return true if antenna is active, false if muted
*/
bool nr_is_rx_antenna_active(const nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Get count of currently active TX antennas
* @param state Pointer to antenna state structure
* @return Number of active TX antennas
*/
int nr_get_active_tx_antenna_count(const nr_antenna_state_t *state);
/**
* @brief Get count of currently active RX antennas
* @param state Pointer to antenna state structure
* @return Number of active RX antennas
*/
int nr_get_active_rx_antenna_count(const nr_antenna_state_t *state);
/**
* @brief Mute a specific TX antenna
* @param state Pointer to antenna state structure
* @param ant_id Antenna index to mute (0-based)
*/
void nr_mute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Unmute a specific TX antenna
* @param state Pointer to antenna state structure
* @param ant_id Antenna index to unmute (0-based)
*/
void nr_unmute_tx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Mute a specific RX antenna
* @param state Pointer to antenna state structure
* @param ant_id Antenna index to mute (0-based)
*/
void nr_mute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Unmute a specific RX antenna
* @param state Pointer to antenna state structure
* @param ant_id Antenna index to unmute (0-based)
*/
void nr_unmute_rx_antenna(nr_antenna_state_t *state, uint8_t ant_id);
/**
* @brief Get effective number of MIMO layers based on active antennas
*
* For MIMO, the number of layers is limited by the requested antenna mask.
* During a staged downshift this stops new high-rank HARQ traffic before the
* physical antenna mask changes.
*
* @param state Pointer to antenna state structure
* @param requested_layers Originally requested number of layers
* @return Effective number of layers that can be used
*/
int nr_get_effective_mimo_layers(const nr_antenna_state_t *state, int requested_layers);
/**
* @brief Log antenna state for debugging
* @param state Pointer to antenna state structure
*/
void nr_log_antenna_state(const nr_antenna_state_t *state);
/**
* @brief Get energy saving statistics
* @param state Pointer to antenna state structure
* @param tx_mute_events Output: number of TX mute events
* @param rx_mute_events Output: number of RX mute events
* @param total_muted_slots Output: total slots with muting
*/
void nr_get_antenna_stats(const nr_antenna_state_t *state,
uint64_t *tx_mute_events,
uint64_t *rx_mute_events,
uint64_t *total_muted_slots);
/**
* @brief Reset energy saving statistics
* @param state Pointer to antenna state structure
*/
void nr_reset_antenna_stats(nr_antenna_state_t *state);
/**
* @brief Unmute all TX antennas and enter recovery mode
* @param state Pointer to antenna state structure
*
* This initiates a recovery period during which the scheduler
* uses conservative settings to allow UE CSI reports to update.
*/
void nr_unmute_all_tx_antennas(nr_antenna_state_t *state);
/**
* @brief Unmute all RX antennas
* @param state Pointer to antenna state structure
*/
void nr_unmute_all_rx_antennas(nr_antenna_state_t *state);
/**
* @brief Check if system is in recovery mode after unmuting
* @param state Pointer to antenna state structure
* @return true if in recovery mode
*/
bool nr_is_in_recovery_mode(const nr_antenna_state_t *state);
/**
* @brief Set antenna 0 protection (SSB antenna)
* @param state Pointer to antenna state structure
* @param protect true to prevent muting antenna 0
*/
void nr_set_antenna0_protection(nr_antenna_state_t *state, bool protect);
#ifdef __cplusplus
}
#endif
#endif /* NR_ANTENNA_STATE_H */
+151 -59
View File
@@ -17,9 +17,55 @@
#include "executables/softmodem-common.h"
#include "SCHED_NR/sched_nr.h"
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <simde/x86/avx512.h>
#define USE128BIT
static pthread_once_t tx_layer_fanout_once = PTHREAD_ONCE_INIT;
static bool tx_layer_fanout = false;
static void configure_tx_layer_fanout(void)
{
const char *value = getenv("OAI_TX_LAYER_FANOUT");
tx_layer_fanout = value != NULL && strcmp(value, "1") == 0;
if (tx_layer_fanout)
LOG_I(PHY,
"[8TX_FANOUT] Fixed identity-layer fan-out enabled; "
"all active physical TX ports carry PDSCH\n");
}
static bool tx_layer_fanout_enabled(void)
{
pthread_once(&tx_layer_fanout_once, configure_tx_layer_fanout);
return tx_layer_fanout;
}
static void copy_normalized_layer(c16_t *dst,
const c16_t *src,
int count,
int replicas)
{
// Q15 approximations of 1/sqrt(n), n=1..8. Normalizing each duplicated
// layer keeps total frequency-domain energy comparable with the 4TX
// identity mapping instead of giving 8TX an artificial power advantage.
static const int16_t gain_q15[] = {
32767, 23170, 18919, 16384, 14654, 13377, 12384, 11585};
AssertFatal(replicas >= 1 && replicas <= 8,
"Invalid 8TX layer replica count %d\n",
replicas);
if (replicas == 1) {
memcpy(dst, src, count * sizeof(*dst));
return;
}
const int32_t gain = gain_q15[replicas - 1];
for (int i = 0; i < count; ++i) {
dst[i].r = ((int32_t)src[i].r * gain) >> 15;
dst[i].i = ((int32_t)src[i].i * gain) >> 15;
}
}
static void nr_pdsch_codeword_scrambling(uint8_t *in, uint32_t size, uint8_t q, uint32_t Nid, uint32_t n_RNTI, uint32_t *out)
{
nr_codeword_scrambling(in, size, q, Nid, n_RNTI, out);
@@ -436,6 +482,49 @@ static inline void do_txdataF(c16_t **txdataF,
int txdataF_offset_per_symbol)
{
NR_DL_FRAME_PARMS *frame_parms = &gNB->frame_parms;
// nakaolab Patch 8: identity-precoding layer-to-antenna remapping.
// When antennas are muted, layer N must NOT be sent to muted antenna N
// (the layer's data would be lost on the blanked port). Instead, find
// this antenna's rank among active antennas — that's the layer index it
// should receive. Muted antennas get zeros.
const nr_antenna_state_t *ast = &gNB->antenna_state;
int identity_layer;
int identity_replicas = 1;
int active_count = nr_get_active_tx_antenna_count(ast);
const bool fanout = frame_parms->nb_antennas_tx == 8
&& tx_layer_fanout_enabled();
if (active_count <= 0 || !nr_is_tx_antenna_active(ast, ant)) {
identity_layer = -1;
} else if (fanout) {
// If physical TX ports outnumber the scheduled layers, map active ports
// cyclically and normalize replicas (for example, 8TX/4 layers). At
// rank 8 this naturally becomes a one-to-one mapping: every port carries
// distinct layer data and no fan-out occurs.
int active_rank = 0;
for (int a = 0; a < ant; ++a)
if (nr_is_tx_antenna_active(ast, a))
active_rank++;
identity_layer = rel15->nrOfLayers > 0
? active_rank % rel15->nrOfLayers
: -1;
if (identity_layer >= 0)
identity_replicas = (active_count + rel15->nrOfLayers - 1
- identity_layer) / rel15->nrOfLayers;
} else if (active_count < frame_parms->nb_antennas_tx) {
if (!nr_is_tx_antenna_active(ast, ant)) {
identity_layer = -1; // muted antenna → zero output
} else {
// count active antennas with index < ant
identity_layer = 0;
for (int a = 0; a < ant; ++a)
if (nr_is_tx_antenna_active(ast, a))
identity_layer++;
if (identity_layer >= rel15->nrOfLayers)
identity_layer = -1;
}
} else {
identity_layer = (ant < rel15->nrOfLayers) ? ant : -1;
}
int rb = 0;
uint16_t subCarrier = get_block_start_sc(rb_start, rel15->BWPStart, symbol_sz);
const nfapi_nr_tx_precoding_and_beamforming_t *pb = &rel15->precodingAndBeamforming;
@@ -452,10 +541,13 @@ static inline void do_txdataF(c16_t **txdataF,
const int rb_step = rb_step0 == 2 && pmi3 == pmi && pmi4 == pmi ? 4 : rb_step0;
const int re_cnt = NR_NB_SC_PER_RB * rb_step;
if (pmi == 0) { // unitary Precoding
if (ant < rel15->nrOfLayers)
memcpy(&txdataF[ant][txdataF_offset_per_symbol + subCarrier],
&txdataF_precoding[ant][subCarrier],
re_cnt * sizeof(**txdataF));
if (identity_layer >= 0) {
copy_normalized_layer(
&txdataF[ant][txdataF_offset_per_symbol + subCarrier],
&txdataF_precoding[identity_layer][subCarrier],
re_cnt,
fanout ? identity_replicas : 1);
}
else
memset(&txdataF[ant][txdataF_offset_per_symbol + subCarrier], 0, re_cnt * sizeof(**txdataF));
subCarrier += re_cnt;
@@ -501,7 +593,7 @@ typedef struct pdschSymbolProc_s {
unsigned int n_ptrs;
unsigned int beam_nb;
unsigned int re_beginning_of_symbol[14];
c16_t *tx_layers[4];
c16_t *tx_layers[NR_MAX_NB_LAYERS];
time_stats_t dlsch_resource_mapping_stats;
time_stats_t dlsch_precoding_stats;
} pdschSymbolProc_t;
@@ -528,20 +620,10 @@ static void nr_pdsch_symbol_processing(void *arg)
for (int l_symbol = rdata->startSymbol; l_symbol < rdata->startSymbol + rdata->numSymbols; l_symbol++) {
start_meas(&rdata->dlsch_resource_mapping_stats);
int l_prime = 0; // single symbol layer 0
int l_overline = get_l0(rel15->dlDmrsSymbPos);
const int l_prime = get_pdsch_dmrs_l_prime(rel15->dlDmrsSymbPos, l_symbol);
/// DMRS QPSK modulation
if ((rel15->dlDmrsSymbPos & (1 << l_symbol))) { // DMRS time occasion
// The reference point for is subcarrier -1 of the lowest-numbered resource block in CORESET 0 if the corresponding
// PDCCH is associated with CORESET -1 and Type0-PDCCH common search space and is addressed to SI-RNTI
// 2GPP TS 38.211 V15.8.0 Section 7.4.1.1.2 Mapping to physical resources
if (l_symbol == (l_overline + 1)) // take into account the double DMRS symbols
l_prime = 1;
else if (l_symbol > (l_overline + 1)) { // new DMRS pair
l_overline = l_symbol;
l_prime = 0;
}
const uint32_t *gold = nr_gold_pdsch(frame_parms->N_RB_DL,
frame_parms->symbols_per_slot,
rel15->dlDmrsScramblingId,
@@ -612,12 +694,11 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
const int layerSz = frame_parms->N_RB_DL * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB;
const int nb_re_dmrs = rel15->numDmrsCdmGrpsNoData * (rel15->dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ? 6 : 4);
const int n_dmrs = freq_alloc->num_rbs * nb_re_dmrs;
const int xOverhead = 0;
const int nb_re =
(12 * rel15->NrOfSymbols - nb_re_dmrs * get_num_dmrs(rel15->dlDmrsSymbPos) - xOverhead) * freq_alloc->num_rbs * rel15->nrOfLayers;
const int Qm = rel15->qamModOrder[0];
const int encoded_length = nb_re * Qm;
uint32_t encoded_length[NR_MAX_NB_CODEWORDS] = {0};
uint32_t n_mod_symbols[NR_MAX_NB_CODEWORDS] = {0};
const c16_t *mod_symbs[NR_MAX_NB_CODEWORDS] = {0};
c16_t *mod_symbs_storage[NR_MAX_NB_CODEWORDS] = {0};
size_t encoded_offset = 0;
/* PTRS */
uint16_t dlPtrsSymPos = 0;
@@ -633,34 +714,51 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
#ifdef DEBUG_DLSCH
printf("PDSCH encoding:\nPayload:\n");
for (int i = 0; i < (dlsch->B >> 3); i += 16) {
for (int j = 0; j < 16; j++)
printf("0x%02x\t", dlsch->pdu[i + j]);
printf("\n");
}
printf("\nEncoded payload:\n");
for (int i = 0; i < encoded_length; i += 8) {
for (int j = 0; j < 8; j++)
printf("%d", (input_ptr[i >> 3] >> j) & 1);
printf("\t");
for (int q = 0; q < rel15->NrOfCodewords; q++) {
printf("codeword %d:\n", q);
for (int i = 0; i < rel15->TBSize[q]; i++)
printf("0x%02x%s", dlsch->pdu[q][i], (i & 15) == 15 ? "\n" : "\t");
}
printf("\n");
#endif
if (IS_SOFTMODEM_DLSIM)
memcpy(dlsch->f, input_ptr, (encoded_length + 7) >> 3);
c16_t mod_symbs[rel15->NrOfCodewords][encoded_length] __attribute__((aligned(64)));
for (int codeWord = 0; codeWord < rel15->NrOfCodewords; codeWord++) {
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, codeWord);
const int Qm = rel15->qamModOrder[codeWord];
encoded_length[codeWord] = nr_get_G(freq_alloc->num_rbs,
rel15->NrOfSymbols,
nb_re_dmrs,
get_num_dmrs(rel15->dlDmrsSymbPos),
dlsch->unav_res,
Qm,
cw_layers);
n_mod_symbols[codeWord] = encoded_length[codeWord] / Qm;
unsigned char *codeword_input = input_ptr + encoded_offset;
const size_t max_encoded_bits =
freq_alloc->num_rbs * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * Qm * cw_layers;
encoded_offset += ceil_mod(max_encoded_bits, 8 * 64) >> 3;
if (IS_SOFTMODEM_DLSIM)
memcpy(dlsch->f[codeWord], codeword_input, (encoded_length[codeWord] + 7) >> 3);
mod_symbs_storage[codeWord] = malloc16(n_mod_symbols[codeWord] * sizeof(c16_t));
AssertFatal(mod_symbs_storage[codeWord], "Cannot allocate codeword %d modulation buffer\n", codeWord);
mod_symbs[codeWord] = mod_symbs_storage[codeWord];
/// scrambling
start_meas(dlsch_scrambling_stats);
uint32_t scrambled_output[(encoded_length >> 5) + 4]; // modulator acces by 4 bytes in some cases
uint32_t scrambled_output[(encoded_length[codeWord] >> 5) + 4]; // modulator accesses four-byte groups
memset(scrambled_output, 0, sizeof(scrambled_output));
nr_pdsch_codeword_scrambling(input_ptr, encoded_length, codeWord, rel15->dataScramblingId, rel15->rnti, scrambled_output);
nr_pdsch_codeword_scrambling(codeword_input,
encoded_length[codeWord],
codeWord,
rel15->dataScramblingId,
rel15->rnti,
scrambled_output);
#ifdef DEBUG_DLSCH
printf("PDSCH scrambling:\n");
for (int i = 0; i < encoded_length >> 8; i++) {
for (int i = 0; i < encoded_length[codeWord] >> 8; i++) {
for (int j = 0; j < 8; j++)
printf("0x%08x\t", scrambled_output[(i << 3) + j]);
printf("\n");
@@ -670,16 +768,10 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
stop_meas(dlsch_scrambling_stats);
/// Modulation
start_meas(dlsch_modulation_stats);
nr_modulation(scrambled_output, encoded_length, Qm, (int16_t *)mod_symbs[codeWord]);
nr_modulation(scrambled_output, encoded_length[codeWord], Qm, (int16_t *)mod_symbs_storage[codeWord]);
stop_meas(dlsch_modulation_stats);
#ifdef DEBUG_DLSCH
printf("PDSCH Modulation: Qm %d(%d)\n", Qm, nb_re);
for (int i = 0; i < nb_re; i += 8) {
for (int j = 0; j < 8; j++) {
printf("%d %d\t", mod_symbs[codeWord][i + j].r, mod_symbs[codeWord][i + j].i);
}
printf("\n");
}
printf("PDSCH codeword %d modulation: Qm %d, %u symbols\n", codeWord, Qm, n_mod_symbols[codeWord]);
#endif
}
@@ -694,7 +786,7 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
int layerSz2 = (layerSz + 63) & ~63;
c16_t tx_layers[rel15->nrOfLayers][layerSz2] __attribute__((aligned(64)));
memset(tx_layers, 0, sizeof(tx_layers));
nr_layer_mapping(rel15->NrOfCodewords, encoded_length, mod_symbs, rel15->nrOfLayers, layerSz2, nb_re, tx_layers);
nr_layer_mapping(rel15->NrOfCodewords, mod_symbs, n_mod_symbols, rel15->nrOfLayers, layerSz2, tx_layers);
/// Layer Precoding and Antenna port mapping
// tx_layers 1-8 are mapped on antenna ports 1000-1007
@@ -777,12 +869,13 @@ static int do_one_dlsch(unsigned char *input_ptr, PHY_VARS_gNB *gNB, NR_gNB_DLSC
merge_meas(&gNB->dlsch_resource_mapping_stats, &arr[i].dlsch_resource_mapping_stats);
merge_meas(&gNB->dlsch_precoding_stats, &arr[i].dlsch_precoding_stats);
}
for (int q = 0; q < rel15->NrOfCodewords; q++)
free16(mod_symbs_storage[q], n_mod_symbols[q] * sizeof(c16_t));
stop_meas(&gNB->dlsch_pdsch_generation_stats);
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
* should remain a multiple of 8 * 64 with enough offset to fit each dlsch
*/
uint32_t size_output_tb = freq_alloc->num_rbs * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * Qm * rel15->nrOfLayers;
return ((size_output_tb + 511) >> 9) << 6;
return encoded_offset;
}
void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_array, int frame, int slot)
@@ -816,8 +909,6 @@ void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_arr
dlsch->freq_alloc.last_rb,
dlsch->freq_alloc.num_rbs);
const int Qm = rel15->qamModOrder[0];
/* PTRS */
uint16_t dlPtrsSymPos = 0;
int n_ptrs = 0;
@@ -834,13 +925,14 @@ void nr_generate_pdsch(PHY_VARS_gNB *gNB, int n_dlsch, NR_gNB_DLSCH_t *dlsch_arr
dlsch->unav_res = ptrsSymbPerSlot * n_ptrs;
/// CRC, coding, interleaving and rate matching
AssertFatal(dlsch->pdu != NULL, "%4d.%2d no PDU for PDSCH generation\n", frame, slot);
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
* => size_output is a sum of parts sizes rounded up to a multiple of 8 * 64
*/
size_t size_output_tb = dlsch->freq_alloc.num_rbs * NR_SYMBOLS_PER_SLOT * NR_NB_SC_PER_RB * Qm * rel15->nrOfLayers;
size_output += ceil_mod(size_output_tb, 8 * 64);
for (int q = 0; q < rel15->NrOfCodewords; q++) {
AssertFatal(dlsch->pdu[q] != NULL, "%4d.%2d no PDU for PDSCH codeword %d generation\n", frame, slot, q);
/* Every LDPC output starts at a 64-byte boundary. */
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
const size_t size_output_tb =
dlsch->freq_alloc.num_rbs * NR_SYMBOLS_PER_SLOT * NR_NB_SC_PER_RB * rel15->qamModOrder[q] * cw_layers;
size_output += ceil_mod(size_output_tb, 8 * 64);
}
}
unsigned char output[size_output >> 3] __attribute__((aligned(64)));
+155 -116
View File
@@ -23,35 +23,53 @@
// #define DEBUG_DLSCH_CODING
// #define DEBUG_DLSCH_FREE 1
/* Keep the PHY encoder independent of the MAC helper with the same rule from
* TS 38.212 section 5.3.2. A dual-codeword PDSCH may have a different TBS
* (and therefore a different base graph) for each codeword. */
static uint8_t dlsch_base_graph(uint32_t A, uint16_t R)
{
const float code_rate = (float)R / 10240.0f;
return (A <= 292 || (A <= NR_MAX_PDSCH_TBS && code_rate <= 0.6667f) || code_rate <= 0.25f) ? 2 : 1;
}
void free_gNB_dlsch(NR_gNB_DLSCH_t *dlsch, uint16_t N_RB, const NR_DL_FRAME_PARMS *frame_parms)
{
int max_layers = (frame_parms->nb_antennas_tx < NR_MAX_NB_LAYERS) ? frame_parms->nb_antennas_tx : NR_MAX_NB_LAYERS;
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_layers;
const int max_cw_layers = min(max_layers, 4);
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_cw_layers;
if (N_RB != 273) {
a_segments = a_segments * N_RB;
a_segments = a_segments / 273 + 1;
}
if (dlsch->b) {
free16(dlsch->b, a_segments * 1056);
dlsch->b = NULL;
const size_t f_bytes = N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * max_cw_layers;
(void)f_bytes; /* free16 ignores its size argument on the supported allocators. */
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++) {
if (dlsch->b[q]) {
free16(dlsch->b[q], a_segments * 1056);
dlsch->b[q] = NULL;
}
if (dlsch->f[q]) {
free16(dlsch->f[q], f_bytes);
dlsch->f[q] = NULL;
}
if (!dlsch->c[q])
continue;
for (int r = 0; r < a_segments; r++) {
free(dlsch->c[q][r]);
dlsch->c[q][r] = NULL;
}
free(dlsch->c[q]);
dlsch->c[q] = NULL;
}
if (dlsch->f) {
free16(dlsch->f, N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
dlsch->f = NULL;
}
for (int r = 0; r < a_segments; r++) {
free(dlsch->c[r]);
dlsch->c[r] = NULL;
}
free(dlsch->c);
}
NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB)
{
int max_layers = (frame_parms->nb_antennas_tx < NR_MAX_NB_LAYERS) ? frame_parms->nb_antennas_tx : NR_MAX_NB_LAYERS;
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_layers; // number of segments to be allocated
const int max_cw_layers = min(max_layers, 4);
uint16_t a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * max_cw_layers; // segments allocated per codeword
if (N_RB != 273) {
a_segments = a_segments * N_RB;
@@ -59,28 +77,29 @@ NR_gNB_DLSCH_t new_gNB_dlsch(NR_DL_FRAME_PARMS *frame_parms, uint16_t N_RB)
}
LOG_D(PHY, "Allocating %d segments (MAX %d, N_PRB %d)\n", a_segments, MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER, N_RB);
uint32_t dlsch_bytes = a_segments * 1056; // allocated bytes per segment
uint32_t dlsch_bytes = a_segments * 1056;
NR_gNB_DLSCH_t dlsch = {0};
const size_t f_bytes = N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * max_cw_layers;
dlsch.b = malloc16(dlsch_bytes);
AssertFatal(dlsch.b, "cannot allocate memory for dlsch.b\n");
bzero(dlsch.b, dlsch_bytes);
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++) {
dlsch.b[q] = malloc16(dlsch_bytes);
AssertFatal(dlsch.b[q], "cannot allocate memory for dlsch.b[%d]\n", q);
bzero(dlsch.b[q], dlsch_bytes);
dlsch.c = (uint8_t **)malloc16(a_segments * sizeof(uint8_t *));
for (int r = 0; r < a_segments; r++) {
// account for filler in first segment and CRCs for multiple segment case
// [hna] 8448 is the maximum CB size in NR
// 68*348 = 68*(maximum size of Zc)
// In section 5.3.2 in 38.212, the for loop is up to N + 2*Zc (maximum size of N is 66*Zc, therefore 68*Zc)
dlsch.c[r] = malloc16(8448);
AssertFatal(dlsch.c[r], "cannot allocate dlsch.c[%d]\n", r);
bzero(dlsch.c[r], 8448);
dlsch.c[q] = (uint8_t **)malloc16(a_segments * sizeof(uint8_t *));
AssertFatal(dlsch.c[q], "cannot allocate memory for dlsch.c[%d]\n", q);
for (int r = 0; r < a_segments; r++) {
// 8448 is the maximum NR code-block size, including filler and CRC.
dlsch.c[q][r] = malloc16(8448);
AssertFatal(dlsch.c[q][r], "cannot allocate dlsch.c[%d][%d]\n", q, r);
bzero(dlsch.c[q][r], 8448);
}
dlsch.f[q] = malloc16(f_bytes);
AssertFatal(dlsch.f[q], "cannot allocate dlsch.f[%d]\n", q);
bzero(dlsch.f[q], f_bytes);
}
dlsch.f = malloc16(N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
AssertFatal(dlsch.f, "cannot allocate dlsch->f\n");
bzero(dlsch.f, N_RB * frame_parms->symbols_per_slot * NR_NB_SC_PER_RB * 8 * NR_MAX_NB_LAYERS);
return (dlsch);
}
@@ -98,92 +117,104 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
time_stats_t *dlsch_interleaving_stats,
time_stats_t *dlsch_segmentation_stats)
{
nrLDPC_TB_encoding_parameters_t TBs[n_dlsch];
int n_tbs = 0;
for (int i = 0; i < n_dlsch; i++) {
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch_array[i].pdsch_pdu->pdsch_pdu_rel15;
AssertFatal(rel15->NrOfCodewords == nr_pdsch_num_codewords(rel15->nrOfLayers),
"PDSCH has %u codewords for %u layers\n",
rel15->NrOfCodewords,
rel15->nrOfLayers);
n_tbs += rel15->NrOfCodewords;
}
nrLDPC_TB_encoding_parameters_t TBs[n_tbs];
memset(TBs, 0, sizeof(TBs));
NR_gNB_DLSCH_t *tb_dlsch[n_tbs];
uint8_t tb_cw[n_tbs];
int num_segments = 0;
int tb_index = 0;
for (int i = 0; i < n_dlsch; i++) {
NR_gNB_DLSCH_t *dlsch = &dlsch_array[i];
unsigned int crc = 1;
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->pdsch_pdu->pdsch_pdu_rel15;
uint32_t A = rel15->TBSize[0] << 3;
unsigned char *a = dlsch->pdu;
if (rel15->rnti != SI_RNTI) {
ws_trace_t tmp = {.nr = true,
.direction = DIRECTION_DOWNLINK,
.pdu_buffer = a,
.pdu_buffer_size = rel15->TBSize[0],
.ueid = 0,
.rntiType = WS_C_RNTI,
.rnti = rel15->rnti,
.sysFrame = frame,
.subframe = slot,
.harq_pid = 0, // difficult to find the harq pid here
.oob_event = 0,
.oob_event_value = 0};
trace_pdu(&tmp);
}
NR_gNB_PHY_STATS_t *phy_stats = NULL;
if (rel15->rnti != 0xFFFF)
phy_stats = get_phy_stats(gNB, rel15->rnti);
if (phy_stats) {
phy_stats->frame = frame;
phy_stats->dlsch_stats.total_bytes_tx += rel15->TBSize[0];
phy_stats->dlsch_stats.current_RI = rel15->nrOfLayers;
phy_stats->dlsch_stats.current_Qm = rel15->qamModOrder[0];
}
int max_bytes = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * rel15->nrOfLayers * 1056;
int B;
if (A > NR_MAX_PDSCH_TBS) {
// Add 24-bit crc (polynomial A) to payload
crc = crc24a(a, A) >> 8;
a[A >> 3] = ((uint8_t *)&crc)[2];
a[1 + (A >> 3)] = ((uint8_t *)&crc)[1];
a[2 + (A >> 3)] = ((uint8_t *)&crc)[0];
// printf("CRC %x (A %d)\n",crc,A);
// printf("a0 %d a1 %d a2 %d\n", a[A>>3], a[1+(A>>3)], a[2+(A>>3)]);
B = A + 24;
AssertFatal((A / 8) + 4 <= max_bytes, "A %d is too big (A/8+4 = %d > %d)\n", A, (A / 8) + 4, max_bytes);
memcpy(dlsch->b, a, (A / 8) + 4); // why is this +4 if the CRC is only 3 bytes?
} else {
// Add 16-bit crc (polynomial A) to payload
crc = crc16(a, A) >> 16;
a[A >> 3] = ((uint8_t *)&crc)[1];
a[1 + (A >> 3)] = ((uint8_t *)&crc)[0];
// printf("CRC %x (A %d)\n",crc,A);
// printf("a0 %d a1 %d \n", a[A>>3], a[1+(A>>3)]);
B = A + 16;
AssertFatal((A / 8) + 3 <= max_bytes, "A %d is too big (A/8+3 = %d > %d)\n", A, (A / 8) + 3, max_bytes);
memcpy(dlsch->b, a, (A / 8) + 3); // using 3 bytes to mimic the case of 24 bit crc
for (int q = 0; q < rel15->NrOfCodewords; q++, tb_index++) {
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
const uint32_t A = rel15->TBSize[q] << 3;
unsigned char *a = dlsch->pdu[q];
AssertFatal(a != NULL, "Missing PDSCH transport block for codeword %d\n", q);
if (rel15->rnti != SI_RNTI) {
ws_trace_t tmp = {.nr = true,
.direction = DIRECTION_DOWNLINK,
.pdu_buffer = a,
.pdu_buffer_size = rel15->TBSize[q],
.ueid = 0,
.rntiType = WS_C_RNTI,
.rnti = rel15->rnti,
.sysFrame = frame,
.subframe = slot,
.harq_pid = 0,
.oob_event = 0,
.oob_event_value = 0};
trace_pdu(&tmp);
}
if (phy_stats)
phy_stats->dlsch_stats.total_bytes_tx += rel15->TBSize[q];
unsigned int crc = 1;
const int max_bytes = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * cw_layers * 1056;
int B;
if (A > NR_MAX_PDSCH_TBS) {
crc = crc24a(a, A) >> 8;
a[A >> 3] = ((uint8_t *)&crc)[2];
a[1 + (A >> 3)] = ((uint8_t *)&crc)[1];
a[2 + (A >> 3)] = ((uint8_t *)&crc)[0];
B = A + 24;
AssertFatal((A / 8) + 4 <= max_bytes, "A %u is too big (%u > %d)\n", A, (A / 8) + 4, max_bytes);
memcpy(dlsch->b[q], a, (A / 8) + 4);
} else {
crc = crc16(a, A) >> 16;
a[A >> 3] = ((uint8_t *)&crc)[1];
a[1 + (A >> 3)] = ((uint8_t *)&crc)[0];
B = A + 16;
AssertFatal((A / 8) + 3 <= max_bytes, "A %u is too big (%u > %d)\n", A, (A / 8) + 3, max_bytes);
memcpy(dlsch->b[q], a, (A / 8) + 3);
}
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[tb_index];
tb_dlsch[tb_index] = dlsch;
tb_cw[tb_index] = q;
// A HARQ PID is not globally unique, so use the flattened TB index.
TB_parameters->harq_unique_pid = tb_index;
TB_parameters->BG = dlsch_base_graph(A, rel15->targetCodeRate[q]);
TB_parameters->A = A;
start_meas(dlsch_segmentation_stats);
TB_parameters->Kb = nr_segmentation(dlsch->b[q],
dlsch->c[q],
B,
&TB_parameters->C,
&TB_parameters->K,
&TB_parameters->Z,
&TB_parameters->F,
TB_parameters->BG);
stop_meas(dlsch_segmentation_stats);
if (TB_parameters->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * cw_layers) {
LOG_E(PHY, "nr_segmentation.c: too many segments %d, B %d, codeword layers %d\n", TB_parameters->C, B, cw_layers);
return -1;
}
num_segments += TB_parameters->C;
}
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
// The harq_pid is not unique among the active HARQ processes in the instance so we use i instead
TB_parameters->harq_unique_pid = i;
TB_parameters->BG = rel15->maintenance_parms_v3.ldpcBaseGraph;
TB_parameters->A = A;
start_meas(dlsch_segmentation_stats);
TB_parameters->Kb = nr_segmentation(dlsch->b,
dlsch->c,
B,
&TB_parameters->C,
&TB_parameters->K,
&TB_parameters->Z,
&TB_parameters->F,
TB_parameters->BG);
stop_meas(dlsch_segmentation_stats);
if (TB_parameters->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * rel15->nrOfLayers) {
LOG_E(PHY, "nr_segmentation.c: too many segments %d, B %d\n", TB_parameters->C, B);
return (-1);
}
num_segments += TB_parameters->C;
}
nrLDPC_segment_encoding_parameters_t segments[num_segments];
@@ -191,19 +222,26 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
size_t segments_offset = 0;
size_t dlsch_offset = 0;
for (int i = 0; i < n_dlsch; i++) {
NR_gNB_DLSCH_t *dlsch = &dlsch_array[i];
for (int i = 0; i < n_tbs; i++) {
NR_gNB_DLSCH_t *dlsch = tb_dlsch[i];
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *rel15 = &dlsch->pdsch_pdu->pdsch_pdu_rel15;
const int q = tb_cw[i];
const int cw_layers = nr_pdsch_layers_per_codeword(rel15->nrOfLayers, q);
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
#ifdef DEBUG_DLSCH_CODING
for (int r = 0; r < TB_parameters->C; r++) {
LOG_D(PHY, "Encoder: B %d F %d \n", dlsch->B, TB_parameters->F);
LOG_D(PHY, "start ldpc encoder segment %d/%d\n", r, TB_parameters->C);
LOG_D(PHY, "input %d %d %d %d %d \n", dlsch->c[r][0], dlsch->c[r][1], dlsch->c[r][2], dlsch->c[r][3], dlsch->c[r][4]);
LOG_D(PHY,
"input %d %d %d %d %d \n",
dlsch->c[q][r][0],
dlsch->c[q][r][1],
dlsch->c[q][r][2],
dlsch->c[q][r][3],
dlsch->c[q][r][4]);
for (int cnt = 0; cnt < 22 * (TB_parameters->Z) / 8; cnt++) {
LOG_D(PHY, "%d ", dlsch->c[r][cnt]);
LOG_D(PHY, "%d ", dlsch->c[q][r][cnt]);
}
LOG_D(PHY, "\n");
}
@@ -211,10 +249,10 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
int rbsize = dlsch->freq_alloc.num_rbs;
TB_parameters->nb_rb = rbsize;
TB_parameters->Qm = rel15->qamModOrder[0];
TB_parameters->mcs = rel15->mcsIndex[0];
TB_parameters->nb_layers = rel15->nrOfLayers;
TB_parameters->rv_index = rel15->rvIndex[0];
TB_parameters->Qm = rel15->qamModOrder[q];
TB_parameters->mcs = rel15->mcsIndex[q];
TB_parameters->nb_layers = cw_layers;
TB_parameters->rv_index = rel15->rvIndex[q];
int nb_re_dmrs =
(rel15->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) ? (6 * rel15->numDmrsCdmGrpsNoData) : (4 * rel15->numDmrsCdmGrpsNoData);
@@ -223,8 +261,8 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
nb_re_dmrs,
get_num_dmrs(rel15->dlDmrsSymbPos),
dlsch->unav_res,
rel15->qamModOrder[0],
rel15->nrOfLayers);
rel15->qamModOrder[q],
cw_layers);
TB_parameters->tbslbrm = rel15->maintenance_parms_v3.tbSizeLbrmBytes;
TB_parameters->output = &output[dlsch_offset >> 3];
@@ -232,8 +270,8 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
for (int r = 0; r < TB_parameters->C; r++) {
nrLDPC_segment_encoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
segment_parameters->c = dlsch->c[r];
segment_parameters->E = nr_get_E(TB_parameters->G, TB_parameters->C, TB_parameters->Qm, rel15->nrOfLayers, r);
segment_parameters->c = dlsch->c[q][r];
segment_parameters->E = nr_get_E(TB_parameters->G, TB_parameters->C, TB_parameters->Qm, cw_layers, r);
reset_meas(&segment_parameters->ts_interleave);
reset_meas(&segment_parameters->ts_rate_match);
@@ -245,13 +283,14 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
/* output and its parts for each dlsch should be aligned on 64 bytes (or 8 * 64 bits)
* => dlsch_offset should remain a multiple of 8 * 64 with enough offset to fit each dlsch
*/
const size_t dlsch_size = rbsize * gNB->frame_parms.symbols_per_slot * NR_NB_SC_PER_RB * rel15->qamModOrder[0] * rel15->nrOfLayers;
const size_t dlsch_size =
rbsize * gNB->frame_parms.symbols_per_slot * NR_NB_SC_PER_RB * rel15->qamModOrder[q] * cw_layers;
dlsch_offset += ceil_mod(dlsch_size, 8 * 64);
}
nrLDPC_slot_encoding_parameters_t slot_parameters = {.frame = frame,
.slot = slot,
.nb_TBs = n_dlsch,
.nb_TBs = n_tbs,
.threadPool = &gNB->threadPool,
.tinput = tinput,
.tprep = tprep,
@@ -260,7 +299,7 @@ int nr_dlsch_encoding(PHY_VARS_gNB *gNB,
.TBs = TBs};
gNB->nrLDPC_coding_interface.nrLDPC_coding_encoder(&slot_parameters);
for (int i = 0; i < n_dlsch; i++) {
for (int i = 0; i < n_tbs; i++) {
nrLDPC_TB_encoding_parameters_t *TB_parameters = &TBs[i];
for (int r = 0; r < TB_parameters->C; r++) {
nrLDPC_segment_encoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
+8
View File
@@ -19,4 +19,12 @@ uint16_t get_dmrs_freq_idx(uint16_t n, uint8_t k_prime, uint8_t delta, uint8_t d
uint8_t get_l0(uint16_t dlDmrsSymbPos);
/* For a double-symbol PDSCH DMRS occasion, l'=1 on the second symbol of
* each adjacent pair and l'=0 on the first. Single-symbol occasions always
* use l'=0. */
static inline uint8_t get_pdsch_dmrs_l_prime(uint16_t dmrs_symbol_mask, uint8_t symbol)
{
return symbol > 0 && ((dmrs_symbol_mask >> (symbol - 1)) & 0x1);
}
#endif
@@ -1282,7 +1282,8 @@ void nr_pdsch_channel_estimation(PHY_VARS_NR_UE *ue,
float beta_dmrs_pdsch = get_beta_dmrs(dlsch->n_dmrs_cdm_groups, config_type == NFAPI_NR_DMRS_TYPE2);
int16_t dmrs_scaling = (int16_t)((1 / beta_dmrs_pdsch) * (1 << 14));
const uint32_t *gold = nr_gold_pdsch(fp->N_RB_DL, fp->symbols_per_slot, dlsch->dlDmrsScramblingId, dlsch->nscid, slot, symbol);
nr_pdsch_dmrs_rx(fp->Ncp, gold, pilot, 1000 + p, 0, nb_rb_pdsch + rb_offset, config_type, dmrs_scaling);
const uint8_t l_prime = get_pdsch_dmrs_l_prime(dlsch->dlDmrsSymbPos, symbol);
nr_pdsch_dmrs_rx(fp->Ncp, gold, pilot, 1000 + p, l_prime, nb_rb_pdsch + rb_offset, config_type, dmrs_scaling);
delay_t delay = {0};
@@ -1374,127 +1375,94 @@ void nr_pdsch_ptrs_processing(int nbRx,
int nl,
c16_t rxdataF_comp[][nl][nbRx][rx_size_symbol],
NR_DL_FRAME_PARMS *frame_parms,
NR_DL_UE_HARQ_t *dlsch0_harq,
NR_DL_UE_HARQ_t *dlsch1_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
uint8_t nr_slot_rx,
unsigned char symbol,
int nb_rb,
uint16_t rnti,
NR_UE_DLSCH_t dlsch[2])
uint16_t *ptrsSymbPos,
uint8_t *ptrsSymbIdx)
{
//#define DEBUG_DL_PTRS 1
int32_t *ptrs_re_symbol = NULL;
int8_t ret = 0;
/* harq specific variables */
uint8_t symbInSlot = 0;
uint16_t *startSymbIndex = NULL;
uint16_t *nbSymb = NULL;
uint8_t *L_ptrs = NULL;
uint8_t *K_ptrs = NULL;
uint16_t *dmrsSymbPos = NULL;
uint16_t *ptrsSymbPos = NULL;
uint8_t *ptrsSymbIdx = NULL;
uint8_t *ptrsReOffset = NULL;
int nscid = 0;
int symbInSlot = dlsch_config->start_symbol + dlsch_config->number_symbols;
int startSymbIndex = dlsch_config->start_symbol;
int nbSymb = dlsch_config->number_symbols;
int L_ptrs = dlsch_config->PTRSTimeDensity;
int K_ptrs = dlsch_config->PTRSFreqDensity;
int dmrsSymbPos = dlsch_config->dlDmrsSymbPos;
int ptrsReOffset = dlsch_config->PTRSReOffset;
int nscid = dlsch_config->nscid;
if(dlsch0_harq->status == NR_ACTIVE) {
symbInSlot = dlsch[0].dlsch_config.start_symbol + dlsch[0].dlsch_config.number_symbols;
startSymbIndex = &dlsch[0].dlsch_config.start_symbol;
nbSymb = &dlsch[0].dlsch_config.number_symbols;
L_ptrs = &dlsch[0].dlsch_config.PTRSTimeDensity;
K_ptrs = &dlsch[0].dlsch_config.PTRSFreqDensity;
dmrsSymbPos = &dlsch[0].dlsch_config.dlDmrsSymbPos;
ptrsReOffset = &dlsch[0].dlsch_config.PTRSReOffset;
ptrsSymbPos = &dlsch[0].ptrs_symbols;
ptrsSymbIdx = &dlsch[0].ptrs_symbol_index;
nscid = dlsch[0].dlsch_config.nscid;
}
if(dlsch1_harq) {
symbInSlot = dlsch[1].dlsch_config.start_symbol + dlsch[1].dlsch_config.number_symbols;
startSymbIndex = &dlsch[1].dlsch_config.start_symbol;
nbSymb = &dlsch[1].dlsch_config.number_symbols;
L_ptrs = &dlsch[1].dlsch_config.PTRSTimeDensity;
K_ptrs = &dlsch[1].dlsch_config.PTRSFreqDensity;
dmrsSymbPos = &dlsch[1].dlsch_config.dlDmrsSymbPos;
ptrsReOffset = &dlsch[1].dlsch_config.PTRSReOffset;
ptrsSymbPos = &dlsch[1].ptrs_symbols;
ptrsSymbIdx = &dlsch[1].ptrs_symbol_index;
nscid = dlsch[1].dlsch_config.nscid;
}
/* loop over antennas */
for (int aarx=0; aarx<frame_parms->nb_antennas_rx; aarx++) {
for (int aarx = 0; aarx < frame_parms->nb_antennas_rx; aarx++) {
c16_t *phase_per_symbol = (c16_t*)ptrs_phase_per_slot[aarx];
ptrs_re_symbol = (int32_t*)ptrs_re_per_slot[aarx];
ptrs_re_symbol[symbol] = 0;
phase_per_symbol[symbol].i = 0; // Imag
/* set DMRS estimates to 0 angle with magnitude 1 */
if(is_dmrs_symbol(symbol,*dmrsSymbPos)) {
if(is_dmrs_symbol(symbol, dmrsSymbPos)) {
/* set DMRS real estimation to 32767 */
phase_per_symbol[symbol].r=INT16_MAX; // 32767
#ifdef DEBUG_DL_PTRS
printf("[PHY][PTRS]: DMRS Symbol %d -> %4d + j*%4d\n", symbol, phase_per_symbol[symbol].r,phase_per_symbol[symbol].i);
#endif
}
else { // real ptrs value is set to 0
} else { // real ptrs value is set to 0
phase_per_symbol[symbol].r = 0; // Real
}
if(dlsch0_harq->status == NR_ACTIVE) {
if(symbol == *startSymbIndex) {
*ptrsSymbPos = 0;
set_ptrs_symb_idx(ptrsSymbPos,
*nbSymb,
*startSymbIndex,
1<< *L_ptrs,
*dmrsSymbPos);
}
/* if not PTRS symbol set current ptrs symbol index to zero*/
*ptrsSymbIdx = 0;
/* Check if current symbol contains PTRS */
if(is_ptrs_symbol(symbol, *ptrsSymbPos)) {
*ptrsSymbIdx = symbol;
/*------------------------------------------------------------------------------------------------------- */
/* 1) Estimate common phase error per PTRS symbol */
/*------------------------------------------------------------------------------------------------------- */
const uint32_t *gold =
if (symbol == startSymbIndex) {
*ptrsSymbPos = 0;
set_ptrs_symb_idx(ptrsSymbPos, nbSymb, startSymbIndex, 1 << L_ptrs, dmrsSymbPos);
}
/* if not PTRS symbol set current ptrs symbol index to zero*/
*ptrsSymbIdx = 0;
/* Check if current symbol contains PTRS */
if (is_ptrs_symbol(symbol, *ptrsSymbPos)) {
*ptrsSymbIdx = symbol;
/*------------------------------------------------------------------------------------------------------- */
/* 1) Estimate common phase error per PTRS symbol */
/*------------------------------------------------------------------------------------------------------- */
const uint32_t *gold =
nr_gold_pdsch(frame_parms->N_RB_DL, frame_parms->symbols_per_slot, frame_parms->Nid_cell, nscid, nr_slot_rx, symbol);
nr_ptrs_cpe_estimation(*K_ptrs,
*ptrsReOffset,
nb_rb,
rnti,
frame_parms->ofdm_symbol_size,
rxdataF_comp[symbol][0][aarx],
gold,
(int16_t *)&phase_per_symbol[symbol],
&ptrs_re_symbol[symbol]);
}
}// HARQ 0
nr_ptrs_cpe_estimation(K_ptrs,
ptrsReOffset,
nb_rb,
rnti,
frame_parms->ofdm_symbol_size,
rxdataF_comp[symbol][0][aarx],
gold,
(int16_t *)&phase_per_symbol[symbol],
&ptrs_re_symbol[symbol]);
}
/* For last OFDM symbol at each antenna perform interpolation and compensation for the slot*/
if(symbol == (symbInSlot -1)) {
if (symbol == (symbInSlot -1)) {
/*------------------------------------------------------------------------------------------------------- */
/* 2) Interpolate PTRS estimated value in TD */
/*------------------------------------------------------------------------------------------------------- */
/* If L-PTRS is > 0 then we need interpolation */
if(*L_ptrs > 0) {
ret = nr_ptrs_process_slot(*dmrsSymbPos, *ptrsSymbPos, (int16_t*)phase_per_symbol, *startSymbIndex, *nbSymb);
if (L_ptrs > 0) {
ret = nr_ptrs_process_slot(dmrsSymbPos, *ptrsSymbPos, (int16_t*)phase_per_symbol, startSymbIndex, nbSymb);
if(ret != 0) {
LOG_W(PHY,"[PTRS] Compensation is skipped due to error in PTRS slot processing !!\n");
}
}
#ifdef DEBUG_DL_PTRS
LOG_M("ptrsEst.m","est",ptrs_phase_per_slot[aarx],frame_parms->symbols_per_slot,1,1 );
LOG_M("rxdataF_bf_ptrs_comp.m", "bf_ptrs_cmp", rxdataF_comp[0][aarx] + (*startSymbIndex) * rx_size_symbol, rx_size_symbol * (*nbSymb), 1, 1);
LOG_M("ptrsEst.m","est", ptrs_phase_per_slot[aarx], frame_parms->symbols_per_slot, 1, 1);
LOG_M("rxdataF_bf_ptrs_comp.m", "bf_ptrs_cmp", rxdataF_comp[0][aarx] + startSymbIndex * rx_size_symbol, rx_size_symbol * nbSymb, 1, 1);
#endif
/*------------------------------------------------------------------------------------------------------- */
/* 3) Compensated DMRS based estimated signal with PTRS estimation */
/*--------------------------------------------------------------------------------------------------------*/
for(uint8_t i = *startSymbIndex; i < symbInSlot; i++) {
for(uint8_t i = startSymbIndex; i < symbInSlot; i++) {
/* DMRS Symbol has 0 phase so no need to rotate the respective symbol */
/* Skip rotation if the slot processing is wrong */
if((!is_dmrs_symbol(i, *dmrsSymbPos)) && (ret == 0)) {
if((!is_dmrs_symbol(i, dmrsSymbPos)) && (ret == 0)) {
#ifdef DEBUG_DL_PTRS
printf("[PHY][DL][PTRS]: Rotate Symbol %2d with %d + j* %d\n", i, phase_per_symbol[i].r,phase_per_symbol[i].i);
printf("[PHY][DL][PTRS]: Rotate Symbol %2d with %d + j* %d\n", i, phase_per_symbol[i].r, phase_per_symbol[i].i);
#endif
rotate_cpx_vector(rxdataF_comp[i][0][aarx], &phase_per_symbol[i], rxdataF_comp[i][0][aarx], nb_rb * NR_NB_SC_PER_RB, 15);
}// if not DMRS Symbol
@@ -126,13 +126,13 @@ void nr_pdsch_ptrs_processing(int nbRx,
int nl,
c16_t rxdataF_comp[][nl][nbRx][rx_size_symbol],
NR_DL_FRAME_PARMS *frame_parms,
NR_DL_UE_HARQ_t *dlsch0_harq,
NR_DL_UE_HARQ_t *dlsch1_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
uint8_t nr_slot_rx,
unsigned char symbol,
int nb_rb,
uint16_t rnti,
NR_UE_DLSCH_t dlsch[2]);
uint16_t *ptrsSymbPos,
uint8_t *ptrsSymbIdx);
int nr_sl_psbch_rsrp_measurements(PHY_VARS_NR_UE *ue,
sl_nr_ue_phy_params_t *sl_phy_params,
@@ -170,7 +170,7 @@ static void send_ssb_rsrp_meas(PHY_VARS_NR_UE *ue,
nr_downlink_indication_t dl_indication = {0};
fapi_nr_rx_indication_t rx_ind = {0};
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, NULL, NULL, 1, proc, &l1_measurements, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, 0, 0, NULL, 1, proc, &l1_measurements, NULL);
ue->if_inst->dl_indication(&dl_indication);
}
+508 -15
View File
@@ -15,6 +15,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <pthread.h>
#include "executables/nr-softmodem-common.h"
#include "nr_transport_proto_ue.h"
@@ -22,6 +25,100 @@
#include "common/utils/nr/nr_common.h"
#include "PHY/NR_UE_ESTIMATION/filt16a_32.h"
// nakaolab CSI muting study: float Gram matrix + Jacobi eigenvalues for RI and
// spatial-spectrum telemetry. MAX_ANT and the local PDSCH rank ceiling are
// eight on this branch.
// Sorts eigenvalues in descending order on output.
static void nr_hermitian_eigenvalues(const int N,
float A_re[MAX_ANT][MAX_ANT],
float A_im[MAX_ANT][MAX_ANT],
float eigenvalues[MAX_ANT])
{
for (int sweep = 0; sweep < 64; sweep++) {
float off_norm = 0;
float diag_norm = 0;
for (int i = 0; i < N; i++)
diag_norm += A_re[i][i] * A_re[i][i];
for (int i = 0; i < N; i++)
for (int j = i + 1; j < N; j++)
off_norm += A_re[i][j] * A_re[i][j] + A_im[i][j] * A_im[i][j];
if (off_norm <= 1e-10f * (diag_norm + 1.0f))
break;
for (int p = 0; p < N - 1; p++) {
for (int q = p + 1; q < N; q++) {
float re_pq = A_re[p][q], im_pq = A_im[p][q];
float mag = sqrtf(re_pq * re_pq + im_pq * im_pq);
if (mag < 1e-12f)
continue;
float cos_phi = re_pq / mag;
float sin_phi = im_pq / mag;
// Phase elimination: rotate column/row q so A[p][q] is real.
// Patch 1: skip k==q (was a bug — corrupted diagonal A[q][q]).
for (int k = 0; k < N; k++) {
if (k == q) continue;
float old_re = A_re[k][q], old_im = A_im[k][q];
A_re[k][q] = old_re * cos_phi + old_im * sin_phi;
A_im[k][q] = -old_re * sin_phi + old_im * cos_phi;
}
for (int k = 0; k < N; k++) {
if (k == q) continue;
A_re[q][k] = A_re[k][q];
A_im[q][k] = -A_im[k][q];
}
// Real Jacobi rotation
float d = A_re[q][q] - A_re[p][p];
float t;
if (fabsf(d) < 1e-12f) {
t = 1.0f;
} else {
float tau = d / (2.0f * mag);
t = ((tau >= 0) ? 1.0f : -1.0f) / (fabsf(tau) + sqrtf(1.0f + tau * tau));
}
float c = 1.0f / sqrtf(1.0f + t * t);
float s = t * c;
A_re[p][p] -= t * mag;
A_re[q][q] += t * mag;
A_re[p][q] = 0;
A_im[p][q] = 0;
A_re[q][p] = 0;
A_im[q][p] = 0;
for (int r = 0; r < N; r++) {
if (r == p || r == q) continue;
float rp_re = A_re[r][p], rp_im = A_im[r][p];
float rq_re = A_re[r][q], rq_im = A_im[r][q];
A_re[r][p] = c * rp_re - s * rq_re;
A_im[r][p] = c * rp_im - s * rq_im;
A_re[p][r] = A_re[r][p];
A_im[p][r] = -A_im[r][p];
A_re[r][q] = s * rp_re + c * rq_re;
A_im[r][q] = s * rp_im + c * rq_im;
A_re[q][r] = A_re[r][q];
A_im[q][r] = -A_im[r][q];
}
}
}
}
// Patch 2: clamp eigenvalues to >=0 (numerical errors can produce negative values)
for (int i = 0; i < N; i++)
eigenvalues[i] = A_re[i][i] > 0.0f ? A_re[i][i] : 0.0f;
// Sort descending
for (int i = 0; i < N - 1; i++)
for (int j = i + 1; j < N; j++)
if (eigenvalues[j] > eigenvalues[i]) {
float tmp = eigenvalues[i];
eigenvalues[i] = eigenvalues[j];
eigenvalues[j] = tmp;
}
}
//#define NR_CSIRS_DEBUG
//#define NR_CSIIM_DEBUG
@@ -65,6 +162,95 @@ void nr_det_A_MF_2x2(int32_t *a_mf_00,
}
}
typedef struct {
int samples;
int dimensions;
float eig_rel_db[NR_MAX_NB_LAYERS];
float rank_capacity[NR_MAX_NB_LAYERS];
// Retained diagonal Gram-matrix energy for active prefix sizes 1/2/4/8,
// relative to all configured CSI-RS ports. This is an offline feature for
// testing whether CSI adds value to antenna-muting decisions.
float prefix_rel_db[4];
} nr_csi_muting_summary_t;
typedef enum {
NR_CSI_RANK_CAPACITY,
NR_CSI_RANK_LEGACY,
NR_CSI_RANK_FIXED,
} nr_csi_rank_policy_t;
typedef struct {
nr_csi_rank_policy_t policy;
uint8_t fixed_rank;
float capacity_margin;
} nr_csi_rank_config_t;
static pthread_once_t csi_rank_config_once = PTHREAD_ONCE_INIT;
static nr_csi_rank_config_t csi_rank_config = {
.policy = NR_CSI_RANK_CAPACITY,
.fixed_rank = 1,
.capacity_margin = 0.05f,
};
static const char *nr_csi_rank_policy_name(const nr_csi_rank_config_t *config)
{
if (config->policy == NR_CSI_RANK_LEGACY)
return "legacy";
if (config->policy == NR_CSI_RANK_FIXED)
return "fixed";
return "capacity";
}
static void configure_csi_rank_policy(void)
{
const char *value = getenv("OAI_CSI_RANK_POLICY");
if (value != NULL && strcmp(value, "legacy") == 0) {
csi_rank_config.policy = NR_CSI_RANK_LEGACY;
} else if (value == NULL || strcmp(value, "capacity") == 0) {
csi_rank_config.policy = NR_CSI_RANK_CAPACITY;
} else if (strncmp(value, "fixed", 5) == 0) {
const char *rank_text = value + 5;
if (*rank_text == ':')
rank_text++;
const long rank = strtol(rank_text, NULL, 10);
if (rank >= 1 && rank <= NR_MAX_NB_LAYERS) {
csi_rank_config.policy = NR_CSI_RANK_FIXED;
csi_rank_config.fixed_rank = rank;
} else {
LOG_W(NR_PHY,
"OAI_CSI_RANK_POLICY=%s has invalid fixed rank; using capacity\n",
value);
}
} else {
LOG_W(NR_PHY,
"Unknown OAI_CSI_RANK_POLICY=%s; using capacity\n",
value);
}
const char *margin_text = getenv("OAI_CSI_RANK_MARGIN");
if (margin_text != NULL) {
const float margin = strtof(margin_text, NULL);
if (isfinite(margin) && margin >= 0.0f && margin <= 1.0f)
csi_rank_config.capacity_margin = margin;
else
LOG_W(NR_PHY,
"OAI_CSI_RANK_MARGIN must be in [0,1]; using %.2f\n",
csi_rank_config.capacity_margin);
}
LOG_I(NR_PHY,
"[CSI_RANK] policy=%s fixed_rank=%u capacity_margin=%.3f\n",
nr_csi_rank_policy_name(&csi_rank_config),
csi_rank_config.fixed_rank,
csi_rank_config.capacity_margin);
}
static const nr_csi_rank_config_t *get_csi_rank_config(void)
{
pthread_once(&csi_rank_config_once, configure_csi_rank_policy);
return &csi_rank_config;
}
void nr_squared_matrix_element(int32_t *a,
int32_t *a_sq,
const unsigned short nb_rb) {
@@ -421,21 +607,136 @@ static int nr_csi_rs_ri_estimation(const PHY_VARS_NR_UE *ue,
const uint8_t N_ports,
c16_t csi_rs_estimated_channel_freq[][N_ports][ue->frame_parms.ofdm_symbol_size],
const int16_t log2_maxh,
uint8_t *rank_indicator)
const uint32_t noise_power,
uint8_t *rank_indicator,
nr_csi_muting_summary_t *muting_summary)
{
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
const nr_csi_rank_config_t *rank_config = get_csi_rank_config();
const int16_t cond_dB_threshold = 5;
int count = 0;
*rank_indicator = 0;
if (muting_summary != NULL)
memset(muting_summary, 0, sizeof(*muting_summary));
if (ue->frame_parms.nb_antennas_rx == 1 || N_ports == 1) {
return 0;
} else if( !(ue->frame_parms.nb_antennas_rx == 2 && N_ports == 2) ) {
LOG_W(NR_PHY, "Rank indicator computation is not implemented for %i x %i system\n",
ue->frame_parms.nb_antennas_rx, N_ports);
}
if (N_ports > MAX_ANT || frame_parms->nb_antennas_rx > MAX_ANT) {
LOG_W(NR_PHY,
"CSI spatial-spectrum computation exceeds platform bound for %i x %i system\n",
frame_parms->nb_antennas_rx,
N_ports);
return -1;
}
const int physical_rank = N_ports < frame_parms->nb_antennas_rx ? N_ports : frame_parms->nb_antennas_rx;
const int max_rank = physical_rank < NR_MAX_NB_LAYERS ? physical_rank : NR_MAX_NB_LAYERS;
if (max_rank < 2) {
return 0;
}
// Legacy 2x2 keeps the existing condition-number path below. Capacity and
// fixed policies use the same eigen-spectrum path for every MIMO size.
const bool legacy_2x2 = rank_config->policy == NR_CSI_RANK_LEGACY
&& ue->frame_parms.nb_antennas_rx == 2 && N_ports == 2;
if (!legacy_2x2) {
int rank_count[NR_MAX_NB_LAYERS] = {0};
double eig_sum[MAX_ANT] = {0};
double port_power[MAX_ANT] = {0};
double capacity_sum[NR_MAX_NB_LAYERS] = {0};
const float eig_ratio_dB_threshold = 8.0f;
const int sc_stride = physical_rank > 4 ? 4 : 1;
for (int rb = csirs_config_pdu->start_rb; rb < (csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs); rb++) {
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != (rb % 2)) continue;
uint16_t k = rb * NR_NB_SC_PER_RB;
for (int sc_idx = 0; sc_idx < NR_NB_SC_PER_RB; sc_idx += sc_stride) {
// Patch 3: float Gram matrix A = H^H H computed directly from channel estimates
// (avoids int16 saturation that capped RI at 2 in 4-RX setups).
float A_re[MAX_ANT][MAX_ANT] = {{0}}, A_im[MAX_ANT][MAX_ANT] = {{0}};
for (int rx = 0; rx < frame_parms->nb_antennas_rx; rx++) {
for (int p1 = 0; p1 < N_ports; p1++) {
c16_t h1 = csi_rs_estimated_channel_freq[rx][p1][k + sc_idx];
float h1_re = (float)h1.r, h1_im = (float)h1.i;
for (int p2 = 0; p2 < N_ports; p2++) {
c16_t h2 = csi_rs_estimated_channel_freq[rx][p2][k + sc_idx];
float h2_re = (float)h2.r, h2_im = (float)h2.i;
A_re[p1][p2] += h1_re * h2_re + h1_im * h2_im;
A_im[p1][p2] += -h1_im * h2_re + h1_re * h2_im;
}
}
}
for (int p = 0; p < N_ports; p++)
port_power[p] += A_re[p][p];
float eig[MAX_ANT] = {0};
nr_hermitian_eigenvalues(N_ports, A_re, A_im, eig);
if (eig[0] < 1e-6f) continue;
float rank_capacity[NR_MAX_NB_LAYERS] = {0};
nr_pdsch_rank_capacity_scores(eig, max_rank, (float)noise_power, rank_capacity);
for (int r = 0; r < max_rank; r++)
capacity_sum[r] += rank_capacity[r];
rank_count[0]++;
for (int r = 0; r < N_ports; r++)
eig_sum[r] += eig[r];
for (int r = 1; r < max_rank; r++) {
if (eig[r] > 1e-6f && 10.0f * log10f(eig[0] / eig[r]) < eig_ratio_dB_threshold) {
rank_count[r]++;
} else {
break;
}
}
}
}
int total_sc = rank_count[0];
if (total_sc > 0) {
float mean_capacity[NR_MAX_NB_LAYERS] = {0};
for (int r = 0; r < max_rank; r++)
mean_capacity[r] = capacity_sum[r] / total_sc;
if (rank_config->policy == NR_CSI_RANK_LEGACY) {
for (int r = max_rank - 1; r >= 1; r--) {
if (rank_count[r] > total_sc / 2) {
*rank_indicator = r;
break;
}
}
} else if (rank_config->policy == NR_CSI_RANK_FIXED) {
*rank_indicator = min(rank_config->fixed_rank, max_rank) - 1;
} else {
*rank_indicator = nr_pdsch_select_rank(mean_capacity,
max_rank,
rank_config->capacity_margin)
- 1;
}
if (muting_summary != NULL) {
muting_summary->samples = total_sc;
muting_summary->dimensions = physical_rank;
for (int r = 0; r < NR_MAX_NB_LAYERS; r++) {
muting_summary->eig_rel_db[r] =
r < physical_rank && eig_sum[r] > 0.0 && eig_sum[0] > 0.0
? 10.0f * log10f((float)(eig_sum[r] / eig_sum[0]))
: -INFINITY;
muting_summary->rank_capacity[r] = r < max_rank ? mean_capacity[r] : 0.0f;
}
double full_power = 0.0;
for (int p = 0; p < N_ports; p++)
full_power += port_power[p];
const int prefix_size[4] = {1, 2, 4, 8};
for (int candidate = 0; candidate < 4; candidate++) {
const int keep = prefix_size[candidate] < N_ports ? prefix_size[candidate] : N_ports;
double prefix_power = 0.0;
for (int p = 0; p < keep; p++)
prefix_power += port_power[p];
muting_summary->prefix_rel_db[candidate] =
prefix_power > 0.0 && full_power > 0.0
? 10.0f * log10f((float)(prefix_power / full_power))
: -INFINITY;
}
}
}
return 0;
}
/* Example 2x2: Hh x H =
* | conjch00 conjch10 | x | ch00 ch01 | = | conjch00*ch00+conjch10*ch10 conjch00*ch01+conjch10*ch11 |
* | conjch01 conjch11 | | ch10 ch11 | | conjch01*ch00+conjch11*ch10 conjch01*ch01+conjch11*ch11 |
@@ -554,10 +855,16 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
const uint32_t interference_plus_noise_power,
const uint8_t rank_indicator,
const int16_t log2_re,
uint16_t *pmi_x1,
uint8_t *i2,
int32_t *precoded_sinr_dB)
{
const NR_DL_FRAME_PARMS *frame_parms = &ue->frame_parms;
// RF simulators can report exactly zero interference/noise. Keep every PMI
// rank path finite, including the eight-port codebook search below.
const uint32_t non_zero_noise_power = interference_plus_noise_power == 0 ? 1 : interference_plus_noise_power;
*pmi_x1 = 0;
i2[0] = 0;
// i1 is a three-element vector in the form of [i11 i12 i13], when CodebookType is specified as 'Type1SinglePanel'.
// Note that i13 is not applicable when the number of transmission layers is one of {1, 5, 6, 7, 8}.
@@ -587,14 +894,14 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
if (count > 0) {
const int64_t avg_signal_power = signal_power / count;
// Non RF devices like ZMQ has virtually zero noise. So here we make noise as 1 to return maximum sinr.
const uint32_t sinr = avg_signal_power / ((interference_plus_noise_power == 0) ? 1 : interference_plus_noise_power);
const uint32_t sinr = avg_signal_power / non_zero_noise_power;
*precoded_sinr_dB = dB_fixed(sinr);
}
return 0;
}
if(rank_indicator == 0 || rank_indicator == 1) {
if ((rank_indicator == 0 || rank_indicator == 1) && N_ports != 8) {
c64_t sum[4] = {0};
c64_t sum2[4] = {0};
int64_t tested_precoded_sinr[4] = {0};
@@ -640,7 +947,7 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
for(int p = 0; p<4; p++) {
int64_t power_re = sum2[p].r - (sum[p].r >> log2_re) * (sum[p].r >> log2_re);
int64_t power_im = sum2[p].i - (sum[p].i >> log2_re) * (sum[p].i >> log2_re);
tested_precoded_sinr[p] = (power_re + power_im) / interference_plus_noise_power;
tested_precoded_sinr[p] = (power_re + power_im) / non_zero_noise_power;
}
if(rank_indicator == 0) {
@@ -655,6 +962,147 @@ static int nr_csi_rs_pmi_estimation(const PHY_VARS_NR_UE *ue,
*precoded_sinr_dB = dB_fixed((tested_precoded_sinr[i2[0]] + tested_precoded_sinr[i2[0]+2])>>1);
}
} else if (rank_indicator < NR_MAX_NB_LAYERS && rank_indicator < N_ports && N_ports == 8
&& rank_indicator != 2 && rank_indicator != 3
&& csirs_config_pdu->codebook_n1 == 4
&& csirs_config_pdu->codebook_n2 == 1
&& csirs_config_pdu->codebook_mode == 1) {
const int nlayers = rank_indicator + 1;
float gram_re[MAX_ANT][MAX_ANT] = {{0}};
float gram_im[MAX_ANT][MAX_ANT] = {{0}};
int samples = 0;
const int sc_stride = 4;
for (int rb = csirs_config_pdu->start_rb;
rb < csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs;
rb++) {
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != rb % 2)
continue;
const uint16_t k = rb * NR_NB_SC_PER_RB;
for (int sc = 0; sc < NR_NB_SC_PER_RB; sc += sc_stride) {
for (int rx = 0; rx < frame_parms->nb_antennas_rx; rx++) {
for (int p = 0; p < N_ports; p++) {
const c16_t hp = csi_rs_estimated_channel_freq[rx][p][k + sc];
for (int q = 0; q < N_ports; q++) {
const c16_t hq = csi_rs_estimated_channel_freq[rx][q][k + sc];
gram_re[p][q] += (float)hp.r * hq.r + (float)hp.i * hq.i;
gram_im[p][q] += -(float)hp.i * hq.r + (float)hp.r * hq.i;
}
}
}
samples++;
}
}
float best_score = -INFINITY;
uint8_t best_i11 = 0;
uint8_t best_i13 = 0;
uint8_t best_i2 = 0;
const uint8_t i11_count = nr_type1_sp_4x1_i11_count(nlayers);
const uint8_t i13_count = nr_type1_sp_4x1_i13_count(nlayers);
const uint8_t i2_count = nr_type1_sp_4x1_i2_count(nlayers);
for (uint8_t candidate_i11 = 0; candidate_i11 < i11_count; candidate_i11++) {
for (uint8_t candidate_i13 = 0; candidate_i13 < i13_count; candidate_i13++) {
for (uint8_t candidate_i2 = 0; candidate_i2 < i2_count; candidate_i2++) {
c16_t weights[NR_MAX_NB_LAYERS][NR_MAX_CSI_PORTS];
AssertFatal(nr_type1_sp_4x1_weights(nlayers, candidate_i11, candidate_i13, candidate_i2, weights),
"Invalid rank-%d PMI candidate i11=%u i13=%u i2=%u\n",
nlayers,
candidate_i11,
candidate_i13,
candidate_i2);
float precoded_re[MAX_ANT][MAX_ANT] = {{0}};
float precoded_im[MAX_ANT][MAX_ANT] = {{0}};
for (int a = 0; a < nlayers; a++) {
for (int b = 0; b < nlayers; b++) {
double value_re = 0.0;
double value_im = 0.0;
for (int p = 0; p < N_ports; p++) {
const double wa_re = weights[a][p].r / (double)SHRT_MAX;
const double wa_im = weights[a][p].i / (double)SHRT_MAX;
for (int q = 0; q < N_ports; q++) {
const double wb_re = weights[b][q].r / (double)SHRT_MAX;
const double wb_im = weights[b][q].i / (double)SHRT_MAX;
const double left_re = wa_re * gram_re[p][q] + wa_im * gram_im[p][q];
const double left_im = wa_re * gram_im[p][q] - wa_im * gram_re[p][q];
value_re += left_re * wb_re - left_im * wb_im;
value_im += left_re * wb_im + left_im * wb_re;
}
}
precoded_re[a][b] = value_re;
precoded_im[a][b] = value_im;
}
}
// Remove accumulated floating-point asymmetry before Jacobi sweeps.
for (int a = 0; a < nlayers; a++) {
precoded_im[a][a] = 0.0f;
for (int b = a + 1; b < nlayers; b++) {
const float avg_re = 0.5f * (precoded_re[a][b] + precoded_re[b][a]);
const float avg_im = 0.5f * (precoded_im[a][b] - precoded_im[b][a]);
precoded_re[a][b] = precoded_re[b][a] = avg_re;
precoded_im[a][b] = avg_im;
precoded_im[b][a] = -avg_im;
}
}
float eigenvalues[MAX_ANT] = {0};
nr_hermitian_eigenvalues(nlayers, precoded_re, precoded_im, eigenvalues);
float score = 0.0f;
if (samples > 0)
for (int layer = 0; layer < nlayers; layer++)
score += log2f(1.0f + eigenvalues[layer] / (samples * (float)non_zero_noise_power));
const float tie_tolerance = fmaxf(1e-4f, fabsf(best_score) * 1e-5f);
if (!isfinite(best_score) || score > best_score + tie_tolerance) {
best_score = score;
best_i11 = candidate_i11;
best_i13 = candidate_i13;
best_i2 = candidate_i2;
}
}
}
}
// x1 is packed i11|i12|i13. N2=1 makes i12 zero length; i13 is
// two bits for ranks 2--4 and absent for the other ranks.
const uint8_t i13_bitlen = i13_count > 1 ? 2 : 0;
*pmi_x1 = (best_i11 << i13_bitlen) | best_i13;
i2[0] = best_i2;
const float equivalent_sinr = best_score > 0.0f ? exp2f(best_score / nlayers) - 1.0f : 0.0f;
const float equivalent_sinr_dB = equivalent_sinr > 0.0f ? 10.0f * log10f(equivalent_sinr) : 0.0f;
*precoded_sinr_dB = lroundf(fmaxf(0.0f, fminf(equivalent_sinr_dB, INT_MAX)));
const char *pmi_telemetry = getenv("OAI_CSI_PMI_TELEMETRY");
if (pmi_telemetry != NULL && strcmp(pmi_telemetry, "1") == 0)
LOG_I(NR_PHY,
"[CSI_PMI] ports=8 geometry=4x1 mode=1 rank=%d i11=%u i13=%u i2=%u candidates=%u "
"capacity=%.4f equivalent_sinr_db=%.2f samples=%d\n",
nlayers,
best_i11,
best_i13,
best_i2,
i11_count * i13_count * i2_count,
best_score,
equivalent_sinr_dB,
samples);
} else if (rank_indicator >= 2 && rank_indicator < NR_MAX_NB_LAYERS
&& rank_indicator < N_ports) {
// Unsupported geometries retain the prior identity-like CQI fallback.
// The eight-port rank-3/4 receive path also remains identity: OAI's
// legacy detector cannot yet decode a non-identity 8-port rank-3/4 PDSCH.
int nlayers = rank_indicator + 1;
int64_t total_power = 0;
for (int rb = csirs_config_pdu->start_rb;
rb < (csirs_config_pdu->start_rb + csirs_config_pdu->nr_of_rbs); rb++) {
if (csirs_config_pdu->freq_density <= 1 && csirs_config_pdu->freq_density != (rb % 2))
continue;
uint16_t k = rb * NR_NB_SC_PER_RB;
for (int ant_rx = 0; ant_rx < frame_parms->nb_antennas_rx; ant_rx++) {
for (int p = 0; p < nlayers; p++) {
const c16_t h = csi_rs_estimated_channel_freq[ant_rx][p][k];
total_power += (int64_t)h.r * h.r + (int64_t)h.i * h.i;
}
}
}
if (total_power > 0)
*precoded_sinr_dB = dB_fixed((total_power / nlayers) / non_zero_noise_power);
} else {
LOG_W(NR_PHY, "PMI computation is not implemented for rank indicator %i\n", rank_indicator+1);
return -1;
@@ -895,6 +1343,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
&noise_power);
uint8_t rank_indicator = 0;
nr_csi_muting_summary_t muting_summary = {0};
// bit 1 in bitmap to indicate RI measurment
if (csirs_config_pdu->measurement_bitmap & 2) {
nr_csi_rs_ri_estimation(ue,
@@ -902,10 +1351,12 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
mapping_parms.ports,
csi_rs_estimated_channel_freq,
log2_maxh,
&rank_indicator);
noise_power,
&rank_indicator,
&muting_summary);
}
uint8_t i1[3] = {0};
uint16_t i1 = 0;
uint8_t i2[1] = {0};
uint8_t cqi = 0;
int32_t precoded_sinr_dB = 0;
@@ -918,6 +1369,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
csi_info->csi_im_meas_computed ? csi_info->interference_plus_noise_power : noise_power,
rank_indicator,
log2_re,
&i1,
i2,
&precoded_sinr_dB);
@@ -926,17 +1378,58 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
nr_csi_rs_cqi_estimation(precoded_sinr_dB, &cqi);
}
const char *muting_telemetry = getenv("OAI_CSI_MUTING_TELEMETRY");
if (muting_summary.samples > 0 && muting_telemetry != NULL && strcmp(muting_telemetry, "1") == 0) {
const nr_csi_rank_config_t *rank_config = get_csi_rank_config();
LOG_I(NR_PHY,
"[CSI_MUTING] frame=%d slot=%d ue=%d rx=%u ports=%u samples=%d "
"rank_policy=%s ri=%u cqi=%u sinr_db=%d "
"eig_rel_db=%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f "
"rank_capacity=%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f "
"prefix_rel_db=%.2f,%.2f,%.2f,%.2f\n",
proc->frame_rx,
proc->nr_slot_rx,
ue->Mod_id,
frame_parms->nb_antennas_rx,
mapping_parms.ports,
muting_summary.samples,
nr_csi_rank_policy_name(rank_config),
rank_indicator + 1,
cqi,
precoded_sinr_dB,
muting_summary.eig_rel_db[0],
muting_summary.eig_rel_db[1],
muting_summary.eig_rel_db[2],
muting_summary.eig_rel_db[3],
muting_summary.eig_rel_db[4],
muting_summary.eig_rel_db[5],
muting_summary.eig_rel_db[6],
muting_summary.eig_rel_db[7],
muting_summary.rank_capacity[0],
muting_summary.rank_capacity[1],
muting_summary.rank_capacity[2],
muting_summary.rank_capacity[3],
muting_summary.rank_capacity[4],
muting_summary.rank_capacity[5],
muting_summary.rank_capacity[6],
muting_summary.rank_capacity[7],
muting_summary.prefix_rel_db[0],
muting_summary.prefix_rel_db[1],
muting_summary.prefix_rel_db[2],
muting_summary.prefix_rel_db[3]);
}
switch (csirs_config_pdu->measurement_bitmap) {
case 1 :
LOG_I(NR_PHY, "[UE %d] RSRP = %i dBm\n", ue->Mod_id, rsrp_dBm);
break;
case 26 :
LOG_I(NR_PHY, "RI = %i i1 = %i.%i.%i, i2 = %i, SINR = %i dB, CQI = %i\n",
rank_indicator + 1, i1[0], i1[1], i1[2], i2[0], precoded_sinr_dB, cqi);
LOG_I(NR_PHY, "RI = %i i1 = %u, i2 = %i, SINR = %i dB, CQI = %i\n",
rank_indicator + 1, i1, i2[0], precoded_sinr_dB, cqi);
break;
case 27 :
LOG_I(NR_PHY, "RSRP = %i dBm, RI = %i i1 = %i.%i.%i, i2 = %i, SINR = %i dB, CQI = %i\n",
rsrp_dBm, rank_indicator + 1, i1[0], i1[1], i1[2], i2[0], precoded_sinr_dB, cqi);
LOG_I(NR_PHY, "RSRP = %i dBm, RI = %i i1 = %u, i2 = %i, SINR = %i dB, CQI = %i\n",
rsrp_dBm, rank_indicator + 1, i1, i2[0], precoded_sinr_dB, cqi);
break;
default :
AssertFatal(false, "Not supported measurement configuration\n");
@@ -953,7 +1446,7 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
.is_neighboring_cell = false,
.rsrp_dBm = rsrp_dBm,
.rank_indicator = rank_indicator,
.i1 = *i1,
.i1 = i1,
.i2 = *i2,
.cqi = cqi,
.radiolink_monitoring = RLM_no_monitoring, // TODO do be activated in case of RLM based on CSI-RS
@@ -961,6 +1454,6 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue,
nr_downlink_indication_t dl_indication;
fapi_nr_rx_indication_t rx_ind = {0};
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, NULL, NULL, 1, proc, (void *)&l1_measurements, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, 0, 0, NULL, 1, proc, (void *)&l1_measurements, NULL);
ue->if_inst->dl_indication(&dl_indication);
}
+12 -7
View File
@@ -290,14 +290,19 @@ static void nr_pdcch_channel_compensation(int arraySz,
static void nr_pdcch_detection_mrc(int nb_ant, int sz, c16_t rxdataF_comp[][sz])
{
c16_t *rx0 = rxdataF_comp[0];
// MRC on each re of rb
// input always aligned and accepting tail padding to process all actual samples
for (int a = 1; a < nb_ant; a++) {
c16_t *rx = rxdataF_comp[a];
for (int i = 0; i < sz; i += 4) {
*(simde__m128i *)(rx0 + i) = simde_mm_adds_epi16(simde_mm_srai_epi16(*(simde__m128i *)(rx0 + i), 1),
simde_mm_srai_epi16(*(simde__m128i *)(rx + i), 1));
/* Accumulate every matched-filter branch with equal weight. The former
* pairwise right shift repeatedly attenuated the existing accumulator, so
* with eight RX antennas branch 0 was divided by 128 and PDCCH decoding
* failed even at high SNR. Use a wide accumulator and saturate once. */
for (int i = 0; i < sz; i++) {
int32_t real = 0;
int32_t imag = 0;
for (int a = 0; a < nb_ant; a++) {
real += rxdataF_comp[a][i].r;
imag += rxdataF_comp[a][i].i;
}
rx0[i].r = min(max(real, INT16_MIN), INT16_MAX);
rx0[i].i = min(max(imag, INT16_MIN), INT16_MAX);
}
}
+185 -244
View File
@@ -43,178 +43,143 @@ void nr_dlsch_unscrambling(int16_t *llr, uint32_t size, uint8_t q, uint32_t Nid,
void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t *dlsch,
int16_t **dlsch_llr,
uint8_t **b,
int *G,
int nb_dlsch,
int cw_idx,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
int16_t *dlsch_llr,
uint8_t *b,
int number_rbs,
uint8_t *DLSCH_ids)
int G)
{
nrLDPC_TB_decoding_parameters_t TBs[nb_dlsch];
memset(TBs, 0, sizeof(TBs));
nrLDPC_TB_decoding_parameters_t TB_parameters = {0};
nrLDPC_slot_decoding_parameters_t slot_parameters = {
.frame = proc->frame_rx,
.slot = proc->nr_slot_rx,
.nb_TBs = nb_dlsch,
.nb_TBs = 1,
.threadPool = &get_nrUE_params()->Tpool,
.TBs = TBs
.TBs = &TB_parameters
};
int max_num_segments = 0;
uint8_t dmrs_Type = dlsch_config->dmrsConfigType;
int harq_pid = dlsch_config->harq_process_nbr;
AssertFatal(dmrs_Type == 0 || dmrs_Type == 1, "Illegal dmrs_type %d\n", dmrs_Type);
fapi_nr_dl_cw_info_t *cw_info = &dlsch->cw_info;
NR_DL_UE_HARQ_t *harq_process = &phy_vars_ue->dl_harq_processes[cw_idx][dlsch_config->harq_process_nbr];
LOG_D(PHY, "Round %d RV idx %d\n", harq_process->DLround, cw_info->rv);
uint8_t nb_re_dmrs;
if (dmrs_Type == NFAPI_NR_DMRS_TYPE1)
nb_re_dmrs = 6 * dlsch_config->n_dmrs_cdm_groups;
else
nb_re_dmrs = 4 * dlsch_config->n_dmrs_cdm_groups;
uint16_t dmrs_length = get_num_dmrs(dlsch_config->dlDmrsSymbPos);
for (uint8_t pdsch_id = 0; pdsch_id < nb_dlsch; pdsch_id++) {
uint8_t DLSCH_id = DLSCH_ids[pdsch_id];
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[DLSCH_id].dlsch_config;
uint8_t dmrs_Type = dlsch_config->dmrsConfigType;
int harq_pid = dlsch_config->harq_process_nbr;
NR_DL_UE_HARQ_t *harq_process = &phy_vars_ue->dl_harq_processes[DLSCH_id][harq_pid];
TB_parameters.harq_unique_pid = 2 * harq_pid + cw_idx;
TB_parameters.G = G;
TB_parameters.nb_rb = number_rbs;
TB_parameters.Qm = cw_info->qamModOrder;
TB_parameters.mcs = cw_info->mcs;
TB_parameters.nb_layers = cw_info->Nl;
TB_parameters.BG = cw_info->ldpcBaseGraph;
TB_parameters.A = cw_info->TBS;
TB_parameters.processedSegments = &harq_process->processedSegments;
AssertFatal(dmrs_Type == 0 || dmrs_Type == 1, "Illegal dmrs_type %d\n", dmrs_Type);
uint8_t nb_re_dmrs;
float Coderate = (float)cw_info->targetCodeRate / 10240.0f;
LOG_D(PHY, "Round %d RV idx %d\n", harq_process->DLround, dlsch->dlsch_config.rv);
if (dmrs_Type == NFAPI_NR_DMRS_TYPE1)
nb_re_dmrs = 6 * dlsch_config->n_dmrs_cdm_groups;
else
nb_re_dmrs = 4 * dlsch_config->n_dmrs_cdm_groups;
uint16_t dmrs_length = get_num_dmrs(dlsch_config->dlDmrsSymbPos);
if (!harq_process) {
LOG_E(PHY, "dlsch_decoding_slot.c: NULL harq_process pointer\n");
return;
}
nrLDPC_TB_decoding_parameters_t *TB_parameters = &TBs[pdsch_id];
/* Neither harq_pid nor DLSCH_id are unique in the instance
* but their combination is.
* Since DLSCH_id < 2
* then 2 * harq_pid + DLSCH_id is unique.
*/
TB_parameters->harq_unique_pid = 2 * harq_pid + DLSCH_id;
// ------------------------------------------------------------------
TB_parameters->G = G[DLSCH_id];
TB_parameters->nb_rb = number_rbs;
TB_parameters->Qm = dlsch_config->qamModOrder;
TB_parameters->mcs = dlsch_config->mcs;
TB_parameters->nb_layers = dlsch[DLSCH_id].Nl;
TB_parameters->BG = dlsch_config->ldpcBaseGraph;
TB_parameters->A = dlsch_config->TBS;
// ------------------------------------------------------------------
TB_parameters->processedSegments = &harq_process->processedSegments;
float Coderate = (float)dlsch->dlsch_config.targetCodeRate / 10240.0f;
LOG_D(
PHY,
LOG_D(PHY,
"%d.%d DLSCH %d Decoding, harq_pid %d TBS %d G %d nb_re_dmrs %d length dmrs %d mcs %d Nl %d nb_symb_sch %d nb_rb %d Qm %d "
"Coderate %f\n",
slot_parameters.frame,
slot_parameters.slot,
DLSCH_id,
cw_idx,
harq_pid,
dlsch_config->TBS,
TB_parameters->G,
cw_info->TBS,
G,
nb_re_dmrs,
dmrs_length,
TB_parameters->mcs,
TB_parameters->nb_layers,
TB_parameters.mcs,
TB_parameters.nb_layers,
dlsch_config->number_symbols,
TB_parameters->nb_rb,
TB_parameters->Qm,
TB_parameters.nb_rb,
TB_parameters.Qm,
Coderate);
if (harq_process->first_rx == 1) {
// This is a new packet, so compute quantities regarding segmentation
nr_segmentation(NULL,
NULL,
lenWithCrc(1, TB_parameters->A), // We give a max size in case of 1 segment
&TB_parameters->C,
&TB_parameters->K,
&TB_parameters->Z, // [hna] Z is Zc
&TB_parameters->F,
TB_parameters->BG);
harq_process->C = TB_parameters->C;
harq_process->K = TB_parameters->K;
harq_process->Z = TB_parameters->Z;
harq_process->F = TB_parameters->F;
if (harq_process->first_rx == 1) {
// This is a new packet, so compute quantities regarding segmentation
nr_segmentation(NULL,
NULL,
lenWithCrc(1, TB_parameters.A), // We give a max size in case of 1 segment
&TB_parameters.C,
&TB_parameters.K,
&TB_parameters.Z, // [hna] Z is Zc
&TB_parameters.F,
TB_parameters.BG);
harq_process->C = TB_parameters.C;
harq_process->K = TB_parameters.K;
harq_process->Z = TB_parameters.Z;
harq_process->F = TB_parameters.F;
if (harq_process->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * TB_parameters->nb_layers) {
LOG_E(PHY, "nr_segmentation.c: too many segments %d, A %d\n", harq_process->C, TB_parameters->A);
return;
}
if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD) && (!slot_parameters.frame % 100))
LOG_I(PHY, "K %d C %d Z %d nl %d \n", harq_process->K, harq_process->C, harq_process->Z, TB_parameters->nb_layers);
// clear HARQ buffer
for (int i = 0; i < harq_process->C; i++)
memset(harq_process->d[i], 0, 5 * 8448 * sizeof(int16_t));
} else {
// This is not a new packet, so retrieve previously computed quantities regarding segmentation
TB_parameters->C = harq_process->C;
TB_parameters->K = harq_process->K;
TB_parameters->Z = harq_process->Z;
TB_parameters->F = harq_process->F;
if (harq_process->C > MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * TB_parameters.nb_layers) {
LOG_E(PHY, "nr_segmentation.c: too many segments %d, A %d\n", harq_process->C, TB_parameters.A);
return;
}
max_num_segments = max(max_num_segments, TB_parameters->C);
if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD))
LOG_I(PHY, "Segmentation: C %d, K %d\n", harq_process->C, harq_process->K);
TB_parameters->max_ldpc_iterations = dlsch[DLSCH_id].max_ldpc_iterations;
TB_parameters->rv_index = dlsch_config->rv;
TB_parameters->tbslbrm = dlsch_config->tbslbrm;
TB_parameters->abort_decode = &harq_process->abort_decode;
set_abort(&harq_process->abort_decode, false);
if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD) && (!slot_parameters.frame % 100))
LOG_I(PHY, "K %d C %d Z %d nl %d \n", harq_process->K, harq_process->C, harq_process->Z, TB_parameters.nb_layers);
// clear HARQ buffer
for (int i = 0; i < harq_process->C; i++)
memset(harq_process->d[i], 0, 5 * 8448 * sizeof(int16_t));
} else {
// This is not a new packet, so retrieve previously computed quantities regarding segmentation
TB_parameters.C = harq_process->C;
TB_parameters.K = harq_process->K;
TB_parameters.Z = harq_process->Z;
TB_parameters.F = harq_process->F;
}
nrLDPC_segment_decoding_parameters_t segments[nb_dlsch][max_num_segments];
if (LOG_DEBUGFLAG(DEBUG_DLSCH_DECOD))
LOG_I(PHY, "Segmentation: C %d, K %d\n", harq_process->C, harq_process->K);
TB_parameters.max_ldpc_iterations = dlsch->max_ldpc_iterations;
TB_parameters.rv_index = cw_info->rv;
TB_parameters.tbslbrm = dlsch_config->tbslbrm;
TB_parameters.abort_decode = &harq_process->abort_decode;
set_abort(&harq_process->abort_decode, false);
nrLDPC_segment_decoding_parameters_t segments[TB_parameters.C];
memset(segments, 0, sizeof(segments));
bool d_to_be_cleared[nb_dlsch][max_num_segments];
bool d_to_be_cleared[TB_parameters.C];
memset(d_to_be_cleared, 0, sizeof(d_to_be_cleared));
for (uint8_t pdsch_id = 0; pdsch_id < nb_dlsch; pdsch_id++) {
uint8_t DLSCH_id = DLSCH_ids[pdsch_id];
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[DLSCH_id].dlsch_config;
int harq_pid = dlsch_config->harq_process_nbr;
NR_DL_UE_HARQ_t *harq_process = &phy_vars_ue->dl_harq_processes[DLSCH_id][harq_pid];
TB_parameters.segments = segments;
nrLDPC_TB_decoding_parameters_t *TB_parameters = &TBs[pdsch_id];
TB_parameters->segments = segments[pdsch_id];
uint32_t r_offset = 0;
for (int r = 0; r < TB_parameters.C; r++) {
if (harq_process->first_rx == 1)
d_to_be_cleared[r] = true;
else
d_to_be_cleared[r] = false;
nrLDPC_segment_decoding_parameters_t *segment_parameters = &TB_parameters.segments[r];
segment_parameters->E = nr_get_E(TB_parameters.G,
TB_parameters.C,
TB_parameters.Qm,
TB_parameters.nb_layers,
r);
segment_parameters->R = nr_get_R_ldpc_decoder(TB_parameters.rv_index,
segment_parameters->E,
TB_parameters.BG,
TB_parameters.Z,
&harq_process->llrLen,
harq_process->DLround);
segment_parameters->llr = dlsch_llr + r_offset;
segment_parameters->d = harq_process->d[r];
segment_parameters->d_to_be_cleared = &d_to_be_cleared[r];
segment_parameters->c = harq_process->c[r];
segment_parameters->decodeSuccess = false;
uint32_t r_offset = 0;
for (int r = 0; r < TB_parameters->C; r++) {
if (harq_process->first_rx == 1)
d_to_be_cleared[pdsch_id][r] = true;
else
d_to_be_cleared[pdsch_id][r] = false;
nrLDPC_segment_decoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
segment_parameters->E = nr_get_E(TB_parameters->G,
TB_parameters->C,
TB_parameters->Qm,
TB_parameters->nb_layers,
r);
segment_parameters->R = nr_get_R_ldpc_decoder(TB_parameters->rv_index,
segment_parameters->E,
TB_parameters->BG,
TB_parameters->Z,
&harq_process->llrLen,
harq_process->DLround);
segment_parameters->llr = dlsch_llr[DLSCH_id] + r_offset;
segment_parameters->d = harq_process->d[r];
segment_parameters->d_to_be_cleared = &d_to_be_cleared[pdsch_id][r];
segment_parameters->c = harq_process->c[r];
segment_parameters->decodeSuccess = false;
reset_meas(&segment_parameters->ts_deinterleave);
reset_meas(&segment_parameters->ts_rate_unmatch);
reset_meas(&segment_parameters->ts_ldpc_decode);
reset_meas(&segment_parameters->ts_deinterleave);
reset_meas(&segment_parameters->ts_rate_unmatch);
reset_meas(&segment_parameters->ts_ldpc_decode);
r_offset += segment_parameters->E;
}
r_offset += segment_parameters->E;
}
int ret_decoder = phy_vars_ue->nrLDPC_coding_interface.nrLDPC_coding_decoder(&slot_parameters);
@@ -224,114 +189,90 @@ void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue,
return;
}
// post decode
for (uint8_t pdsch_id = 0; pdsch_id < nb_dlsch; pdsch_id++) {
uint8_t DLSCH_id = DLSCH_ids[pdsch_id];
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[DLSCH_id].dlsch_config;
int harq_pid = dlsch_config->harq_process_nbr;
NR_DL_UE_HARQ_t *harq_process = &phy_vars_ue->dl_harq_processes[DLSCH_id][harq_pid];
nrLDPC_TB_decoding_parameters_t *TB_parameters = &TBs[pdsch_id];
uint32_t offset = 0;
for (int r = 0; r < TB_parameters->C; r++) {
nrLDPC_segment_decoding_parameters_t *segment_parameters = &TB_parameters->segments[r];
if (segment_parameters->decodeSuccess) {
memcpy(b[DLSCH_id] + offset,
harq_process->c[r],
(harq_process->K >> 3) - (harq_process->F >> 3) - ((harq_process->C > 1) ? 3 : 0));
} else {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[DLSCH_id].dlsch_config;
LOG_D(PHY, "frame=%d, slot=%d, first_rx=%d, rv_index=%d\n", proc->frame_rx, proc->nr_slot_rx, harq_process->first_rx, dlsch_config->rv);
LOG_D(PHY, "downlink segment error %d/%d\n", r, harq_process->C);
LOG_D(PHY, "DLSCH %d in error\n", DLSCH_id);
}
offset += (harq_process->K >> 3) - (harq_process->F >> 3) - ((harq_process->C > 1) ? 3 : 0);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_DEINTERLEAVING_STATS], &segment_parameters->ts_deinterleave);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_RATE_UNMATCHING_STATS], &segment_parameters->ts_rate_unmatch);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_LDPC_DECODING_STATS], &segment_parameters->ts_ldpc_decode);
}
kpiStructure.nb_total++;
kpiStructure.blockSize = dlsch_config->TBS;
kpiStructure.dl_mcs = dlsch_config->mcs;
kpiStructure.nofRBs = number_rbs;
harq_process->decodeResult = harq_process->processedSegments == harq_process->C;
if (harq_process->decodeResult && harq_process->C > 1) {
/* check global CRC */
int A = dlsch->dlsch_config.TBS;
// we have regrouped the transport block
if (!check_crc(b[DLSCH_id], lenWithCrc(1, A), crcType(1, A))) {
LOG_E(PHY,
" Frame %d.%d LDPC global CRC fails, but individual LDPC CRC succeeded. %d segs\n",
proc->frame_rx,
proc->nr_slot_rx,
harq_process->C);
harq_process->decodeResult = false;
}
}
if (harq_process->decodeResult) {
// We search only a reccuring OAI error that propagates all 0 packets with a 0 CRC, so we
const int sz = dlsch->dlsch_config.TBS / 8;
if (b[DLSCH_id][sz] == 0 && b[DLSCH_id][sz + 1] == 0) {
// do the check only if the 2 first bytes of the CRC are 0 (it can be CRC16 or CRC24)
int i = 0;
while (b[DLSCH_id][i] == 0 && i < sz)
i++;
if (i == sz) {
LOG_E(PHY,
"received all 0 pdu, consider it false reception, even if the TS 38.212 7.2.1 says only we should attach the "
"corresponding CRC, and nothing prevents to have a all 0 packet\n");
harq_process->decodeResult = false;
}
}
}
if (harq_process->decodeResult) {
LOG_D(PHY, "DLSCH received ok \n");
harq_process->status = NR_SCH_IDLE;
dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations - 1;
uint32_t offset = 0;
for (int r = 0; r < TB_parameters.C; r++) {
nrLDPC_segment_decoding_parameters_t *segment_parameters = &TB_parameters.segments[r];
if (segment_parameters->decodeSuccess) {
memcpy(b + offset, harq_process->c[r], (harq_process->K >> 3) - (harq_process->F >> 3) - ((harq_process->C > 1) ? 3 : 0));
} else {
LOG_D(PHY, "DLSCH received nok \n");
kpiStructure.nb_nack++;
dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations;
UEdumpScopeData(phy_vars_ue, proc->nr_slot_rx, proc->frame_rx, "DLSCH_NACK");
LOG_D(PHY, "frame=%d, slot=%d, first_rx=%d, rv_index=%d\n", proc->frame_rx, proc->nr_slot_rx, harq_process->first_rx, cw_info->rv);
LOG_D(PHY, "downlink segment error %d/%d\n", r, harq_process->C);
}
offset += (harq_process->K >> 3) - (harq_process->F >> 3) - ((harq_process->C > 1) ? 3 : 0);
uint8_t dmrs_Type = dlsch_config->dmrsConfigType;
uint8_t nb_re_dmrs;
if (dmrs_Type == NFAPI_NR_DMRS_TYPE1)
nb_re_dmrs = 6 * dlsch_config->n_dmrs_cdm_groups;
else
nb_re_dmrs = 4 * dlsch_config->n_dmrs_cdm_groups;
uint16_t dmrs_length = get_num_dmrs(dlsch_config->dlDmrsSymbPos);
float Coderate = (float)dlsch->dlsch_config.targetCodeRate / 10240.0f;
LOG_D(PHY,
"%d.%d DLSCH Decoded, harq_pid %d, round %d, result: %d TBS %d (%d) G %d nb_re_dmrs %d length dmrs %d mcs %d Nl %d "
"nb_symb_sch %d "
"nb_rb %d Qm %d Coderate %f\n",
proc->frame_rx,
proc->nr_slot_rx,
harq_pid,
harq_process->DLround,
harq_process->decodeResult,
dlsch->dlsch_config.TBS,
dlsch->dlsch_config.TBS / 8,
G[DLSCH_id],
nb_re_dmrs,
dmrs_length,
dlsch->dlsch_config.mcs,
dlsch->Nl,
dlsch_config->number_symbols,
number_rbs,
dlsch_config->qamModOrder,
Coderate);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_DEINTERLEAVING_STATS], &segment_parameters->ts_deinterleave);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_RATE_UNMATCHING_STATS], &segment_parameters->ts_rate_unmatch);
merge_meas(&phy_vars_ue->phy_cpu_stats.cpu_time_stats[DLSCH_LDPC_DECODING_STATS], &segment_parameters->ts_ldpc_decode);
}
kpiStructure.nb_total++;
kpiStructure.blockSize = cw_info->TBS;
kpiStructure.dl_mcs = cw_info->mcs;
kpiStructure.nofRBs = number_rbs;
harq_process->decodeResult = harq_process->processedSegments == harq_process->C;
if (harq_process->decodeResult && harq_process->C > 1) {
/* check global CRC */
// we have regrouped the transport block
if (!check_crc(b, lenWithCrc(1, cw_info->TBS), crcType(1, cw_info->TBS))) {
LOG_E(PHY,
" Frame %d.%d LDPC global CRC fails, but individual LDPC CRC succeeded. %d segs\n",
proc->frame_rx,
proc->nr_slot_rx,
harq_process->C);
harq_process->decodeResult = false;
}
}
if (harq_process->decodeResult) {
// We search only a reccuring OAI error that propagates all 0 packets with a 0 CRC, so we
const int sz = cw_info->TBS / 8;
if (b[sz] == 0 && b[sz + 1] == 0) {
// do the check only if the 2 first bytes of the CRC are 0 (it can be CRC16 or CRC24)
int i = 0;
while (b[i] == 0 && i < sz)
i++;
if (i == sz) {
LOG_E(PHY,
"received all 0 pdu, consider it false reception, even if the TS 38.212 7.2.1 says only we should attach the "
"corresponding CRC, and nothing prevents to have a all 0 packet\n");
harq_process->decodeResult = false;
}
}
}
if (harq_process->decodeResult) {
LOG_D(PHY, "DLSCH received ok \n");
harq_process->status = NR_SCH_IDLE;
dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations - 1;
} else {
LOG_D(PHY, "DLSCH received nok \n");
kpiStructure.nb_nack++;
dlsch->last_iteration_cnt = dlsch->max_ldpc_iterations;
UEdumpScopeData(phy_vars_ue, proc->nr_slot_rx, proc->frame_rx, "DLSCH_NACK");
}
LOG_D(PHY,
"%d.%d DLSCH Decoded, harq_pid %d, cw_idx %d round %d, result: %d TBS %d (%d) G %d nb_re_dmrs %d length dmrs %d mcs %d Nl %d "
"nb_symb_sch %d "
"nb_rb %d Qm %d Coderate %f\n",
proc->frame_rx,
proc->nr_slot_rx,
harq_pid,
cw_idx,
harq_process->DLround,
harq_process->decodeResult,
cw_info->TBS,
cw_info->TBS / 8,
G,
nb_re_dmrs,
dmrs_length,
cw_info->mcs,
cw_info->Nl,
dlsch_config->number_symbols,
number_rbs,
cw_info->qamModOrder,
Coderate);
}
@@ -18,6 +18,7 @@
#include "PHY/NR_REFSIG/dmrs_nr.h"
#include "common/utils/nr/nr_common.h"
#include <complex.h>
#include <pthread.h>
#include "openair1/PHY/TOOLS/phy_scope_interface.h"
#include "nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface.h"
@@ -852,16 +853,638 @@ static void nr_dlsch_mmse(uint32_t rx_size_symbol,
}
}
/* Joint linear MMSE detector for rank 5--8 PDSCH.
*
* The legacy fixed-point detector above constructs cofactors recursively and
* is deliberately limited to four layers. Two-codeword PDSCH still shares
* one physical MIMO channel, so detecting each four-layer codeword in
* isolation is incorrect. This path builds H^H H over all active layers for
* every data RE, inverts the Hermitian matrix with pivoted Gauss-Jordan
* elimination, and equalizes all layers together. The right half of the
* augmented matrix is retained to remove the MMSE bias before the existing
* QAM soft demappers are used.
*/
static int nr_dlsch_mmse_high_rank_reference(uint32_t rx_size_symbol,
uint8_t n_rx,
uint8_t nl,
const c16_t rxdataF_ext[][rx_size_symbol],
int32_t dl_ch_estimates_ext[][rx_size_symbol],
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
uint8_t symbol,
int first_re,
int length,
uint32_t noise_var)
{
AssertFatal(nl > 4 && nl <= NR_MAX_NB_LAYERS, "Invalid high-rank PDSCH layer count %u\n", nl);
if (n_rx < nl) {
LOG_E(NR_PHY, "Rank-%u PDSCH requires at least %u UE RX antennas, only %u configured\n", nl, nl, n_rx);
return -1;
}
const double q15 = 32768.0;
const double normalized_noise = (double)noise_var / (q15 * q15);
for (int re = first_re; re < first_re + length; re++) {
double complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS] = {{0}};
double complex rhs[NR_MAX_NB_LAYERS] = {0};
for (int aarx = 0; aarx < n_rx; aarx++) {
const c16_t y_fixed = rxdataF_ext[aarx][re];
const double complex y = (double)y_fixed.r + I * (double)y_fixed.i;
double complex h[NR_MAX_NB_LAYERS];
for (int l = 0; l < nl; l++) {
const c16_t *ch = (const c16_t *)dl_ch_estimates_ext[l * n_rx + aarx];
h[l] = (double)ch[re].r / q15 + I * ((double)ch[re].i / q15);
rhs[l] += conj(h[l]) * y;
}
for (int row = 0; row < nl; row++)
for (int col = 0; col < nl; col++)
gram[row][col] += conj(h[row]) * h[col];
}
double trace = 0.0;
for (int l = 0; l < nl; l++)
trace += creal(gram[l][l]);
const double average_gain = trace / nl;
/* Channel-estimation residuals include some uncancelled CDM energy before
* the two front-loaded DMRS symbols are averaged. Bound that estimate so
* it regularizes an ill-conditioned channel without suppressing a valid
* spatial stream. */
const double ridge_floor = fmax(average_gain * 1e-7, 1e-12);
const double ridge = fmax(ridge_floor, fmin(normalized_noise, average_gain * 0.25));
double complex aug[NR_MAX_NB_LAYERS][2 * NR_MAX_NB_LAYERS] = {{0}};
for (int row = 0; row < nl; row++) {
for (int col = 0; col < nl; col++)
aug[row][col] = gram[row][col] + (row == col ? ridge : 0.0);
aug[row][nl + row] = 1.0;
}
for (int col = 0; col < nl; col++) {
int pivot_row = col;
double pivot_abs = cabs(aug[col][col]);
for (int row = col + 1; row < nl; row++) {
const double candidate = cabs(aug[row][col]);
if (candidate > pivot_abs) {
pivot_abs = candidate;
pivot_row = row;
}
}
if (pivot_abs < 1e-15) {
LOG_W(NR_PHY, "Singular rank-%u PDSCH channel matrix at RE %d\n", nl, re);
return -1;
}
if (pivot_row != col) {
for (int j = 0; j < 2 * nl; j++) {
const double complex tmp = aug[col][j];
aug[col][j] = aug[pivot_row][j];
aug[pivot_row][j] = tmp;
}
}
const double complex pivot = aug[col][col];
for (int j = 0; j < 2 * nl; j++)
aug[col][j] /= pivot;
for (int row = 0; row < nl; row++) {
if (row == col)
continue;
const double complex factor = aug[row][col];
if (cabs(factor) == 0.0)
continue;
for (int j = 0; j < 2 * nl; j++)
aug[row][j] -= factor * aug[col][j];
}
}
for (int layer = 0; layer < nl; layer++) {
double complex detected = 0.0;
for (int col = 0; col < nl; col++)
detected += aug[layer][nl + col] * rhs[col];
/* diag((G + lambda I)^-1 G) = 1 - lambda*diag(inv),
* so divide out the linear-MMSE attenuation before applying fixed QAM
* decision boundaries. */
const double gain = fmax(0.05, 1.0 - ridge * creal(aug[layer][nl + layer]));
detected /= gain;
/* Channel-estimation normalization leaves the equalized constellation
* sqrt(2) above the native NR modulation-table scale. At 256-QAM that
* clips the outer levels in Q15 (15/sqrt(170) * 32768 * sqrt(2) is
* greater than INT16_MAX), making the two outermost levels
* indistinguishable. Restore the native scale before quantization. */
detected *= 0.7071067811865475244;
const long real = lround(creal(detected));
const long imag = lround(cimag(detected));
rxdataF_comp[symbol][layer][0][re].r = min(max(real, -32767), 32767);
rxdataF_comp[symbol][layer][0][re].i = min(max(imag, -32767), 32767);
}
}
return 0;
}
typedef enum {
NR_HIGH_RANK_CHOLESKY,
NR_HIGH_RANK_REFERENCE,
} nr_high_rank_detector_t;
typedef struct {
nr_high_rank_detector_t detector;
int re_group;
} nr_high_rank_detector_config_t;
static pthread_once_t high_rank_detector_once = PTHREAD_ONCE_INIT;
static nr_high_rank_detector_config_t high_rank_detector_config = {
.detector = NR_HIGH_RANK_CHOLESKY,
.re_group = 1,
};
static void configure_high_rank_detector(void)
{
const char *detector = getenv("OAI_HIGH_RANK_DETECTOR");
if (detector != NULL && strcmp(detector, "reference") == 0)
high_rank_detector_config.detector = NR_HIGH_RANK_REFERENCE;
else if (detector != NULL && strcmp(detector, "cholesky") != 0)
LOG_W(NR_PHY,
"Unknown OAI_HIGH_RANK_DETECTOR=%s; using cholesky\n",
detector);
const char *group = getenv("OAI_HIGH_RANK_GROUP");
if (group != NULL) {
const long parsed = strtol(group, NULL, 10);
if (parsed >= 1 && parsed <= NR_NB_SC_PER_RB)
high_rank_detector_config.re_group = parsed;
else
LOG_W(NR_PHY,
"OAI_HIGH_RANK_GROUP must be in [1,%d]; using 1\n",
NR_NB_SC_PER_RB);
}
LOG_I(NR_PHY,
"[HIGH_RANK_DETECTOR] mode=%s re_group=%d\n",
high_rank_detector_config.detector == NR_HIGH_RANK_REFERENCE ? "reference" : "cholesky",
high_rank_detector_config.re_group);
}
static const nr_high_rank_detector_config_t *get_high_rank_detector_config(void)
{
pthread_once(&high_rank_detector_once, configure_high_rank_detector);
return &high_rank_detector_config;
}
/* Factor A = L L^H. A is Hermitian positive definite after MMSE
* regularization, so Cholesky is both cheaper and more stable than forming an
* explicit inverse. */
static bool nr_high_rank_cholesky(uint8_t nl,
const float complex A[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS],
float complex L[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS])
{
memset(L, 0, sizeof(float complex) * NR_MAX_NB_LAYERS * NR_MAX_NB_LAYERS);
for (int row = 0; row < nl; row++) {
for (int col = 0; col <= row; col++) {
float complex value = A[row][col];
for (int k = 0; k < col; k++)
value -= L[row][k] * conjf(L[col][k]);
if (row == col) {
const float diagonal = crealf(value);
if (!isfinite(diagonal) || diagonal <= 1e-12f)
return false;
L[row][col] = sqrtf(diagonal);
} else {
L[row][col] = value / crealf(L[col][col]);
}
}
}
return true;
}
static void nr_high_rank_cholesky_solve(uint8_t nl,
const float complex L[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS],
const float complex b[NR_MAX_NB_LAYERS],
float complex x[NR_MAX_NB_LAYERS])
{
float complex z[NR_MAX_NB_LAYERS] = {0};
for (int row = 0; row < nl; row++) {
float complex value = b[row];
for (int col = 0; col < row; col++)
value -= L[row][col] * z[col];
z[row] = value / crealf(L[row][row]);
}
for (int row = nl - 1; row >= 0; row--) {
float complex value = z[row];
for (int col = row + 1; col < nl; col++)
value -= conjf(L[col][row]) * x[col];
x[row] = value / crealf(L[row][row]);
}
}
static void nr_high_rank_inverse_diagonal(uint8_t nl,
const float complex L[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS],
float diagonal[NR_MAX_NB_LAYERS])
{
for (int basis = 0; basis < nl; basis++) {
float complex column[NR_MAX_NB_LAYERS] = {0};
for (int row = 0; row < nl; row++) {
float complex value = row == basis ? 1.0f : 0.0f;
for (int col = 0; col < row; col++)
value -= L[row][col] * column[col];
column[row] = value / crealf(L[row][row]);
}
diagonal[basis] = 0.0f;
for (int row = basis; row < nl; row++)
diagonal[basis] += crealf(column[row] * conjf(column[row]));
}
}
/* A permutation-diagonal channel does not need an NxN matrix inversion. This
* is the common RF-simulator case (and also occurs with sufficiently isolated
* physical paths): every layer has one distinct, dominant receive antenna.
* Keep the decision data-driven so correlated channels automatically use the
* full joint detector below. */
static bool nr_high_rank_permutation_diagonal(uint8_t n_rx,
uint8_t nl,
const float complex h[MAX_ANT][NR_MAX_NB_LAYERS],
int dominant_rx[NR_MAX_NB_LAYERS])
{
bool rx_claimed[MAX_ANT] = {false};
for (int layer = 0; layer < nl; layer++) {
float total_power = 0.0f;
float dominant_power = 0.0f;
int dominant = -1;
for (int aarx = 0; aarx < n_rx; aarx++) {
const float power = crealf(h[aarx][layer] * conjf(h[aarx][layer]));
total_power += power;
if (power > dominant_power) {
dominant_power = power;
dominant = aarx;
}
}
if (dominant < 0 || dominant_power <= 1e-12f || rx_claimed[dominant])
return false;
/* Bound the aggregate ignored energy to -20 dB. PDSCH channel
* estimation leaves small fixed-point residue on otherwise diagonal
* RF-simulator/isolated channels; a -30 dB total bound was brittle and
* needlessly selected the full 8x8 solver. At or above one percent,
* retain the exact joint detector. */
const float leakage_power = fmaxf(0.0f, total_power - dominant_power);
if (leakage_power > dominant_power * 1e-2f)
return false;
dominant_rx[layer] = dominant;
rx_claimed[dominant] = true;
}
return true;
}
static void nr_high_rank_equalize_permutation_diagonal(
uint32_t rx_size_symbol,
uint8_t n_rx,
uint8_t nl,
const c16_t rxdataF_ext[][rx_size_symbol],
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
uint8_t symbol,
int first_re,
int group_length,
const float complex h[MAX_ANT][NR_MAX_NB_LAYERS],
const int dominant_rx[NR_MAX_NB_LAYERS])
{
float complex coefficient[NR_MAX_NB_LAYERS] = {0};
for (int layer = 0; layer < nl; layer++) {
const float complex dominant_h = h[dominant_rx[layer]][layer];
const float power = crealf(dominant_h * conjf(dominant_h));
coefficient[layer] = 0.7071067811865475244f * conjf(dominant_h) / power;
}
for (int re = first_re; re < first_re + group_length; re++) {
for (int layer = 0; layer < nl; layer++) {
const c16_t y = rxdataF_ext[dominant_rx[layer]][re];
const float complex detected = coefficient[layer] * ((float)y.r + I * (float)y.i);
const long real = lroundf(crealf(detected));
const long imag = lroundf(cimagf(detected));
rxdataF_comp[symbol][layer][0][re].r = min(max(real, -32767), 32767);
rxdataF_comp[symbol][layer][0][re].i = min(max(imag, -32767), 32767);
}
}
}
/* Unitary precoders produce dense effective channel matrices whose columns
* are nevertheless orthogonal. In that case H^H H is diagonal and the MMSE
* solution (after removing its scalar bias) is exactly a normalized matched
* filter; no Cholesky factorization is required. */
static bool nr_high_rank_orthogonal(uint8_t nl,
const float complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS])
{
for (int row = 0; row < nl; row++) {
const float row_power = crealf(gram[row][row]);
if (!isfinite(row_power) || row_power <= 1e-12f)
return false;
float normalized_interference = 0.0f;
for (int col = 0; col < nl; col++) {
if (col == row)
continue;
const float col_power = crealf(gram[col][col]);
if (!isfinite(col_power) || col_power <= 1e-12f)
return false;
const float cross_power = crealf(gram[row][col] * conjf(gram[row][col]));
normalized_interference += cross_power / (row_power * col_power);
}
/* The normalized matched-filter output sees this sum as residual
* inter-layer interference. A one-percent aggregate limit guarantees
* at least 20 dB spatial isolation; less isolated channels use the full
* Cholesky detector. */
if (!isfinite(normalized_interference) || normalized_interference > 1e-2f)
return false;
}
return true;
}
static void nr_high_rank_log_path_once(unsigned int path, uint8_t nl, int re_group)
{
static bool logged[3] = {false};
if (!__atomic_exchange_n(&logged[path], true, __ATOMIC_RELAXED)) {
static const char *const name[3] = {"permutation-diagonal", "orthogonal", "full-cholesky"};
LOG_I(NR_PHY,
"[HIGH_RANK_PATH] layers=%u re_group=%d detector=%s\n",
nl,
re_group,
name[path]);
}
}
static void nr_high_rank_equalize_orthogonal(
uint32_t rx_size_symbol,
uint8_t n_rx,
uint8_t nl,
const c16_t rxdataF_ext[][rx_size_symbol],
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
uint8_t symbol,
int first_re,
int group_length,
const float complex h[MAX_ANT][NR_MAX_NB_LAYERS],
const float complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS])
{
float complex coefficient[NR_MAX_NB_LAYERS][MAX_ANT] = {{0}};
for (int layer = 0; layer < nl; layer++) {
const float normalization = 0.7071067811865475244f / crealf(gram[layer][layer]);
for (int aarx = 0; aarx < n_rx; aarx++)
coefficient[layer][aarx] = normalization * conjf(h[aarx][layer]);
}
for (int re = first_re; re < first_re + group_length; re++) {
for (int layer = 0; layer < nl; layer++) {
float complex detected = 0.0f;
for (int aarx = 0; aarx < n_rx; aarx++) {
const c16_t y = rxdataF_ext[aarx][re];
detected += coefficient[layer][aarx] * ((float)y.r + I * (float)y.i);
}
const long real = lroundf(crealf(detected));
const long imag = lroundf(cimagf(detected));
rxdataF_comp[symbol][layer][0][re].r = min(max(real, -32767), 32767);
rxdataF_comp[symbol][layer][0][re].i = min(max(imag, -32767), 32767);
}
}
}
static int nr_dlsch_mmse_high_rank_cholesky(uint32_t rx_size_symbol,
uint8_t n_rx,
uint8_t nl,
const c16_t rxdataF_ext[][rx_size_symbol],
int32_t dl_ch_estimates_ext[][rx_size_symbol],
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
uint8_t symbol,
int length,
uint32_t noise_var,
int re_group)
{
AssertFatal(nl > 4 && nl <= NR_MAX_NB_LAYERS, "Invalid high-rank PDSCH layer count %u\n", nl);
if (n_rx < nl) {
LOG_E(NR_PHY, "Rank-%u PDSCH requires at least %u UE RX antennas, only %u configured\n", nl, nl, n_rx);
return -1;
}
const float inverse_q15 = 1.0f / 32768.0f;
const float normalized_noise = (float)noise_var * inverse_q15 * inverse_q15;
for (int first_re = 0; first_re < length; first_re += re_group) {
const int group_length = min(re_group, length - first_re);
const int representative_re = first_re + group_length / 2;
float complex h[MAX_ANT][NR_MAX_NB_LAYERS] = {{0}};
for (int aarx = 0; aarx < n_rx; aarx++) {
for (int layer = 0; layer < nl; layer++) {
const c16_t *ch = (const c16_t *)dl_ch_estimates_ext[layer * n_rx + aarx];
h[aarx][layer] = (float)ch[representative_re].r * inverse_q15
+ I * ((float)ch[representative_re].i * inverse_q15);
}
}
int dominant_rx[NR_MAX_NB_LAYERS] = {0};
if (nr_high_rank_permutation_diagonal(n_rx, nl, h, dominant_rx)) {
nr_high_rank_log_path_once(0, nl, re_group);
nr_high_rank_equalize_permutation_diagonal(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
rxdataF_comp,
symbol,
first_re,
group_length,
h,
dominant_rx);
continue;
}
/* The common identity/permutation-diagonal path needs no Gram matrix.
* Build H^H H only after that data-driven check, avoiding O(N^3) work
* while preserving the exact fallback for coupled channels. */
float complex gram[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS] = {{0}};
for (int row = 0; row < nl; row++)
for (int col = 0; col <= row; col++) {
for (int aarx = 0; aarx < n_rx; aarx++)
gram[row][col] += conjf(h[aarx][row]) * h[aarx][col];
gram[col][row] = conjf(gram[row][col]);
}
if (nr_high_rank_orthogonal(nl, gram)) {
nr_high_rank_log_path_once(1, nl, re_group);
nr_high_rank_equalize_orthogonal(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
rxdataF_comp,
symbol,
first_re,
group_length,
h,
gram);
continue;
}
nr_high_rank_log_path_once(2, nl, re_group);
float trace = 0.0f;
for (int layer = 0; layer < nl; layer++)
trace += crealf(gram[layer][layer]);
const float average_gain = trace / nl;
const float ridge_floor = fmaxf(average_gain * 1e-7f, 1e-12f);
const float ridge = fmaxf(ridge_floor, fminf(normalized_noise, average_gain * 0.25f));
for (int layer = 0; layer < nl; layer++)
gram[layer][layer] += ridge;
float complex L[NR_MAX_NB_LAYERS][NR_MAX_NB_LAYERS];
if (!nr_high_rank_cholesky(nl, gram, L)) {
LOG_W(NR_PHY,
"Rank-%u Cholesky factorization failed at RE %d; using reference detector\n",
nl,
representative_re);
if (nr_dlsch_mmse_high_rank_reference(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
rxdataF_comp,
symbol,
first_re,
group_length,
noise_var)
< 0)
return -1;
continue;
}
/* For the exact one-RE mode, solve A*x=H^H*y directly. This avoids
* constructing all n_rx columns of W just to multiply them by one y. */
if (group_length == 1) {
float complex rhs[NR_MAX_NB_LAYERS] = {0};
float complex detected[NR_MAX_NB_LAYERS] = {0};
for (int layer = 0; layer < nl; layer++)
for (int aarx = 0; aarx < n_rx; aarx++) {
const c16_t y = rxdataF_ext[aarx][first_re];
rhs[layer] += conjf(h[aarx][layer]) * ((float)y.r + I * (float)y.i);
}
nr_high_rank_cholesky_solve(nl, L, rhs, detected);
float inverse_diagonal[NR_MAX_NB_LAYERS] = {0};
nr_high_rank_inverse_diagonal(nl, L, inverse_diagonal);
for (int layer = 0; layer < nl; layer++) {
const float gain = fmaxf(0.05f, 1.0f - ridge * inverse_diagonal[layer]);
detected[layer] *= 0.7071067811865475244f / gain;
const long real = lroundf(crealf(detected[layer]));
const long imag = lroundf(cimagf(detected[layer]));
rxdataF_comp[symbol][layer][0][first_re].r = min(max(real, -32767), 32767);
rxdataF_comp[symbol][layer][0][first_re].i = min(max(imag, -32767), 32767);
}
continue;
}
/* W=(H^H H + lambda I)^-1 H^H. Reusing W across a configurable
* frequency group is an optional approximation; group=1 is exact. */
float complex weights[NR_MAX_NB_LAYERS][MAX_ANT] = {{0}};
for (int aarx = 0; aarx < n_rx; aarx++) {
float complex b[NR_MAX_NB_LAYERS] = {0};
float complex x[NR_MAX_NB_LAYERS] = {0};
for (int layer = 0; layer < nl; layer++)
b[layer] = conjf(h[aarx][layer]);
nr_high_rank_cholesky_solve(nl, L, b, x);
for (int layer = 0; layer < nl; layer++)
weights[layer][aarx] = x[layer];
}
float gain[NR_MAX_NB_LAYERS] = {0};
for (int layer = 0; layer < nl; layer++) {
float complex diagonal = 0.0f;
for (int aarx = 0; aarx < n_rx; aarx++)
diagonal += weights[layer][aarx] * h[aarx][layer];
gain[layer] = fmaxf(0.05f, crealf(diagonal));
}
for (int re = first_re; re < first_re + group_length; re++) {
for (int layer = 0; layer < nl; layer++) {
float complex detected = 0.0f;
for (int aarx = 0; aarx < n_rx; aarx++) {
const c16_t y = rxdataF_ext[aarx][re];
detected += weights[layer][aarx] * ((float)y.r + I * (float)y.i);
}
detected *= 0.7071067811865475244f / gain[layer];
const long real = lroundf(crealf(detected));
const long imag = lroundf(cimagf(detected));
rxdataF_comp[symbol][layer][0][re].r = min(max(real, -32767), 32767);
rxdataF_comp[symbol][layer][0][re].i = min(max(imag, -32767), 32767);
}
}
}
return 0;
}
static int nr_dlsch_mmse_high_rank(uint32_t rx_size_symbol,
uint8_t n_rx,
uint8_t nl,
const c16_t rxdataF_ext[][rx_size_symbol],
int32_t dl_ch_estimates_ext[][rx_size_symbol],
c16_t rxdataF_comp[][nl][n_rx][rx_size_symbol],
uint8_t symbol,
int length,
uint32_t noise_var)
{
const nr_high_rank_detector_config_t *config = get_high_rank_detector_config();
if (config->detector == NR_HIGH_RANK_REFERENCE)
return nr_dlsch_mmse_high_rank_reference(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
rxdataF_comp,
symbol,
0,
length,
noise_var);
return nr_dlsch_mmse_high_rank_cholesky(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
rxdataF_comp,
symbol,
length,
noise_var,
config->re_group);
}
static void set_high_rank_qam_thresholds(uint8_t mod_order,
int length,
c16_t *mag,
c16_t *magb,
c16_t *magr)
{
/* The joint detector is rescaled to the native NR modulation-table domain
* before Q15 quantization. Those tables include the per-I/Q 1/sqrt(2)
* factor, unlike the legacy matched-filter output for which the QAM*_n
* constants are used directly. Scale the decision boundaries likewise. */
#define HIGH_RANK_QAM_BOUNDARY(value) (((int32_t)(value) * ONE_OVER_SQRT2_Q15 + (1 << 14)) >> 15)
int16_t a = 0;
int16_t b = 0;
int16_t c = 0;
if (mod_order == 4)
a = HIGH_RANK_QAM_BOUNDARY(QAM16_n1);
else if (mod_order == 6) {
a = HIGH_RANK_QAM_BOUNDARY(QAM64_n1);
b = HIGH_RANK_QAM_BOUNDARY(QAM64_n2);
} else if (mod_order == 8) {
a = HIGH_RANK_QAM_BOUNDARY(QAM256_n1);
b = HIGH_RANK_QAM_BOUNDARY(QAM256_n2);
c = HIGH_RANK_QAM_BOUNDARY(QAM256_n3);
}
#undef HIGH_RANK_QAM_BOUNDARY
for (int re = 0; re < length; re++) {
mag[re] = (c16_t){a, a};
magb[re] = (c16_t){b, b};
magr[re] = (c16_t){c, c};
}
}
static void nr_dlsch_layer_demapping(const uint8_t Nl,
const uint8_t mod_order,
const int llrLayerSize,
const int16_t llr_layers[NR_SYMBOLS_PER_SLOT][Nl][llrLayerSize],
const NR_UE_DLSCH_t *dlsch,
const fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
const uint32_t re_len[NR_SYMBOLS_PER_SLOT],
int16_t *llr)
{
const int s0 = dlsch->dlsch_config.start_symbol;
const int s1 = dlsch->dlsch_config.number_symbols;
const int s0 = dlsch_config->start_symbol;
const int s1 = dlsch_config->number_symbols;
int k = 0;
switch (Nl) {
@@ -875,6 +1498,10 @@ static void nr_dlsch_layer_demapping(const uint8_t Nl,
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
for (int i = s0; i < (s0 + s1); i++) {
int m = 0;
for (int j = 0; j < re_len[i]; j++) {
@@ -901,28 +1528,28 @@ static int nr_dlsch_llr(const NR_UE_DLSCH_t *dlsch,
const c16_t dl_ch_magb[rx_size_symbol],
const c16_t dl_ch_magr[rx_size_symbol],
const int nb_antennas_rx,
const c16_t rxdataF_comp[dlsch->Nl][nb_antennas_rx][rx_size_symbol],
const c16_t rxdataF_comp[dlsch->cw_info.Nl][nb_antennas_rx][rx_size_symbol],
const int llrSize,
int16_t layer_llr[dlsch->Nl][llrSize])
int16_t layer_llr[dlsch->cw_info.Nl][llrSize])
{
switch (dlsch->dlsch_config.qamModOrder) {
switch (dlsch->cw_info.qamModOrder) {
case 2 :
for (int l = 0; l < dlsch[0].Nl; l++)
for (int l = 0; l < dlsch->cw_info.Nl; l++)
nr_qpsk_llr(rxdataF_comp[l][0], layer_llr[l], len);
break;
case 4 :
for (int l = 0; l < dlsch[0].Nl; l++)
for (int l = 0; l < dlsch->cw_info.Nl; l++)
nr_16qam_llr(rxdataF_comp[l][0], dl_ch_mag, layer_llr[l], len);
break;
case 6 :
for(int l=0; l < dlsch[0].Nl; l++)
for(int l=0; l < dlsch->cw_info.Nl; l++)
nr_64qam_llr(rxdataF_comp[l][0], dl_ch_mag, dl_ch_magb, layer_llr[l], len);
break;
case 8:
for(int l=0; l < dlsch[0].Nl; l++)
for(int l=0; l < dlsch->cw_info.Nl; l++)
nr_256qam_llr(rxdataF_comp[l][0],
dl_ch_mag,
dl_ch_magb,
@@ -943,23 +1570,25 @@ static int nr_dlsch_llr(const NR_UE_DLSCH_t *dlsch,
/* Main Function */
int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t dlsch[2],
NR_UE_DLSCH_t *dlsch,
const freq_alloc_bitmap_t *freq_alloc,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
NR_DL_UE_HARQ_t *dlsch_harq,
unsigned char symbol,
bool first_symbol_flag,
unsigned char harq_pid,
uint32_t pdsch_est_size,
int32_t dl_ch_estimates[][pdsch_est_size],
int16_t *llr[2],
int16_t *llr,
uint32_t dl_valid_re[NR_SYMBOLS_PER_SLOT],
c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
int32_t *log2_maxh,
int rx_size_symbol,
int nbRx,
c16_t rxdataF_comp[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_mag[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magb[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magr[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t rxdataF_comp[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_mag[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magb[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magr[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t ptrs_phase_per_slot[][NR_SYMBOLS_PER_SLOT],
int32_t ptrs_re_per_slot[][NR_SYMBOLS_PER_SLOT],
int G,
@@ -967,7 +1596,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
pdsch_scope_req_t *scope_req)
{
NR_DL_FRAME_PARMS *fp = &ue->frame_parms;
const int nl = dlsch[0].Nl;
const int nl = dlsch->cw_info.Nl;
const int n_rx = fp->nb_antennas_rx;
const int matrixSz = n_rx * nl;
__attribute__((aligned(32))) int32_t dl_ch_estimates_ext[matrixSz][rx_size_symbol];
@@ -979,72 +1608,8 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
const int gNB_id = proc->gNB_id;
uint8_t slot = 0;
int32_t codeword_TB0 = -1;
int32_t codeword_TB1 = -1;
uint32_t nb_re_pdsch = -1;
NR_DL_UE_HARQ_t *dlsch0_harq, *dlsch1_harq = NULL;
dlsch0_harq = &ue->dl_harq_processes[0][harq_pid];
if (NR_MAX_NB_LAYERS>4)
dlsch1_harq = &ue->dl_harq_processes[1][harq_pid];
if (dlsch0_harq && dlsch1_harq){
LOG_D(PHY,
"AbsSubframe %d.%d / Sym %d harq_pid %d, harq status %d.%d \n",
frame,
nr_slot_rx,
symbol,
harq_pid,
dlsch0_harq->status,
dlsch1_harq->status);
if ((dlsch0_harq->status == NR_ACTIVE) && (dlsch1_harq->status == NR_ACTIVE)){
codeword_TB0 = dlsch0_harq->codeword; // SV: where is this set? revisit for DL MIMO.
codeword_TB1 = dlsch1_harq->codeword;
dlsch0_harq = &ue->dl_harq_processes[codeword_TB0][harq_pid];
dlsch1_harq = &ue->dl_harq_processes[codeword_TB1][harq_pid];
DEBUG_HARQ("[DEMOD] I am assuming both TBs are active, in cw0 %d and cw1 %d \n", codeword_TB0, codeword_TB1);
} else if ((dlsch0_harq->status == NR_ACTIVE) && (dlsch1_harq->status != NR_ACTIVE) ) {
codeword_TB0 = dlsch0_harq->codeword;
dlsch0_harq = &ue->dl_harq_processes[codeword_TB0][harq_pid];
dlsch1_harq = NULL;
DEBUG_HARQ("[DEMOD] I am assuming only TB0 is active, in cw %d \n", codeword_TB0);
} else if ((dlsch0_harq->status != NR_ACTIVE) && (dlsch1_harq->status == NR_ACTIVE)){
codeword_TB1 = dlsch1_harq->codeword;
dlsch0_harq = NULL;
dlsch1_harq = &ue->dl_harq_processes[codeword_TB1][harq_pid];
DEBUG_HARQ("[DEMOD] I am assuming only TB1 is active, it is in cw %d\n", codeword_TB1);
LOG_E(PHY, "[DEMOD] slot %d TB0 not active and TB1 active case is not supported\n", nr_slot_rx);
return -1;
} else {
LOG_E(PHY, "[DEMOD] slot %d: no active DLSCH (2 layers case)\n", nr_slot_rx);
return (-1);
}
} else if (dlsch0_harq) {
if (dlsch0_harq->status == NR_ACTIVE) {
codeword_TB0 = dlsch0_harq->codeword;
dlsch0_harq = &ue->dl_harq_processes[0][harq_pid];
DEBUG_HARQ("[DEMOD] I am assuming only TB0 is active\n");
} else {
LOG_E(PHY, "[DEMOD] slot %d nr_rx_pdsch no active DLSCH (one layer case)\n", nr_slot_rx);
return (-1);
}
} else {
LOG_E(PHY, "[DEMOD] slot %d Inconsistent call to nr_rx_pdsch (no layer 0)\n", nr_slot_rx);
return -1;
}
DEBUG_HARQ("[DEMOD] cw for TB0 = %d, cw for TB1 = %d\n", codeword_TB0, codeword_TB1);
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[0].dlsch_config;
DevAssert(dlsch0_harq);
DevAssert(dlsch_harq);
if (gNB_id > 2) {
LOG_E(PHY, "Illegal gNB_id %d\n", gNB_id);
@@ -1089,7 +1654,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
nl,
fp,
csi_res_bitmap,
ue->chest_time);
nl > 4 ? 1 : ue->chest_time);
stop_meas_nr_ue_phy(ue, DLSCH_EXTRACT_RBS_STATS);
if (scope_req->copy_chanest_to_scope) {
size_t size = sizeof(c16_t) * nb_rb_pdsch * NR_NB_SC_PER_RB;
@@ -1216,22 +1781,42 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
//----------------------------------------------------------
// Disable correlation measurement for optimizing UE
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_COMPENSATION_STATS);
nr_dlsch_channel_compensation(rx_size_symbol,
nbRx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
dl_ch_mag[symbol],
dl_ch_magb[symbol],
dl_ch_magr[symbol],
rxdataF_comp,
NULL,
fp,
symbol,
nb_re_pdsch,
dlsch_config->qamModOrder,
nb_rb_pdsch,
*log2_maxh); // log2_maxh+I0_shift
if (nl > 4) {
if (nb_re_pdsch
&& nr_dlsch_mmse_high_rank(rx_size_symbol,
n_rx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
rxdataF_comp,
symbol,
nb_re_pdsch,
nvar)
< 0)
return -1;
set_high_rank_qam_thresholds(dlsch->cw_info.qamModOrder,
nb_re_pdsch,
dl_ch_mag[symbol][0][0],
dl_ch_magb[symbol][0][0],
dl_ch_magr[symbol][0][0]);
} else {
nr_dlsch_channel_compensation(rx_size_symbol,
nbRx,
nl,
rxdataF_ext,
dl_ch_estimates_ext,
dl_ch_mag[symbol],
dl_ch_magb[symbol],
dl_ch_magr[symbol],
rxdataF_comp,
NULL,
fp,
symbol,
nb_re_pdsch,
dlsch->cw_info.qamModOrder,
nb_rb_pdsch,
*log2_maxh); // log2_maxh+I0_shift
}
stop_meas_nr_ue_phy(ue, DLSCH_CHANNEL_COMPENSATION_STATS);
if (meas_enabled) {
LOG_D(PHY,
@@ -1265,7 +1850,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
}
start_meas_nr_ue_phy(ue, DLSCH_MRC_MMSE_STATS);
if (n_rx > 1) {
if (nl <= 4 && n_rx > 1) {
nr_dlsch_detection_mrc(rx_size_symbol,
nl,
n_rx,
@@ -1286,7 +1871,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
dl_ch_magb[symbol],
dl_ch_magr[symbol],
dl_ch_estimates_ext,
dlsch_config->qamModOrder,
dlsch->cw_info.qamModOrder,
*log2_maxh,
symbol,
nb_re_pdsch,
@@ -1312,14 +1897,14 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
int nbSymb = 0;
int pduBitmap = 0;
if(dlsch0_harq->status == NR_ACTIVE) {
if(dlsch_harq->status == NR_ACTIVE) {
startSymbIdx = dlsch_config->start_symbol;
nbSymb = dlsch_config->number_symbols;
pduBitmap = dlsch_config->pduBitmap;
}
/* Check for PTRS bitmap and process it respectively */
if((pduBitmap & 0x1) && (dlsch[0].rnti_type == TYPE_C_RNTI_)) {
if((pduBitmap & 0x1) && (dlsch->rnti_type == TYPE_C_RNTI_)) {
nr_pdsch_ptrs_processing(nbRx,
ptrs_phase_per_slot,
ptrs_re_per_slot,
@@ -1327,13 +1912,13 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
nl,
rxdataF_comp,
fp,
dlsch0_harq,
dlsch1_harq,
dlsch_config,
nr_slot_rx,
symbol,
freq_alloc->num_rbs,
dlsch[0].rnti,
dlsch);
dlsch->rnti,
&dlsch->ptrs_symbols,
&dlsch->ptrs_symbol_index);
dl_valid_re[symbol] -= ptrs_re_per_slot[0][symbol];
}
/* at last symbol in a slot calculate LLR's for whole slot */
@@ -1341,7 +1926,7 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
/* create LLR layer buffer */
int max_symb_re = 0;
GET_ARRAY_MAX(dl_valid_re, NR_SYMBOLS_PER_SLOT, max_symb_re);
const int llr_per_symbol = max_symb_re * dlsch->dlsch_config.qamModOrder;
const int llr_per_symbol = max_symb_re * dlsch->cw_info.qamModOrder;
__attribute__((aligned(32))) int16_t layer_llr[NR_SYMBOLS_PER_SLOT][nl][llr_per_symbol];
// Generate LLR from PTRS compensated signal
@@ -1360,14 +1945,9 @@ int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
}
stop_meas_nr_ue_phy(ue, DLSCH_LLR_STATS);
start_meas_nr_ue_phy(ue, DLSCH_LAYER_DEMAPPING);
nr_dlsch_layer_demapping(nl, dlsch_config->qamModOrder, llr_per_symbol, layer_llr, dlsch, dl_valid_re, llr[0]);
nr_dlsch_layer_demapping(nl, dlsch->cw_info.qamModOrder, llr_per_symbol, layer_llr, dlsch_config, dl_valid_re, llr);
stop_meas_nr_ue_phy(ue, DLSCH_LAYER_DEMAPPING);
/*
for (int i=0; i < 2; i++){
snprintf(filename, 50, "llr%d_symb_%d_nr_slot_rx_%d.m", i, symbol, nr_slot_rx);
write_output(filename,"llr", &llr[i][0], (NR_SYMBOLS_PER_SLOT*nb_rb_pdsch*NR_NB_SC_PER_RB*dlsch1_harq->Qm) - 4*(nb_rb_pdsch*4*dlsch1_harq->Qm), 1, 0);
}
*/
if (UEScopeHasTryLock(ue)) {
metadata mt = {.frame = proc->frame_rx, .slot = proc->nr_slot_rx };
int total_valid_res = 0;
@@ -446,7 +446,8 @@ nr_initial_sync_t nr_initial_sync(UE_nr_rxtx_proc_t *proc,
nr_fill_rx_indication(&rx_ind,
FAPI_NR_RX_PDU_TYPE_SSB,
ue,
NULL,
0,
0,
NULL,
number_pdus,
proc,
+2 -2
View File
@@ -404,7 +404,7 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue,
if (decoderState) {
if (ue) { // decoding failed in synced state
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, NULL, NULL, number_pdus, proc, NULL, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, 0, 0, NULL, number_pdus, proc, NULL, NULL);
if (ue->if_inst && ue->if_inst->dl_indication)
ue->if_inst->dl_indication(&dl_indication);
}
@@ -464,7 +464,7 @@ int nr_rx_pbch(PHY_VARS_NR_UE *ue,
if (ue) {
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, NULL, NULL, number_pdus, proc, (void *)result, NULL);
nr_fill_rx_indication(&rx_ind, FAPI_NR_RX_PDU_TYPE_SSB, ue, 0, 0, NULL, number_pdus, proc, (void *)result, NULL);
if (ue->if_inst && ue->if_inst->dl_indication)
ue->if_inst->dl_indication(&dl_indication);
@@ -60,19 +60,17 @@ int get_max_pdcch_monOcc(const NR_UE_PDCCH_CONFIG *phy_pdcch_config, int nb_symb
@param[in] dlsch_llr Pointers to LLR values computed by dlsch_demodulation
@param[in] b
@param[in] G array of Gs
@param[in] nb_dlsch number of active downlink shared channels
@param[in] DLSCH_ids array of active downlink shared channels
@returns 0 on success, 1 on unsuccessful decoding
*/
void nr_dlsch_decoding(PHY_VARS_NR_UE *phy_vars_ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t *dlsch,
short **dlsch_llr,
uint8_t **b,
int *G,
int nb_dlsch,
int cw_idx,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
int16_t *dlsch_llr,
uint8_t *b,
int number_rbs,
uint8_t *DLSCH_ids);
int G);
int nr_ulsch_pre_encoding(PHY_VARS_NR_UE *ue,
const NR_UE_ULSCH_t *ulsch,
@@ -278,23 +276,25 @@ void nr_sl_rf_card_config_freq(PHY_VARS_NR_UE *ue,
*/
int nr_rx_pdsch(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t dlsch[2],
NR_UE_DLSCH_t *dlsch,
const freq_alloc_bitmap_t *freq_alloc,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config,
NR_DL_UE_HARQ_t *dlsch_harq,
unsigned char symbol,
bool first_symbol_flag,
unsigned char harq_pid,
uint32_t pdsch_est_size,
int32_t dl_ch_estimates[][pdsch_est_size],
int16_t *llr[2],
int16_t *llr,
uint32_t dl_valid_re[NR_SYMBOLS_PER_SLOT],
c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
int32_t *log2_maxh,
int rx_size_symbol,
int nbRx,
c16_t rxdataF_comp[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_mag[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magb[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magr[][dlsch->Nl][nbRx][rx_size_symbol],
c16_t rxdataF_comp[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_mag[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magb[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t dl_ch_magr[][dlsch->cw_info.Nl][nbRx][rx_size_symbol],
c16_t ptrs_phase_per_slot[][NR_SYMBOLS_PER_SLOT],
int32_t ptrs_re_per_slot[][NR_SYMBOLS_PER_SLOT],
int G,
@@ -97,16 +97,13 @@ typedef struct {
} NR_DL_UE_HARQ_t;
typedef struct {
fapi_nr_dl_cw_info_t cw_info;
/// RNTI
uint16_t rnti;
/// RNTI type
uint8_t rnti_type;
/// Active flag for DLSCH demodulation
bool active;
/// Structure to hold dlsch config from MAC
fapi_nr_dl_config_dlsch_pdu_rel15_t dlsch_config;
/// Number of MIMO layers (streams)
uint8_t Nl;
/// Maximum number of LDPC iterations
uint8_t max_ldpc_iterations;
/// number of iterations used in last turbo decoding
+7 -4
View File
@@ -21,6 +21,7 @@
#include "common/utils/threadPool/task_ans.h"
#include "openair1/PHY/defs_RU.h"
#include "common/utils/ds/spsc_q.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
#define MAX_NUM_RU_PER_gNB 8
#define MAX_PUCCH0_NID 8
@@ -105,13 +106,13 @@ typedef struct {
/// freq allocation information
freq_alloc_bitmap_t freq_alloc;
/// pointer to pdu from MAC interface (this is "a" in 36.212)
uint8_t *pdu;
uint8_t *pdu[NR_MAX_NB_CODEWORDS];
/// Pointer to the payload
uint8_t *b;
uint8_t *b[NR_MAX_NB_CODEWORDS];
/// Pointers to transport block segments
uint8_t **c;
uint8_t **c[NR_MAX_NB_CODEWORDS];
/// Interleaver outputs
uint8_t *f;
uint8_t *f[NR_MAX_NB_CODEWORDS];
/// REs unavailable for DLSCH (overlapping with PTRS, CSIRS etc.)
uint32_t unav_res;
} NR_gNB_DLSCH_t;
@@ -430,6 +431,8 @@ typedef struct PHY_VARS_gNB_s {
bool enable_analog_das;
nr_antenna_state_t antenna_state;
time_stats_t l1_tx_proc;
time_stats_t l1_rx_proc;
+6 -5
View File
@@ -302,11 +302,11 @@ typedef struct PHY_VARS_NR_UE_s {
/// \brief Total gain of the RX chain (antenna to baseband I/Q) This is a function of rx_gain_mode (and the corresponding gain) and the rx_gain of the card.
uint32_t rx_total_gain_dB;
/// \brief Total gains with maximum RF gain stage (ExpressMIMO2/Lime)
uint32_t rx_gain_max[4];
uint32_t rx_gain_max[MAX_ANT];
/// \brief Total gains with medium RF gain stage (ExpressMIMO2/Lime)
uint32_t rx_gain_med[4];
uint32_t rx_gain_med[MAX_ANT];
/// \brief Total gains with bypassed RF gain stage (ExpressMIMO2/Lime)
uint32_t rx_gain_byp[4];
uint32_t rx_gain_byp[MAX_ANT];
/// \brief Current transmit power
int16_t tx_power_dBm[NR_MAX_SLOTS_PER_FRAME];
/// \brief Total number of REs in current transmission
@@ -521,8 +521,9 @@ typedef struct nr_phy_data_tx_s {
typedef struct nr_phy_data_s {
NR_UE_PDCCH_CONFIG phy_pdcch_config;
fapi_nr_dl_config_dlsch_pdu_rel15_t dlsch_config;
NR_UE_DLSCH_t dlsch[2];
int n_dlsch_codewords;
// Sidelink Rx action decided by MAC
sl_nr_rx_config_type_enum_t sl_rx_action;
NR_UE_CSI_RS csirs_vars;
@@ -535,7 +536,7 @@ enum stream_status_e { STREAM_STATUS_UNSYNC, STREAM_STATUS_SYNCING, STREAM_STATU
*/
typedef struct nr_rxtx_thread_data_s {
UE_nr_rxtx_proc_t proc;
PHY_VARS_NR_UE *UE;
PHY_VARS_NR_UE *UE;
int writeBlockSize;
nr_phy_data_t phy_data;
dynamic_barrier_t* next_barrier;
+76 -11
View File
@@ -19,8 +19,36 @@
#include "assertions.h"
#include <pthread.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
static pthread_once_t tx_chain_telemetry_once = PTHREAD_ONCE_INIT;
static bool tx_chain_telemetry = false;
static atomic_uint tx_chain_proven_mask = ATOMIC_VAR_INIT(0);
static void configure_tx_chain_telemetry(void)
{
const char *value = getenv("OAI_TX_CHAIN_TELEMETRY");
tx_chain_telemetry = value != NULL && strcmp(value, "1") == 0;
}
static bool tx_chain_telemetry_enabled(void)
{
pthread_once(&tx_chain_telemetry_once, configure_tx_chain_telemetry);
return tx_chain_telemetry;
}
static inline int count_nnz_c16(const c16_t *v, int n)
{
int nnz = 0;
for (int i = 0; i < n; i++)
nnz += (v[i].r != 0) || (v[i].i != 0);
return nnz;
}
// RU OFDM Modulator gNodeB
// OFDM modulation core routine, generates a first_symbol to first_symbol+num_symbols on a particular slot and TX antenna port
void nr_feptx0(RU_t *ru, int tti_tx, int first_symbol, int num_symbols, int aa)
@@ -165,12 +193,20 @@ void nr_feptx_prec(RU_t *ru, int frame_tx, int slot_tx)
int Ptx = cfg->carrier_config.num_tx_ant.value;
// If there is no digital beamforming we just need to copy the data to RU
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
const nr_antenna_state_t *ant_state = &gNB->antenna_state;
for (int b = 0; b < ru->num_beams_period; b++) {
for (int i = 0; i < Ptx; ++i) {
int tx_idx = i + b * ru->nb_tx;
memcpy((void *)ru->common.txdataF_BF[tx_idx],
(void *)gNB->common_vars.txdataF[b][i],
fp->samples_per_slot_wCP * sizeof(int32_t));
// Check antenna muting state for energy saving
if (!nr_is_tx_antenna_active(ant_state, i)) {
// Antenna is muted - zero out the buffer (transmission blanking)
memset((void *)ru->common.txdataF_BF[tx_idx], 0, fp->samples_per_slot_wCP * sizeof(int32_t));
LOG_D(PHY, "[ANTENNA_MUTE] TX antenna %d muted in nr_feptx_prec, blanking slot %d\n", i, slot_tx);
} else {
memcpy((void *)ru->common.txdataF_BF[tx_idx],
(void *)gNB->common_vars.txdataF[b][i],
fp->samples_per_slot_wCP * sizeof(int32_t));
}
}
}
} else {
@@ -205,19 +241,48 @@ void nr_feptx(void *arg)
// If there is no digital beamforming we just need to copy the data to RU
if (ru->config.dbt_config.num_dig_beams == 0 || ru->gNB_list[0]->common_vars.analog_bf) {
// FFT shift
const NR_DL_FRAME_PARMS *fp = &ru->gNB_list[0]->frame_parms;
fft_shift(ru->gNB_list[0]->common_vars.txdataF[bb][aa],
fp->ofdm_symbol_size,
fp->N_RB_DL,
(c16_t *)ru->common.txdataF_BF[tx_idx],
fp->ofdm_symbol_size,
startSymbol,
numSymbols);
const nr_antenna_state_t *ant_state = &ru->gNB_list[0]->antenna_state;
if (!nr_is_tx_antenna_active(ant_state, aa)) {
// Antenna is muted - zero out the FFT-shift output (transmission blanking)
memset((void *)&ru->common.txdataF_BF[tx_idx][startSymbol * fp->ofdm_symbol_size], 0,
numSymbols * fp->ofdm_symbol_size * sizeof(int32_t));
} else {
// FFT shift
fft_shift(ru->gNB_list[0]->common_vars.txdataF[bb][aa],
fp->ofdm_symbol_size,
fp->N_RB_DL,
(c16_t *)ru->common.txdataF_BF[tx_idx],
fp->ofdm_symbol_size,
startSymbol,
numSymbols);
}
} else {
AssertFatal(false, "This needs to be fixed by using appropriate beams from config\n");
}
// One-shot proof that an 8TX run exercises every physical frequency-domain
// chain. Check each generated symbol chunk until its chain first carries
// energy; normal runs leave this disabled to avoid counting overhead.
if (tx_chain_telemetry_enabled()) {
const int sample_count = numSymbols * fp->ofdm_symbol_size;
const int sample_offset = startSymbol * fp->ofdm_symbol_size;
const c16_t *dst = (const c16_t *)&ru->common.txdataF_BF[tx_idx][sample_offset];
const int nnz = count_nnz_c16(dst, sample_count);
if (nnz > 0) {
const unsigned bit = 1u << aa;
const unsigned seen = atomic_fetch_or_explicit(
&tx_chain_proven_mask, bit, memory_order_relaxed);
if ((seen & bit) == 0)
LOG_I(PHY,
"[TX_CHAIN_PROOF] frame=%d slot=%d ant=%d active=1 nnz=%d\n",
ru->proc.frame_tx,
slot,
aa,
nnz);
}
}
if (tx_idx == 0)
stop_meas(&ru->precoding_stats);
+10 -1
View File
@@ -270,7 +270,16 @@ void phy_procedures_gNB_TX(PHY_VARS_gNB *gNB,
// reuse dlsch variables, as there are multiple very large memory
// buffers
gNB->dlsch[num_pdsch].pdsch_pdu = &dl_tti_pdu->pdsch_pdu;
gNB->dlsch[num_pdsch].pdu = (uint8_t *)TX_req->pdu_list[tx_data_idx].TLVs[0].value.direct;
const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *pdsch = &dl_tti_pdu->pdsch_pdu.pdsch_pdu_rel15;
const nfapi_nr_pdu_t *tx_pdu = &TX_req->pdu_list[tx_data_idx];
AssertFatal(tx_pdu->num_TLV == pdsch->NrOfCodewords,
"%4d.%2d PDSCH has %u codewords but TX request has %u TLVs\n",
frame,
slot,
pdsch->NrOfCodewords,
tx_pdu->num_TLV);
for (int q = 0; q < NR_MAX_NB_CODEWORDS; q++)
gNB->dlsch[num_pdsch].pdu[q] = q < tx_pdu->num_TLV ? (uint8_t *)tx_pdu->TLVs[q].value.direct : NULL;
DevAssert(num_pdsch < gNB->max_nb_pdsch);
num_pdsch++;
} else {
+3 -2
View File
@@ -102,8 +102,9 @@ void nr_fill_dl_indication(nr_downlink_indication_t *dl_ind,
void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
uint8_t pdu_type,
PHY_VARS_NR_UE *ue,
NR_UE_DLSCH_t *dlsch0,
NR_UE_DLSCH_t *dlsch1,
int cw_idx,
int harq_pid,
NR_UE_DLSCH_t *dlsch,
uint16_t n_pdus,
const UE_nr_rxtx_proc_t *proc,
void *typeSpecific,
+35 -34
View File
@@ -21,48 +21,44 @@ extern PHY_VARS_NR_UE ***PHY_vars_UE_g;
const char *const dl_pdu_type[] = {"DCI", "DLSCH", "RA_DLSCH", "SI_DLSCH", "P_DLSCH", "CSI_RS", "CSI_IM", "TA"};
const char *const ul_pdu_type[] = {"PRACH", "PUCCH", "PUSCH", "SRS"};
static void configure_dlsch(NR_UE_DLSCH_t *dlsch0,
static void configure_dlsch(NR_UE_DLSCH_t *dlsch,
NR_DL_UE_HARQ_t *harq_list,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config_pdu,
NR_UE_MAC_INST_t *mac,
int cw_idx,
int rnti)
{
const uint8_t current_harq_pid = dlsch_config_pdu->harq_process_nbr;
dlsch0->active = true;
dlsch0->rnti = rnti;
dlsch->active = true;
dlsch->rnti = rnti;
LOG_D(PHY,"current_harq_pid = %d\n", current_harq_pid);
LOG_D(PHY,"Configure DLSCH for pid = %d\n", current_harq_pid);
NR_DL_UE_HARQ_t *dlsch0_harq = &harq_list[current_harq_pid];
NR_DL_UE_HARQ_t *dlsch_harq = &harq_list[current_harq_pid];
dlsch->cw_info = dlsch_config_pdu->cw_info[cw_idx];
//get nrOfLayers from DCI info
uint8_t Nl = 0;
for (int i = 0; i < 12; i++) { // max 12 ports
if ((dlsch_config_pdu->dmrs_ports >> i) & 0x01)
Nl += 1;
}
if (Nl == 0) {
LOG_E(PHY, "Invalid number of layers %d for DLSCH\n", Nl);
if (dlsch->cw_info.Nl == 0) {
LOG_E(PHY, "Invalid number of layers %d for DLSCH\n", dlsch->cw_info.Nl);
// setting NACK for this TB
dlsch0->active = false;
update_harq_status(mac, current_harq_pid, 0);
dlsch->active = false;
update_harq_status(mac, current_harq_pid, cw_idx, 0);
return;
}
dlsch0->Nl = Nl;
if (dlsch_config_pdu->new_data_indicator) {
dlsch0_harq->first_rx = true;
dlsch0_harq->DLround = 0;
if (dlsch->cw_info.new_data_indicator) {
dlsch_harq->first_rx = true;
dlsch_harq->DLround = 0;
} else {
dlsch0_harq->first_rx = false;
dlsch0_harq->DLround++;
dlsch_harq->first_rx = false;
dlsch_harq->DLround++;
}
downlink_harq_process(dlsch0_harq, current_harq_pid, dlsch_config_pdu->new_data_indicator, dlsch_config_pdu->rv, dlsch0->rnti_type);
if (dlsch0_harq->status != NR_ACTIVE) {
// dlsch0_harq->status not ACTIVE due to false retransmission
downlink_harq_process(dlsch_harq, current_harq_pid, dlsch->cw_info.new_data_indicator, dlsch->cw_info.rv, dlsch->rnti_type);
if (dlsch_harq->status != NR_ACTIVE) {
// dlsch_harq->status not ACTIVE due to false retransmission
// Reset the following flag to skip PDSCH procedures in that case and retrasmit harq status
dlsch0->active = false;
dlsch->active = false;
LOG_W(NR_MAC, "dlsch0_harq->status not ACTIVE due to false retransmission harq pid: %d\n", current_harq_pid);
update_harq_status(mac, current_harq_pid, dlsch0_harq->decodeResult);
update_harq_status(mac, current_harq_pid, cw_idx, dlsch_harq->decodeResult);
}
}
@@ -161,23 +157,28 @@ static void nr_ue_scheduled_response_dl(NR_UE_MAC_INST_t *mac,
case FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH: {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config_pdu = &pdu->dlsch_config_pdu.dlsch_config_rel15;
NR_UE_DLSCH_t *dlsch0 = phy_data->dlsch + 0;
phy_data->n_dlsch_codewords = dlsch_config_pdu->n_codewords;
phy_data->dlsch_config = *dlsch_config_pdu;
dlsch0->rnti_type = TYPE_RA_RNTI_;
dlsch0->dlsch_config = *dlsch_config_pdu;
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, pdu->dlsch_config_pdu.rnti);
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, 0, pdu->dlsch_config_pdu.rnti);
} break;
case FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH: {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config_pdu = &pdu->dlsch_config_pdu.dlsch_config_rel15;
NR_UE_DLSCH_t *dlsch0 = phy_data->dlsch + 0;
phy_data->n_dlsch_codewords = dlsch_config_pdu->n_codewords;
phy_data->dlsch_config = *dlsch_config_pdu;
dlsch0->rnti_type = TYPE_SI_RNTI_;
dlsch0->dlsch_config = *dlsch_config_pdu;
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, pdu->dlsch_config_pdu.rnti);
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, 0, pdu->dlsch_config_pdu.rnti);
} break;
case FAPI_NR_DL_CONFIG_TYPE_DLSCH: {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config_pdu = &pdu->dlsch_config_pdu.dlsch_config_rel15;
NR_UE_DLSCH_t *dlsch0 = &phy_data->dlsch[0];
dlsch0->rnti_type = TYPE_C_RNTI_;
dlsch0->dlsch_config = *dlsch_config_pdu;
configure_dlsch(dlsch0, phy->dl_harq_processes[0], dlsch_config_pdu, mac, pdu->dlsch_config_pdu.rnti);
phy_data->n_dlsch_codewords = dlsch_config_pdu->n_codewords;
phy_data->dlsch_config = *dlsch_config_pdu;
for (int c = 0; c < dlsch_config_pdu->n_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
dlsch->rnti_type = TYPE_C_RNTI_;
configure_dlsch(dlsch, phy->dl_harq_processes[c], dlsch_config_pdu, mac, c, pdu->dlsch_config_pdu.rnti);
}
} break;
case FAPI_NR_CONFIG_TA_COMMAND:
configure_ta_command(phy, &pdu->ta_command_pdu);
+1 -1
View File
@@ -29,6 +29,6 @@ void nr_ue_sl_phy_config_request(nr_sl_phy_config_t *phy_config);
\param synch_request including target_Nid_cell*/
void nr_ue_synch_request(nr_synch_request_t *synch_request);
void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, uint8_t ack_nack);
void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, int cw_idx, uint8_t ack_nack);
#endif
+304 -188
View File
@@ -55,6 +55,44 @@ static const unsigned int gain_table[31] = {100, 112, 126, 141, 158, 178,
359, 398, 447, 501, 562, 631, 708, 794, 891, 1000, 1122,
1258, 1412, 1585, 1778, 1995, 2239, 2512, 2818, 3162};
typedef struct {
fourDimArray_t *array;
size_t capacity;
} nr_ue_reusable_array_t;
/* Return a correctly-strided view without shrinking an existing allocation.
* Rank and RB allocation vary from one PDSCH to the next; exact-dimension
* resize logic otherwise oscillates between small control grants and large
* rank-8 data grants, reintroducing allocation/zeroing on every slot. */
static fourDimArray_t *nr_ue_reuse_array(nr_ue_reusable_array_t *storage,
size_t element_size,
int dim1,
int dim2,
int dim3,
int dim4)
{
DevAssert(dim1 > 0);
size_t elements = dim1;
if (dim2 > 0)
elements *= dim2;
if (dim3 > 0)
elements *= dim3;
if (dim4 > 0)
elements *= dim4;
const size_t required = sizeof(fourDimArray_t) + elements * element_size;
if (required > storage->capacity) {
free(storage->array);
storage->array = malloc16_clear(required);
AssertFatal(storage->array != NULL, "Could not allocate %zu-byte UE PDSCH workspace\n", required);
storage->capacity = required;
}
storage->array->dim1 = dim1;
storage->array->dim2 = dim2;
storage->array->dim3 = dim3;
storage->array->dim4 = dim4;
return storage->array;
}
static void nr_ue_prach_procedures(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, c16_t **txData);
void nr_fill_dl_indication(nr_downlink_indication_t *dl_ind,
@@ -94,31 +132,35 @@ static uint32_t get_ssb_arfcn(NR_DL_FRAME_PARMS *frame_parms)
uint64_t ssb_freq = frame_parms->dl_CarrierFreq - (band_size_hz / 2) + frame_parms->subcarrier_spacing * ssb_center_sc;
return to_nrarfcn(ssb_freq);
}
void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
uint8_t pdu_type,
PHY_VARS_NR_UE *ue,
NR_UE_DLSCH_t *dlsch0,
NR_UE_DLSCH_t *dlsch1,
int cw_idx,
int harq_pid,
NR_UE_DLSCH_t *dlsch,
uint16_t n_pdus,
const UE_nr_rxtx_proc_t *proc,
void *typeSpecific,
uint8_t *b)
{
if (n_pdus > 1){
LOG_E(PHY, "In %s: multiple number of DL PDUs not supported yet...\n", __FUNCTION__);
if (n_pdus > 1) {
LOG_E(PHY, "Multiple number of DL PDUs not supported yet...\n");
return;
}
fapi_nr_rx_indication_body_t *rx = rx_ind->rx_indication_body + n_pdus - 1;
switch (pdu_type){
case FAPI_NR_RX_PDU_TYPE_SIB:
case FAPI_NR_RX_PDU_TYPE_RAR:
case FAPI_NR_RX_PDU_TYPE_DLSCH:
if(dlsch0) {
NR_DL_UE_HARQ_t *dl_harq0 = &ue->dl_harq_processes[0][dlsch0->dlsch_config.harq_process_nbr];
rx->pdsch_pdu.harq_pid = dlsch0->dlsch_config.harq_process_nbr;
rx->pdsch_pdu.ack_nack = dl_harq0->decodeResult;
if(dlsch) {
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[cw_idx][harq_pid];
rx->pdsch_pdu.harq_pid = harq_pid;
rx->pdsch_pdu.cw_idx = cw_idx;
rx->pdsch_pdu.ack_nack = dl_harq->decodeResult;
rx->pdsch_pdu.pdu = b;
rx->pdsch_pdu.pdu_length = dlsch0->dlsch_config.TBS / 8;
if (dl_harq0->decodeResult) {
rx->pdsch_pdu.pdu_length = dlsch->cw_info.TBS / 8;
if (dl_harq->decodeResult) {
int t = WS_C_RNTI;
if (pdu_type == FAPI_NR_RX_PDU_TYPE_RAR)
t = WS_RA_RNTI;
@@ -130,16 +172,13 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
.pdu_buffer_size = rx->pdsch_pdu.pdu_length,
.ueid = 0,
.rntiType = t,
.rnti = dlsch0->rnti,
.rnti = dlsch->rnti,
.sysFrame = proc->frame_rx,
.subframe = proc->nr_slot_rx,
.harq_pid = dlsch0->dlsch_config.harq_process_nbr};
.harq_pid = harq_pid};
trace_pdu(&tmp);
}
}
if(dlsch1) {
AssertFatal(1==0,"Second codeword currently not supported\n");
}
break;
case FAPI_NR_RX_PDU_TYPE_SSB: {
if (typeSpecific) {
@@ -168,7 +207,6 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
rx->pdu_type = pdu_type;
rx_ind->number_pdus = n_pdus;
}
int get_tx_amp_prach(int power_dBm, int power_max_dBm, int N_RB_UL){
@@ -488,8 +526,10 @@ static int nr_ue_pbch_procedures(PHY_VARS_NR_UE *ue,
static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t dlsch[2],
int16_t *llr[2],
NR_UE_DLSCH_t *dlsch,
NR_DL_UE_HARQ_t *dlsch_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlschCfg,
int16_t *llr,
c16_t rxdataF[][ue->frame_parms.samples_per_slot_wCP],
int G,
freq_alloc_bitmap_t *freq_alloc)
@@ -497,16 +537,35 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
// We handle only one CW now
if (NR_MAX_NB_LAYERS > 4) {
LOG_E(NR_PHY, "Handling of more than one CW not implemented\n");
if (dlsch->cw_info.Nl < 1 || dlsch->cw_info.Nl > NR_MAX_NB_LAYERS) {
LOG_E(NR_PHY, "Invalid PDSCH layer count %u\n", dlsch->cw_info.Nl);
return -1;
}
NR_UE_DLSCH_t *dlsch0 = &dlsch[0];
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlschCfg = &dlsch0->dlsch_config;
if (dlsch->cw_info.Nl > ue->frame_parms.nb_antennas_rx) {
LOG_E(NR_PHY,
"Cannot detect rank-%u PDSCH with only %u UE RX antennas\n",
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx);
return -1;
}
if (dlsch->cw_info.Nl > 4 && (dlschCfg->pduBitmap & 0x1)) {
LOG_E(NR_PHY, "PTRS is not supported by the rank-5--8 joint PDSCH detector\n");
return -1;
}
int harq_pid = dlschCfg->harq_process_nbr;
// dlsch0_harq contains the previous transmissions data for this harq pid
NR_DL_UE_HARQ_t *dlsch0_harq = &ue->dl_harq_processes[0][harq_pid];
/* These workspaces are several tens of megabytes at rank 8. Allocating and
* zeroing them for every PDSCH made the UE miss its HARQ-feedback slot even
* when the cheap permutation-diagonal detector was selected. Each DL actor
* owns one persistent thread, so thread-local storage is race-free and
* preserves the legacy zero-initialized first use. Every active RE is
* overwritten by channel estimation/equalization before it is consumed. */
static _Thread_local nr_ue_reusable_array_t pdsch_dl_ch_estimates_storage;
static _Thread_local nr_ue_reusable_array_t rxdataF_comp_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_mag_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_magb_storage;
static _Thread_local nr_ue_reusable_array_t dl_ch_magr_storage;
LOG_D(PHY,
"[UE %d] frame_rx %d, nr_slot_rx %d, harq_pid %d (%d), BWP start %d, start RB %d, end RB %d, symbol_start %d, nb_symbols "
@@ -516,18 +575,24 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
frame_rx,
nr_slot_rx,
harq_pid,
dlsch0_harq->status,
dlsch_harq->status,
dlschCfg->BWPStart,
freq_alloc->first_rb,
freq_alloc->last_rb,
dlschCfg->start_symbol,
dlschCfg->number_symbols,
dlschCfg->dlDmrsSymbPos,
dlsch0->Nl);
dlsch->cw_info.Nl);
const uint32_t pdsch_est_size = ((ue->frame_parms.symbols_per_slot * ue->frame_parms.ofdm_symbol_size + 15) / 16) * 16;
fourDimArray_t *toFree = NULL;
allocCast2D(pdsch_dl_ch_estimates, int32_t, toFree, ue->frame_parms.nb_antennas_rx * dlsch0->Nl, pdsch_est_size, false);
fourDimArray_t *pdsch_dl_ch_estimates_view =
nr_ue_reuse_array(&pdsch_dl_ch_estimates_storage,
sizeof(int32_t),
ue->frame_parms.nb_antennas_rx * dlsch->cw_info.Nl,
pdsch_est_size,
0,
0);
cast2Darray(pdsch_dl_ch_estimates, int32_t, pdsch_dl_ch_estimates_view);
c16_t ptrs_phase_per_slot[ue->frame_parms.nb_antennas_rx][NR_SYMBOLS_PER_SLOT];
memset(ptrs_phase_per_slot, 0, sizeof(ptrs_phase_per_slot));
@@ -536,22 +601,20 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
memset(ptrs_re_per_slot, 0, sizeof(ptrs_re_per_slot));
const uint32_t rx_size_symbol = (freq_alloc->num_rbs * NR_NB_SC_PER_RB + 15) & ~15;
fourDimArray_t *toFree2 = NULL;
allocCast4D(rxdataF_comp,
c16_t,
toFree2,
ue->frame_parms.symbols_per_slot,
dlsch[0].Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol,
false);
fourDimArray_t *rxdataF_comp_view = nr_ue_reuse_array(&rxdataF_comp_storage,
sizeof(c16_t),
ue->frame_parms.symbols_per_slot,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(rxdataF_comp, c16_t, rxdataF_comp_view);
uint32_t nvar = 0;
start_meas_nr_ue_phy(ue, DLSCH_CHANNEL_ESTIMATION_STATS);
for (int m = dlschCfg->start_symbol; m < (dlschCfg->start_symbol + dlschCfg->number_symbols); m++) {
if (dlschCfg->dlDmrsSymbPos & (1 << m)) {
for (int nl = 0; nl < dlsch0->Nl; nl++) { // for MIMO Config: it shall loop over no_layers
for (int nl = 0; nl < dlsch->cw_info.Nl; nl++) { // for MIMO Config: it shall loop over no_layers
LOG_D(PHY, "PDSCH Channel estimation layer %d, slot %d, symbol %d\n", nl, nr_slot_rx, m);
uint32_t nvar_tmp = 0;
nr_pdsch_channel_estimation(ue,
@@ -580,16 +643,26 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
}
}
stop_meas_nr_ue_phy(ue, DLSCH_CHANNEL_ESTIMATION_STATS);
nvar /= (dlschCfg->number_symbols * dlsch0->Nl * ue->frame_parms.nb_antennas_rx);
nvar /= (dlschCfg->number_symbols * dlsch->cw_info.Nl * ue->frame_parms.nb_antennas_rx);
nr_ue_measurement_procedures(2, ue, proc, freq_alloc->num_rbs, pdsch_est_size, pdsch_dl_ch_estimates);
if (ue->chest_time == 1) { // averaging time domain channel estimates
nr_chest_time_domain_avg(&ue->frame_parms,
(int32_t **)pdsch_dl_ch_estimates,
dlschCfg->number_symbols,
dlschCfg->start_symbol,
dlschCfg->dlDmrsSymbPos,
freq_alloc->num_rbs);
if (ue->chest_time == 1 || dlsch->cw_info.Nl > 4) {
/* Rank 5--8 uses maxLength=len2 DMRS. Ports 1004--1007 share
* frequency-domain OCC with ports 1000--1003 and are separated by the
* time-domain OCC across the two front-loaded symbols, so averaging is
* mandatory. Apply it to every layer, not only the first RX block. */
for (int layer = 0; layer < dlsch->cw_info.Nl; layer++) {
int32_t *layer_ch_estimates[ue->frame_parms.nb_antennas_rx];
for (int aarx = 0; aarx < ue->frame_parms.nb_antennas_rx; aarx++)
layer_ch_estimates[aarx] = pdsch_dl_ch_estimates[layer * ue->frame_parms.nb_antennas_rx + aarx];
nr_chest_time_domain_avg(&ue->frame_parms,
layer_ch_estimates,
dlschCfg->number_symbols,
dlschCfg->start_symbol,
dlschCfg->dlDmrsSymbPos,
freq_alloc->num_rbs);
}
}
uint16_t first_symbol_with_data = dlschCfg->start_symbol;
@@ -625,33 +698,28 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
freq_alloc->num_rbs * NR_NB_SC_PER_RB * dlschCfg->number_symbols,
&mt);
}
fourDimArray_t *toFree3 = NULL;
allocCast4D(dl_ch_mag,
c16_t,
toFree3,
NR_SYMBOLS_PER_SLOT,
dlsch[0].Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol,
false);
fourDimArray_t *toFree4 = NULL;
allocCast4D(dl_ch_magb,
c16_t,
toFree4,
NR_SYMBOLS_PER_SLOT,
dlsch[0].Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol,
false);
fourDimArray_t *toFree5 = NULL;
allocCast4D(dl_ch_magr,
c16_t,
toFree5,
NR_SYMBOLS_PER_SLOT,
dlsch[0].Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol,
false);
fourDimArray_t *dl_ch_mag_view = nr_ue_reuse_array(&dl_ch_mag_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_mag, c16_t, dl_ch_mag_view);
fourDimArray_t *dl_ch_magb_view = nr_ue_reuse_array(&dl_ch_magb_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_magb, c16_t, dl_ch_magb_view);
fourDimArray_t *dl_ch_magr_view = nr_ue_reuse_array(&dl_ch_magr_storage,
sizeof(c16_t),
NR_SYMBOLS_PER_SLOT,
dlsch->cw_info.Nl,
ue->frame_parms.nb_antennas_rx,
rx_size_symbol);
cast4Darray(dl_ch_magr, c16_t, dl_ch_magr_view);
int pdsch_status = 0;
for (int m = dlschCfg->start_symbol; m < (dlschCfg->number_symbols + dlschCfg->start_symbol); m++) {
bool first_symbol_flag = false;
if (m == first_symbol_with_data)
@@ -663,6 +731,8 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
proc,
dlsch,
freq_alloc,
dlschCfg,
dlsch_harq,
m,
first_symbol_flag,
harq_pid,
@@ -684,13 +754,8 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
nvar,
&scope_req)
< 0) {
if (scope_req.copy_chanest_to_scope) {
UEunlockScopeData(ue, pdschChanEstimates);
}
if (scope_req.copy_rxdataF_to_scope) {
UEunlockScopeData(ue, pdschRxdataF);
}
return -1;
pdsch_status = -1;
break;
}
} // CRNTI active
stop_meas_nr_ue_phy(ue, RX_PDSCH_STATS);
@@ -700,12 +765,7 @@ static int nr_ue_pdsch_procedures(PHY_VARS_NR_UE *ue,
if (scope_req.copy_rxdataF_to_scope) {
UEunlockScopeData(ue, pdschRxdataF);
}
free(toFree);
free(toFree2);
free(toFree3);
free(toFree4);
free(toFree5);
return 0;
return pdsch_status;
}
static uint32_t compute_csi_rm_unav_res(fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config)
@@ -760,90 +820,63 @@ static uint32_t compute_csi_rm_unav_res(fapi_nr_dl_config_dlsch_pdu_rel15_t *dls
*/
static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
const UE_nr_rxtx_proc_t *proc,
NR_UE_DLSCH_t dlsch[2],
int16_t *llr[2],
int G[2],
freq_alloc_bitmap_t *freq_alloc)
NR_UE_DLSCH_t *dlsch,
int cw_idx,
int G,
freq_alloc_bitmap_t *freq_alloc,
fapi_nr_dl_config_dlsch_pdu_rel15_t *config,
int16_t *llr)
{
if (dlsch[0].active == false) {
if (dlsch->active == false) {
LOG_E(PHY, "DLSCH should be active when calling this function\n");
return;
}
int harq_pid = dlsch[0].dlsch_config.harq_process_nbr;
int harq_pid = config->harq_process_nbr;
int frame_rx = proc->frame_rx;
int nr_slot_rx = proc->nr_slot_rx;
NR_DL_UE_HARQ_t *dl_harq0 = &ue->dl_harq_processes[0][harq_pid];
NR_DL_UE_HARQ_t *dl_harq1 = &ue->dl_harq_processes[1][harq_pid];
nr_downlink_indication_t dl_indication;
fapi_nr_rx_indication_t rx_ind = {0};
uint16_t number_pdus = 1;
uint8_t is_cw0_active = dl_harq0->status;
uint8_t is_cw1_active = dl_harq1->status;
int nb_dlsch = 0;
nb_dlsch += (is_cw0_active == NR_ACTIVE) ? 1 : 0;
nb_dlsch += (is_cw1_active == NR_ACTIVE) ? 1 : 0;
LOG_D(PHY, "AbsSubframe %d.%d Start LDPC Decoder for CW0 [harq_pid %d] ? %d \n", frame_rx % 1024, nr_slot_rx, harq_pid, is_cw0_active);
LOG_D(PHY, "AbsSubframe %d.%d Start LDPC Decoder for CW1 [harq_pid %d] ? %d \n", frame_rx % 1024, nr_slot_rx, harq_pid, is_cw1_active);
LOG_D(PHY, "AbsSubframe %d.%d Start LDPC Decoder for CW%d [harq_pid %d]\n", frame_rx % 1024, nr_slot_rx, cw_idx, harq_pid);
// exit dlsch procedures as there are no active dlsch
if (is_cw0_active != ACTIVE && is_cw1_active != NR_ACTIVE) {
// don't wait anymore
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[cw_idx][harq_pid];
if (dl_harq->status != NR_ACTIVE) {
// The slot-level caller releases the RX/TX barrier after all codewords.
LOG_E(NR_PHY, "Internal error nr_ue_dlsch_procedure() called but no active cw on slot %d, harq %d\n", nr_slot_rx, harq_pid);
if (dlsch[0].dlsch_config.k1_feedback) {
const int ack_nack_slot_and_frame =
(proc->nr_slot_rx + dlsch[0].dlsch_config.k1_feedback) + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
return;
}
uint8_t DLSCH_ids[nb_dlsch];
int pdsch_id = 0;
uint8_t *p_b[2] = {NULL};
for (uint8_t DLSCH_id = 0; DLSCH_id < 2; DLSCH_id++) {
NR_DL_UE_HARQ_t *dl_harq = &ue->dl_harq_processes[DLSCH_id][harq_pid];
if (dl_harq->status != NR_ACTIVE)
continue;
DLSCH_ids[pdsch_id++] = DLSCH_id;
start_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
nr_dlsch_unscrambling(llr[DLSCH_id], G[DLSCH_id], 0, dlsch[DLSCH_id].dlsch_config.dlDataScramblingId, dlsch[DLSCH_id].rnti);
stop_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
p_b[DLSCH_id] = dl_harq->b;
}
start_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
nr_dlsch_unscrambling(llr, G, cw_idx, config->dlDataScramblingId, dlsch->rnti);
stop_meas_nr_ue_phy(ue, DLSCH_UNSCRAMBLING_STATS);
start_meas_nr_ue_phy(ue, DLSCH_DECODING_STATS);
nr_dlsch_decoding(ue, proc, dlsch, llr, p_b, G, nb_dlsch, freq_alloc->num_rbs, DLSCH_ids);
nr_dlsch_decoding(ue, proc, dlsch, cw_idx, config, llr, dl_harq->b, freq_alloc->num_rbs, G);
stop_meas_nr_ue_phy(ue, DLSCH_DECODING_STATS);
int ind_type = -1;
switch (dlsch[0].rnti_type) {
switch (dlsch->rnti_type) {
case TYPE_RA_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_RAR;
break;
case TYPE_SI_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_SIB;
break;
case TYPE_C_RNTI_:
ind_type = FAPI_NR_RX_PDU_TYPE_DLSCH;
break;
default:
AssertFatal(true, "Invalid DLSCH type %d\n", dlsch[0].rnti_type);
AssertFatal(false, "Invalid DLSCH type %d\n", dlsch->rnti_type);
break;
}
nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL);
nr_fill_rx_indication(&rx_ind, ind_type, ue, &dlsch[0], NULL, number_pdus, proc, NULL, p_b[0]);
nr_fill_rx_indication(&rx_ind, ind_type, ue, cw_idx, harq_pid, dlsch, number_pdus, proc, NULL, dl_harq->b);
LOG_D(PHY, "DL PDU length in bits: %d, in bytes: %d \n", dlsch[0].dlsch_config.TBS, dlsch[0].dlsch_config.TBS / 8);
LOG_D(PHY, "DL PDU length in bits: %d, in bytes: %d \n", dlsch->cw_info.TBS, dlsch->cw_info.TBS / 8);
if (cpumeas(CPUMEAS_GETSTATE)) {
LOG_D(PHY,
" --> Unscrambling %5.3f\n",
@@ -860,13 +893,6 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
ue->if_inst->dl_indication(&dl_indication);
}
// DLSCH decoding finished! don't wait anymore in Tx process, we know if we should answer ACK/NACK PUCCH
if ((dlsch[0].rnti_type == TYPE_C_RNTI_ || dlsch[0].rnti_type == TYPE_RA_RNTI_) && dlsch[0].dlsch_config.k1_feedback) {
const int ack_nack_slot_and_frame =
(proc->nr_slot_rx + dlsch[0].dlsch_config.k1_feedback) + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER * NR_MAX_NB_LAYERS; // number of segments to be allocated
if (freq_alloc->num_rbs != 273) {
a_segments = a_segments * freq_alloc->num_rbs;
@@ -874,11 +900,8 @@ static void nr_ue_dlsch_procedures(PHY_VARS_NR_UE *ue,
}
uint32_t dlsch_bytes = a_segments * 1056; // allocated bytes per segment
if (ue->phy_sim_dlsch_b && is_cw0_active == NR_ACTIVE)
memcpy(ue->phy_sim_dlsch_b, p_b[0], dlsch_bytes);
else if (ue->phy_sim_dlsch_b && is_cw1_active == NR_ACTIVE)
memcpy(ue->phy_sim_dlsch_b, p_b[1], dlsch_bytes);
if (ue->phy_sim_dlsch_b)
memcpy(ue->phy_sim_dlsch_b + cw_idx * dlsch_bytes, dl_harq->b, dlsch_bytes);
}
static bool check_meas_to_perform(PHY_VARS_NR_UE *ue, int nr_slot_rx)
@@ -1150,7 +1173,6 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
int nr_slot_rx = proc->nr_slot_rx;
int gNB_id = proc->gNB_id;
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[0];
// do procedures for C-RNTI
bool slot_fep_map[14] = {0};
@@ -1182,9 +1204,16 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
nr_ue_csi_rs_procedures(ue, proc, rxdataF, &phy_data->csirs_vars.csirs_config_pdu);
}
int G[2] = {0};
if (dlsch[0].active) {
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &dlsch[0].dlsch_config;
int16_t *llr[NR_MAX_NB_CODEWORDS] = {0};
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_config = &phy_data->dlsch_config;
AssertFatal(phy_data->n_dlsch_codewords >= 0 && phy_data->n_dlsch_codewords <= NR_MAX_NB_CODEWORDS,
"Invalid number of PDSCH codewords %d\n",
phy_data->n_dlsch_codewords);
bool has_active_dlsch = false;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++)
has_active_dlsch |= phy_data->dlsch[c].active;
if (has_active_dlsch) {
uint16_t nb_symb_sch = dlsch_config->number_symbols;
uint16_t start_symb_sch = dlsch_config->start_symbol;
@@ -1219,55 +1248,142 @@ void pdsch_processing(PHY_VARS_NR_UE *ue, const UE_nr_rxtx_proc_t *proc, nr_phy_
unav_res = n_ptrs * ptrsSymbPerSlot;
}
unav_res += compute_csi_rm_unav_res(dlsch_config);
G[0] = nr_get_G(freq_alloc.num_rbs,
dlsch_config->number_symbols,
nb_re_dmrs,
dmrs_len,
unav_res,
dlsch_config->qamModOrder,
dlsch[0].Nl);
const uint32_t rx_llr_buf_sz = ALIGNARRAYSIZE(G[0], 32); // each LLR is 2 bytes hence 64 byte aligned
const uint32_t nb_codewords = NR_MAX_NB_LAYERS > 4 ? 2 : 1;
int16_t* llr[2];
for (int i = 0; i < nb_codewords; i++)
llr[i] = (int16_t *)malloc16_clear(rx_llr_buf_sz * sizeof(int16_t));
// it returns -1 in case of internal failure, or 0 in case of normal result
int ret_pdsch = nr_ue_pdsch_procedures(ue, proc, dlsch, llr, rxdataF, G[0], &freq_alloc);
TracyCPlot("pdsch mcs", dlsch->dlsch_config.mcs);
UEscopeCopy(ue, pdschLlr, llr[0], sizeof(int16_t), 1, G[0], 0);
LOG_D(PHY, "DLSCH data reception at nr_slot_rx: %d\n", nr_slot_rx);
start_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (ret_pdsch >= 0) {
nr_ue_dlsch_procedures(ue, proc, dlsch, llr, G, &freq_alloc);
int G[NR_MAX_NB_CODEWORDS] = {0};
uint32_t rx_llr_buf_sz[NR_MAX_NB_CODEWORDS] = {0};
static _Thread_local nr_ue_reusable_array_t llr_storage[NR_MAX_NB_CODEWORDS];
static _Thread_local nr_ue_reusable_array_t joint_llr_storage;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
G[c] = nr_get_G(freq_alloc.num_rbs,
dlsch_config->number_symbols,
nb_re_dmrs,
dmrs_len,
unav_res,
dlsch->cw_info.qamModOrder,
dlsch->cw_info.Nl);
rx_llr_buf_sz[c] = ALIGNARRAYSIZE(G[c], 32); // each LLR is 2 bytes hence 64 byte aligned
fourDimArray_t *llr_view =
nr_ue_reuse_array(&llr_storage[c], sizeof(int16_t), rx_llr_buf_sz[c], 0, 0, 0);
llr[c] = (int16_t *)llr_view->data;
}
else {
LOG_E(NR_PHY, "Demodulation impossible, internal error\n");
if (dlsch_config->k1_feedback) {
const int ack_nack_slot_and_frame =
proc->nr_slot_rx + dlsch_config->k1_feedback + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
int ret_pdsch = 0;
if (phy_data->n_dlsch_codewords == 2) {
NR_UE_DLSCH_t *cw0 = &phy_data->dlsch[0];
NR_UE_DLSCH_t *cw1 = &phy_data->dlsch[1];
const int total_layers = cw0->cw_info.Nl + cw1->cw_info.Nl;
if (total_layers <= 4 || total_layers > NR_MAX_NB_LAYERS) {
LOG_E(NR_PHY, "Two-codeword PDSCH has invalid aggregate rank %d\n", total_layers);
ret_pdsch = -1;
} else if (cw0->cw_info.qamModOrder != cw1->cw_info.qamModOrder) {
/* The joint detector is modulation-independent, but the current slot
* LLR layout uses one modulation order. The local gNB scheduler sends
* the same MCS on both TBs; reject unsupported external FAPI input
* explicitly rather than silently corrupting CW1. */
LOG_E(NR_PHY,
"Rank-%d PDSCH with different modulation orders per codeword (%u/%u) is not supported\n",
total_layers,
cw0->cw_info.qamModOrder,
cw1->cw_info.qamModOrder);
ret_pdsch = -1;
} else {
NR_UE_DLSCH_t joint_dlsch = *cw0;
joint_dlsch.active = true;
joint_dlsch.cw_info.Nl = total_layers;
const int joint_G = G[0] + G[1];
const uint32_t joint_buf_sz = ALIGNARRAYSIZE(joint_G, 32);
fourDimArray_t *joint_llr_view =
nr_ue_reuse_array(&joint_llr_storage, sizeof(int16_t), joint_buf_sz, 0, 0, 0);
int16_t *joint_llr = (int16_t *)joint_llr_view->data;
NR_DL_UE_HARQ_t joint_harq = ue->dl_harq_processes[0][dlsch_config->harq_process_nbr];
joint_harq.status = NR_ACTIVE;
ret_pdsch = nr_ue_pdsch_procedures(
ue, proc, &joint_dlsch, &joint_harq, dlsch_config, joint_llr, rxdataF, joint_G, &freq_alloc);
if (ret_pdsch >= 0) {
const int qm = cw0->cw_info.qamModOrder;
const int joint_stride = total_layers * qm;
AssertFatal(joint_G % joint_stride == 0,
"Rank-%d PDSCH G=%d is not divisible by the layer stride %d\n",
total_layers,
joint_G,
joint_stride);
int cw_offset[NR_MAX_NB_CODEWORDS] = {0};
for (int re = 0; re < joint_G / joint_stride; re++) {
const int16_t *joint_re = joint_llr + re * joint_stride;
const int cw0_bits = cw0->cw_info.Nl * qm;
const int cw1_bits = cw1->cw_info.Nl * qm;
memcpy(llr[0] + cw_offset[0], joint_re, cw0_bits * sizeof(*joint_re));
memcpy(llr[1] + cw_offset[1], joint_re + cw0_bits, cw1_bits * sizeof(*joint_re));
cw_offset[0] += cw0_bits;
cw_offset[1] += cw1_bits;
}
AssertFatal(cw_offset[0] == G[0] && cw_offset[1] == G[1],
"Dual-codeword LLR split mismatch: %d/%d expected %d/%d\n",
cw_offset[0],
cw_offset[1],
G[0],
G[1]);
}
}
LOG_W(NR_PHY, "nr_ue_pdsch_procedures failed in slot %d\n", proc->nr_slot_rx);
} else {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[0];
NR_DL_UE_HARQ_t *harq = &ue->dl_harq_processes[0][dlsch_config->harq_process_nbr];
ret_pdsch = nr_ue_pdsch_procedures(ue, proc, dlsch, harq, dlsch_config, llr[0], rxdataF, G[0], &freq_alloc);
}
stop_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (cpumeas(CPUMEAS_GETSTATE)) {
LOG_D(PHY, "[SFN %d] Slot0 Slot1: Dlsch Proc %5.2f\n",nr_slot_rx,ue->phy_cpu_stats.cpu_time_stats[DLSCH_PROCEDURES_STATS].p_time/(cpuf*1000.0));
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
TracyCPlot("pdsch mcs", dlsch->cw_info.mcs);
if (dlsch->active)
UEscopeCopy(ue, pdschLlr, llr[c], sizeof(int16_t), 1, G[c], 0);
LOG_D(PHY, "DLSCH CW%d data reception at nr_slot_rx: %d\n", c, nr_slot_rx);
start_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (ret_pdsch >= 0) {
if (dlsch->active)
nr_ue_dlsch_procedures(ue, proc, dlsch, c, G[c], &freq_alloc, dlsch_config, llr[c]);
} else if (dlsch->active) {
LOG_E(NR_PHY, "PDSCH demodulation failed for CW%d\n", c);
LOG_W(NR_PHY, "nr_ue_pdsch_procedures failed in slot %d\n", proc->nr_slot_rx);
}
stop_meas_nr_ue_phy(ue, DLSCH_PROCEDURES_STATS);
if (cpumeas(CPUMEAS_GETSTATE))
LOG_D(PHY,
"[SFN %d] Slot0 Slot1: Dlsch Proc %5.2f\n",
nr_slot_rx,
ue->phy_cpu_stats.cpu_time_stats[DLSCH_PROCEDURES_STATS].p_time / (cpuf * 1000.0));
}
/* One PDSCH occasion has one RX/TX synchronization point even when it
* carries two transport blocks. Joining this barrier in each codeword's
* decoder advances it twice: the second join waits for a TX participant
* in the next generation and deadlocks the UE after its first rank>4
* PDSCH. Publish both ACK/NACK results first, then release TX once. */
bool needs_ack_barrier = false;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
const NR_UE_DLSCH_t *dlsch = &phy_data->dlsch[c];
needs_ack_barrier |= dlsch->active
&& (dlsch->rnti_type == TYPE_C_RNTI_ || dlsch->rnti_type == TYPE_RA_RNTI_);
}
if (needs_ack_barrier && dlsch_config->k1_feedback) {
const int ack_nack_slot_and_frame =
proc->nr_slot_rx + dlsch_config->k1_feedback + proc->frame_rx * ue->frame_parms.slots_per_frame;
dynamic_barrier_join(&ue->process_slot_tx_barriers[ack_nack_slot_and_frame % NUM_PROCESS_SLOT_TX_BARRIERS]);
}
if (ue->phy_sim_rxdataF)
memcpy(ue->phy_sim_rxdataF + start_symb_sch * ue->frame_parms.ofdm_symbol_size * sizeof(c16_t),
&rxdataF[0][start_symb_sch * ue->frame_parms.ofdm_symbol_size],
sizeof(int32_t) * nb_symb_sch * ue->frame_parms.ofdm_symbol_size);
if (ue->phy_sim_pdsch_llr)
memcpy(ue->phy_sim_pdsch_llr, llr[0], sizeof(int16_t) * rx_llr_buf_sz);
if (ue->phy_sim_pdsch_llr) {
int16_t *sim_llr = ue->phy_sim_pdsch_llr;
size_t llr_offset = 0;
for (int c = 0; c < phy_data->n_dlsch_codewords; c++) {
memcpy(sim_llr + llr_offset, llr[c], sizeof(*sim_llr) * G[c]);
llr_offset += G[c];
}
}
for (int i=0; i<nb_codewords; i++)
free(llr[i]);
}
if (nr_slot_rx==9) {
+24 -27
View File
@@ -457,23 +457,25 @@ int main(int argc, char **argv)
NR_UE_DLSCH_t *dlsch0_ue = &dlsch_ue[0];
NR_DL_UE_HARQ_t *harq_process = &UE->dl_harq_processes[0][harq_pid];
harq_process->first_rx = 1;
dlsch0_ue->dlsch_config.mcs = Imcs;
dlsch0_ue->dlsch_config.mcs_table = mcs_table;
dlsch0_ue->Nl = Nl;
dlsch0_ue->dlsch_config.number_rbs = nb_rb;
dlsch0_ue->dlsch_config.qamModOrder = mod_order;
dlsch0_ue->dlsch_config.rv = rvidx;
dlsch0_ue->dlsch_config.targetCodeRate = rate;
dlsch0_ue->dlsch_config.TBS = TBS;
dlsch0_ue->dlsch_config.ldpcBaseGraph = get_BG(TBS, rate);
dlsch0_ue->dlsch_config.dmrsConfigType = NFAPI_NR_DMRS_TYPE1;
dlsch0_ue->dlsch_config.dlDmrsSymbPos = 4;
dlsch0_ue->dlsch_config.n_dmrs_cdm_groups = 1;
dlsch0_ue->dlsch_config.tbslbrm = Tbslbrm;
printf("harq process ue mcs = %d Qm = %d, symb %d\n", dlsch0_ue->dlsch_config.mcs, dlsch0_ue->dlsch_config.qamModOrder, nb_symb_sch);
fapi_nr_dl_config_dlsch_pdu_rel15_t dlsch_config;
dlsch_config.cw_info[0].mcs = Imcs;
dlsch_config.mcs_table = mcs_table;
dlsch_config.number_rbs = nb_rb;
dlsch_config.cw_info[0].Nl = Nl;
dlsch_config.cw_info[0].qamModOrder = mod_order;
dlsch_config.cw_info[0].rv = rvidx;
dlsch_config.cw_info[0].targetCodeRate = rate;
dlsch_config.cw_info[0].TBS = TBS;
dlsch_config.cw_info[0].ldpcBaseGraph = get_BG(TBS, rate);
dlsch_config.dmrsConfigType = NFAPI_NR_DMRS_TYPE1;
dlsch_config.dlDmrsSymbPos = 4;
dlsch_config.n_dmrs_cdm_groups = 1;
dlsch_config.tbslbrm = Tbslbrm;
dlsch0_ue->cw_info = dlsch_config.cw_info[0];
printf("harq process ue mcs = %d Qm = %d, symb %d\n", dlsch_config.cw_info[0].mcs, dlsch_config.cw_info[0].qamModOrder, nb_symb_sch);
uint8_t test_input[TBS / 8 + 4]; // + 3 for CRC + 1 additional byte, see nr_dlsch_encoding()
dlsch->pdu = test_input;
dlsch->pdu[0] = test_input;
//unsigned char test_input[TBS / 8] __attribute__ ((aligned(16)));
for (i = 0; i < TBS / 8; i++)
test_input[i] = (unsigned char) rand();
@@ -536,27 +538,23 @@ int main(int argc, char **argv)
exit(-1);
#endif
int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS_PER_LAYER*NR_MAX_NB_LAYERS; //number of segments to be allocated
int num_rb = dlsch0_ue->dlsch_config.number_rbs;
int a_segments = MAX_NUM_NR_DLSCH_SEGMENTS; // number of segments to be allocated
int num_rb = dlsch_config.number_rbs;
if (num_rb != 273) {
a_segments = a_segments*num_rb;
a_segments = (a_segments/273)+1;
}
uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment
__attribute__ ((aligned(32))) uint8_t b[dlsch_bytes];
uint8_t DLSCH_ids[1] = {0};
short *p_channel_output_fixed = channel_output_fixed;
uint8_t *p_b = b;
int available_bits_array[1] = { available_bits };
nr_dlsch_decoding(UE,
&proc,
dlsch0_ue,
&p_channel_output_fixed,
&p_b,
available_bits_array,
1,
0,
&dlsch_config,
channel_output_fixed,
b,
num_rb,
DLSCH_ids);
available_bits);
if (dlsch0_ue->last_iteration_cnt > dlsch0_ue->max_ldpc_iterations)
n_errors++;
@@ -634,4 +632,3 @@ int main(int argc, char **argv)
return (n_errors);
}
+112 -64
View File
@@ -240,14 +240,7 @@ void nr_dlsim_preprocessor(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch)
sched_pdsch.Qm = nr_get_Qm_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
sched_pdsch.R = nr_get_code_rate_dl(sched_pdsch.mcs, current_BWP->mcsTableIdx);
sched_pdsch.tb_size = nr_compute_tbs(sched_pdsch.Qm,
sched_pdsch.R,
sched_pdsch.rbSize,
sched_pdsch.tda_info.nrOfSymbols,
sched_pdsch.dmrs_parms.N_PRB_DMRS * sched_pdsch.dmrs_parms.N_DMRS_SLOT,
0 /* N_PRB_oh, 0 for initialBWP */,
0 /* tb_scaling */,
sched_pdsch.nrOfLayers) >> 3;
update_sched_pdsch_codeword_tbs(&sched_pdsch);
/* the simulator assumes the HARQ PID is equal to the slot number */
sched_pdsch.dl_harq_pid = pp_pdsch->slot;
@@ -471,7 +464,7 @@ int main(int argc, char **argv)
case 'x':
g_nrOfLayers = atoi(optarg);
if ((g_nrOfLayers == 0) || (g_nrOfLayers > 4)) {
if ((g_nrOfLayers == 0) || (g_nrOfLayers > NR_MAX_NB_LAYERS)) {
printf("Unsupported nr Of Layers %d\n", g_nrOfLayers);
exit(-1);
}
@@ -490,7 +483,7 @@ int main(int argc, char **argv)
case 'y':
n_tx=atoi(optarg);
if ((n_tx==0) || (n_tx>4)) {//extend gNB to support n_tx = 4
if ((n_tx == 0) || (n_tx > NR_MAX_NB_LAYERS)) {
printf("Unsupported number of tx antennas %d\n",n_tx);
exit(-1);
}
@@ -500,7 +493,7 @@ int main(int argc, char **argv)
case 'z':
n_rx=atoi(optarg);
if ((n_rx==0) || (n_rx>4)) {//extend UE to support n_tx = 4
if ((n_rx == 0) || (n_rx > NR_MAX_NB_LAYERS)) {
printf("Unsupported number of rx antennas %d\n",n_rx);
exit(-1);
}
@@ -752,6 +745,7 @@ int main(int argc, char **argv)
.num_dlharq = 16,
.num_ulharq = 16,
.maxMIMO_layers = g_nrOfLayers,
.pdsch_dmrs_additional_position = 1,
.force_256qam_off = false,
.timer_config.sr_ProhibitTimer = 0,
.timer_config.sr_TransMax = 64,
@@ -940,8 +934,6 @@ int main(int argc, char **argv)
UE_mac->if_module = nr_ue_if_module_init(0);
unsigned int available_bits=0;
unsigned char *estimated_output_bit=NULL;
unsigned char *test_input_bit=NULL;
unsigned int errors_bit = 0;
initFloatingCoresTpool(dlsch_threads, &nrUE_params.Tpool, false, "UE-tpool");
@@ -987,7 +979,7 @@ int main(int argc, char **argv)
a_segments = (a_segments/273)+1;
}
uint32_t dlsch_bytes = a_segments*1056; // allocated bytes per segment
UE->phy_sim_dlsch_b = calloc(1, dlsch_bytes);
UE->phy_sim_dlsch_b = calloc(NR_MAX_NB_CODEWORDS, dlsch_bytes);
// csv file
if (filename_csv != NULL) {
@@ -1069,20 +1061,26 @@ int main(int argc, char **argv)
UE_proc.nr_slot_rx = slot;
UE_proc.gNB_id = 0;
NR_UE_DLSCH_t *dlsch0 = &phy_data.dlsch[0];
int harq_pid = slot;
NR_DL_UE_HARQ_t *UE_harq_process = &UE->dl_harq_processes[0][harq_pid];
UE_harq_process->decodeResult = false;
NR_DL_UE_HARQ_t *UE_harq_process[NR_MAX_NB_CODEWORDS];
round = 0;
UE_harq_process->DLround = round;
UE_harq_process->first_rx = 1;
for (int c = 0; c < NR_MAX_NB_CODEWORDS; c++) {
UE_harq_process[c] = &UE->dl_harq_processes[c][harq_pid];
UE_harq_process[c]->decodeResult = false;
UE_harq_process[c]->DLround = round;
UE_harq_process[c]->first_rx = 1;
}
int n_codewords = 0;
bool all_codewords_decoded = false;
while (round < num_rounds && !UE_harq_process->decodeResult && !stop) {
while (round < num_rounds && !all_codewords_decoded && !stop) {
reset_sched_response(Sched_INFO, frame, slot, 0, 0);
clear_nr_nfapi_information(RC.nrmac[0], 0, frame, slot);
UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi = !(trial&1);
/* The high-rank PDSCH carries two independently HARQ-managed TBs.
* Advance both NDIs between independent trials; leaving CW1's NDI
* unchanged makes the UE treat every later trial as a retransmission. */
UE_info->UE_sched_ctrl.harq_processes[harq_pid].ndi2 = !(trial&1);
UE_info->UE_sched_ctrl.harq_processes[harq_pid].round = round;
// nr_schedule_ue_spec() requires the mutex to be locked
@@ -1302,57 +1300,97 @@ int main(int argc, char **argv)
//---------------------- count errors ----------------------
//----------------------------------------------------------
if (dlsch0->last_iteration_cnt >= dlsch0->max_ldpc_iterations)
n_codewords = phy_data.n_dlsch_codewords;
AssertFatal(n_codewords >= 1 && n_codewords <= NR_MAX_NB_CODEWORDS,
"Invalid number of decoded PDSCH codewords %d\n",
n_codewords);
bool block_error = false;
bool any_active_codeword = false;
all_codewords_decoded = true;
for (int c = 0; c < n_codewords; c++) {
NR_UE_DLSCH_t *dlsch = &phy_data.dlsch[c];
any_active_codeword |= dlsch->active;
block_error |= dlsch->active && dlsch->last_iteration_cnt >= dlsch->max_ldpc_iterations;
all_codewords_decoded &= UE_harq_process[c]->decodeResult;
}
if (block_error)
n_errors[round]++;
if (dlsch0->active)
if (any_active_codeword)
round_trials[round]++;
int16_t *UE_llr = (int16_t*)UE->phy_sim_pdsch_llr;
TBS = dlsch0->dlsch_config.TBS;
uint16_t length_dmrs = get_num_dmrs(dlsch0->dlsch_config.dlDmrsSymbPos);
uint16_t nb_rb = dlsch0->dlsch_config.number_rbs;
uint8_t nb_re_dmrs = dlsch0->dlsch_config.dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ? 6*dlsch0->dlsch_config.n_dmrs_cdm_groups : 4*dlsch0->dlsch_config.n_dmrs_cdm_groups;
uint8_t mod_order = dlsch0->dlsch_config.qamModOrder;
uint8_t nb_symb_sch = dlsch0->dlsch_config.number_symbols;
TBS = 0;
uint16_t length_dmrs = get_num_dmrs(phy_data.dlsch_config.dlDmrsSymbPos);
uint16_t nb_rb = phy_data.dlsch_config.number_rbs;
uint8_t nb_re_dmrs = phy_data.dlsch_config.dmrsConfigType == NFAPI_NR_DMRS_TYPE1 ?
6 * phy_data.dlsch_config.n_dmrs_cdm_groups :
4 * phy_data.dlsch_config.n_dmrs_cdm_groups;
uint8_t nb_symb_sch = phy_data.dlsch_config.number_symbols;
uint32_t unav_res = ptrsSymbPerSlot * ptrsRePerSymb;
available_bits = nr_get_G(nb_rb, nb_symb_sch, nb_re_dmrs, length_dmrs, unav_res, mod_order, pdsch_pdu_rel15->nrOfLayers);
available_bits = 0;
for (int c = 0; c < n_codewords; c++) {
const fapi_nr_dl_cw_info_t *cw_info = &phy_data.dlsch_config.cw_info[c];
TBS += cw_info->TBS;
available_bits += nr_get_G(nb_rb,
nb_symb_sch,
nb_re_dmrs,
length_dmrs,
unav_res,
cw_info->qamModOrder,
cw_info->Nl);
}
if (pdu_bit_map & 0x1) {
if (trial == 0 && round == 0) {
printf("[DLSIM][PTRS] Available bits are: %5u, removed PTRS bits are: %5u \n", available_bits, (ptrsSymbPerSlot * ptrsRePerSymb * pdsch_pdu_rel15->nrOfLayers * mod_order));
printf("[DLSIM][PTRS] Available bits are: %5u, removed PTRS REs are: %5u \n",
available_bits,
ptrsSymbPerSlot * ptrsRePerSymb * pdsch_pdu_rel15->nrOfLayers);
}
}
for (i = 0; i < available_bits; i++) {
const uint8_t current_bit = (gNB_dlsch->f[i / 8] & (1 << (i & 7))) >> (i & 7);
if (((current_bit == 0) && (UE_llr[i] <= 0)) || ((current_bit == 1) && (UE_llr[i] >= 0))) {
if (errors_scrambling[round] == 0) {
LOG_D(PHY,"First bit in error in unscrambling = %d\n",i);
uint32_t llr_offset = 0;
for (int c = 0; c < n_codewords; c++) {
const fapi_nr_dl_cw_info_t *cw_info = &phy_data.dlsch_config.cw_info[c];
const uint32_t codeword_bits = nr_get_G(nb_rb,
nb_symb_sch,
nb_re_dmrs,
length_dmrs,
unav_res,
cw_info->qamModOrder,
cw_info->Nl);
for (i = 0; i < codeword_bits; i++) {
const uint8_t current_bit = (gNB_dlsch->f[c][i / 8] & (1 << (i & 7))) >> (i & 7);
if (((current_bit == 0) && (UE_llr[llr_offset + i] <= 0))
|| ((current_bit == 1) && (UE_llr[llr_offset + i] >= 0))) {
if (errors_scrambling[round] == 0)
LOG_D(PHY, "First bit in error in unscrambling = CW%d bit %d\n", c, i);
errors_scrambling[round]++;
}
errors_scrambling[round]++;
}
llr_offset += codeword_bits;
}
AssertFatal(llr_offset == available_bits,
"PDSCH LLR accounting mismatch: %u of %u bits\n",
llr_offset,
available_bits);
//printf("dlsim round %d ends\n",round);
round++;
} // round
if (test_input_bit == NULL) {
test_input_bit = (unsigned char *)malloc16(8 * pdsch_pdu_rel15->TBSize[0]);
estimated_output_bit = (unsigned char *)malloc16(8 * pdsch_pdu_rel15->TBSize[0]);
}
for (i = 0; i < TBS; i++) {
for (int c = 0; c < n_codewords; c++) {
const uint32_t codeword_tbs = phy_data.dlsch_config.cw_info[c].TBS;
const uint8_t *decoded = UE->phy_sim_dlsch_b + c * dlsch_bytes;
for (i = 0; i < codeword_tbs; i++) {
const uint8_t estimated_output_bit = (decoded[i / 8] & (1 << (i & 7))) >> (i & 7);
const uint8_t test_input_bit = (gNB_dlsch->b[c][i / 8] & (1 << (i & 7))) >> (i & 7);
estimated_output_bit[i] = (UE->phy_sim_dlsch_b[i/8] & (1 << (i & 7))) >> (i & 7);
test_input_bit[i] = (gNB_dlsch->b[i / 8] & (1 << (i & 7))) >> (i & 7); // Further correct for multiple segments
if (estimated_output_bit[i] != test_input_bit[i]) {
if(errors_bit == 0)
LOG_D(PHY,"First bit in error in decoding = %d\n",i);
errors_bit++;
}
if (estimated_output_bit != test_input_bit) {
if (errors_bit == 0)
LOG_D(PHY, "First bit in error in decoding = CW%d bit %d\n", c, i);
errors_bit++;
}
}
}
////////////////////////////////////////////////////////////
@@ -1362,7 +1400,7 @@ int main(int argc, char **argv)
printf("errors_bit = %u (trial %d)\n", errors_bit, trial);
}
roundStats += round;
if (UE_harq_process->decodeResult)
if (all_codewords_decoded)
effRate += ((double)TBS) / round;
} // noise trials
@@ -1389,7 +1427,11 @@ int main(int argc, char **argv)
printf("), Channel BER (%e", berStats[0]);
for (int r = 1; r < num_rounds; r++)
printf(",%e", berStats[r]);
printf(") Avg round %.2f, Eff Rate %.4f bits/slot, Eff Throughput %.2f, TBS %u bits/slot\n", roundStats, effRate, effRate / (8 * pdsch_pdu_rel15->TBSize[0]) * 100, 8 * pdsch_pdu_rel15->TBSize[0]);
printf(") Avg round %.2f, Eff Rate %.4f bits/slot, Eff Throughput %.2f, TBS %u bits/slot\n",
roundStats,
effRate,
effRate / TBS * 100,
TBS);
printf("*****************************************\n");
printf("\n");
// writing to csv file
@@ -1397,7 +1439,7 @@ int main(int argc, char **argv)
fprintf(csv_file,"%f,%d/%d,",SNR,n_false_positive,n_trials);
for (int r = 0; r < num_rounds; r++)
fprintf(csv_file,"%d/%d,%u/%u,%f,%e,",n_errors[r], round_trials[r], errors_scrambling[r], available_bits * round_trials[r],blerStats[r],berStats[r]);
fprintf(csv_file,"%.2f,%.4f,%.2f,%u\n", roundStats, effRate, effRate / (8 * pdsch_pdu_rel15->TBSize[0]) * 100, 8 * pdsch_pdu_rel15->TBSize[0]);
fprintf(csv_file, "%.2f,%.4f,%.2f,%u\n", roundStats, effRate, effRate / TBS * 100, TBS);
}
if (print_perf==1) {
printf("\ngNB TX function statistics (per %d us slot, NPRB %d, mcs %d, C %d, block %d)\n",
@@ -1405,7 +1447,7 @@ int main(int argc, char **argv)
g_rbSize,
g_mcsIndex,
UE->dl_harq_processes[0][slot].C,
8 * pdsch_pdu_rel15->TBSize[0]);
TBS);
printDistribution(&gNB->phy_proc_tx,table_tx,"PHY proc tx");
printStatIndent2(&gNB->dci_generation_stats, "DCI encoding time");
printStatIndent2(&gNB->dlsch_encoding_stats,"DLSCH encoding time");
@@ -1457,10 +1499,10 @@ int main(int argc, char **argv)
const int s = pdsch_pdu_rel15->StartSymbolIndex;
const int n = pdsch_pdu_rel15->NrOfSymbols;
for (int i = s; i < s + n; i++) {
const uint32_t dmrsBitMap = phy_data.dlsch[0].dlsch_config.dlDmrsSymbPos;
const uint32_t dmrsCfg = phy_data.dlsch[0].dlsch_config.dmrsConfigType;
const uint32_t nrb = phy_data.dlsch[0].dlsch_config.number_rbs;
const uint32_t ncdmg = phy_data.dlsch[0].dlsch_config.n_dmrs_cdm_groups;
const uint32_t dmrsBitMap = phy_data.dlsch_config.dlDmrsSymbPos;
const uint32_t dmrsCfg = phy_data.dlsch_config.dmrsConfigType;
const uint32_t nrb = phy_data.dlsch_config.number_rbs;
const uint32_t ncdmg = phy_data.dlsch_config.n_dmrs_cdm_groups;
const uint32_t numValidReSym = ((dmrsBitMap >> i) & 1)
? ((dmrsCfg == NFAPI_NR_DMRS_TYPE1) ? nrb * (12 - 6 * ncdmg) : nrb * (12 - 4 * ncdmg))
: (nrb * 12);
@@ -1488,10 +1530,11 @@ int main(int argc, char **argv)
LOG_M("chestF0.m", "chF0", UE->phy_sim_pdsch_dl_ch_estimates, frame_parms->ofdm_symbol_size * 14, dec, op_format);
op_format = (op_format & (~1));
LOG_M("rxF_llr.m", "rxFllr", UE->phy_sim_pdsch_llr, available_bits, dec, op_format);
ret = n_errors[0] == 0 && n_false_positive == 0 ? 0 : 1;
break;
}
if (effRate >= (eff_tp_check * 8 * pdsch_pdu_rel15->TBSize[0])) {
if (effRate >= eff_tp_check * TBS) {
printf("PDSCH test OK\n");
ret = 0;
break;
@@ -1528,8 +1571,6 @@ int main(int argc, char **argv)
free(r_re);
free(r_im);
free(txdata);
free(test_input_bit);
free(estimated_output_bit);
free(UE->phy_sim_rxdataF);
free(UE->phy_sim_pdsch_llr);
free(UE->phy_sim_pdsch_rxdataF_ext);
@@ -1666,6 +1707,13 @@ void update_dmrs_config(NR_CellGroupConfig_t *scg, int8_t* dmrs_arg)
NR_DMRS_DownlinkConfig_t *dmrs_config = (mapping_type == typeA) ? dmrs_MappingtypeA->choice.setup : dmrs_MappingtypeB->choice.setup;
/* Ranks 5--8 need the two-symbol Type-1 DMRS that provides ports 1000--1007.
* Keep that mandatory maxLength setting when -U changes only the additional
* DMRS position; otherwise the high-rank scheduler correctly rejects the
* inconsistent single-symbol configuration. */
if (g_nrOfLayers > 4)
asn1cCallocOne(dmrs_config->maxLength, NR_DMRS_DownlinkConfig__maxLength_len2);
if (add_pos != 2) { // pos0,pos1,pos3
if (dmrs_config->dmrs_AdditionalPosition == NULL) {
dmrs_config->dmrs_AdditionalPosition = calloc(1,sizeof(*dmrs_MappingtypeA->choice.setup->dmrs_AdditionalPosition));
+3 -2
View File
@@ -83,8 +83,9 @@ void nr_fill_dl_indication(nr_downlink_indication_t *dl_ind,
void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind,
uint8_t pdu_type,
PHY_VARS_NR_UE *ue,
NR_UE_DLSCH_t *dlsch0,
NR_UE_DLSCH_t *dlsch1,
int cw_idx,
int harq_pid,
NR_UE_DLSCH_t *dlsch,
uint16_t n_pdus,
const UE_nr_rxtx_proc_t *proc,
void *typeSpecific,
@@ -2277,6 +2277,14 @@ void init_channelmod(void) {
int load_channellist(uint8_t nb_tx, uint8_t nb_rx, double sampling_rate, uint64_t center_freq, double channel_bandwidth) {
// nakaolab Fix 9: ensure RNG is initialized exactly once, before any
// random_channel call (TDL/EPA/Rayleigh need gaussZiggurat). Static guard
// prevents multiple init races on the global tableNor* arrays.
static bool nakaolab_rng_done = false;
if (!nakaolab_rng_done) {
randominit();
nakaolab_rng_done = true;
}
paramdef_t achannel_params[] = CHANNELMOD_MODEL_PARAMS_DESC;
paramlist_def_t channel_list;
memset(&channel_list,0,sizeof(paramlist_def_t));
@@ -17,6 +17,11 @@ bool read_mac_sm(void* data)
//fill_mac_ind_data(mac);
mac->msg.tstamp = time_now_us();
// Export the physical denominator beside the per-UE TBS/PRB counters. This
// avoids estimating PRB opportunity from a rounded, differently-windowed
// KPM percentage in the rApp.
mac->msg.dl_prb_opportunities = RC.nrmac[mod_id]->mac_stats.dl.total_prb_aggregate;
mac->msg.dl_used_prbs = RC.nrmac[mod_id]->mac_stats.dl.used_prb_aggregate;
NR_UEs_t *UE_info = &RC.nrmac[mod_id]->UE_info;
size_t num_ues = 0;
@@ -33,6 +38,10 @@ bool read_mac_sm(void* data)
size_t i = 0; //TODO
UE_iterator(UE_info->connected_ue_list, UE) {
// Bound the write against the calloc'd buffer size: a UE attach between
// the count loop above and this iterator can grow connected_ue_list and
// overflow mac->msg.ue_stats (corrupts the heap → later free() abort).
if (i >= num_ues) break;
const NR_UE_sched_ctrl_t* sched_ctrl = &UE->UE_sched_ctrl;
mac_ue_stats_impl_t* rd = &mac->msg.ue_stats[i];
@@ -58,8 +67,11 @@ bool read_mac_sm(void* data)
rd->dl_aggr_retx_prb = UE->mac_stats.dl.total_rbs_retx;
rd->ul_aggr_retx_prb = UE->mac_stats.ul.total_rbs_retx;
rd->dl_aggr_bytes_sdus = UE->mac_stats.dl.lc_bytes[3];
rd->ul_aggr_bytes_sdus = UE->mac_stats.ul.lc_bytes[3];
// The service-model field is aggregate SDU bytes. Using LCID 3 silently
// drops payload whenever the DRB is assigned another logical channel and
// corrupts the MAC payload/TBS fill ratio used by capacity estimation.
rd->dl_aggr_bytes_sdus = UE->mac_stats.dl.total_sdu_bytes;
rd->ul_aggr_bytes_sdus = UE->mac_stats.ul.total_sdu_bytes;
rd->dl_aggr_sdus = UE->mac_stats.dl.num_mac_sdu;
rd->ul_aggr_sdus = UE->mac_stats.ul.num_mac_sdu;
@@ -68,6 +80,16 @@ bool read_mac_sm(void* data)
rd->pucch_snr = sched_ctrl->pucch_pc.avg_snr; //: float = -64;
rd->wb_cqi = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb;
rd->csi_valid = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report;
rd->dl_ri = rd->csi_valid ? sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1 : 0;
rd->dl_layers = UE->mac_stats.dl.last_layers;
rd->pmi_x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
rd->pmi_x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
rd->csi_frame = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_frame;
rd->csi_slot = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_slot;
rd->active_tx_chains = RC.nrmac[mod_id]->antenna_state != NULL
? nr_get_active_tx_antenna_count(RC.nrmac[mod_id]->antenna_state)
: 0;
rd->dl_mcs1 = sched_ctrl->dl_bler_stats.mcs;
rd->dl_bler = sched_ctrl->dl_bler_stats.bler;
rd->ul_mcs1 = sched_ctrl->ul_bler_stats.mcs;
@@ -110,4 +132,3 @@ sm_ag_if_ans_t write_ctrl_mac_sm(void const* data)
sm_ag_if_ans_t ans = {0};
return ans;
}
@@ -25,7 +25,7 @@
#endif
#if defined (NGRAN_GNB_CUUP)
void fill_pdcp_stats_for_all_rbs(pdcp_radio_bearer_stats_t* rb, const size_t num_ues, const uint64_t* ue_id_list)
void fill_pdcp_stats_for_all_rbs(pdcp_radio_bearer_stats_t* rb, const size_t num_ues, const uint64_t* ue_id_list, const size_t rb_capacity)
{
size_t j = 0;
for (size_t i = 0; i < num_ues; i++) {
@@ -37,6 +37,10 @@ void fill_pdcp_stats_for_all_rbs(pdcp_radio_bearer_stats_t* rb, const size_t num
if (!rc) continue;
// Bound the write against the original capacity: an RB activating
// between the count loop in read_pdcp_sm and this fill can grow the
// observed count and overflow rb[] (corrupts the heap).
if (j >= rb_capacity) return;
pdcp_radio_bearer_stats_t *rd = &rb[j++];
rd->txpdu_pkts = rb_pdcp.txpdu_pkts; /* aggregated number of tx packets */
@@ -114,7 +118,7 @@ bool read_pdcp_sm(void* data)
// Filling Radio Bearer Statistics
#if defined (NGRAN_GNB_CUUP)
fill_pdcp_stats_for_all_rbs(pdcp->msg.rb, num_ues, ue_id_list);
fill_pdcp_stats_for_all_rbs(pdcp->msg.rb, num_ues, ue_id_list, pdcp->msg.len);
#endif
return true;
}
@@ -83,6 +83,11 @@ bool read_rlc_sm(void* data)
const int srb_flag = 0;
const bool rc = nr_rlc_get_statistics(rnti, srb_flag, rb_id, &rb_rlc);
if(!rc) continue;
// Bound the write against the originally-calloc'd buffer size. A UE
// attach between num_act_rb() above and this iterator can grow the
// connected-UE list; without this check we'd overflow rlc->msg.rb
// and corrupt the heap (crash later in free_rlc_ind_msg()).
if (i >= act_rb) goto done_iter;
rlc_radio_bearer_stats_t* sm_rb = &rlc->msg.rb[i];
/* TX */
@@ -143,6 +148,7 @@ bool read_rlc_sm(void* data)
++i;
}
}
done_iter:
return act_rb > 0;
}
@@ -86,8 +86,42 @@ static meas_record_lst_t fill_DRB_PdcpSduVolumeUL(__attribute__((unused)) const
}
#if defined (NGRAN_GNB_DU)
static uldlcounter_t last_rlc_pdu_total_bytes[MAX_MOBILES_PER_GNB] = {0};
static uldlcounter_t last_total_prbs[MAX_MOBILES_PER_GNB] = {0};
typedef struct ue_uldlcounter {
const NR_UE_info_t *ue;
rnti_t rnti;
uldlcounter_t value;
bool dl_valid;
bool ul_valid;
} ue_uldlcounter_t;
static ue_uldlcounter_t last_rlc_pdu_total_bytes[MAX_MOBILES_PER_GNB] = {0};
static ue_uldlcounter_t last_total_prbs[MAX_MOBILES_PER_GNB] = {0};
/* The connected UE list is compacted when a UE leaves, so its iteration index
* is not a stable identity. Keep baselines by the MAC-assigned UID instead and
* reset them when that UID is reused for another UE. */
static ue_uldlcounter_t *get_ue_counter(ue_uldlcounter_t counters[MAX_MOBILES_PER_GNB],
const cudu_ue_info_pair_t ue_info)
{
AssertFatal(ue_info.ue != NULL, "KPM UE measurement requires MAC UE information\n");
const uid_t uid = ue_info.ue->uid;
AssertFatal(uid < MAX_MOBILES_PER_GNB, "KPM UE UID %u exceeds maximum %u\n", uid, MAX_MOBILES_PER_GNB);
ue_uldlcounter_t *counter = &counters[uid];
if (counter->ue != ue_info.ue || counter->rnti != ue_info.ue->rnti)
*counter = (ue_uldlcounter_t){.ue = ue_info.ue, .rnti = ue_info.ue->rnti};
return counter;
}
/* A new/reused UE counter and a reset/wrap have no valid preceding sample.
* Establish a fresh baseline and report zero for that single period. */
static uint32_t counter_delta(uint32_t current, uint32_t *previous, bool *valid)
{
const uint32_t delta = *valid && current >= *previous ? current - *previous : 0;
*previous = current;
*valid = true;
return delta;
}
static nr_rlc_statistics_t get_rlc_stats_per_drb(const rnti_t rnti, const int rb_id)
{
@@ -120,8 +154,16 @@ static meas_record_lst_t fill_DRB_RlcSduDelayDl(__attribute__((unused)) const la
meas_record.value = REAL_MEAS_VALUE;
// Get the value of sojourn time at the RLC buffer
meas_record.real_val = rlc.txsdu_avg_time_to_tx; // [μs]
// Report the windowed-average RLC SDU sojourn (mean per-packet wait over the
// last 100 ms), not the instantaneous tx_list HOL snapshot. The snapshot
// (txsdu_wt_us) reads 0 whenever the tx_list is momentarily drained at the
// single sampling instant -- common with bursty DL even at high throughput --
// which systematically under-reports delay (era/load-dependent). The averaging
// accumulator is now activated once (reset guarded to off->on in
// nr_rlc_activate_avg_time_to_tx), so it accumulates continuously instead of
// being wiped on every read; it returns 0 only when no SDU was transmitted in
// the window (genuine idle/starvation).
meas_record.real_val = (double)rlc.txsdu_avg_time_to_tx; // [us] avg RLC SDU sojourn
return meas_record;
}
@@ -132,7 +174,7 @@ static meas_record_lst_t fill_DRB_RlcSduDelayDl(__attribute__((unused)) const la
static meas_record_lst_t fill_DRB_UEThpDl(__attribute__((unused)) const label_info_lst_t label,
uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info,
const size_t ue_idx,
__attribute__((unused)) const size_t ue_idx,
__attribute__((unused))e2_node_level_stats_t* node_stats)
{
meas_record_lst_t meas_record = {0};
@@ -142,8 +184,9 @@ static meas_record_lst_t fill_DRB_UEThpDl(__attribute__((unused)) const label_in
meas_record.value = REAL_MEAS_VALUE;
// Calculate DL Thp
meas_record.real_val = (double)(rlc.txpdu_bytes - last_rlc_pdu_total_bytes[ue_idx].dl)*8/gran_period_ms; // [kbps]
last_rlc_pdu_total_bytes[ue_idx].dl = rlc.txpdu_bytes;
ue_uldlcounter_t *counter = get_ue_counter(last_rlc_pdu_total_bytes, ue_info);
const uint32_t bytes = counter_delta(rlc.txpdu_bytes, &counter->value.dl, &counter->dl_valid);
meas_record.real_val = gran_period_ms == 0 ? 0 : (double)bytes * 8 / gran_period_ms; // [kbps]
return meas_record;
}
@@ -154,7 +197,7 @@ static meas_record_lst_t fill_DRB_UEThpDl(__attribute__((unused)) const label_in
static meas_record_lst_t fill_DRB_UEThpUl(__attribute__((unused)) const label_info_lst_t label,
uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info,
const size_t ue_idx,
__attribute__((unused)) const size_t ue_idx,
__attribute__((unused))e2_node_level_stats_t* node_stats)
{
meas_record_lst_t meas_record = {0};
@@ -166,8 +209,9 @@ static meas_record_lst_t fill_DRB_UEThpUl(__attribute__((unused)) const label_in
meas_record.value = REAL_MEAS_VALUE;
// Calculate UL Thp
meas_record.real_val = (double)(rlc.rxpdu_bytes - last_rlc_pdu_total_bytes[ue_idx].ul)*8/gran_period_ms; // [kbps]
last_rlc_pdu_total_bytes[ue_idx].ul = rlc.rxpdu_bytes;
ue_uldlcounter_t *counter = get_ue_counter(last_rlc_pdu_total_bytes, ue_info);
const uint32_t bytes = counter_delta(rlc.rxpdu_bytes, &counter->value.ul, &counter->ul_valid);
meas_record.real_val = gran_period_ms == 0 ? 0 : (double)bytes * 8 / gran_period_ms; // [kbps]
return meas_record;
}
@@ -177,7 +221,7 @@ static meas_record_lst_t fill_DRB_UEThpUl(__attribute__((unused)) const label_in
static meas_record_lst_t fill_RRU_PrbTotDl(__attribute__((unused)) const label_info_lst_t label,
__attribute__((unused)) uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info,
const size_t ue_idx,
__attribute__((unused)) const size_t ue_idx,
e2_node_level_stats_t* node_stats)
{
meas_record_lst_t meas_record = {0};
@@ -185,8 +229,13 @@ static meas_record_lst_t fill_RRU_PrbTotDl(__attribute__((unused)) const label_i
meas_record.value = INTEGER_MEAS_VALUE;
// Get the number of DL PRBs
meas_record.int_val = (ue_info.ue->mac_stats.dl.total_rbs - last_total_prbs[ue_idx].dl) * 100 / (node_stats[1].mac_stats.dl.total_prb_aggregate - node_stats[0].mac_stats.dl.total_prb_aggregate); // [%]
last_total_prbs[ue_idx].dl = ue_info.ue->mac_stats.dl.total_rbs;
ue_uldlcounter_t *counter = get_ue_counter(last_total_prbs, ue_info);
const uint32_t ue_delta = counter_delta(ue_info.ue->mac_stats.dl.total_rbs, &counter->value.dl, &counter->dl_valid);
const uint64_t current = node_stats[1].mac_stats.dl.total_prb_aggregate;
const uint64_t previous = node_stats[0].mac_stats.dl.total_prb_aggregate;
const uint64_t total_delta = current >= previous ? current - previous : 0;
const uint64_t percentage = total_delta == 0 ? 0 : (uint64_t)ue_delta * 100 / total_delta;
meas_record.int_val = percentage > 100 ? 100 : percentage; // [%]
return meas_record;
}
@@ -196,7 +245,7 @@ static meas_record_lst_t fill_RRU_PrbTotDl(__attribute__((unused)) const label_i
static meas_record_lst_t fill_RRU_PrbTotUl(__attribute__((unused)) const label_info_lst_t label,
__attribute__((unused)) uint32_t gran_period_ms,
cudu_ue_info_pair_t ue_info,
const size_t ue_idx,
__attribute__((unused)) const size_t ue_idx,
e2_node_level_stats_t* node_stats)
{
meas_record_lst_t meas_record = {0};
@@ -204,8 +253,13 @@ static meas_record_lst_t fill_RRU_PrbTotUl(__attribute__((unused)) const label_i
meas_record.value = INTEGER_MEAS_VALUE;
// Get the number of UL PRBs
meas_record.int_val = (ue_info.ue->mac_stats.ul.total_rbs - last_total_prbs[ue_idx].ul) * 100 / (node_stats[1].mac_stats.ul.total_prb_aggregate - node_stats[0].mac_stats.ul.total_prb_aggregate); // [%]
last_total_prbs[ue_idx].ul = ue_info.ue->mac_stats.ul.total_rbs;
ue_uldlcounter_t *counter = get_ue_counter(last_total_prbs, ue_info);
const uint32_t ue_delta = counter_delta(ue_info.ue->mac_stats.ul.total_rbs, &counter->value.ul, &counter->ul_valid);
const uint64_t current = node_stats[1].mac_stats.ul.total_prb_aggregate;
const uint64_t previous = node_stats[0].mac_stats.ul.total_prb_aggregate;
const uint64_t total_delta = current >= previous ? current - previous : 0;
const uint64_t percentage = total_delta == 0 ? 0 : (uint64_t)ue_delta * 100 / total_delta;
meas_record.int_val = percentage > 100 ? 100 : percentage; // [%]
return meas_record;
}
+12 -7
View File
@@ -592,7 +592,7 @@ static rc_ind_data_t* fill_ue_rrc_state_change(const gNB_RRC_UE_t *rrc_ue_contex
// Generate Indication Header
rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR;
rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t));
rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t));
*rc_ind->hdr.frmt_1.ev_trigger_id = cond_id;
// Generate Indication Message
@@ -629,11 +629,11 @@ static void send_aper_ric_ind(const uint32_t ric_req_id, rc_ind_data_t* rc_ind_d
static rc_ind_data_t* fill_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint16_t cond_id)
{
rc_ind_data_t* rc_ind = malloc_or_fail(sizeof(rc_ind_data_t));
rc_ind_data_t* rc_ind = calloc_or_fail(1, sizeof(rc_ind_data_t));
// Generate Indication Header
rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR;
rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t));
rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t));
*rc_ind->hdr.frmt_1.ev_trigger_id = cond_id;
// Generate Indication Message
@@ -646,7 +646,7 @@ static rc_ind_data_t* fill_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint1
// Fill the RAN Parameter details for UE ID
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_id = E2SM_RC_RS1_UE_ID;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.type = ELEMENT_KEY_FLAG_FALSE_RAN_PARAMETER_VAL_TYPE;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = malloc_or_fail(sizeof(ran_parameter_value_t));
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = calloc_or_fail(1, sizeof(ran_parameter_value_t));
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->type = OCTET_STRING_RAN_PARAMETER_VALUE;
const ngran_node_t node_type = get_e2_node_type();
@@ -734,11 +734,11 @@ void signal_rrc_state_changed_to(const gNB_RRC_UE_t *rrc_ue_context, const rrc_s
static rc_ind_data_t* fill_rrc_msg_copy(const byte_array_t rrc_ba, const uint16_t cond_id)
{
rc_ind_data_t* rc_ind = malloc_or_fail(sizeof(rc_ind_data_t));
rc_ind_data_t* rc_ind = calloc_or_fail(1, sizeof(rc_ind_data_t));
// Generate Indication Header
rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR;
rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t));
rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t));
*rc_ind->hdr.frmt_1.ev_trigger_id = cond_id;
// Generate Indication Message
@@ -751,7 +751,7 @@ static rc_ind_data_t* fill_rrc_msg_copy(const byte_array_t rrc_ba, const uint16_
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_id = E2SM_RC_RS1_RRC_MESSAGE;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.type = ELEMENT_KEY_FLAG_FALSE_RAN_PARAMETER_VAL_TYPE;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = malloc_or_fail(sizeof(ran_parameter_value_t));
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = calloc_or_fail(1, sizeof(ran_parameter_value_t));
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->type = OCTET_STRING_RAN_PARAMETER_VALUE;
rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->octet_str_ran = copy_byte_array(rrc_ba);
@@ -776,6 +776,11 @@ static void check_rrc_msg_copy(const nr_rrc_class_e nr_channel, const uint32_t r
}
}
/* Public lock/unlock used by RRC to serialize UE-context teardown against
* in-flight RC indications fired by signal_rrc_msg(). See header comment. */
void rc_teardown_lock(void) { pthread_mutex_lock(&rc_mutex); }
void rc_teardown_unlock(void) { pthread_mutex_unlock(&rc_mutex); }
void signal_rrc_msg(const nr_rrc_class_e nr_channel, const uint32_t rrc_msg_id, const byte_array_t rrc_ba)
{
pthread_mutex_lock(&rc_mutex);
@@ -16,4 +16,13 @@ void signal_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint16_t class, cons
void signal_rrc_state_changed_to(const gNB_RRC_UE_t *rrc_ue_context, const rrc_state_e2sm_rc_e rrc_state);
/* Serialize UE-context teardown against in-flight RC indications.
* Without this, signal_rrc_msg(rrcRelease) can hand a malloc'd indication
* to the async E2 agent thread; if the gNB then frees the UE state while
* the agent is still encoding, glibc detects heap corruption ("double free
* or corruption (!prev)") and the process aborts.
* The teardown caller must wrap rrc_gNB_remove_ue_context() in these. */
void rc_teardown_lock(void);
void rc_teardown_unlock(void);
#endif
+12
View File
@@ -645,6 +645,9 @@ void RCconfig_verify(configmodule_interface_t *cfg, ngran_node_t node_type)
verify_gnb_param_notset(gnbp, GNB_DO_SRS_IDX, GNB_CONFIG_STRING_DOSRS);
verify_gnb_param_notset(gnbp, GNB_FORCE256QAMOFF_IDX, GNB_CONFIG_STRING_FORCE256QAMOFF);
verify_gnb_param_notset(gnbp, GNB_MAXMIMOLAYERS_IDX, GNB_CONFIG_STRING_MAXMIMOLAYERS);
verify_gnb_param_notset(gnbp,
GNB_PDSCH_DMRS_ADDITIONAL_POSITION_IDX,
GNB_CONFIG_STRING_PDSCH_DMRS_ADDITIONAL_POSITION);
verify_gnb_param_notset(gnbp, GNB_DISABLE_HARQ_IDX, GNB_CONFIG_STRING_DISABLE_HARQ);
verify_gnb_param_notset(gnbp, GNB_NUM_DL_HARQ_IDX, GNB_CONFIG_STRING_NUM_DL_HARQPROCESSES);
verify_gnb_param_notset(gnbp, GNB_NUM_UL_HARQ_IDX, GNB_CONFIG_STRING_NUM_UL_HARQPROCESSES);
@@ -1553,6 +1556,12 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
config.force_UL256qam_off = *GNBParamList.paramarray[0][GNB_FORCEUL256QAMOFF_IDX].iptr;
config.use_deltaMCS = *GNBParamList.paramarray[0][GNB_USE_DELTA_MCS_IDX].iptr != 0;
config.maxMIMO_layers = *GNBParamList.paramarray[0][GNB_MAXMIMOLAYERS_IDX].iptr;
config.pdsch_dmrs_additional_position =
*GNBParamList.paramarray[0][GNB_PDSCH_DMRS_ADDITIONAL_POSITION_IDX].iptr;
AssertFatal(config.pdsch_dmrs_additional_position == 0 || config.pdsch_dmrs_additional_position == 1,
"%s must be 0 (pos0) or 1 (pos1), got %d\n",
GNB_CONFIG_STRING_PDSCH_DMRS_ADDITIONAL_POSITION,
config.pdsch_dmrs_additional_position);
config.disable_harq = *GNBParamList.paramarray[0][GNB_DISABLE_HARQ_IDX].iptr;
config.num_dlharq = *GNBParamList.paramarray[0][GNB_NUM_DL_HARQ_IDX].iptr;
config.num_ulharq = *GNBParamList.paramarray[0][GNB_NUM_UL_HARQ_IDX].iptr;
@@ -1572,6 +1581,9 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
config.num_ulharq);
int tot_ant = config.pdsch_AntennaPorts.N1 * config.pdsch_AntennaPorts.N2 * config.pdsch_AntennaPorts.XP;
AssertFatal(config.maxMIMO_layers != 0 && config.maxMIMO_layers <= tot_ant, "Invalid maxMIMO_layers %d\n", config.maxMIMO_layers);
LOG_I(GNB_APP,
"PDSCH DMRS additional position pos%d\n",
config.pdsch_dmrs_additional_position);
config.redcap = get_redcap_config(0);
config.ptrs = get_ptrs_config();
+5
View File
@@ -101,6 +101,7 @@ typedef enum {
#define GNB_CONFIG_STRING_CONFIG_REP "CSI_report_type"
#define GNB_CONFIG_STRING_1ST_ACTIVE_BWP "first_active_bwp"
#define GNB_CONFIG_STRING_LIMIT_RSRP_REPORT "max_num_RSRP_reported"
#define GNB_CONFIG_STRING_PDSCH_DMRS_ADDITIONAL_POSITION "pdsch_dmrs_additional_position"
#define GNB_CONFIG_HLP_STRING_ENABLE_SDAP "enable the SDAP layer\n"
#define GNB_CONFIG_HLP_FORCE256QAMOFF "suppress activation of 256 QAM despite UE support"
@@ -114,6 +115,7 @@ typedef enum {
#define GNB_CONFIG_HLP_CU_SIBS "List of CU generated SIBs to be transmitted"
#define GNB_CONFIG_HLP_DU_SIBS "List of DU generated SIBs to be transmitted"
#define GNB_CONFIG_HLP_CONFIG_REP "Define quantity for CSI report (options: ssb_rsrp, ssb_sinr and cri_rsrp)"
#define GNB_CONFIG_HLP_PDSCH_DMRS_ADDITIONAL_POSITION "PDSCH DMRS additional position: 0=pos0, 1=pos1"
/*-----------------------------------------------------------------------------------------------------------------------------------------*/
@@ -163,6 +165,7 @@ typedef enum {
{GNB_CONFIG_STRING_CONFIG_REP, GNB_CONFIG_HLP_CONFIG_REP, 0, .strptr=NULL, .defstrval="ssb_rsrp", TYPE_STRING, 0}, \
{GNB_CONFIG_STRING_1ST_ACTIVE_BWP, NULL, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \
{GNB_CONFIG_STRING_LIMIT_RSRP_REPORT, NULL, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \
{GNB_CONFIG_STRING_PDSCH_DMRS_ADDITIONAL_POSITION, GNB_CONFIG_HLP_PDSCH_DMRS_ADDITIONAL_POSITION, 0, .iptr=NULL, .defintval=1, TYPE_INT, 0}, \
}
// clang-format on
@@ -207,6 +210,7 @@ typedef enum {
#define GNB_CONFIG_REP_IDX 37
#define GNB_1ST_ACTIVE_BWP_IDX 38
#define GNB_LIMIT_RSRP_REPORT_IDX 39
#define GNB_PDSCH_DMRS_ADDITIONAL_POSITION_IDX 40
#define TRACKING_AREA_CODE_OKRANGE {0x0001,0xFFFD}
#define NUM_DL_HARQ_OKVALUES {2,4,6,8,10,12,16,32}
@@ -256,6 +260,7 @@ typedef enum {
3 } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
}
/*-------------------------------------------------------------------------------------------------------------------------------------------------*/
+18 -6
View File
@@ -3033,8 +3033,11 @@ uint16_t nr_dci_size(const NR_UE_DL_BWP_t *DL_BWP,
size += 8;
// TB2
long *maxCWperDCI = pdsch_Config ? pdsch_Config->maxNrofCodeWordsScheduledByDCI : NULL;
if ((maxCWperDCI != NULL) && (*maxCWperDCI == 2)) {
size += 8;
if (maxCWperDCI && (*maxCWperDCI == NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2)) {
dci_pdu->mcs2.nbits = 5;
dci_pdu->ndi2.nbits = 1;
dci_pdu->rv2.nbits = 2;
size += dci_pdu->mcs2.nbits + dci_pdu->ndi2.nbits + dci_pdu->rv2.nbits;
}
// HARQ process number – 5 bits if higher layer parameter harq-ProcessNumberSizeDCI-1-1 is configured;
// otherwise 4 bits. Spec 38.212 Section 7.3.1.2.2
@@ -4130,12 +4133,19 @@ uint16_t compute_pucch_prb_size(uint8_t format,
nr_prbs);
if (format==2){
/* nr_decode_pucch2() requires at least two PRB-symbols. Preserve the
* one-symbol format-2 resource at symbol 13 (which does not overlap the
* usual PUSCH ending at symbol 12) and use frequency width to meet that
* decoder invariant. This common helper keeps UE TX and gNB RX aligned. */
const int min_prbs = n_symb == 1 ? 2 : 1;
AssertFatal(nr_prbs >= min_prbs,
"PUCCH format 2 needs at least %d PRBs for %d symbol(s)", min_prbs, n_symb);
// TODO fix this for multiple CSI reports
for (int i = nr_prbs; i > 0; i--) {
for (int i = nr_prbs; i >= min_prbs; i--) {
// compute code rate factor for next prb value
int next_prb_factor = (i - 1) * n_symb * Qm * n_re_ctrl * r;
// if it does not sa
if (O_tot > next_prb_factor)
if (i == min_prbs || O_tot > next_prb_factor)
return i;
}
}
@@ -4549,8 +4559,10 @@ static void set_bitlen_size_singlepanel(CSI_Meas_bitlen_t *csi_bitlen, int n1, i
case 7:
case 8:
if(n1 == 4 && n2 == 1) {
csi_bitlen->pmi_i11_bitlen[i] = ceil(log2(n1 * o1));
csi_bitlen->pmi_i12_bitlen[i] = ceil(log2(n2 * o2 / 2));
// TS 38.212 Table 6.3.1.1.2-1: only half of the horizontal
// oversampled beams are valid for rank 7/8 in the 4x1 geometry.
csi_bitlen->pmi_i11_bitlen[i] = ceil(log2(n1 * o1 / 2));
csi_bitlen->pmi_i12_bitlen[i] = 0;
csi_bitlen->pmi_i13_bitlen[i] = 0;
csi_bitlen->pmi_x2_bitlen[i] = 1;
}
+1 -1
View File
@@ -595,7 +595,7 @@ typedef struct NR_UE_MAC_INST_s {
// Defined for abstracted mode
nr_downlink_indication_t dl_info;
NR_UE_DL_HARQ_STATUS_t dl_harq_info[NR_MAX_HARQ_PROCESSES];
NR_UE_DL_HARQ_STATUS_t dl_harq_info[NR_MAX_HARQ_PROCESSES][2]; // one harq process for each codeword
NR_UE_UL_HARQ_INFO_t ul_harq_info[NR_MAX_HARQ_PROCESSES];
NR_TAG_Id_t tag_Id;
+8 -6
View File
@@ -200,12 +200,14 @@ void reset_mac_inst(NR_UE_MAC_INST_t *nr_mac)
nr_mac->scheduling_info.phr_info.phr_reporting = 0;
nr_mac->scheduling_info.phr_info.was_mac_reset = true;
// flush the soft buffers for all DL HARQ processes
memset(nr_mac->dl_harq_info, 0, sizeof(nr_mac->dl_harq_info));
// for each DL HARQ process, consider the next received transmission for a TB as the very first transmission
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++)
nr_mac->dl_harq_info[k].last_ndi = -1; // initialize to invalid value
for (int c = 0; c < 2; c++) {
// flush the soft buffers for all DL HARQ processes
// for each DL HARQ process, consider the next received transmission for a TB as the very first transmission
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++) {
memset(&nr_mac->dl_harq_info[k][c], 0, sizeof(NR_UE_DL_HARQ_STATUS_t));
nr_mac->dl_harq_info[k][c].last_ndi = -1; // initialize to invalid value
}
}
// release, if any, Temporary C-RNTI
nr_mac->ra.t_crnti = 0;
+1 -1
View File
@@ -1190,7 +1190,7 @@ void trigger_MAC_UE_RA(NR_UE_MAC_INST_t *mac, dci_pdu_rel15_t *pdcch_order)
void prepare_msg4_msgb_feedback(NR_UE_MAC_INST_t *mac, int pid, int ack_nack)
{
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[pid];
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[pid][0]; // single cw for MSG4
int sched_slot = current_harq->ul_slot;
int sched_frame = current_harq->ul_frame;
PUCCH_sched_t pucch = {.n_CCE = current_harq->n_CCE,
@@ -218,9 +218,11 @@ int get_sum_delta_pucch(NR_UE_MAC_INST_t *mac, int slot, frame_t frame)
const int num_dl_harq = get_nrofHARQ_ProcessesForPDSCH(&mac->sc_info);
for (int i = 0; i < num_dl_harq; i++) {
if (mac->dl_harq_info[i].active && mac->dl_harq_info[i].ul_slot == slot && mac->dl_harq_info[i].ul_frame == frame) {
delta_tpc_sum += mac->dl_harq_info[i].delta_pucch;
mac->dl_harq_info[i].delta_pucch = 0;
for (int c = 0; c < 2; c++) {
if (mac->dl_harq_info[i][c].active && mac->dl_harq_info[i][c].ul_slot == slot && mac->dl_harq_info[i][c].ul_frame == frame) {
delta_tpc_sum += mac->dl_harq_info[i][c].delta_pucch;
mac->dl_harq_info[i][c].delta_pucch = 0;
}
}
}
return delta_tpc_sum;
+258 -213
View File
@@ -435,6 +435,7 @@ int8_t nr_ue_process_dci_freq_dom_resource_assignment(nfapi_nr_ue_pusch_pdu_t *p
static void set_harq_status(NR_UE_MAC_INST_t *mac,
uint8_t pucch_id,
uint8_t harq_id,
int cw_id,
int8_t delta_pucch,
uint16_t data_toul_fb,
uint8_t dai,
@@ -443,7 +444,7 @@ static void set_harq_status(NR_UE_MAC_INST_t *mac,
frame_t frame,
int slot)
{
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[harq_id];
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[harq_id][cw_id];
current_harq->active = true;
current_harq->ack_received = false;
current_harq->pucch_resource_indicator = pucch_id;
@@ -469,13 +470,15 @@ static void set_harq_status(NR_UE_MAC_INST_t *mac,
// looking for other active HARQ processes with feedback in the same frame/slot
if (i == harq_id)
continue;
NR_UE_DL_HARQ_STATUS_t *harq = &mac->dl_harq_info[i];
if (harq->active &&
harq->ul_frame == current_harq->ul_frame &&
harq->ul_slot == current_harq->ul_slot) {
// highest_dai is the largest cumulative dai in the set of HARQ allocations for a given slot
if (harq->dai_cumul > highest_dai)
highest_dai = harq->dai_cumul - 1;
for (int c = 0; c < 2; c++) {
NR_UE_DL_HARQ_STATUS_t *harq = &mac->dl_harq_info[i][c];
if (harq->active &&
harq->ul_frame == current_harq->ul_frame &&
harq->ul_slot == current_harq->ul_slot) {
// highest_dai is the largest cumulative dai in the set of HARQ allocations for a given slot
if (harq->dai_cumul > highest_dai)
highest_dai = harq->dai_cumul - 1;
}
}
}
@@ -667,6 +670,99 @@ static int nr_ue_process_dci_ul_01(NR_UE_MAC_INST_t *mac,
return ret;
}
// Table 7.3.1.3-1 of 38.211
static int get_nl_for_cw(int Nl, int cw_idx)
{
AssertFatal(Nl >= 0 && Nl <= 8, "Invalid number of layers %d\n", Nl);
if (Nl < 5)
return Nl;
else {
if (cw_idx == 0)
return Nl / 2;
else
return Nl / 2 + Nl % 2;
}
}
static bool get_cw_info(NR_UE_DL_HARQ_STATUS_t *current_harq,
fapi_nr_dl_config_dlsch_pdu_rel15_t *dlsch_pdu,
uint8_t pdu_type,
fapi_nr_dl_cw_info_t *cw_info,
int number_rbs,
int nb_re_dmrs,
int nb_rb_oh,
int rv,
int mcs,
int ndi,
int cw_idx)
{
uint8_t Nl = 0;
for (int i = 0; i < 12; i++) { // max 12 ports
if ((dlsch_pdu->dmrs_ports >> i) & 0x01)
Nl += 1;
}
cw_info->mcs = mcs;
/* RV for transport block */
cw_info->rv = rv;
/* NDI for transport block*/
if (pdu_type == FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH || pdu_type == FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH || ndi != current_harq->last_ndi) {
// new data
cw_info->new_data_indicator = true;
current_harq->R = 0;
current_harq->TBS = 0;
} else
cw_info->new_data_indicator = false;
if (pdu_type != FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH && pdu_type != FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH) {
current_harq->last_ndi = ndi;
if (cw_info->new_data_indicator)
current_harq->round = 0;
else
current_harq->round++;
}
cw_info->qamModOrder = nr_get_Qm_dl(cw_info->mcs, dlsch_pdu->mcs_table);
if (cw_info->qamModOrder == 0) {
LOG_W(NR_MAC, "Invalid code rate or Mod order, likely due to unexpected DL DCI\n");
return false;
}
cw_info->Nl = get_nl_for_cw(Nl, cw_idx);
int R = nr_get_code_rate_dl(cw_info->mcs, dlsch_pdu->mcs_table);
if (R > 0) {
cw_info->targetCodeRate = R;
cw_info->TBS = nr_compute_tbs(cw_info->qamModOrder,
R,
number_rbs,
dlsch_pdu->number_symbols,
nb_re_dmrs * get_num_dmrs(dlsch_pdu->dlDmrsSymbPos),
nb_rb_oh,
0,
cw_info->Nl);
// storing for possible retransmissions
if (!cw_info->new_data_indicator && current_harq->TBS != cw_info->TBS) {
LOG_W(NR_MAC,
"NDI indicates re-transmission but computed TBS %d doesn't match with what previously stored %d\n",
cw_info->TBS,
current_harq->TBS);
cw_info->new_data_indicator = true; // treated as new data
}
current_harq->R = cw_info->targetCodeRate;
current_harq->TBS = cw_info->TBS;
}
else {
cw_info->targetCodeRate = current_harq->R;
cw_info->TBS = current_harq->TBS;
}
if (cw_info->TBS == 0) {
LOG_E(MAC, "Invalid TBS = 0. Probably caused by missed detection of DCI\n");
return false;
}
cw_info->ldpcBaseGraph = get_BG(cw_info->TBS, cw_info->targetCodeRate);
return true;
}
static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
frame_t frame,
int slot,
@@ -789,7 +885,7 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
0,
dci->frequency_domain_assignment)
< 0) {
LOG_W(MAC, "[%d.%d] Invalid frequency_domain_assignment. Possibly due to false DCI. Ignoring DCI!\n", frame, slot);
LOG_W(NR_MAC, "[%d.%d] Invalid frequency_domain_assignment. Possibly due to false DCI. Ignoring DCI!\n", frame, slot);
return -1;
}
@@ -873,76 +969,28 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
NULL, // SPS not implemented,
false, // as above
NULL); // MCS-C-RNTI not implemented
/* MCS */
dlsch_pdu->mcs = dci->mcs;
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dci->harq_pid.val];
/* NDI (only if CRC scrambled by C-RNTI or CS-RNTI or new-RNTI or TC-RNTI)*/
if (dl_conf_req->pdu_type == FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH ||
dl_conf_req->pdu_type == FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH ||
dci->ndi != current_harq->last_ndi) {
// new data
dlsch_pdu->new_data_indicator = true;
current_harq->R = 0;
current_harq->TBS = 0;
} else {
dlsch_pdu->new_data_indicator = false;
}
if (dl_conf_req->pdu_type != FAPI_NR_DL_CONFIG_TYPE_SI_DLSCH &&
dl_conf_req->pdu_type != FAPI_NR_DL_CONFIG_TYPE_RA_DLSCH) {
current_harq->last_ndi = dci->ndi;
}
dlsch_pdu->qamModOrder = nr_get_Qm_dl(dlsch_pdu->mcs, dlsch_pdu->mcs_table);
if (dlsch_pdu->qamModOrder == 0) {
LOG_W(MAC, "Invalid code rate or Mod order, likely due to unexpected DL DCI.\n");
int nb_re_dmrs = ((dlsch_pdu->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) ? 6 : 4) * dlsch_pdu->n_dmrs_cdm_groups;
int nb_rb_oh = mac->sc_info.xOverhead_PDSCH ? nb_rb_oh = 6 * (1 + *mac->sc_info.xOverhead_PDSCH) : 0;
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dci->harq_pid.val][0];
if (!get_cw_info(current_harq,
dlsch_pdu,
dl_conf_req->pdu_type,
&dlsch_pdu->cw_info[0],
dlsch_pdu->number_rbs,
nb_re_dmrs,
nb_rb_oh,
dci->rv,
dci->mcs,
dci->ndi,
0))
return -1;
}
int R = nr_get_code_rate_dl(dlsch_pdu->mcs, dlsch_pdu->mcs_table);
if (R > 0) {
dlsch_pdu->targetCodeRate = R;
int nb_rb_oh;
if (mac->sc_info.xOverhead_PDSCH)
nb_rb_oh = 6 * (1 + *mac->sc_info.xOverhead_PDSCH);
else
nb_rb_oh = 0;
int nb_re_dmrs = ((dlsch_pdu->dmrsConfigType == NFAPI_NR_DMRS_TYPE1) ? 6 : 4) * dlsch_pdu->n_dmrs_cdm_groups;
dlsch_pdu->TBS = nr_compute_tbs(dlsch_pdu->qamModOrder,
R,
dlsch_pdu->number_rbs,
dlsch_pdu->number_symbols,
nb_re_dmrs * get_num_dmrs(dlsch_pdu->dlDmrsSymbPos),
nb_rb_oh,
0,
1);
// storing for possible retransmissions
current_harq->R = dlsch_pdu->targetCodeRate;
if (!dlsch_pdu->new_data_indicator && current_harq->TBS != dlsch_pdu->TBS) {
LOG_W(NR_MAC, "NDI indicates re-transmission but computed TBS %d doesn't match with what previously stored %d\n",
dlsch_pdu->TBS, current_harq->TBS);
dlsch_pdu->new_data_indicator = true; // treated as new data
}
current_harq->TBS = dlsch_pdu->TBS;
}
else {
dlsch_pdu->targetCodeRate = current_harq->R;
dlsch_pdu->TBS = current_harq->TBS;
}
dlsch_pdu->ldpcBaseGraph = get_BG(dlsch_pdu->TBS, dlsch_pdu->targetCodeRate);
if (dlsch_pdu->TBS == 0) {
LOG_E(MAC, "Invalid TBS = 0. Probably caused by missed detection of DCI\n");
return -1;
}
dlsch_pdu->n_codewords = 1;
int bw_tbslbrm = current_DL_BWP ? mac->sc_info.dl_bw_tbslbrm : dlsch_pdu->BWPSize;
dlsch_pdu->tbslbrm = nr_compute_tbslbrm(dlsch_pdu->mcs_table, bw_tbslbrm, 1);
/* RV (only if CRC scrambled by C-RNTI or CS-RNTI or new-RNTI or TC-RNTI)*/
dlsch_pdu->rv = dci->rv;
/* HARQ_PROCESS_NUMBER (only if CRC scrambled by C-RNTI or CS-RNTI or new-RNTI or TC-RNTI)*/
dlsch_pdu->harq_process_nbr = dci->harq_pid.val;
/* TB_SCALING (only if CRC scrambled by P-RNTI or RA-RNTI) */
@@ -1009,6 +1057,7 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
set_harq_status(mac,
dci->pucch_resource_indicator,
dci->harq_pid.val,
0,
tpc[dci->tpc],
feedback_ti,
dci->dai[0].val,
@@ -1017,10 +1066,6 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
frame,
slot);
}
if (dlsch_pdu->new_data_indicator)
current_harq->round = 0;
else
current_harq->round++;
if (current_harq->round < sizeofArray(mac->stats.dl.rounds))
mac->stats.dl.rounds[current_harq->round]++;
}
@@ -1043,9 +1088,9 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
"(nr_ue_procedures.c) vrb_to_prb_mapping=%d \n>>> mcs=%d\n>>> ndi=%d\n>>> rv=%d\n>>> harq_process_nbr=%d\n>>> dai=%d\n>>> "
"scaling_factor_S=%f\n>>> tpc_pucch=%d\n>>> pucch_res_ind=%d\n>>> pdsch_to_harq_feedback_time_ind=%d\n",
dlsch_pdu->vrb_to_prb_mapping,
dlsch_pdu->mcs,
dlsch_pdu->new_data_indicator,
dlsch_pdu->rv,
dlsch_pdu->cw_info[0].mcs,
dlsch_pdu->cw_info[0].new_data_indicator,
dlsch_pdu->cw_info[0].rv,
dlsch_pdu->harq_process_nbr,
dci->dai[0].val,
dlsch_pdu->scaling_factor_S,
@@ -1055,7 +1100,7 @@ static int nr_ue_process_dci_dl_10(NR_UE_MAC_INST_t *mac,
dlsch_pdu->k1_feedback = feedback_ti;
LOG_D(MAC, "(nr_ue_procedures.c) pdu_type=%d\n\n", dl_conf_req->pdu_type);
LOG_D(MAC, "(nr_ue_procedures.c) pdu_type=%d\n", dl_conf_req->pdu_type);
// the prepared dci is valid, we add it in the list
dl_config->number_pdus++;
@@ -1213,9 +1258,6 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
dlsch_pdu->dmrsConfigType = dl_dmrs_config->dmrs_Type == NULL ? NFAPI_NR_DMRS_TYPE1 : NFAPI_NR_DMRS_TYPE2;
/* TODO: fix number of DM-RS CDM groups without data according to subclause 5.1.6.2 of 3GPP TS 38.214,
using tables 7.3.1.2.2-1, 7.3.1.2.2-2, 7.3.1.2.2-3, 7.3.1.2.2-4 of 3GPP TS 38.212 */
dlsch_pdu->n_dmrs_cdm_groups = 1;
/* VRB_TO_PRB_MAPPING */
if ((pdsch_Config->resourceAllocation == 1) && (pdsch_Config->vrb_ToPRB_Interleaver != NULL))
dlsch_pdu->vrb_to_prb_mapping =
@@ -1226,28 +1268,7 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
dlsch_pdu->rate_matching_ind = dci->rate_matching_indicator.val;
/* ZP_CSI_RS_TRIGGER */
dlsch_pdu->zp_csi_rs_trigger = dci->zp_csi_rs_trigger.val;
/* MCS (for transport block 1)*/
dlsch_pdu->mcs = dci->mcs;
/* NDI (for transport block 1)*/
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dci->harq_pid.val];
if (dci->ndi != current_harq->last_ndi) {
// new data
dlsch_pdu->new_data_indicator = true;
current_harq->R = 0;
current_harq->TBS = 0;
}
else {
dlsch_pdu->new_data_indicator = false;
}
current_harq->last_ndi = dci->ndi;
/* RV (for transport block 1)*/
dlsch_pdu->rv = dci->rv;
/* MCS (for transport block 2)*/
dlsch_pdu->tb2_mcs = dci->mcs2.val;
/* NDI (for transport block 2)*/
dlsch_pdu->tb2_new_data_indicator = dci->ndi2.val;
/* RV (for transport block 2)*/
dlsch_pdu->tb2_rv = dci->rv2.val;
/* HARQ_PROCESS_NUMBER */
dlsch_pdu->harq_process_nbr = dci->harq_pid.val;
/* TPC_PUCCH */
@@ -1276,13 +1297,32 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
return -1;
}
NR_UE_ServingCell_Info_t *sc_info = &mac->sc_info;
int nb_rb_oh;
if (sc_info->xOverhead_PDSCH)
nb_rb_oh = 6 * (1 + *sc_info->xOverhead_PDSCH);
else
nb_rb_oh = 0;
/* ANTENNA_PORTS */
uint8_t n_codewords = 1; // FIXME!!!
long *max_length = dl_dmrs_config->maxLength;
long *dmrs_type = dl_dmrs_config->dmrs_Type;
// In case the higher layer parameter maxNrofCodeWordsScheduledByDCI indicates that two codeword transmission is enabled,
// then one of the two transport blocks is disabled by DCI format 1_1
// if IMCS = 26 and if rvid = 1 for the corresponding transport block
bool cw0 = true;
bool cw1 = true;
if (pdsch_Config->maxNrofCodeWordsScheduledByDCI
&& *pdsch_Config->maxNrofCodeWordsScheduledByDCI == NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2) {
cw0 = dci->rv != 1 || dci->mcs != 26;
cw1 = dci->rv2.val != 1 || dci->mcs2.val != 26;
} else
cw1 = false;
dlsch_pdu->n_front_load_symb = 1; // default value
set_antenna_port_parameters(dlsch_pdu, n_codewords, max_length, dmrs_type, dci->antenna_ports.val);
set_antenna_port_parameters(dlsch_pdu, cw0 + cw1, max_length, dmrs_type, dci->antenna_ports.val);
int nb_re_dmrs = ((dmrs_type == NULL) ? 6 : 4) * dlsch_pdu->n_dmrs_cdm_groups;
/* dmrs symbol positions*/
dlsch_pdu->dlDmrsSymbPos = fill_dmrs_mask(pdsch_Config,
@@ -1293,6 +1333,105 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
tda_info.mapping_type,
dlsch_pdu->n_front_load_symb);
dlsch_pdu->mcs_table = get_dlsch_mcs_table(NR_DL_DCI_FORMAT_1_1,
rnti_type,
dci_ind->ss_type,
pdsch_Config ? pdsch_Config->mcs_Table : NULL,
NULL, // SPS not implemented,
false, // as above
NULL); // MCS-C-RNTI not implemented
/* PDSCH_TO_HARQ_FEEDBACK_TIME_IND */
// according to TS 38.213 Table 9.2.3-1
const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing);
uint16_t feedback_ti = 0;
const int tpc[] = {-1, 0, 1, 3};
if (!get_FeedbackDisabled(mac->sc_info.downlinkHARQ_FeedbackDisabled_r17, dci->harq_pid.val)) {
feedback_ti = pucch_Config->dl_DataToUL_ACK->list.array[dci->pdsch_to_harq_feedback_timing_indicator.val][0] + ntn_ue_koffset;
AssertFatal(feedback_ti >= GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing),
"PDSCH to HARQ feedback time (%d) needs to be higher than DURATION_RX_TO_TX (%ld). Min feedback time set in config "
"file (min_rxtxtime).\n",
feedback_ti,
GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing));
}
int number_rbs;
if (dlsch_pdu->resource_alloc == 1) {
number_rbs = dlsch_pdu->number_rbs;
} else {
uint32_t temp_bitmap[9];
memcpy(temp_bitmap, dlsch_pdu->rb_bitmap, 36);
number_rbs = count_bits(temp_bitmap, 9);
}
int cw_idx = 0;
NR_UE_DL_HARQ_STATUS_t *current_harq = NULL;
if (cw0) {
current_harq = &mac->dl_harq_info[dci->harq_pid.val][0];
if (get_cw_info(current_harq,
dlsch_pdu,
dl_conf_req->pdu_type,
&dlsch_pdu->cw_info[0],
number_rbs,
nb_re_dmrs,
nb_rb_oh,
dci->rv,
dci->mcs,
dci->ndi,
cw_idx)) {
if (current_harq->round < sizeofArray(mac->stats.dl.rounds))
mac->stats.dl.rounds[current_harq->round]++;
// set the harq status at MAC for feedback
set_harq_status(mac,
dci->pucch_resource_indicator,
dci->harq_pid.val,
0,
tpc[dci->tpc],
feedback_ti,
dci->dai[0].val,
dci_ind->n_CCE,
dci_ind->N_CCE,
frame,
slot);
cw_idx++;
} else
return -1;
}
if (cw1) {
current_harq = &mac->dl_harq_info[dci->harq_pid.val][cw_idx];
if (get_cw_info(current_harq,
dlsch_pdu,
dl_conf_req->pdu_type,
&dlsch_pdu->cw_info[1],
number_rbs,
nb_re_dmrs,
nb_rb_oh,
dci->rv2.val,
dci->mcs2.val,
dci->ndi2.val,
cw_idx)) {
if (current_harq->round < sizeofArray(mac->stats.dl.rounds))
mac->stats.dl.rounds[current_harq->round]++;
// set the harq status at MAC for feedback
set_harq_status(mac,
dci->pucch_resource_indicator,
dci->harq_pid.val,
1,
tpc[dci->tpc],
feedback_ti,
dci->dai[0].val,
dci_ind->n_CCE,
dci_ind->N_CCE,
frame,
slot);
cw_idx++;
} else
return -1;
}
dlsch_pdu->n_codewords = cw_idx;
/* TCI */
if (dl_conf_req->dci_config_pdu.dci_config_rel15.coreset.tci_present_in_dci == 1) {
// 0 bit if higher layer parameter tci-PresentInDCI is not enabled
@@ -1311,107 +1450,9 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
/* DMRS_SEQ_INI */
// FIXME!!!
/* PDSCH_TO_HARQ_FEEDBACK_TIME_IND */
// according to TS 38.213 Table 9.2.3-1
const int ntn_ue_koffset = GET_NTN_UE_K_OFFSET(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing);
uint16_t feedback_ti = 0;
if (!get_FeedbackDisabled(mac->sc_info.downlinkHARQ_FeedbackDisabled_r17, dci->harq_pid.val)) {
feedback_ti = pucch_Config->dl_DataToUL_ACK->list.array[dci->pdsch_to_harq_feedback_timing_indicator.val][0] + ntn_ue_koffset;
AssertFatal(feedback_ti >= GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing),
"PDSCH to HARQ feedback time (%d) needs to be higher than DURATION_RX_TO_TX (%ld). Min feedback time set in config "
"file (min_rxtxtime).\n",
feedback_ti,
GET_DURATION_RX_TO_TX(&mac->phy_config.config_req.ntn_config, dlsch_pdu->SubcarrierSpacing));
// set the harq status at MAC for feedback
const int tpc[] = {-1, 0, 1, 3};
set_harq_status(mac,
dci->pucch_resource_indicator,
dci->harq_pid.val,
tpc[dci->tpc],
feedback_ti,
dci->dai[0].val,
dci_ind->n_CCE,
dci_ind->N_CCE,
frame,
slot);
}
if (dlsch_pdu->new_data_indicator)
current_harq->round = 0;
else
current_harq->round++;
if (current_harq->round < sizeofArray(mac->stats.dl.rounds))
mac->stats.dl.rounds[current_harq->round]++;
// send the ack/nack slot number to phy to indicate tx thread to wait for DLSCH decoding
dlsch_pdu->k1_feedback = feedback_ti;
dlsch_pdu->mcs_table = get_dlsch_mcs_table(NR_DL_DCI_FORMAT_1_1,
rnti_type,
dci_ind->ss_type,
pdsch_Config ? pdsch_Config->mcs_Table : NULL,
NULL, // SPS not implemented,
false, // as above
NULL); // MCS-C-RNTI not implemented
dlsch_pdu->qamModOrder = nr_get_Qm_dl(dlsch_pdu->mcs, dlsch_pdu->mcs_table);
if (dlsch_pdu->qamModOrder == 0) {
LOG_W(MAC, "Invalid code rate or Mod order, likely due to unexpected DL DCI.\n");
return -1;
}
uint8_t Nl = 0;
for (int i = 0; i < 12; i++) { // max 12 ports
if ((dlsch_pdu->dmrs_ports >> i) & 0x01)
Nl += 1;
}
NR_UE_ServingCell_Info_t *sc_info = &mac->sc_info;
int nb_rb_oh;
if (sc_info->xOverhead_PDSCH)
nb_rb_oh = 6 * (1 + *sc_info->xOverhead_PDSCH);
else
nb_rb_oh = 0;
int nb_re_dmrs = ((dmrs_type == NULL) ? 6 : 4) * dlsch_pdu->n_dmrs_cdm_groups;
int number_rbs;
if (dlsch_pdu->resource_alloc == 1) {
number_rbs = dlsch_pdu->number_rbs;
} else {
uint32_t temp_bitmap[9];
memcpy(temp_bitmap, dlsch_pdu->rb_bitmap, 36);
number_rbs = count_bits(temp_bitmap, 9);
}
int R = nr_get_code_rate_dl(dlsch_pdu->mcs, dlsch_pdu->mcs_table);
if (R > 0) {
dlsch_pdu->targetCodeRate = R;
dlsch_pdu->TBS = nr_compute_tbs(dlsch_pdu->qamModOrder,
R,
number_rbs,
dlsch_pdu->number_symbols,
nb_re_dmrs * get_num_dmrs(dlsch_pdu->dlDmrsSymbPos),
nb_rb_oh,
0,
Nl);
// storing for possible retransmissions
if (!dlsch_pdu->new_data_indicator && current_harq->TBS != dlsch_pdu->TBS) {
LOG_W(NR_MAC, "NDI indicates re-transmission but computed TBS %d doesn't match with what previously stored %d\n",
dlsch_pdu->TBS, current_harq->TBS);
dlsch_pdu->new_data_indicator = true; // treated as new data
}
current_harq->R = dlsch_pdu->targetCodeRate;
current_harq->TBS = dlsch_pdu->TBS;
}
else {
dlsch_pdu->targetCodeRate = current_harq->R;
dlsch_pdu->TBS = current_harq->TBS;
}
dlsch_pdu->ldpcBaseGraph = get_BG(dlsch_pdu->TBS, dlsch_pdu->targetCodeRate);
if (dlsch_pdu->TBS == 0) {
LOG_E(MAC, "Invalid TBS = 0. Probably caused by missed detection of DCI\n");
return -1;
}
// TBS_LBRM according to section 5.4.2.1 of 38.212
int max_mimo_layers = 0;
if (sc_info->maxMIMO_Layers_PDSCH)
@@ -1424,10 +1465,11 @@ static int nr_ue_process_dci_dl_11(NR_UE_MAC_INST_t *mac,
/*PTRS configuration */
dlsch_pdu->pduBitmap = 0;
if (dl_dmrs_config->phaseTrackingRS != NULL) {
AssertFatal(cw_idx == 1, "Cannot handle PTRS with 2 codewords\n");
bool valid_ptrs_setup =
set_dl_ptrs_values(dl_dmrs_config->phaseTrackingRS->choice.setup,
number_rbs,
dlsch_pdu->mcs,
dlsch_pdu->cw_info[0].mcs,
dlsch_pdu->mcs_table,
&dlsch_pdu->PTRSFreqDensity,
&dlsch_pdu->PTRSTimeDensity,
@@ -2329,7 +2371,8 @@ bool get_downlink_ack(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, PUCCH_sche
if (current_DL_BWP
&& current_DL_BWP->pdsch_Config
&& current_DL_BWP->pdsch_Config->maxNrofCodeWordsScheduledByDCI
&& current_DL_BWP->pdsch_Config->maxNrofCodeWordsScheduledByDCI[0] == 2) {
&& *current_DL_BWP->pdsch_Config->maxNrofCodeWordsScheduledByDCI
== NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2) {
two_transport_blocks = true;
number_of_code_word = 2;
}
@@ -2339,11 +2382,8 @@ bool get_downlink_ack(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, PUCCH_sche
/* look for dl acknowledgment which should be done on current uplink slot */
for (int code_word = 0; code_word < number_of_code_word; code_word++) {
for (int dl_harq_pid = 0; dl_harq_pid < num_dl_harq; dl_harq_pid++) {
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dl_harq_pid];
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[dl_harq_pid][code_word];
if (current_harq->active) {
LOG_D(PHY, "HARQ pid %d is active for %d.%d\n",
dl_harq_pid, current_harq->ul_frame, current_harq->ul_slot);
@@ -2925,6 +2965,11 @@ static nfapi_nr_ue_csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *
int pmi_x1_bitlen = csi_report->csi_meas_bitlen.pmi_x1_bitlen[mac->csirs_measurements.ri];
int pmi_x2_bitlen = csi_report->csi_meas_bitlen.pmi_x2_bitlen[mac->csirs_measurements.ri];
int cqi_bitlen = csi_report->csi_meas_bitlen.cqi_bitlen[mac->csirs_measurements.ri];
/* For ranks with two transport blocks, the report carries one
* wideband CQI per codeword. Until separate per-codeword estimates
* are available, report the measured wideband CQI for both. */
const uint16_t cqi = mac->csirs_measurements.cqi & 0x0f;
const uint16_t cqi_payload = cqi_bitlen > 4 ? (cqi << 4) | cqi : cqi;
int padding_bitlen = 0;
// TODO: Improvements will be needed to cri_bitlen>0 and pmi_x1_bitlen>0
if (mapping_type == ON_PUSCH) {
@@ -2932,7 +2977,7 @@ static nfapi_nr_ue_csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *
p2_bits = pmi_x1_bitlen + pmi_x2_bitlen;
temp_payload_1 = (0/*mac->csi_measurements.cri*/ << (cqi_bitlen + ri_bitlen)) |
(mac->csirs_measurements.ri << cqi_bitlen) |
(mac->csirs_measurements.cqi);
cqi_payload;
temp_payload_2 = (mac->csirs_measurements.i1 << pmi_x2_bitlen) |
mac->csirs_measurements.i2;
}
@@ -2943,7 +2988,7 @@ static nfapi_nr_ue_csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *
(mac->csirs_measurements.ri << (cqi_bitlen + pmi_x2_bitlen + pmi_x1_bitlen + padding_bitlen)) |
(mac->csirs_measurements.i1 << (cqi_bitlen + pmi_x2_bitlen)) |
(mac->csirs_measurements.i2 << (cqi_bitlen)) |
(mac->csirs_measurements.cqi);
cqi_payload;
}
temp_payload_1 = reverse_bits(temp_payload_1, p1_bits);
+38 -14
View File
@@ -116,15 +116,22 @@ static void trigger_regular_bsr(NR_UE_MAC_INST_t *mac, NR_LogicalChannelIdentity
nr_timer_stop(&mac->scheduling_info.sr_DelayTimer);
}
static void flush_harq_buffers(NR_UE_MAC_INST_t *mac)
{
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++) {
for (int c = 0; c < 2; c++) {
memset(&mac->dl_harq_info[k][c], 0, sizeof(NR_UE_DL_HARQ_STATUS_t));
mac->dl_harq_info[k][c].last_ndi = -1; // initialize to invalid value
}
memset(&mac->ul_harq_info[k], 0, sizeof(*mac->ul_harq_info));
mac->ul_harq_info[k].last_ndi = -1; // initialize to invalid value
}
}
void handle_time_alignment_timer_expired(NR_UE_MAC_INST_t *mac)
{
// flush all HARQ buffers for all Serving Cells
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++) {
memset(&mac->dl_harq_info[k], 0, sizeof(*mac->dl_harq_info));
memset(&mac->ul_harq_info[k], 0, sizeof(*mac->ul_harq_info));
mac->dl_harq_info[k].last_ndi = -1; // initialize to invalid value
mac->ul_harq_info[k].last_ndi = -1; // initialize to invalid value
}
flush_harq_buffers(mac);
// release PUCCH for all Serving Cells;
// release SRS for all Serving Cells;
release_PUCCH_SRS(mac);
@@ -142,12 +149,7 @@ void handle_time_alignment_timer_expired(NR_UE_MAC_INST_t *mac)
void handle_ulsync_loss(NR_UE_MAC_INST_t *mac)
{
// flush all HARQ buffers for all Serving Cells
for (int k = 0; k < NR_MAX_HARQ_PROCESSES; k++) {
memset(&mac->dl_harq_info[k], 0, sizeof(*mac->dl_harq_info));
memset(&mac->ul_harq_info[k], 0, sizeof(*mac->ul_harq_info));
mac->dl_harq_info[k].last_ndi = -1; // initialize to invalid value
mac->ul_harq_info[k].last_ndi = -1; // initialize to invalid value
}
flush_harq_buffers(mac);
// clear any configured downlink assignments and uplink grants;
if (mac->dl_config_request)
memset(mac->dl_config_request, 0, sizeof(*mac->dl_config_request));
@@ -1152,15 +1154,36 @@ static NR_CSI_ResourceConfigId_t find_CSI_resourceconfig(NR_CSI_MeasConfig_t *cs
return -1; // not found any CSI-resource in current DL BWP associated with this CSI-RS ID
}
static uint8_t set_csirs_measurement_bitmap(NR_CSI_MeasConfig_t *csi_measconfig, NR_CSI_ResourceConfigId_t csi_res_id)
static uint8_t set_csirs_measurement_bitmap(const NR_UE_MAC_INST_t *mac,
NR_CSI_MeasConfig_t *csi_measconfig,
NR_CSI_ResourceConfigId_t csi_res_id,
fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu)
{
uint8_t meas_bitmap = 0;
csirs_config_pdu->codebook_n1 = 0;
csirs_config_pdu->codebook_n2 = 0;
csirs_config_pdu->codebook_mode = 0;
if (csi_res_id > NR_maxNrofCSI_ResourceConfigurations)
return meas_bitmap; // CSI-RS for tracking
for(int i = 0; i < csi_measconfig->csi_ReportConfigToAddModList->list.count; i++) {
NR_CSI_ReportConfig_t *report_config = csi_measconfig->csi_ReportConfigToAddModList->list.array[i];
if(report_config->resourcesForChannelMeasurement != csi_res_id)
continue;
const nr_csi_report_t *report = &mac->csi_report_template[i];
if (report->reportConfigId == report_config->reportConfigId && report->N1 > 0 && report->N2 > 0) {
if (csirs_config_pdu->codebook_n1 != 0
&& (csirs_config_pdu->codebook_n1 != report->N1
|| csirs_config_pdu->codebook_n2 != report->N2
|| csirs_config_pdu->codebook_mode != report->codebook_mode)) {
LOG_W(NR_MAC,
"CSI-RS resource %ld is associated with conflicting PMI codebooks; retaining the first one\n",
csi_res_id);
} else {
csirs_config_pdu->codebook_n1 = report->N1;
csirs_config_pdu->codebook_n2 = report->N2;
csirs_config_pdu->codebook_mode = report->codebook_mode;
}
}
// bit 0 RSRP bit 1 RI bit 2 LI bit 3 PMI bit 4 CQI bit 5 i1
switch (report_config->reportQuantity.present) {
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI:
@@ -1218,7 +1241,8 @@ static void nr_schedule_csirs_reception(NR_UE_MAC_INST_t *mac, int frame, int sl
continue;
LOG_D(MAC,"Scheduling reception of CSI-RS in frame %d slot %d\n", frame, slot);
fapi_nr_dl_config_csirs_pdu_rel15_t *csirs_config_pdu = &dl_config->dl_config_list[dl_config->number_pdus].csirs_config_pdu.csirs_config_rel15;
csirs_config_pdu->measurement_bitmap = set_csirs_measurement_bitmap(csi_measconfig, csi_res_id);
csirs_config_pdu->measurement_bitmap =
set_csirs_measurement_bitmap(mac, csi_measconfig, csi_res_id, csirs_config_pdu);
csirs_config_pdu->subcarrier_spacing = mu;
csirs_config_pdu->cyclic_prefix = current_DL_BWP->cyclicprefix ? *current_DL_BWP->cyclicprefix : 0;
+49 -4
View File
@@ -186,6 +186,28 @@ int precoding_weigths_generation(nfapi_nr_pm_list_t *mat,
return pmiq;
}
static int precoding_weights_generation_4x1_high_rank(nfapi_nr_pm_list_t *mat,
int pmiq,
int layers)
{
const uint8_t i11_count = nr_type1_sp_4x1_i11_count(layers);
for (uint8_t i11 = 0; i11 < i11_count; i11++) {
for (uint8_t i2 = 0; i2 < 2; i2++) {
nfapi_nr_pm_pdu_t *pdu = &mat->pmi_pdu[pmiq];
pdu->pm_idx = pmiq + 1; // index zero is the identity matrix
pdu->numLayers = layers;
pdu->num_ant_ports = 8;
AssertFatal(nr_type1_sp_4x1_weights(layers, i11, 0, i2, pdu->weights),
"Invalid rank-%d Type-I single-panel PMI i11=%u i2=%u\n",
layers,
i11,
i2);
pmiq++;
}
}
return pmiq;
}
nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPorts_t antenna_ports)
{
int num_antenna_ports = antenna_ports.N1 * antenna_ports.N2 * antenna_ports.XP;
@@ -207,18 +229,33 @@ nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPort
int O2 = N2 > 1 ? 4 : 1; //Vertical beam oversampling (1 or 4)
int O1 = num_antenna_ports > 2 ? 4 : 1; //Horizontal beam oversampling (1 or 4)
int max_mimo_layers = (num_antenna_ports < NR_MAX_NB_LAYERS) ? num_antenna_ports : NR_MAX_NB_LAYERS;
AssertFatal(max_mimo_layers <= 4, "Max number of layers supported is 4\n");
const int max_mimo_layers = min(num_antenna_ports, NR_MAX_NB_LAYERS);
const int low_codebook_layers = min(max_mimo_layers, 4);
const bool high_rank_4x1 = num_antenna_ports == 8 && antenna_ports.XP == 2 && N1 == 4 && N2 == 1;
AssertFatal(num_antenna_ports < 16, "Max number of antenna ports supported is currently 16\n");
int K1 = 0, K2 = 0;
nfapi_nr_pm_list_t mat = {.num_pm_idx = 0};
for (int i = 0; i < max_mimo_layers; i++) {
memset(gNB->precoding_matrix_size, 0, sizeof(gNB->precoding_matrix_size));
for (int i = 0; i < low_codebook_layers; i++) {
get_K1_K2(N1, N2, &K1, &K2, i + 1);
int i2_size = i == 0 ? 4 : 2;
gNB->precoding_matrix_size[i] = i2_size * N1 * O1 * N2 * O2 * K1 * K2;
mat.num_pm_idx += gNB->precoding_matrix_size[i];
}
if (max_mimo_layers > 4 && !high_rank_4x1) {
LOG_W(NR_MAC,
"Rank-5--8 PMI is only implemented for the eight-port Type-I single-panel 4x1 geometry; "
"unsupported geometry XP=%d N1=%d N2=%d will use identity precoding\n",
antenna_ports.XP,
N1,
N2);
} else if (high_rank_4x1) {
for (int layers = 5; layers <= max_mimo_layers; layers++) {
gNB->precoding_matrix_size[layers - 1] = 2 * nr_type1_sp_4x1_i11_count(layers);
mat.num_pm_idx += gNB->precoding_matrix_size[layers - 1];
}
}
mat.pmi_pdu = malloc16(mat.num_pm_idx * sizeof(*mat.pmi_pdu));
AssertFatal(mat.pmi_pdu != NULL, "out of memory\n");
@@ -274,8 +311,16 @@ nfapi_nr_pm_list_t init_DL_MIMO_codebook(gNB_MAC_INST *gNB, nr_pdsch_AntennaPort
}
int pmiq = 0;
for (int layers = 1; layers <= max_mimo_layers; layers++)
for (int layers = 1; layers <= low_codebook_layers; layers++)
pmiq = precoding_weigths_generation(&mat, pmiq, layers, N1, N2, O1, O2, num_antenna_ports, theta_n, v_lm);
if (high_rank_4x1)
for (int layers = 5; layers <= max_mimo_layers; layers++)
pmiq = precoding_weights_generation_4x1_high_rank(&mat, pmiq, layers);
AssertFatal(pmiq == mat.num_pm_idx,
"Generated %d precoding matrices but allocated %d\n",
pmiq,
mat.num_pm_idx);
return mat;
}
@@ -115,6 +115,51 @@ static void copy_ul_tti_req(nfapi_nr_ul_tti_request_t *to, nfapi_nr_ul_tti_reque
to->groups_list[i] = from->groups_list[i];
}
static bool has_incompatible_dl_harq(gNB_MAC_INST *gNB, int target_layers)
{
UE_iterator(gNB->UE_info.connected_ue_list, UE) {
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
for (int pid = 0; pid < NR_MAX_HARQ_PROCESSES; ++pid) {
const NR_UE_harq_t *harq = &sched_ctrl->harq_processes[pid];
const bool in_flight = harq->is_waiting || harq->round > 0;
if (in_flight && harq->sched_pdsch.nrOfLayers > target_layers) {
LOG_D(NR_MAC,
"[UE %04x] defer TX-mask downshift: HARQ PID %d still uses %d layers (target %d)\n",
UE->rnti,
pid,
harq->sched_pdsch.nrOfLayers,
target_layers);
return true;
}
}
}
return false;
}
/* A physical downshift cannot occur while a buffered retransmission needs
* more layers (and possibly a second codeword) than the target supports.
* The control thread has already made the target visible to new scheduling,
* so the old high-rank HARQ set can only drain from this point onward. */
static void apply_pending_tx_mask_if_harq_safe(gNB_MAC_INST *gNB)
{
nr_antenna_state_t *state = gNB->antenna_state;
uint8_t target_mask = 0;
if (!nr_get_pending_tx_antenna_bitmask(state, &target_mask))
return;
int target_layers = 0;
for (uint8_t mask = target_mask; mask != 0; mask >>= 1)
target_layers += mask & 1u;
if (has_incompatible_dl_harq(gNB, target_layers))
return;
if (nr_apply_requested_tx_antenna_bitmask(state, target_mask))
LOG_I(NR_MAC,
"Applied HARQ-safe TX-mask downshift to 0x%02X (%d layers)\n",
target_mask,
target_layers);
}
void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slot, NR_Sched_Rsp_t *sched_info)
{
protocol_ctxt_t ctxt = {0};
@@ -125,6 +170,7 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo
NR_ServingCellConfigCommon_t *scc = cc->ServingCellConfigCommon;
NR_SCHED_LOCK(&gNB->sched_lock);
apply_pending_tx_mask_if_harq_safe(gNB);
int slots_frame = gNB->frame_structure.numb_slots_frame;
clear_beam_information(&gNB->beam_info, frame, slot, slots_frame);
+323 -124
View File
@@ -13,6 +13,9 @@
#include "LAYER2/NR_MAC_gNB/mac_proto.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include <stdlib.h>
#include <string.h>
/*TAG*/
#include "NR_TAG-Id.h"
@@ -348,6 +351,9 @@ void finish_nr_dl_harq(NR_UE_sched_ctrl_t *sched_ctrl, int harq_pid)
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid];
harq->ndi ^= 1;
if (harq->sched_pdsch.nrOfLayers > 4)
harq->ndi2 ^= 1;
harq->pending_cw_mask = 0;
harq->round = 0;
add_tail_nr_list(&sched_ctrl->available_dl_harq, harq_pid);
@@ -404,6 +410,37 @@ bwp_info_t get_pdsch_bwp_start_size(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE)
return bwp_info;
}
static uint32_t compute_sched_pdsch_tbs(const NR_sched_pdsch_t *sched, uint16_t rb_size, uint8_t codeword)
{
const int cw_layers = nr_pdsch_layers_per_codeword(sched->nrOfLayers, codeword);
return nr_compute_tbs(sched->Qm,
sched->R,
rb_size,
sched->tda_info.nrOfSymbols,
sched->dmrs_parms.N_PRB_DMRS * sched->dmrs_parms.N_DMRS_SLOT,
0,
0,
cw_layers)
>> 3;
}
void update_sched_pdsch_codeword_tbs(NR_sched_pdsch_t *sched)
{
sched->tb_size = compute_sched_pdsch_tbs(sched, sched->rbSize, 0);
sched->tb_size2 = nr_pdsch_num_codewords(sched->nrOfLayers) == 2 ? compute_sched_pdsch_tbs(sched, sched->rbSize, 1) : 0;
}
static void fit_dual_codeword_tbs(NR_sched_pdsch_t *sched, uint32_t requested_bytes, uint16_t max_rb_size)
{
if (nr_pdsch_num_codewords(sched->nrOfLayers) == 1)
return;
update_sched_pdsch_codeword_tbs(sched);
while ((uint64_t)sched->tb_size + sched->tb_size2 < requested_bytes && sched->rbSize < max_rb_size) {
sched->rbSize++;
update_sched_pdsch_codeword_tbs(sched);
}
}
static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
post_process_pdsch_t *pp_pdsch,
int *n_rb_sched,
@@ -420,14 +457,23 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
NR_sched_pdsch_t new_sched = sched_ctrl->harq_processes[current_harq_pid].sched_pdsch;
int layers = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
int pm_index = get_pm_index(nr_mac, UE, dl_bwp->dci_format, layers, nr_mac->radio_config.pdsch_AntennaPorts.XP);
const int layers = new_sched.nrOfLayers;
// If the RI changed between current rtx and a previous transmission
// we need to verify if it is not decreased
// othwise it wouldn't be possible to transmit the same TBS
layers = (layers < new_sched.nrOfLayers) ? layers : new_sched.nrOfLayers;
pm_index = (layers < new_sched.nrOfLayers) ? pm_index : new_sched.pm_index;
/* HARQ retransmission must retain the original layer mapping, codeword
* count, and TBS. Recomputing it from the current RI/TX target can turn a
* rank-8 two-codeword HARQ process into a one-codeword transmission and
* silently discard TB2. Normally a staged mask downshift keeps enough
* physical chains active until this process drains; if that mechanism was
* bypassed, defer instead of emitting an invalid retransmission. */
if (nr_mac->antenna_state != NULL) {
const int active_tx = nr_get_active_tx_antenna_count(nr_mac->antenna_state);
if (active_tx < layers) {
LOG_W(NR_MAC,
"[UE %04x][%4d.%2d] defer DL HARQ PID %d: original rank %d exceeds %d active TX chains\n",
UE->rnti, frame, slot, current_harq_pid, layers, active_tx);
return false;
}
}
const int coresetid = sched_ctrl->coreset->controlResourceSetId;
const int tda = get_dl_tda(nr_mac, slot);
@@ -473,26 +519,43 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
* that we have enough resources */
NR_pdsch_dmrs_t temp_dmrs = get_dl_dmrs_params(scc, dl_bwp, &temp_tda, layers);
uint32_t new_tbs;
uint16_t new_rbSize;
bool success = nr_find_nb_rb(new_sched.Qm,
new_sched.R,
1, // no transform precoding for DL
layers,
temp_tda.nrOfSymbols,
temp_dmrs.N_PRB_DMRS * temp_dmrs.N_DMRS_SLOT,
new_sched.tb_size,
1, /* minimum of 1RB: need to find exact TBS, don't preclude any number */
bwp_size,
&new_tbs,
&new_rbSize);
uint16_t new_rbSize = 0;
if (nr_pdsch_num_codewords(layers) == 1) {
uint32_t new_tbs = 0;
bool success = nr_find_nb_rb(new_sched.Qm,
new_sched.R,
1, // no transform precoding for DL
layers,
temp_tda.nrOfSymbols,
temp_dmrs.N_PRB_DMRS * temp_dmrs.N_DMRS_SLOT,
new_sched.tb_size,
1, /* find an exact retransmission TBS */
bwp_size,
&new_tbs,
&new_rbSize);
if (!success || new_tbs != new_sched.tb_size)
new_rbSize = 0;
} else {
NR_sched_pdsch_t candidate = new_sched;
candidate.nrOfLayers = layers;
candidate.dmrs_parms = temp_dmrs;
candidate.tda_info = temp_tda;
for (uint16_t rb = 1; rb <= bwp_size; rb++) {
candidate.rbSize = rb;
update_sched_pdsch_codeword_tbs(&candidate);
if (candidate.tb_size == new_sched.tb_size && candidate.tb_size2 == new_sched.tb_size2) {
new_rbSize = rb;
break;
}
}
}
if (!success || new_tbs != new_sched.tb_size) {
if (new_rbSize == 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] allocation of DL retransmission failed: new TBS %d of new TDA does not match old TBS %d\n",
UE->rnti,
frame,
slot,
new_tbs,
0,
new_sched.tb_size);
return false; /* the maximum TBsize we might have is smaller than what we need */
}
@@ -505,13 +568,12 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
/* we can allocate it. Overwrite the time_domain_allocation, the number
* of RBs, and the new TB size. The rest is done below */
new_sched.tb_size = new_tbs;
new_sched.rbSize = new_rbSize;
new_sched.time_domain_allocation = tda;
new_sched.nrOfLayers = layers;
new_sched.pm_index = pm_index;
new_sched.dmrs_parms = temp_dmrs;
new_sched.tda_info = temp_tda;
update_sched_pdsch_codeword_tbs(&new_sched);
}
/* Find a free CCE */
@@ -538,6 +600,8 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
if (!get_FeedbackDisabled(UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, current_harq_pid)) {
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
alloc = nr_acknack_scheduling(nr_mac, UE, frame, slot, UE->UE_beam_index, r_pucch, 0);
/* DAI counts PDSCH assignments, not transport blocks. A two-codeword
* PDSCH uses one PUCCH allocation carrying two HARQ-ACK bits. */
if (alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI retransmission\n", UE->rnti, frame, slot);
return false;
@@ -565,7 +629,6 @@ static bool allocate_dl_retransmission(gNB_MAC_INST *nr_mac,
static void ack_reconfig(gNB_MAC_INST *mac, NR_UE_info_t *UE)
{
if (!UE->reconfigCellGroup) {
LOG_W(NR_MAC, "Received ACK for RRCReconfiguration, but nothing to apply!\n");
return;
}
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup);
@@ -607,6 +670,24 @@ static int comparator(const void *p, const void *q)
return 0;
}
/* Keep latency bounded under multi-UE load by preventing the first PF-selected
* UE from consuming the complete contiguous PRB region. The ordinary OAI PF
* loop already supports several frequency-disjoint PDSCHs per slot, but a UE
* with an accumulated RLC burst can otherwise take every PRB and make the
* remaining UEs wait for later DL slots. This switch only divides resources
* when at least two eligible UEs remain; single-UE peak throughput is unchanged. */
static bool dl_fair_rb_cap_enabled(void)
{
static int enabled = -1;
if (enabled < 0) {
const char *setting = getenv("OAI_DL_FAIR_RB_CAP");
enabled = setting != NULL && atoi(setting) != 0;
if (enabled)
LOG_I(NR_MAC, "DL fair-RB cap enabled for multi-UE low-latency scheduling\n");
}
return enabled;
}
static void pf_dl(gNB_MAC_INST *mac,
post_process_pdsch_t *pp_pdsch,
NR_UE_info_t **UE_list,
@@ -692,7 +773,13 @@ static void pf_dl(gNB_MAC_INST *mac,
/* Calculate coeff */
const NR_bler_options_t *bo = &mac->dl_bler;
const int max_mcs_table = current_BWP->mcsTableIdx == 1 ? 27 : 28;
const int max_mcs = min(sched_ctrl->dl_max_mcs, max_mcs_table);
/* The experimental identity-precoded rank-8 path can make a Type-I
* codebook CQI pessimistic for the actual PDSCH precoder. Keep CQI as
* the default cap, but allow a bounded HARQ/BLER probe when explicitly
* requested. The configured min/max MCS still apply in either mode. */
const char *mcs_policy = getenv("OAI_DL_MCS_POLICY");
const bool bler_only = mcs_policy != NULL && strcmp(mcs_policy, "bler_only") == 0;
const int max_mcs = min(bler_only ? bo->max_mcs : sched_ctrl->dl_max_mcs, max_mcs_table);
int selected_mcs;
if (bo->harq_round_max == 1) {
int new_mcs = min(bo->max_mcs, max_mcs);
@@ -700,7 +787,7 @@ static void pf_dl(gNB_MAC_INST *mac,
sched_ctrl->dl_bler_stats.mcs = selected_mcs;
} else
selected_mcs = get_mcs_from_bler(bo, stats, &sched_ctrl->dl_bler_stats, max_mcs, frame);
int l = get_dl_nrOfLayers(sched_ctrl, current_BWP->dci_format);
int l = get_dl_nrOfLayers(mac, sched_ctrl, current_BWP->dci_format);
const uint8_t Qm = nr_get_Qm_dl(selected_mcs, current_BWP->mcsTableIdx);
const uint16_t R = nr_get_code_rate_dl(selected_mcs, current_BWP->mcsTableIdx);
uint32_t tbs = nr_compute_tbs(Qm,
@@ -764,6 +851,16 @@ static void pf_dl(gNB_MAC_INST *mac,
continue;
}
int eligible_ues_left = 0;
if (dl_fair_rb_cap_enabled()) {
for (UEsched_t *candidate = iterator; candidate->UE != NULL; candidate++) {
NR_UE_sched_ctrl_t *candidate_ctrl = &candidate->UE->UE_sched_ctrl;
if (nr_mac_ue_is_active(candidate->UE)
&& candidate_ctrl->available_dl_harq.head >= 0)
eligible_ues_left++;
}
}
/* MCS has been set above */
int tda = get_dl_tda(mac, slot);
AssertFatal(tda >= 0,"Unable to find PDSCH time domain allocation in list\n");
@@ -800,6 +897,15 @@ static void pf_dl(gNB_MAC_INST *mac,
iterator++;
continue;
}
if (eligible_ues_left > 1) {
const int fair_ues = min(remainUEs[beam.idx], eligible_ues_left);
if (fair_ues > 1) {
/* Recompute after every allocation: unused shares from a lightly
* loaded UE remain available to later UEs in this same slot. */
const int fair_rb_cap = (n_rb_sched[beam.idx] + fair_ues - 1) / fair_ues;
max_rbSize = min(max_rbSize, max(min_rbSize, fair_rb_cap));
}
}
int CCEIndex = get_cce_index(mac,
CC_id,
@@ -822,10 +928,13 @@ static void pf_dl(gNB_MAC_INST *mac,
/* Find PUCCH occasion: if it fails, undo CCE allocation (undoing PUCCH
* allocation after CCE alloc fail would be more complex) */
const int l = get_dl_nrOfLayers(mac, sched_ctrl, dl_bwp->dci_format);
int alloc = -1;
if (!get_FeedbackDisabled(iterator->UE->sc_info.downlinkHARQ_FeedbackDisabled_r17, sched_ctrl->available_dl_harq.head)) {
int r_pucch = nr_get_pucch_resource(sched_ctrl->coreset, ul_bwp->pucch_Config, CCEIndex);
alloc = nr_acknack_scheduling(mac, iterator->UE, frame, slot, iterator->UE->UE_beam_index, r_pucch, 0);
/* One PDSCH assignment has one DAI increment even when it carries two
* codewords; the UE multiplexes both ACK bits on this allocation. */
if (alloc < 0) {
LOG_D(NR_MAC, "[UE %04x][%4d.%2d] could not find PUCCH for DL DCI\n", rnti, frame, slot);
reset_beam_status(&mac->beam_info, frame, slot, iterator->UE->UE_beam_index, slots_per_frame, beam.new_beam);
@@ -837,7 +946,6 @@ static void pf_dl(gNB_MAC_INST *mac,
sched_ctrl->cce_index = CCEIndex;
fill_pdcch_vrb_map(mac, CC_id, &sched_ctrl->sched_pdcch, CCEIndex, sched_ctrl->aggregation_level, beam.idx);
int l = get_dl_nrOfLayers(sched_ctrl, dl_bwp->dci_format);
NR_sched_pdsch_t sched_pdsch = {
// rbSize below
.rbStart = rbStart,
@@ -877,6 +985,7 @@ static void pf_dl(gNB_MAC_INST *mac,
max_rbSize,
&sched_pdsch.tb_size,
&sched_pdsch.rbSize);
fit_dual_codeword_tbs(&sched_pdsch, sched_ctrl->num_total_bytes + oh, max_rbSize);
post_process_dlsch(mac, pp_pdsch, iterator->UE, &sched_pdsch);
@@ -943,13 +1052,15 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
pdsch_pdu->SubcarrierSpacing = dl_bwp ? dl_bwp->scs : *scc->ssbSubcarrierSpacing;
pdsch_pdu->CyclicPrefix = dl_bwp && dl_bwp->cyclicprefix ? *dl_bwp->cyclicprefix : 0;
// Codeword information
pdsch_pdu->NrOfCodewords = 1;
pdsch_pdu->targetCodeRate[0] = sched_pdsch->R;
pdsch_pdu->qamModOrder[0] = sched_pdsch->Qm;
pdsch_pdu->mcsIndex[0] = sched_pdsch->mcs;
pdsch_pdu->mcsTable[0] = dl_bwp ? dl_bwp->mcsTableIdx : 0;
pdsch_pdu->rvIndex[0] = nr_get_rv(harq_round % 4);
pdsch_pdu->TBSize[0] = sched_pdsch->tb_size;
pdsch_pdu->NrOfCodewords = nr_pdsch_num_codewords(sched_pdsch->nrOfLayers);
for (int q = 0; q < pdsch_pdu->NrOfCodewords; q++) {
pdsch_pdu->targetCodeRate[q] = sched_pdsch->R;
pdsch_pdu->qamModOrder[q] = sched_pdsch->Qm;
pdsch_pdu->mcsIndex[q] = sched_pdsch->mcs;
pdsch_pdu->mcsTable[q] = dl_bwp ? dl_bwp->mcsTableIdx : 0;
pdsch_pdu->rvIndex[q] = nr_get_rv(harq_round % 4);
pdsch_pdu->TBSize[q] = q == 0 ? sched_pdsch->tb_size : sched_pdsch->tb_size2;
}
pdsch_pdu->dataScramblingId = pdsch_Config && pdsch_Config->dataScramblingIdentityPDSCH ? *pdsch_Config->dataScramblingIdentityPDSCH : *scc->physCellId;
pdsch_pdu->nrOfLayers = sched_pdsch->nrOfLayers;
pdsch_pdu->transmissionScheme = 0;
@@ -961,7 +1072,7 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
pdsch_pdu->SCID = dmrs_parms->n_scid;
pdsch_pdu->dlDmrsScramblingId = dmrs_parms->scrambling_id;
pdsch_pdu->numDmrsCdmGrpsNoData = dmrs_parms->numDmrsCdmGrpsNoData;
pdsch_pdu->dmrsPorts = (1 << sched_pdsch->nrOfLayers) - 1; // FIXME with a better implementation
pdsch_pdu->dmrsPorts = dmrs_parms->dmrs_ports;
// Pdsch Allocation in frequency domain
pdsch_pdu->resourceAlloc = 1;
pdsch_pdu->rbStart = sched_pdsch->rbStart;
@@ -998,6 +1109,105 @@ nfapi_nr_dl_tti_pdsch_pdu_rel15_t *prepare_pdsch_pdu(nfapi_nr_dl_tti_request_pdu
return pdsch_pdu;
}
typedef struct {
int sdus;
int sdu_bytes;
} dlsch_fill_result_t;
static dlsch_fill_result_t fill_dlsch_transport_block(gNB_MAC_INST *mac,
NR_UE_info_t *UE,
uint8_t *tb,
uint32_t tbs,
frame_t frame,
slot_t slot,
bool include_ces)
{
const int module_id = mac->Mod_id;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
dlsch_fill_result_t result = {0};
uint8_t *buf = tb;
if (include_ces)
buf += nr_write_ce_dlsch_pdu(module_id, sched_ctrl, buf, 255, NULL);
DevAssert(buf < tb + tbs);
uint8_t *buf_end = tb + tbs;
start_meas(&mac->rlc_data_req);
if (sched_ctrl->num_total_bytes > 0) {
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
const int lcid = c->lcid;
if (sched_ctrl->rlc_status[lcid].bytes_in_buffer == 0)
continue;
tb_size_t pdu_siz[MAX_NUM_DATA_REQ];
int num = nr_mac_rlc_multi_data_req(module_id,
UE->rnti,
true,
lcid,
buf_end - buf,
(char *)buf,
pdu_siz,
sizeofArray(pdu_siz));
DevAssert(num <= sizeofArray(pdu_siz));
result.sdus += num;
int lcid_bytes = 0;
for (int j = 0; j < num; ++j) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *)buf;
header->R = 0;
header->F = 1;
header->LCID = lcid;
header->L = htons(pdu_siz[j]);
buf += pdu_siz[j] + sizeof(NR_MAC_SUBHEADER_LONG);
result.sdu_bytes += pdu_siz[j];
lcid_bytes += pdu_siz[j];
}
T(T_GNB_MAC_LCID_DL,
T_INT(UE->rnti),
T_INT(frame),
T_INT(slot),
T_INT(lcid),
T_INT(lcid_bytes),
T_INT(nr_rlc_tx_list_occupancy(UE->rnti, lcid)));
UE->mac_stats.dl.lc_bytes[lcid] += lcid_bytes;
}
} else if (get_softmodem_params()->phy_test || get_softmodem_params()->do_ra) {
LOG_D(NR_MAC, "Configuring DL_TX in %d.%d: TBS %d of random data\n", frame, slot, tbs);
if (buf_end - buf > sizeof(NR_MAC_SUBHEADER_LONG)) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *)buf;
header->R = 0;
header->F = 1;
header->LCID = DL_SCH_LCID_PADDING;
buf += sizeof(NR_MAC_SUBHEADER_LONG);
header->L = htons(buf_end - buf);
for (; ((intptr_t)buf) % 4; buf++)
*buf = lrand48() & 0xff;
for (; buf < buf_end - 3; buf += 4)
*(uint32_t *)buf = lrand48();
for (; buf < buf_end; buf++)
*buf = lrand48() & 0xff;
result.sdus++;
}
}
stop_meas(&mac->rlc_data_req);
if (buf_end - buf > 0) {
NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *)buf;
padding->R = 0;
padding->LCID = DL_SCH_LCID_PADDING;
buf++;
memset(buf, 0, buf_end - buf);
}
return result;
}
static void fill_padding_only_transport_block(uint8_t *tb, uint32_t tbs)
{
DevAssert(tbs >= sizeof(NR_MAC_SUBHEADER_FIXED));
memset(tb, 0, tbs);
((NR_MAC_SUBHEADER_FIXED *)tb)->LCID = DL_SCH_LCID_PADDING;
}
void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE_info_t *UE, NR_sched_pdsch_t *sched_pdsch)
{
int CC_id = 0;
@@ -1014,6 +1224,7 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
/* POST processing */
const uint8_t nrOfLayers = sched_pdsch->nrOfLayers;
const uint32_t TBS = sched_pdsch->tb_size;
const uint32_t TBS2 = sched_pdsch->tb_size2;
int8_t current_harq_pid = sched_pdsch->dl_harq_pid;
if (current_harq_pid < 0) {
@@ -1037,6 +1248,19 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
NR_tda_info_t *tda_info = &sched_pdsch->tda_info;
NR_pdsch_dmrs_t *dmrs_parms = &sched_pdsch->dmrs_parms;
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[current_harq_pid];
const int active_codewords = nr_pdsch_num_codewords(nrOfLayers);
if (harq->round == 0) {
harq->pending_cw_mask = (1U << active_codewords) - 1;
} else if (active_codewords == 2) {
DevAssert(harq->pending_cw_mask > 0 && harq->pending_cw_mask < 4);
/* A codeword already ACKed must not be sent again with its old NDI:
* the UE would otherwise deliver its MAC SDUs twice. Give its padding
* replacement a fresh NDI and RV0 while the peer TB is retransmitted. */
if (!(harq->pending_cw_mask & 1))
harq->ndi ^= 1;
if (!(harq->pending_cw_mask & 2))
harq->ndi2 ^= 1;
}
NR_sched_pucch_t *pucch = NULL;
DevAssert(!harq->is_waiting);
if (sched_pdsch->pucch_allocation >= 0) {
@@ -1048,6 +1272,28 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
}
UE->mac_stats.dl.rounds[harq->round]++;
int tpc = nr_mac_get_tpc(&sched_ctrl->pucch_pc);
if (nrOfLayers > 4 && sched_pdsch->rbSize >= 100) {
static bool logged_full_high_rank_shape[32] = {false};
if (!__atomic_exchange_n(&logged_full_high_rank_shape[sched_pdsch->mcs], true, __ATOMIC_RELAXED))
LOG_I(NR_MAC,
"[HIGH_RANK_SCHEDULE] layers=%u RB=%u symbols=%u start=%u "
"MCS=%u Qm=%u R=%u TBS=%u/%u total=%u DMRS_mask=0x%x "
"DMRS_RE_per_RB=%u DMRS_symbols=%u CDM_groups=%u\n",
nrOfLayers,
sched_pdsch->rbSize,
tda_info->nrOfSymbols,
tda_info->startSymbolIndex,
sched_pdsch->mcs,
sched_pdsch->Qm,
sched_pdsch->R,
TBS,
TBS2,
TBS + TBS2,
dmrs_parms->dl_dmrs_symb_pos,
dmrs_parms->N_PRB_DMRS,
dmrs_parms->N_DMRS_SLOT,
dmrs_parms->numDmrsCdmGrpsNoData);
}
LOG_D(NR_MAC,
"%4d.%2d [DLSCH/PDSCH/PUCCH] RNTI %04x DCI L %d start %3d RBs %3d startSymbol %2d nb_symbol %2d dmrspos %x MCS %2d nrOfLayers %d TBS %4d HARQ PID %2d round %d RV %d NDI %d dl_data_to_ULACK %d (%d.%d) PUCCH allocation %d TPC %d\n",
frame,
@@ -1125,6 +1371,10 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
fapi_beam,
nl_tbslbrm,
pduindex);
if (harq->round > 0 && active_codewords == 2)
for (int q = 0; q < active_codewords; q++)
if (!(harq->pending_cw_mask & (1U << q)))
pdsch_pdu->rvIndex[q] = 0;
LOG_D(NR_MAC,"Configuring DCI/PDCCH in %d.%d at CCE %d, rnti %x\n", frame,slot,sched_ctrl->cce_index,rnti);
@@ -1208,15 +1458,28 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
harq->round,
harq->ndi);
if (!get_softmodem_params()->phy_test)
AssertFatal(harq->sched_pdsch.tb_size == TBS,
"UE %04x mismatch between scheduled TBS and buffered TB for HARQ PID %d\n",
AssertFatal(harq->sched_pdsch.tb_size == TBS && harq->sched_pdsch.tb_size2 == TBS2,
"UE %04x mismatch between scheduled TBS (%u,%u) and buffered TB (%u,%u) for HARQ PID %d\n",
UE->rnti,
TBS,
TBS2,
harq->sched_pdsch.tb_size,
harq->sched_pdsch.tb_size2,
current_harq_pid);
else if (harq->sched_pdsch.tb_size != TBS)
else if (harq->sched_pdsch.tb_size != TBS || harq->sched_pdsch.tb_size2 != TBS2)
LOG_E(NR_MAC,
"Mismatch between scheduled TBS and buffered TB for HARQ PID %d. No RTX control in phy-test mode. "
"Possible causes: presence of CSI-RS or DLSCH scheduled in the mixed slot.\n",
current_harq_pid);
if (active_codewords == 2)
for (int q = 0; q < active_codewords; q++) {
if (harq->pending_cw_mask & (1U << q))
continue;
byte_array_t *transport_block = q == 0 ? &harq->transportBlock : &harq->transportBlock2;
const uint32_t cw_tbs = q == 0 ? TBS : TBS2;
uint8_t *buf = allocate_transportBlock_buffer(transport_block, cw_tbs);
fill_padding_only_transport_block(buf, cw_tbs);
}
T(T_GNB_MAC_RETRANSMISSION_DL_PDU_WITH_DATA, T_INT(module_id), T_INT(CC_id), T_INT(rnti),
T_INT(frame), T_INT(slot), T_INT(current_harq_pid), T_INT(harq->round), T_BUFFER(harq->transportBlock.buf, TBS));
UE->mac_stats.dl.total_rbs_retx += sched_pdsch->rbSize;
@@ -1225,94 +1488,24 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
LOG_D(NR_MAC, "Initial HARQ transmission in %d.%d\n", frame, slot);
// Flag HARQ process to start TCI timer at ACK
harq->start_tci_timer = sched_ctrl->UE_mac_ce_ctrl.tci_state_ind.is_scheduled;
uint8_t *buf = allocate_transportBlock_buffer(&harq->transportBlock, TBS);
/* first, write all CEs that might be there */
int written = nr_write_ce_dlsch_pdu(module_id,
sched_ctrl,
(unsigned char *)buf,
255, // no drx
NULL); // contention res id
buf += written;
uint8_t *bufEnd = buf + TBS - written;
DevAssert(TBS > written);
int dlsch_total_bytes = 0;
/* next, get RLC data */
start_meas(&nr_mac->rlc_data_req);
int sdus = 0;
if (sched_ctrl->num_total_bytes > 0) {
/* loop over all activated logical channels */
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
const int lcid = c->lcid;
if (sched_ctrl->rlc_status[lcid].bytes_in_buffer == 0)
continue; // no data for this LC tbs_size_t len = 0;
tb_size_t pdu_siz[MAX_NUM_DATA_REQ];
int num = nr_mac_rlc_multi_data_req(module_id, rnti, true, lcid, bufEnd - buf, (char *)buf, pdu_siz, sizeofArray(pdu_siz));
DevAssert(num <= sizeofArray(pdu_siz));
sdus += num;
int lcid_bytes = 0;
for (int j = 0; j < num; ++j) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *) buf;
header->R = 0;
header->F = 1;
header->LCID = lcid;
header->L = htons(pdu_siz[j]);
buf += pdu_siz[j] + sizeof(NR_MAC_SUBHEADER_LONG);
dlsch_total_bytes += pdu_siz[j];
lcid_bytes += pdu_siz[j];
}
T(T_GNB_MAC_LCID_DL, T_INT(rnti), T_INT(frame), T_INT(slot), T_INT(lcid), T_INT(lcid_bytes), T_INT(nr_rlc_tx_list_occupancy(rnti, lcid)));
UE->mac_stats.dl.lc_bytes[lcid] += lcid_bytes;
}
} else if (get_softmodem_params()->phy_test || get_softmodem_params()->do_ra) {
/* we will need the large header, phy-test typically allocates all
* resources and fills to the last byte below */
LOG_D(NR_MAC, "Configuring DL_TX in %d.%d: TBS %d of random data\n", frame, slot, TBS);
if (bufEnd-buf > sizeof(NR_MAC_SUBHEADER_LONG) ) {
NR_MAC_SUBHEADER_LONG *header = (NR_MAC_SUBHEADER_LONG *) buf;
// fill dlsch_buffer with random data
header->R = 0;
header->F = 1;
header->LCID = DL_SCH_LCID_PADDING;
buf += sizeof(NR_MAC_SUBHEADER_LONG);
header->L = htons(bufEnd-buf);
for (; ((intptr_t)buf) % 4; buf++)
*buf = lrand48() & 0xff;
for (; buf < bufEnd - 3; buf += 4) {
uint32_t *buf32 = (uint32_t *)buf;
*buf32 = lrand48();
}
for (; buf < bufEnd; buf++)
*buf = lrand48() & 0xff;
sdus +=1;
}
for (int q = 0; q < nr_pdsch_num_codewords(nrOfLayers); q++) {
const uint32_t cw_tbs = q == 0 ? TBS : sched_pdsch->tb_size2;
byte_array_t *transport_block = q == 0 ? &harq->transportBlock : &harq->transportBlock2;
uint8_t *buf = allocate_transportBlock_buffer(transport_block, cw_tbs);
dlsch_fill_result_t fill = fill_dlsch_transport_block(nr_mac, UE, buf, cw_tbs, frame, slot, q == 0);
sdus += fill.sdus;
dlsch_total_bytes += fill.sdu_bytes;
}
stop_meas(&nr_mac->rlc_data_req);
// Add padding header and zero rest out if there is space left
if (bufEnd-buf > 0) {
NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *) buf;
padding->R = 0;
padding->LCID = DL_SCH_LCID_PADDING;
buf += 1;
memset(buf,0,bufEnd-buf);
buf=bufEnd;
}
UE->mac_stats.dl.total_bytes += TBS;
UE->mac_stats.dl.current_bytes = TBS;
const uint32_t total_tbs = TBS + sched_pdsch->tb_size2;
UE->mac_stats.dl.total_bytes += total_tbs;
UE->mac_stats.dl.current_bytes = total_tbs;
UE->mac_stats.dl.total_rbs += sched_pdsch->rbSize;
UE->mac_stats.dl.num_mac_sdu += sdus;
UE->mac_stats.dl.current_rbs = sched_pdsch->rbSize;
UE->mac_stats.dl.last_layers = sched_pdsch->nrOfLayers;
UE->mac_stats.dl.total_sdu_bytes += dlsch_total_bytes;
nr_mac->mac_stats.dl.used_prb_aggregate += sched_pdsch->rbSize;
@@ -1349,10 +1542,16 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE
const int ntx_req = pdsch->TX_req->Number_of_PDUs;
nfapi_nr_pdu_t *tx_req = &pdsch->TX_req->pdu_list[ntx_req];
tx_req->PDU_index = pduindex;
tx_req->num_TLV = 1;
tx_req->TLVs[0].length = TBS;
tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, tx_req->TLVs[0].length);
memcpy(tx_req->TLVs[0].value.direct, harq->transportBlock.buf, TBS);
tx_req->num_TLV = nr_pdsch_num_codewords(nrOfLayers);
uint32_t total_tlv_length = 0;
for (int q = 0; q < tx_req->num_TLV; q++) {
const uint32_t cw_tbs = q == 0 ? TBS : TBS2;
const byte_array_t *transport_block = q == 0 ? &harq->transportBlock : &harq->transportBlock2;
tx_req->TLVs[q].length = cw_tbs;
memcpy(tx_req->TLVs[q].value.direct, transport_block->buf, cw_tbs);
total_tlv_length += cw_tbs;
}
tx_req->PDU_length = compute_PDU_length(tx_req->num_TLV, total_tlv_length);
pdsch->TX_req->Number_of_PDUs++;
pdsch->TX_req->SFN = frame;
pdsch->TX_req->Slot = slot;
@@ -166,15 +166,7 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)
.tda_info = tda_info,
};
sched_ctrl->dl_bler_stats.mcs = target_dl_mcs; /* for logging output */
sched_pdsch.tb_size = nr_compute_tbs(sched_pdsch.Qm,
sched_pdsch.R,
sched_pdsch.rbSize,
tda_info.nrOfSymbols,
sched_pdsch.dmrs_parms.N_PRB_DMRS * sched_pdsch.dmrs_parms.N_DMRS_SLOT,
0 /* N_PRB_oh, 0 for initialBWP */,
0 /* tb_scaling */,
target_dl_Nl)
>> 3;
update_sched_pdsch_codeword_tbs(&sched_pdsch);
post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch);
@@ -101,14 +101,13 @@ int get_ssbidx_from_beam(gNB_MAC_INST *mac, int beam_idx)
return 0;
}
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
{
// TODO check this but it should be enough for now
// if there is not csi report activated RI is 0 from initialization
if(dci_format == NR_DL_DCI_FORMAT_1_0)
return 1;
else
return sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
int layers = (dci_format == NR_DL_DCI_FORMAT_1_0) ? 1
: sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
if (mac && mac->antenna_state)
return nr_get_effective_mimo_layers(mac->antenna_state, layers);
return layers;
}
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format)
@@ -165,63 +164,126 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
int layers,
int xp_pdsch_antenna_ports)
{
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
const int N1 = csi_report->N1;
const int N2 = csi_report->N2;
const int antenna_ports = (N1 * N2) << 1;
if (antenna_ports < 2)
return 0; // single antenna port
if (dci_format == NR_DL_DCI_FORMAT_1_0 || nrmac->identity_pm || xp_pdsch_antenna_ports == 1)
return 0; //identity matrix (basic 5G configuration handled by PMI report is with XP antennas)
if (layers < 1 || layers > NR_MAX_NB_LAYERS || nrmac->precoding_matrix_size[layers - 1] == 0)
return 0;
const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);
int prev_layers_size = 0;
for (int i = 1; i < layers; i++)
prev_layers_size += nrmac->precoding_matrix_size[i - 1];
// need to return PM index to matrix initialized in init_DL_MIMO_codebook
// index 0 is for identity matrix
// order of matrices depends on layers to be transmitted
// elements from 1 to n for 1 layer
// elements from n+1 to m for 2 layers etc.
if (antenna_ports == 2)
return 1 + prev_layers_size + x2; // 0 for identity matrix
else {
int idx = layers - 1;
// the order of i1x in X1 report needs to be verified
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
// it says: PMI wideband information fields X1 , from left to right
int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
if (layers == 1 && bitlen != 0) {
LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
return 0;
int reported_ri_layers = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri + 1;
if (layers != reported_ri_layers) {
LOG_D(NR_MAC, "PMI mismatch: using %d layers but RI report is for %d layers, using identity matrix\n",
layers, reported_ri_layers);
return 0;
}
const int i13 = x1 & ((1 << bitlen) - 1);
x1 >>= bitlen;
bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
const int i12 = x1 & ((1 << bitlen) - 1);
x1 >>= bitlen;
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
const int i11 = x1 & ((1 << bitlen) - 1);
const int i2 = x2;
int k1, k2;
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // get indices k1 and k2 for PHY matrix (not actual k1 and k2 values)
const int O2 = N2 == 1 ? 1 : 4;
int O1 = 4; //Horizontal beam oversampling = 4 for more than 2 antenna ports
int max_i2 = 0;
int lay_index = 0;
max_i2 = layers == 1 ? 4 : 2;
int K1, K2;
get_K1_K2(N1, N2, &K1, &K2, layers);
// computing precoding matrix index according to rule set in allocation function init_codebook_gNB
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1) + (k1 * max_i2 * N1 * O1 * N2 * O2) + (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
return 1 + prev_layers_size + lay_index;
}
nr_antenna_state_t* ant_state = nrmac->antenna_state;
if (ant_state) {
if (nr_is_in_recovery_mode(ant_state)) {
LOG_D(NR_MAC, "[ANTENNA_MUTE] Recovery mode active, using identity matrix\n");
return 0;
}
const int active_tx = nr_get_active_tx_antenna_count(ant_state);
if (active_tx < ant_state->num_tx_antennas) {
// A full-panel codebook spreads every layer over ports that the RU
// subsequently blanks. Identity mapping instead places the
// reduced layer count one-to-one on the remaining active ports.
LOG_D(NR_MAC,
"[ANTENNA_MUTE] %d/%u TX ports active, using identity matrix\n",
active_tx,
ant_state->num_tx_antennas);
return 0;
}
}
const int report_id = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id;
const nr_csi_report_t *csi_report = &UE->csi_report_template[report_id];
const int N1 = csi_report->N1;
const int N2 = csi_report->N2;
const int antenna_ports = (N1 * N2) << 1;
if (antenna_ports < 2) return 0; // single antenna port
if (antenna_ports == 8 && (layers == 3 || layers == 4)) {
LOG_D(NR_MAC,
"Using identity matrix for rank-%d over eight ports: the legacy UE rank-3/4 detector "
"does not support non-identity eight-port precoding\n",
layers);
return 0;
}
int x1 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x1;
const int x2 = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2;
LOG_D(NR_MAC,"PMI report: x1 %d x2 %d layers: %d\n", x1, x2, layers);
int prev_layers_size = 0;
for (int i = 1; i < layers; i++)
prev_layers_size += nrmac->precoding_matrix_size[i - 1];
// need to return PM index to matrix initialized in init_DL_MIMO_codebook
// index 0 is for identity matrix
// order of matrices depends on layers to be transmitted
// elements from 1 to n for 1 layer
// elements from n+1 to m for 2 layers etc.
int pm_index;
if (antenna_ports == 2)
pm_index = 1 + prev_layers_size + x2; // 0 for identity matrix
else {
int idx = layers - 1;
// the order of i1x in X1 report needs to be verified
// the standard is not very clear (Table 6.3.1.1.2-7 in 38.212)
// it says: PMI wideband information fields X1 , from left to right
int bitlen = csi_report->csi_meas_bitlen.pmi_i13_bitlen[idx];
if (layers == 1 && bitlen != 0) {
LOG_E(NR_MAC, "Invalid i13 bit length %d for single layer! It should be 0\n", bitlen);
return 0;
}
const int i13 = x1 & ((1 << bitlen) - 1);
x1 >>= bitlen;
bitlen = csi_report->csi_meas_bitlen.pmi_i12_bitlen[idx];
const int i12 = x1 & ((1 << bitlen) - 1);
x1 >>= bitlen;
bitlen = csi_report->csi_meas_bitlen.pmi_i11_bitlen[idx];
const int i11 = x1 & ((1 << bitlen) - 1);
const int i2 = x2;
int lay_index = 0;
if (layers > 4) {
const int i11_count = nr_type1_sp_4x1_i11_count(layers);
if (antenna_ports != 8 || N1 != 4 || N2 != 1 || i11_count == 0
|| i11 >= i11_count || i12 != 0 || i13 != 0 || i2 < 0 || i2 >= 2) {
LOG_W(NR_MAC,
"Unsupported rank-%d high-rank PMI for N1=%d N2=%d: i11=%d i12=%d i13=%d i2=%d; "
"using identity matrix\n",
layers,
N1,
N2,
i11,
i12,
i13,
i2);
return 0;
}
// High-rank matrices are generated in i11-major, i2-minor order.
lay_index = i11 * 2 + i2;
} else {
int k1, k2;
get_k1_k2_indices(layers, N1, N2, i13, &k1, &k2); // indices for the generated PHY matrix
const int O2 = N2 == 1 ? 1 : 4;
const int O1 = 4; // horizontal beam oversampling for more than two antenna ports
const int max_i2 = layers == 1 ? 4 : 2;
int K1, K2;
get_K1_K2(N1, N2, &K1, &K2, layers);
// computing precoding matrix index according to init_DL_MIMO_codebook ordering
lay_index = i2 + (i11 * max_i2) + (i12 * max_i2 * N1 * O1)
+ (k1 * max_i2 * N1 * O1 * N2 * O2)
+ (k2 * max_i2 * N1 * O1 * N2 * O2 * K1);
}
pm_index = 1 + prev_layers_size + lay_index;
}
int max_valid_pm = prev_layers_size + nrmac->precoding_matrix_size[layers - 1];
if (pm_index < 1 || pm_index > max_valid_pm) {
LOG_W(NR_MAC, "PM index %d out of range [1, %d] for %d layers, using identity matrix\n",
pm_index, max_valid_pm, layers);
return 0;
}
return pm_index;
}
// look-up table for AMC. Based on BLER vs SNR curves from the nr_dlsim simulation
@@ -304,6 +366,11 @@ NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
{
NR_pdsch_dmrs_t dmrs = {0};
int frontloaded_symb = 1; // default value
NR_PDSCH_Config_t *pdsch_Config = dl_bwp ? dl_bwp->pdsch_Config : NULL;
NR_DMRS_DownlinkConfig_t *dmrs_Config = NULL;
if (pdsch_Config)
dmrs_Config = tda_info->mapping_type == typeB ? pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup
: pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
nr_dci_format_t dci_format = dl_bwp ? dl_bwp->dci_format : NR_DL_DCI_FORMAT_1_0;
if (dci_format == NR_DL_DCI_FORMAT_1_0) {
dmrs.numDmrsCdmGrpsNoData = tda_info->nrOfSymbols <= 2 ? 1 : 2;
@@ -338,19 +405,29 @@ NR_pdsch_dmrs_t get_dl_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
dmrs.numDmrsCdmGrpsNoData = 2;
frontloaded_symb = 1;
break;
case 5:
case 6:
case 7:
case 8:
AssertFatal(dmrs_Config && dmrs_Config->dmrs_Type == NULL && dmrs_Config->maxLength,
"Rank %d PDSCH requires DMRS type 1 with maxLength len2\n",
Layers);
/* TS 38.212 Table 7.3.1.2.2-2, two-codeword rows 0--3. */
dmrs.dmrs_ports_id = Layers - 5;
dmrs.numDmrsCdmGrpsNoData = 2;
frontloaded_symb = 2;
break;
default:
AssertFatal(1==0,"Number of layers %d\n not supported or not valid\n",Layers);
}
}
dmrs.n_scid = 0;
static const uint16_t two_cw_dmrs_ports[] = {0x1f, 0x5f, 0x7f, 0xff};
dmrs.dmrs_ports = Layers > 4 ? two_cw_dmrs_ports[Layers - 5] : (1U << Layers) - 1;
long *scramblingID = NULL;
NR_PDSCH_Config_t *pdsch_Config = dl_bwp ? dl_bwp->pdsch_Config : NULL;
if (pdsch_Config) {
NR_DMRS_DownlinkConfig_t *dmrs_Config = tda_info->mapping_type == typeB ?
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeB->choice.setup :
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
dmrs.phaseTrackingRS = dmrs_Config->phaseTrackingRS ? dmrs_Config->phaseTrackingRS->choice.setup : NULL;
dmrs.phaseTrackingRS = Layers > 4 ? NULL : (dmrs_Config->phaseTrackingRS ? dmrs_Config->phaseTrackingRS->choice.setup : NULL);
dmrs.dmrsConfigType = dmrs_Config->dmrs_Type != NULL;
scramblingID = dmrs.n_scid ? dmrs_Config->scramblingID1 : dmrs_Config->scramblingID0;
} else {
@@ -821,7 +898,12 @@ int get_mcs_from_bler(const NR_bler_options_t *bler_options,
if (diff < 0) // wrap around
diff += 1024;
max_mcs = min(max_mcs, bler_options->max_mcs);
/* Keep the configured interval valid even when CSI supplies a lower cap.
* In particular, min_mcs == max_mcs is an explicit fixed-MCS request and
* must not alternate between the CQI fallback and the configured value at
* each BLER update boundary. */
max_mcs = max((int)bler_options->min_mcs,
min(max_mcs, (int)bler_options->max_mcs));
const uint8_t old_mcs = min(bler_stats->mcs, max_mcs);
if (diff < BLER_UPDATE_FRAME)
return old_mcs; // no update
@@ -930,6 +1012,20 @@ dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac,
AssertFatal(harq, "HARQ process should be available for DCI with RNTI %s\n", rnti_types(rnti_type));
dci_payload.harq_pid.val = harq_pid;
dci_payload.ndi = harq->ndi;
if (pdsch_pdu->NrOfCodewords == 2) {
dci_payload.mcs2.val = sched_pdsch->mcs;
dci_payload.ndi2.val = harq->ndi2;
dci_payload.rv2.val = pdsch_pdu->rvIndex[1];
} else {
/* maxNrofCodeWordsScheduledByDCI=n2 keeps the second-TB fields in DCI
* format 1_1 even for a one-codeword (rank <= 4) PDSCH. TS 38.214
* disables that transport block with I_MCS=26 and RV=1. Leaving the
* zero-initialized values here makes the UE treat CW1 as active and
* interpret the antenna-port row through the two-codeword DMRS table. */
dci_payload.mcs2.val = 26;
dci_payload.ndi2.val = 0;
dci_payload.rv2.val = 1;
}
dci_payload.pucch_resource_indicator = pucch ? pucch->resource_indicator : 0;
dci_payload.pdsch_to_harq_feedback_timing_indicator.val = pucch ? pucch->timing_indicator : 0; // PDSCH to HARQ TI
dci_payload.dai[0].val = pucch ? (pucch->dai_c - 1) & 3 : 0;
@@ -2518,8 +2614,10 @@ void delete_nr_ue_data(NR_UE_info_t *UE, uid_allocator_t *uia)
destroy_nr_list(&sched_ctrl->available_ul_harq);
destroy_nr_list(&sched_ctrl->feedback_ul_harq);
destroy_nr_list(&sched_ctrl->retrans_ul_harq);
for (int i = 0; i < NR_MAX_HARQ_PROCESSES; ++i)
for (int i = 0; i < NR_MAX_HARQ_PROCESSES; ++i) {
free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock);
free_transportBlock_buffer(&sched_ctrl->harq_processes[i].transportBlock2);
}
free_sched_pucch_list(sched_ctrl);
uid_linear_allocator_free(uia, UE->uid);
LOG_I(NR_MAC, "Remove NR rnti 0x%04x\n", UE->rnti);
@@ -3614,9 +3712,16 @@ void nr_mac_update_timers(module_id_t module_id)
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
if (nr_mac_check_release(sched_ctrl)) {
// trigger release first as nr_mac_release_ue() invalidates UE ptr
nr_mac_trigger_release_complete(mac, UE->rnti);
nr_mac_release_ue(mac, UE->rnti);
// nakaolab UE release race fix:
// Save rnti before nr_mac_trigger_release_complete — that call drops
// the scheduler lock briefly (to send F1AP UE Context Release Complete),
// and another thread (e.g., RRC DL handler) can free this UE during
// that window, leaving UE->rnti pointing at freed memory.
rnti_t rnti = UE->rnti;
nr_mac_trigger_release_complete(mac, rnti);
// Re-validate after lock re-acquired: skip release if already gone.
if (find_nr_UE(&mac->UE_info, rnti) != NULL)
nr_mac_release_ue(mac, rnti);
// go back to examine the next UE, which is at the position the
// current UE was
UE--;
+250 -37
View File
@@ -15,6 +15,24 @@
#include "common/ran_context.h"
#include "common/utils/nr/nr_common.h"
#include "nfapi/oai_integration/vendor_ext.h"
/* DAI counts PDSCH assignments, while O_ACK counts HARQ-ACK bits. With
* maxNrofCodeWordsScheduledByDCI=n2, the type-2 codebook reserves two ACK
* bits per assignment even when DCI disables the second transport block.
* The reserved bit still occupies the UCI payload, but it must not turn a
* successfully decoded one-codeword PDSCH into a HARQ failure. */
static uint8_t harq_ack_bits_per_assignment(const NR_UE_info_t *UE, bool is_common)
{
if (is_common)
return 1;
const NR_PDSCH_Config_t *pdsch_config = UE->current_DL_BWP.pdsch_Config;
return pdsch_config != NULL && pdsch_config->maxNrofCodeWordsScheduledByDCI != NULL
&& *pdsch_config->maxNrofCodeWordsScheduledByDCI
== NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2
? 2
: 1;
}
static void nr_fill_nfapi_pucch(gNB_MAC_INST *nrmac, frame_t frame, slot_t slot, const NR_sched_pucch_t *pucch, NR_UE_info_t* UE)
{
@@ -48,14 +66,15 @@ static void nr_fill_nfapi_pucch(gNB_MAC_INST *nrmac, frame_t frame, slot_t slot,
future_ul_tti_req->n_pdus += 1;
LOG_D(NR_MAC,
"%s %4d.%2d Scheduling pucch reception in %4d.%2d: bits SR %d, DAI %d, CSI %d on res %d\n",
pucch->dai_c>0 ? "pucch_acknack" : "",
"%s %4d.%2d Scheduling pucch reception in %4d.%2d: bits SR %d, DAI %d, HARQ-ACK %d, CSI %d on res %d\n",
pucch->harq_ack_bits > 0 ? "pucch_acknack" : "",
frame,
slot,
pucch->frame,
pucch->ul_slot,
pucch->sr_flag,
pucch->dai_c,
pucch->harq_ack_bits,
pucch->csi_bits,
pucch->resource_indicator);
NR_COMMON_channels_t * common_ch=nrmac->common_channels;
@@ -75,7 +94,7 @@ static void nr_fill_nfapi_pucch(gNB_MAC_INST *nrmac, frame_t frame, slot_t slot,
UE,
pucch->resource_indicator,
pucch->csi_bits,
pucch->dai_c,
pucch->harq_ack_bits,
pucch->sr_flag,
pucch->r_pucch,
nrmac->beam_info.beam_mode);
@@ -151,7 +170,7 @@ static void schedule_pucch_core(gNB_MAC_INST *nrmac, NR_UE_info_t *UE, frame_t f
return;
}
const uint16_t O_ack = curr_pucch->dai_c;
const uint16_t O_ack = curr_pucch->harq_ack_bits;
const uint16_t O_csi = curr_pucch->csi_bits;
const uint8_t O_sr = curr_pucch->sr_flag;
LOG_D(NR_MAC,
@@ -359,10 +378,15 @@ int get_pucch_resourceid(NR_PUCCH_Config_t *pucch_Config, int O_uci, int pucch_r
return *resource_id;
}
static void handle_dl_harq(gNB_MAC_INST *mac, NR_UE_info_t * UE, int8_t harq_pid, bool success, int harq_round_max)
static void handle_dl_harq(gNB_MAC_INST *mac, NR_UE_info_t * UE, int8_t harq_pid, uint8_t ack_mask, int harq_round_max)
{
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid];
const uint8_t active_mask = harq->sched_pdsch.nrOfLayers > 4 ? 3 : 1;
harq->pending_cw_mask = nr_pdsch_pending_after_ack(harq->pending_cw_mask, active_mask, ack_mask);
LOG_D(NR_MAC, "DL HARQ UE %04x PID %d ACK mask 0x%x pending CW mask 0x%x round %u\n",
UE->rnti, harq_pid, ack_mask, harq->pending_cw_mask, harq->round);
const bool success = harq->pending_cw_mask == 0;
harq->feedback_slot = -1;
harq->is_waiting = false;
if (success) {
@@ -497,7 +521,9 @@ static void evaluate_sinr_report(NR_UE_info_t *UE,
stats->num_sinr_meas++;
const int mcs_table = UE->current_DL_BWP.mcsTableIdx;
const int nrOfLayers = get_dl_nrOfLayers(sched_ctrl, UE->current_DL_BWP.dci_format);
/* NULL mac arg: SINR-based MCS estimation skips antenna-state rank cap
(caller doesn't have nrmac in scope; this is only for max-MCS hint). */
const int nrOfLayers = get_dl_nrOfLayers(NULL, sched_ctrl, UE->current_DL_BWP.dci_format);
sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->r[0].SINRx10, nrOfLayers);
LOG_D(MAC, "Reported SSB-SINR = %01f, dl_max_mcs %d\n", sinr_report->r[0].SINRx10 / 10.0, sched_ctrl->dl_max_mcs);
@@ -631,7 +657,7 @@ static void evaluate_cqi_report(uint8_t *payload,
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb = temp_cqi;
LOG_D(MAC,"Wide-band CQI for the first TB %d\n", temp_cqi);
if (cqi_bitlen > 4) {
temp_cqi = pickandreverse_bits(payload, 4, cumul_bits);
temp_cqi = pickandreverse_bits(payload, 4, cumul_bits + 4);
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_2tb = temp_cqi;
LOG_D(MAC,"Wide-band CQI for the second TB %d\n", temp_cqi);
}
@@ -716,6 +742,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
int cumul_bits = 0;
int r_index = -1;
int new_bf_index = -1;
bool link_csi_decoded = false;
for (int csi_report_id = 0; csi_report_id < csi_MeasConfig->csi_ReportConfigToAddModList->list.count; csi_report_id++) {
nr_csi_report_t *csi_report = &UE->csi_report_template[csi_report_id];
csi_report->nb_of_csi_ssb_report = 0;
@@ -761,12 +788,25 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
cumul_bits += cri_bitlen;
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
if (ri_bitlen)
if (ri_bitlen) {
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
} else {
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
* Pick that RI directly — without this, r_index stays -1 and trips
* the DevAssert(ri>=0) in evaluate_cqi_report. */
for (int i = 0; i < 8; i++) {
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
r_index = i;
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
break;
}
}
}
cumul_bits += ri_bitlen;
if (r_index != -1)
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
link_csi_decoded |= r_index >= 0;
break;
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_PMI_CQI:
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report = true;
@@ -775,8 +815,20 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
cumul_bits += cri_bitlen;
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
if (ri_bitlen)
if (ri_bitlen) {
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
} else {
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
* Pick that RI directly — without this, r_index stays -1 and trips
* the DevAssert(ri>=0) in evaluate_cqi_report. */
for (int i = 0; i < 8; i++) {
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
r_index = i;
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
break;
}
}
}
cumul_bits += ri_bitlen;
if (r_index != -1)
skip_zero_padding(&cumul_bits, csi_report, r_index, bitlen);
@@ -784,6 +836,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id = csi_report_id;
cumul_bits += pmi_bitlen;
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
link_csi_decoded |= r_index >= 0;
break;
case NR_CSI_ReportConfig__reportQuantity_PR_cri_RI_LI_PMI_CQI:
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.print_report = true;
@@ -792,8 +845,20 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
evaluate_cri_report(payload, cri_bitlen, cumul_bits, sched_ctrl);
cumul_bits += cri_bitlen;
ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen;
if (ri_bitlen)
if (ri_bitlen) {
r_index = evaluate_ri_report(payload, ri_bitlen, csi_report->csi_meas_bitlen.ri_restriction, cumul_bits, sched_ctrl);
} else {
/* nakaolab: ri_bitlen=0 (single RI allowed via ri_restriction).
* Pick that RI directly — without this, r_index stays -1 and trips
* the DevAssert(ri>=0) in evaluate_cqi_report. */
for (int i = 0; i < 8; i++) {
if ((csi_report->csi_meas_bitlen.ri_restriction >> i) & 0x01) {
r_index = i;
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.ri = i;
break;
}
}
}
cumul_bits += ri_bitlen;
li_bitlen = evaluate_li_report(payload, csi_report, cumul_bits, r_index, sched_ctrl);
cumul_bits += li_bitlen;
@@ -803,6 +868,7 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.csi_report_id = csi_report_id;
cumul_bits += pmi_bitlen;
evaluate_cqi_report(payload, csi_report, cumul_bits, r_index, UE, cqi_table);
link_csi_decoded |= r_index >= 0;
break;
default:
AssertFatal(1 == 0, "Invalid or not supported CSI measurement report\n");
@@ -810,6 +876,10 @@ static void extract_pucch_csi_report(NR_CSI_MeasConfig_t *csi_MeasConfig,
}
}
}
if (link_csi_decoded) {
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_frame = frame;
sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.report_slot = slot;
}
if ((new_bf_index !=-1) && !nrmac->radio_config.do_TCI)
// Trigger RRCReconfiguration. Need to be out of the for loop as it may modify csi_MeasConfig
beam_switching_procedure(nrmac, UE, new_bf_index);
@@ -840,7 +910,7 @@ static NR_UE_harq_t *find_harq(frame_t frame, slot_t slot, NR_UE_info_t * UE, in
frame,
slot);
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
handle_dl_harq(NULL, UE, pid, false, harq_round_max);
handle_dl_harq(NULL, UE, pid, 0, harq_round_max);
pid = sched_ctrl->feedback_dl_harq.head;
if (pid < 0)
return NULL;
@@ -882,8 +952,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
if (((uci_01->pduBitmap >> 1) & 0x01)) {
// iterate over received harq bits
for (int harq_bit = 0; harq_bit < uci_01->harq.num_harq; harq_bit++) {
const uint8_t harq_value = uci_01->harq.harq_list[harq_bit].harq_value;
for (int harq_bit = 0; harq_bit < uci_01->harq.num_harq;) {
const uint8_t harq_confidence = uci_01->harq.harq_confidence_level;
NR_UE_harq_t *harq = find_harq(frame, slot, UE, nrmac->dl_bler.harq_round_max);
if (!harq) {
@@ -891,11 +960,29 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
break;
}
DevAssert(harq->is_waiting);
const int n_ack_bits = harq_ack_bits_per_assignment(UE, is_ra);
/* Msg4/common HARQ entries can carry the legacy zero layer sentinel.
* Preserve the scheduler's established 0--4 => one-codeword rule. */
const int active_codewords = harq->sched_pdsch.nrOfLayers > 4 ? 2 : 1;
const int8_t pid = sched_ctrl->feedback_dl_harq.head;
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
LOG_D(NR_MAC,"%4d.%2d bit %d pid %d ack/nack %d\n",frame, slot, harq_bit,pid,harq_value);
const bool feedback_valid = harq_confidence == 0 && harq_bit + n_ack_bits <= uci_01->harq.num_harq;
bool success = feedback_valid;
uint8_t ack_mask = 0;
for (int q = 0; q < n_ack_bits && harq_bit + q < uci_01->harq.num_harq; q++) {
const uint8_t harq_value = uci_01->harq.harq_list[harq_bit + q].harq_value;
LOG_D(NR_MAC, "%4d.%2d bit %d pid %d cw %d active %d ack/nack %d\n",
frame, slot, harq_bit + q, pid, q, q < active_codewords, harq_value);
if (q < active_codewords) {
const bool ack = harq_value == 0;
success &= ack;
if (feedback_valid && ack)
ack_mask |= 1U << q;
}
}
if (active_codewords == 2)
success = feedback_valid && (harq->pending_cw_mask & ~ack_mask) == 0;
nr_mac_update_pdcch_closed_loop_adjust(sched_ctrl, harq_confidence != 0);
bool success = harq_value == 0 && harq_confidence == 0;
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
if (success && harq->start_tci_timer) {
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
@@ -903,7 +990,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
nr_timer_start(&sched_ctrl->tci_beam_switch);
harq->start_tci_timer = false;
}
handle_dl_harq(nrmac, UE, pid, success, nrmac->dl_bler.harq_round_max);
handle_dl_harq(nrmac, UE, pid, ack_mask, nrmac->dl_bler.harq_round_max);
if (is_ra) {
bool ue_rejected = nr_check_Msg4_MsgB_Ack(mod_id, frame, slot, UE, success);
if (ue_rejected) {
@@ -919,6 +1006,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con
// DTX for each _bit_, but maybe once for PUCCH is enough? not sure
nr_mac_signal_dtx(&sched_ctrl->pucch_pc);
}
harq_bit += n_ack_bits;
}
// tpc (power control) only if we received AckNack
@@ -984,26 +1072,50 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c
if ((uci_234->pduBitmap >> 1) & 0x01) {
// iterate over received harq bits
for (int harq_bit = 0; harq_bit < uci_234->harq.harq_bit_len; harq_bit++) {
const int acknack = ((uci_234->harq.harq_payload[harq_bit >> 3]) >> harq_bit) & 0x01;
for (int harq_bit = 0; harq_bit < uci_234->harq.harq_bit_len;) {
NR_UE_harq_t *harq = find_harq(frame, slot, UE, RC.nrmac[mod_id]->dl_bler.harq_round_max);
if (!harq) {
LOG_E(NR_MAC, "UE %04x: Could not find a HARQ process at %4d.%2d!\n", UE->rnti, frame, slot);
break;
}
DevAssert(harq->is_waiting);
const int n_ack_bits = harq_ack_bits_per_assignment(UE, false);
/* Msg4/common HARQ entries can carry the legacy zero layer sentinel.
* Preserve the scheduler's established 0--4 => one-codeword rule. */
const int active_codewords = harq->sched_pdsch.nrOfLayers > 4 ? 2 : 1;
const int8_t pid = sched_ctrl->feedback_dl_harq.head;
remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
LOG_D(NR_MAC,"%4d.%2d bit %d pid %d ack/nack %d\n",frame, slot, harq_bit, pid, acknack);
const bool feedback_valid = uci_234->harq.harq_crc != 1 && harq_bit + n_ack_bits <= uci_234->harq.harq_bit_len;
bool success = feedback_valid;
uint8_t ack_mask = 0;
for (int q = 0; q < n_ack_bits && harq_bit + q < uci_234->harq.harq_bit_len; q++) {
const int logical_bit = harq_bit + q;
/* get_downlink_ack() reverses the Type-2 codebook before PUCCH
* encoding. PUCCH formats 2/3/4 expose that wire order directly,
* unlike the format-0/1 harq_list. Convert it back before assigning
* bits to DAI/codeword positions. This matters when CW1 is disabled:
* the reserved NACK and the active CW0 ACK otherwise exchange roles. */
const int payload_bit = uci_234->harq.harq_bit_len - 1 - logical_bit;
const int acknack = (uci_234->harq.harq_payload[payload_bit >> 3] >> (payload_bit & 7)) & 0x01;
LOG_D(NR_MAC, "%4d.%2d logical bit %d payload bit %d pid %d cw %d active %d ack/nack %d\n",
frame, slot, logical_bit, payload_bit, pid, q, q < active_codewords, acknack);
if (q < active_codewords) {
success &= acknack;
if (feedback_valid && acknack)
ack_mask |= 1U << q;
}
}
if (active_codewords == 2)
success = feedback_valid && (harq->pending_cw_mask & ~ack_mask) == 0;
// TCI state switch occurs at the first slot that is after slot n_+ T_HARQ + 3N_sf_slot (8.10.3 of 38.133)
bool success = uci_234->harq.harq_crc != 1 && acknack;
if (success && harq->start_tci_timer) {
int slots = 3 * nrmac->frame_structure.numb_slots_frame / 10;
nr_timer_setup(&sched_ctrl->tci_beam_switch, slots, 1);
nr_timer_start(&sched_ctrl->tci_beam_switch);
harq->start_tci_timer = false;
}
handle_dl_harq(nrmac, UE, pid, success, nrmac->dl_bler.harq_round_max);
handle_dl_harq(nrmac, UE, pid, ack_mask, nrmac->dl_bler.harq_round_max);
harq_bit += n_ack_bits;
}
free(uci_234->harq.harq_payload);
}
@@ -1080,6 +1192,15 @@ static void set_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch, uint16_t *v
}
}
static void clear_pucch0_vrb_occupation(const NR_sched_pucch_t *pucch, uint16_t *vrb_map_UL, const int bwp_start)
{
for (int l = 0; l < pucch->nr_of_symb; l++) {
const uint16_t symb = SL_to_bitmap(pucch->start_symb + l, 1);
const int prb = l == 1 && pucch->second_hop_prb != 0 ? pucch->second_hop_prb : pucch->prb_start;
vrb_map_UL[bwp_start + prb] &= ~symb;
}
}
bool check_bits_vs_coderate_limit(NR_PUCCH_Config_t *pucch_Config, int O_uci, int pucch_resource)
{
int resource_id = get_pucch_resourceid(pucch_Config, O_uci, pucch_resource);
@@ -1125,6 +1246,84 @@ bool check_bits_vs_coderate_limit(NR_PUCCH_Config_t *pucch_Config, int O_uci, in
AssertFatal(false, "No PUCCH resource found\n");
}
/* PUCCH resource set 0 is limited to two UCI bits. A rank-8 PDSCH consumes
* two HARQ-ACK bits by itself, so multiplexing a second assignment changes
* the selected resource to set 1 (format 2). Keep the scheduler's VRB map in
* sync with the resource that nr_configure_pucch() and the UE will select from
* the final payload size. Without this migration every feedback occasion
* accepts only one rank-8 assignment and halves the usable DL slots. */
static bool migrate_pucch_to_large_payload_resource(gNB_MAC_INST *mac,
NR_UE_info_t *UE,
NR_sched_pucch_t *pucch,
int old_uci_bits,
int new_uci_bits,
int ue_beam)
{
if (old_uci_bits > 2 || new_uci_bits <= 2 || pucch->r_pucch >= 0)
return true;
NR_PUCCH_Config_t *pucch_config = UE->current_UL_BWP.pucch_Config;
const int resource_id = get_pucch_resourceid(pucch_config, new_uci_bits, pucch->resource_indicator);
const NR_PUCCH_Resource_t *resource = NULL;
for (int i = 0; i < pucch_config->resourceToAddModList->list.count; i++) {
const NR_PUCCH_Resource_t *candidate = pucch_config->resourceToAddModList->list.array[i];
if (candidate->pucch_ResourceId == resource_id) {
resource = candidate;
break;
}
}
if (resource == NULL || resource->format.present != NR_PUCCH_Resource__format_PR_format2
|| resource->intraSlotFrequencyHopping != NULL || resource->secondHopPRB != NULL) {
LOG_W(NR_MAC,
"UE %04x cannot migrate %d-bit HARQ feedback to the configured non-hopping PUCCH format-2 resource\n",
UE->rnti,
new_uci_bits);
return false;
}
const int n_slots_frame = mac->frame_structure.numb_slots_frame;
NR_beam_alloc_t beam =
beam_allocation_procedure(&mac->beam_info, pucch->frame, pucch->ul_slot, ue_beam, n_slots_frame);
if (beam.idx < 0)
return false;
const int index = ul_buffer_index(pucch->frame, pucch->ul_slot, n_slots_frame, mac->vrb_map_UL_size);
const int bwp_start = UE->current_UL_BWP.BWPStart;
uint16_t *vrb_map_UL = &mac->common_channels[0].vrb_map_UL[beam.idx][index * MAX_BWP_SIZE];
/* Release the format-0 reservation before testing format 2 so overlapping
* resource layouts do not conflict with themselves. Restore it if the
* larger footprint is occupied by another UL allocation. */
clear_pucch0_vrb_occupation(pucch, vrb_map_UL, bwp_start);
const int start_prb = resource->startingPRB;
const int n_prbs = resource->format.choice.format2->nrofPRBs;
const int start_symbol = resource->format.choice.format2->startingSymbolIndex;
const int n_symbols = resource->format.choice.format2->nrofSymbols;
const uint16_t symbol_mask = SL_to_bitmap(start_symbol, n_symbols);
bool available = true;
for (int prb = start_prb; prb < start_prb + n_prbs; prb++) {
if ((vrb_map_UL[bwp_start + prb] & symbol_mask) != 0) {
available = false;
break;
}
}
if (!available) {
set_pucch0_vrb_occupation(pucch, vrb_map_UL, bwp_start);
return false;
}
for (int prb = start_prb; prb < start_prb + n_prbs; prb++)
vrb_map_UL[bwp_start + prb] |= symbol_mask;
pucch->prb_start = start_prb;
pucch->second_hop_prb = 0;
pucch->nr_of_symb = n_symbols;
pucch->start_symb = start_symbol;
LOG_D(NR_MAC,
"UE %04x migrated HARQ feedback from PUCCH format 0 to format 2 for %d UCI bits\n",
UE->rnti,
new_uci_bits);
return true;
}
// this function returns an index to NR_sched_pucch structure
// if the function returns -1 it was not possible to schedule acknack
int nr_acknack_scheduling(gNB_MAC_INST *mac,
@@ -1146,6 +1345,7 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
const NR_UE_UL_BWP_t *ul_bwp = &UE->current_UL_BWP;
const frame_structure_t *fs = &mac->frame_structure;
const int n_slots_frame = fs->numb_slots_frame;
const uint8_t n_ack_bits = harq_ack_bits_per_assignment(UE, is_common);
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_PUCCH_Config_t *pucch_Config = ul_bwp->pucch_Config;
@@ -1181,11 +1381,28 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
if (curr_pucch->active &&
curr_pucch->frame == pucch_frame &&
curr_pucch->ul_slot == pucch_slot) { // if there is already a PUCCH in given frame and slot
LOG_D(NR_MAC, "pucch_acknack DL %4d.%2d, UL_ACK %4d.%2d Bits already in current PUCCH: DAI_C %d CSI %d\n",
frame, slot, pucch_frame, pucch_slot, curr_pucch->dai_c, curr_pucch->csi_bits);
// we can't schedule if short pucch is already full
if (curr_pucch->csi_bits == 0 &&
curr_pucch->dai_c == 2)
LOG_D(NR_MAC, "pucch_acknack DL %4d.%2d, UL_ACK %4d.%2d Bits already in current PUCCH: DAI_C %d ACK %d CSI %d\n",
frame, slot, pucch_frame, pucch_slot, curr_pucch->dai_c, curr_pucch->harq_ack_bits, curr_pucch->csi_bits);
const int old_uci_bits = curr_pucch->csi_bits + curr_pucch->harq_ack_bits + curr_pucch->sr_flag;
const int new_uci_bits = old_uci_bits + n_ack_bits;
/* Preserve the proven two-PDSCH processing budget per feedback
* occasion. Rank-8 needs format 2 because those two assignments
* produce four HARQ-ACK bits, but accepting more assignments here
* can leave the UE insufficient time to decode every transport block
* before their common feedback deadline. The scheduler will try the
* next configured feedback occasion for additional assignments. */
const int max_pdsch_assignments_per_feedback = 2;
if (curr_pucch->dai_c >= max_pdsch_assignments_per_feedback)
continue;
if (new_uci_bits > 2
&& !check_bits_vs_coderate_limit(pucch_Config, new_uci_bits, curr_pucch->resource_indicator))
continue;
if (!migrate_pucch_to_large_payload_resource(mac,
UE,
curr_pucch,
old_uci_bits,
new_uci_bits,
ue_beam))
continue;
// if there is CSI but simultaneous HARQ+CSI is disable we can't schedule
if (curr_pucch->csi_bits > 0 && !curr_pucch->simultaneous_harqcsi)
@@ -1195,21 +1412,17 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
// the number of bits in the check need to include possible SR (1 bit)
// and the ack/nack bit to be scheduled (1 bit)
// so the number of bits already scheduled in current pucch + 2
if (curr_pucch->csi_bits > 0
&& !check_bits_vs_coderate_limit(pucch_Config,
curr_pucch->csi_bits + curr_pucch->dai_c + 2,
curr_pucch->resource_indicator))
continue;
// TODO temporarily limit ack/nak to 3 bits because of performances of polar for PUCCH (required for > 11 bits)
if (curr_pucch->csi_bits > 0 && curr_pucch->dai_c >= 3)
if (curr_pucch->csi_bits > 0 && curr_pucch->harq_ack_bits + n_ack_bits > 3)
continue;
// otherwise we can schedule in this active PUCCH
// no need to check VRB occupation because already done when PUCCH has been activated
curr_pucch->timing_indicator = f;
curr_pucch->dai_c++;
LOG_D(NR_MAC, "DL %4d.%2d, UL_ACK %4d.%2d Scheduling ACK/NACK in PUCCH %d with timing indicator %d DAI %d CSI %d\n",
frame,slot,curr_pucch->frame,curr_pucch->ul_slot,pucch_index,f,curr_pucch->dai_c,curr_pucch->csi_bits);
curr_pucch->harq_ack_bits += n_ack_bits;
LOG_D(NR_MAC, "DL %4d.%2d, UL_ACK %4d.%2d Scheduling ACK/NACK in PUCCH %d with timing indicator %d DAI %d ACK %d CSI %d\n",
frame,slot,curr_pucch->frame,curr_pucch->ul_slot,pucch_index,f,curr_pucch->dai_c,curr_pucch->harq_ack_bits,curr_pucch->csi_bits);
return pucch_index; // index of current PUCCH structure
}
else if (curr_pucch->active) {
@@ -1253,11 +1466,12 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
curr_pucch->ul_slot = pucch_slot;
curr_pucch->timing_indicator = f; // index in the list of timing indicators
curr_pucch->dai_c++;
curr_pucch->harq_ack_bits += n_ack_bits;
curr_pucch->resource_indicator = 0; // each UE has dedicated PUCCH resources
curr_pucch->r_pucch=r_pucch;
LOG_D(NR_MAC, "DL %4d.%2d, UL_ACK %4d.%2d Scheduling ACK/NACK in PUCCH %d with timing indicator %d DAI %d\n",
frame, slot, curr_pucch->frame, curr_pucch->ul_slot, pucch_index, f, curr_pucch->dai_c);
LOG_D(NR_MAC, "DL %4d.%2d, UL_ACK %4d.%2d Scheduling ACK/NACK in PUCCH %d with timing indicator %d DAI %d ACK %d\n",
frame, slot, curr_pucch->frame, curr_pucch->ul_slot, pucch_index, f, curr_pucch->dai_c, curr_pucch->harq_ack_bits);
// blocking resources for current PUCCH in VRB map
set_pucch0_vrb_occupation(curr_pucch, vrb_map_UL, bwp_start);
@@ -1353,4 +1567,3 @@ void nr_sr_reporting(gNB_MAC_INST *nrmac, frame_t SFN, slot_t slot)
}
}
}
+2 -1
View File
@@ -373,7 +373,7 @@ uint16_t get_pm_index(const gNB_MAC_INST *nrmac,
int get_mcs_from_SINRx10(int mcs_table, int SINRx10, int Nl);
uint8_t get_mcs_from_cqi(int mcs_table, int cqi_table, int cqi_idx);
uint8_t get_dl_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
uint8_t get_dl_nrOfLayers(const gNB_MAC_INST *mac, const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
int get_ul_nrOfLayers(const NR_UE_sched_ctrl_t *sched_ctrl, const nr_dci_format_t dci_format);
void free_sched_pucch_list(NR_UE_sched_ctrl_t *sched_ctrl);
@@ -464,6 +464,7 @@ bool prepare_initial_ul_rrc_message(gNB_MAC_INST *mac, NR_UE_info_t *UE);
void send_initial_ul_rrc_message(int rnti, const uint8_t *sdu, sdu_size_t sdu_len, void *data);
void finish_nr_dl_harq(NR_UE_sched_ctrl_t *sched_ctrl, int harq_pid);
void update_sched_pdsch_codeword_tbs(NR_sched_pdsch_t *sched);
void abort_nr_dl_harq(NR_UE_info_t* UE, int8_t harq_pid);
void nr_mac_trigger_release_timer(NR_UE_sched_ctrl_t *sched_ctrl, NR_SubcarrierSpacing_t subcarrier_spacing);
+20
View File
@@ -65,6 +65,7 @@
#include "LAYER2/NR_MAC_COMMON/nr_mac_common.h"
#include "LAYER2/NR_MAC_gNB/mac_config.h"
#include "NR_TAG.h"
#include "PHY/NR_TRANSPORT/nr_antenna_state.h"
/* Defs */
#define MAX_NUM_BWP 5
@@ -187,6 +188,8 @@ typedef struct nr_mac_config_s {
bool force_UL256qam_off;
bool use_deltaMCS;
int maxMIMO_layers;
/// PDSCH dmrs-AdditionalPosition: 0 for pos0, 1 for pos1.
int pdsch_dmrs_additional_position;
bool disable_harq;
nr_power_config_t pusch;
/// SNR threshold needed to put or not a PRB in the black list
@@ -402,7 +405,9 @@ typedef struct NR_sched_pucch {
bool sr_flag;
int csi_bits;
bool simultaneous_harqcsi;
/// Number of PDSCH assignments represented by the DAI field.
uint8_t dai_c;
uint8_t harq_ack_bits; ///< Actual O_ACK payload width (one or two bits per assignment).
uint8_t timing_indicator;
uint8_t resource_indicator;
int r_pucch;
@@ -459,6 +464,7 @@ typedef struct NR_sched_pusch {
typedef struct NR_pdsch_dmrs {
uint8_t dmrs_ports_id;
uint16_t dmrs_ports;
uint8_t N_PRB_DMRS;
uint8_t N_DMRS_SLOT;
uint16_t dl_dmrs_symb_pos;
@@ -484,6 +490,8 @@ typedef struct NR_sched_pdsch {
uint16_t R;
uint8_t Qm;
uint32_t tb_size;
/// Second transport block for rank 5--8 (same MCS/R/Qm for bundled HARQ)
uint32_t tb_size2;
/// DL HARQ PID to use for this UE, or -1 for "any new"
int8_t dl_harq_pid;
@@ -504,12 +512,15 @@ typedef struct NR_sched_pdsch {
typedef struct NR_UE_harq {
bool is_waiting;
uint8_t ndi;
uint8_t ndi2; /* independent NDI for the second transport block */
uint8_t pending_cw_mask; /* original TBs awaiting ACK; padding CWs are not pending */
uint8_t round;
uint16_t feedback_frame;
uint16_t feedback_slot;
/* Transport block to be sent using this HARQ process */
byte_array_t transportBlock;
byte_array_t transportBlock2;
uint32_t tb_size; // size of currently stored TB
bool start_tci_timer;
/// sched_pdsch keeps information on MCS etc used for the initial transmission
@@ -546,6 +557,8 @@ struct CRI_RI_LI_PMI_CQI {
uint8_t wb_cqi_2tb;
uint8_t cqi_table;
uint8_t csi_report_id;
uint16_t report_frame;
uint16_t report_slot;
bool print_report;
};
@@ -714,6 +727,9 @@ typedef struct NR_mac_dir_stats {
uint32_t total_rbs_retx;
uint32_t num_mac_sdu;
uint32_t current_rbs;
// Most recently scheduled spatial layer count. Unlike current_rbs this is
// intentionally persistent so asynchronous E2 sampling does not miss it.
uint8_t last_layers;
} NR_mac_dir_stats_t;
typedef struct NR_mac_stats {
@@ -965,6 +981,10 @@ typedef struct gNB_MAC_INST_s {
nr_mac_config_t radio_config;
nr_rlc_configuration_t rlc_config;
/// Pointer to PHY antenna state (port mute mask) — set in nr-gnb init,
/// used by get_dl_nrOfLayers to cap MIMO rank to active TX port count.
nr_antenna_state_t *antenna_state;
NR_sched_ctrl_sib1_t *sched_ctrlSIB1;
NR_sched_pdcch_t *sched_pdcch_otherSI;
uint16_t cset0_bwp_start;
+44 -15
View File
@@ -32,7 +32,7 @@
#include "utils.h"
#include "xer_encoder.h"
#define NR_MAX_SUPPORTED_DL_LAYERS 4
#define NR_MAX_SUPPORTED_DL_LAYERS NR_MAX_NB_LAYERS
/* Default values for measurement gap configuration */
#define DEFAULT_MGRP NR_GapConfig__mgrp_ms160
@@ -655,10 +655,18 @@ static void set_dl_maxmimolayers(NR_PDSCH_ServingCellConfig_t *pdsch_servingcell
pdsch_servingcellconfig->ext1->maxMIMO_Layers = calloc(1, sizeof(*pdsch_servingcellconfig->ext1->maxMIMO_Layers));
long l = ue_supported_dl_layers(scc, uecap);
long ue_supported_layers = l > 1 ? l : 2; // min UE supported layers = 2
if (maxMIMO_layers == -1)
// nakaolab Patch 6: when called with NULL uecap (initial config before
// capability exchange), ue_supported_dl_layers returns -1. Fall back to
// maxMIMO_layers from config directly instead of clamping to 2.
long ue_supported_layers;
if (l < 1) {
ue_supported_layers = (maxMIMO_layers > 0) ? maxMIMO_layers : NR_MAX_SUPPORTED_DL_LAYERS;
} else {
ue_supported_layers = l;
}
if (maxMIMO_layers == -1)
*pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(NR_MAX_SUPPORTED_DL_LAYERS, ue_supported_layers);
else
else
*pdsch_servingcellconfig->ext1->maxMIMO_Layers = min(maxMIMO_layers, ue_supported_layers);
}
@@ -1282,7 +1290,9 @@ static void config_pucch_resset1(const NR_ServingCellConfigCommon_t *scc,
pucchfmt2->interslotFrequencyHopping = NULL;
pucchfmt2->additionalDMRS = NULL;
pucchfmt2->maxCodeRate = calloc(1,sizeof(*pucchfmt2->maxCodeRate));
*pucchfmt2->maxCodeRate = NR_PUCCH_MaxCodeRate_zeroDot15;
// nakaolab Patch 5: bump from zeroDot15 (19.2 bit capacity) to zeroDot25
// (32 bit capacity) so 4-port CSI report (20 bits) fits in PUCCH.
*pucchfmt2->maxCodeRate = NR_PUCCH_MaxCodeRate_zeroDot25;
pucchfmt2->nrofSlots = NULL;
pucchfmt2->pi2BPSK = NULL;
@@ -1691,8 +1701,14 @@ static NR_PTRS_DownlinkConfig_t *config_dlptrs(const nr_ptrs_config_t *ptrs)
return ptrs_config;
}
static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap, int bwp_Id, bool do_TCI, nr_ptrs_config_t *ptrs)
static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap,
int bwp_Id,
const nr_mac_config_t *configuration)
{
const int antenna_ports = configuration->pdsch_AntennaPorts.N1 * configuration->pdsch_AntennaPorts.N2
* configuration->pdsch_AntennaPorts.XP;
const int configured_layers = configuration->maxMIMO_layers > 0 ? configuration->maxMIMO_layers : antenna_ports;
const bool two_codewords = min(configured_layers, NR_MAX_SUPPORTED_DL_LAYERS) > 4;
NR_SetupRelease_PDSCH_Config_t *setup_pdsch_Config = calloc(1,sizeof(*setup_pdsch_Config));
setup_pdsch_Config->present = NR_SetupRelease_PDSCH_Config_PR_setup;
NR_PDSCH_Config_t *pdsch_Config = calloc(1, sizeof(*pdsch_Config));
@@ -1701,30 +1717,43 @@ static NR_SetupRelease_PDSCH_Config_t *config_pdsch(uint64_t ssb_bitmap, int bwp
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->present = NR_SetupRelease_DMRS_DownlinkConfig_PR_setup;
pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup = calloc(1, sizeof(*pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup));
NR_DMRS_DownlinkConfig_t *dmrs_DownlinkForPDSCH_MappingTypeA = pdsch_Config->dmrs_DownlinkForPDSCH_MappingTypeA->choice.setup;
if (ptrs) {
NR_PTRS_DownlinkConfig_t *ptrs_config = config_dlptrs(ptrs);
if (configuration->ptrs && !two_codewords) {
NR_PTRS_DownlinkConfig_t *ptrs_config = config_dlptrs(configuration->ptrs);
NR_SetupRelease_PTRS_DownlinkConfig_t *phaseTrackingRS = calloc(1, sizeof(*phaseTrackingRS));
phaseTrackingRS->present = NR_SetupRelease_PTRS_DownlinkConfig_PR_setup;
phaseTrackingRS->choice.setup = ptrs_config;
dmrs_DownlinkForPDSCH_MappingTypeA->phaseTrackingRS = phaseTrackingRS;
}
if (configuration->ptrs && two_codewords)
LOG_W(NR_MAC, "PDSCH PTRS is disabled for rank 5--8 until two-codeword PTRS is supported\n");
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_Type = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->maxLength = NULL;
if (two_codewords)
asn1cCallocOne(dmrs_DownlinkForPDSCH_MappingTypeA->maxLength, NR_DMRS_DownlinkConfig__maxLength_len2);
else
dmrs_DownlinkForPDSCH_MappingTypeA->maxLength = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID0 = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->scramblingID1 = NULL;
dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = calloc(1, sizeof(*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition));
// TODO possible improvement is to select based on some input additional position
*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition = NR_DMRS_DownlinkConfig__dmrs_AdditionalPosition_pos1;
AssertFatal(configuration->pdsch_dmrs_additional_position == 0 || configuration->pdsch_dmrs_additional_position == 1,
"Invalid PDSCH DMRS additional position %d\n",
configuration->pdsch_dmrs_additional_position);
*dmrs_DownlinkForPDSCH_MappingTypeA->dmrs_AdditionalPosition =
configuration->pdsch_dmrs_additional_position == 0
? NR_DMRS_DownlinkConfig__dmrs_AdditionalPosition_pos0
: NR_DMRS_DownlinkConfig__dmrs_AdditionalPosition_pos1;
pdsch_Config->dataScramblingIdentityPDSCH = NULL;
pdsch_Config->resourceAllocation = NR_PDSCH_Config__resourceAllocation_resourceAllocationType1;
if (two_codewords)
asn1cCallocOne(pdsch_Config->maxNrofCodeWordsScheduledByDCI,
NR_PDSCH_Config__maxNrofCodeWordsScheduledByDCI_n2);
pdsch_Config->prb_BundlingType.present = NR_PDSCH_Config__prb_BundlingType_PR_staticBundling;
pdsch_Config->prb_BundlingType.choice.staticBundling = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling));
pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = calloc(1, sizeof(*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize));
*pdsch_Config->prb_BundlingType.choice.staticBundling->bundleSize = NR_PDSCH_Config__prb_BundlingType__staticBundling__bundleSize_wideband;
if (do_TCI) {
if (configuration->do_TCI) {
int n_ssb = 0;
if (!pdsch_Config->tci_StatesToAddModList)
pdsch_Config->tci_StatesToAddModList=calloc(1,sizeof(*pdsch_Config->tci_StatesToAddModList));
@@ -1840,7 +1869,7 @@ static NR_BWP_Downlink_t *config_downlinkBWP(const NR_ServingCellConfigCommon_t
asn1cSeqAdd(&bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToAddModList->list, ss3);
bwp->bwp_Dedicated->pdcch_Config->choice.setup->searchSpacesToReleaseList = NULL;
bwp->bwp_Dedicated->pdsch_Config = config_pdsch(ssb_bitmap, bwp->bwp_Id, configuration->do_TCI, configuration->ptrs);
bwp->bwp_Dedicated->pdsch_Config = config_pdsch(ssb_bitmap, bwp->bwp_Id, configuration);
set_dl_mcs_table(bwp->bwp_Common->genericParameters.subcarrierSpacing,
force_256qam_off ? NULL : uecap,
@@ -3389,7 +3418,7 @@ static NR_BWP_DownlinkDedicated_t *configure_initial_dl_bwp(const NR_ServingCell
asn1cSeqAdd(&pdcch_Config->searchSpacesToAddModList->list, ss2);
bwp_Dedicated->pdcch_Config->choice.setup = pdcch_Config;
bwp_Dedicated->pdsch_Config = config_pdsch(bitmap, 0, configuration->do_TCI, configuration->ptrs);
bwp_Dedicated->pdsch_Config = config_pdsch(bitmap, 0, configuration);
// we might call configuration of initial BWP for BWP switch when we already have UE capabilities
set_dl_mcs_table(scc->uplinkConfigCommon->initialUplinkBWP->genericParameters.subcarrierSpacing,
configuration->force_256qam_off ? NULL : uecap,
@@ -4062,7 +4091,7 @@ NR_CellGroupConfig_t *get_default_secondaryCellGroup(const NR_ServingCellConfigC
configDedicated->initialDownlinkBWP = calloc(1, sizeof(*configDedicated->initialDownlinkBWP));
configDedicated->initialDownlinkBWP->pdcch_Config = NULL;
configDedicated->initialDownlinkBWP->pdsch_Config = config_pdsch(bitmap, 0, configuration->do_TCI, configuration->ptrs);
configDedicated->initialDownlinkBWP->pdsch_Config = config_pdsch(bitmap, 0, configuration);
configDedicated->initialDownlinkBWP->sps_Config = NULL; // calloc(1,sizeof(struct NR_SetupRelease_SPS_Config));
configDedicated->initialDownlinkBWP->radioLinkMonitoringConfig = NULL;
+3 -3
View File
@@ -123,7 +123,7 @@ nr_rlc_entity_t *new_nr_rlc_entity_am(int rx_maxsize,
/* let's take average over the last 100 milliseconds
* initial_size of 1024 (packets) is arbitrary
*/
ret->common.txsdu_avg_time_to_tx = time_average_new(100 * 1000, 1024);
ret->common.txsdu_avg_time_to_tx = time_average_new(1000 * 1000, 1024);
ret->rx = nr_rlc_new_rx_manager(1 << (sn_field_length - 1));
@@ -176,7 +176,7 @@ nr_rlc_entity_t *new_nr_rlc_entity_um(int rx_maxsize,
/* let's take average over the last 100 milliseconds
* initial_size of 1024 is arbitrary
*/
ret->common.txsdu_avg_time_to_tx = time_average_new(100 * 1000, 1024);
ret->common.txsdu_avg_time_to_tx = time_average_new(1000 * 1000, 1024);
return (nr_rlc_entity_t *)ret;
}
@@ -215,7 +215,7 @@ nr_rlc_entity_t *new_nr_rlc_entity_tm(int tx_maxsize,
/* let's take average over the last 100 milliseconds
* initial_size of 1024 is arbitrary
*/
ret->common.txsdu_avg_time_to_tx = time_average_new(100 * 1000, 1024);
ret->common.txsdu_avg_time_to_tx = time_average_new(1000 * 1000, 1024);
return (nr_rlc_entity_t *)ret;
}
+8 -1
View File
@@ -1023,8 +1023,15 @@ void nr_rlc_activate_avg_time_to_tx(const int ue_id,
}
if (rb != NULL) {
// Reset the accumulator only on an off->on transition, NOT on every call.
// KPM measurement fills call this once per report period (after reading the
// stats); resetting on every call wiped the sliding-window average before it
// could accumulate, forcing a fallback to the noisy instantaneous HOL
// snapshot. The window self-expires old samples, so no periodic reset is
// needed -- activate once and let it accumulate continuously.
if (is_on && !rb->avg_time_is_on)
time_average_reset(rb->txsdu_avg_time_to_tx);
rb->avg_time_is_on = is_on;
time_average_reset(rb->txsdu_avg_time_to_tx);
} else {
LOG_E(RLC, "Radio Bearer (channel ID %d) is NULL for UE %d\n", channel_id, ue_id);
}
+3 -2
View File
@@ -145,6 +145,7 @@ static int8_t handle_dlsch(NR_UE_MAC_INST_t *mac, nr_downlink_indication_t *dl_i
if (mac->ra.ra_state != nrRA_WAIT_RAR) // no HARQ for MSG2
update_harq_status(mac,
dl_info->rx_ind->rx_indication_body[pdu_id].pdsch_pdu.harq_pid,
dl_info->rx_ind->rx_indication_body[pdu_id].pdsch_pdu.cw_idx,
dl_info->rx_ind->rx_indication_body[pdu_id].pdsch_pdu.ack_nack);
if(dl_info->rx_ind->rx_indication_body[pdu_id].pdsch_pdu.ack_nack)
nr_ue_send_sdu(mac, dl_info, pdu_id);
@@ -167,9 +168,9 @@ static int8_t handle_l1_measurements(NR_UE_MAC_INST_t *mac, frame_t frame, int s
return 0;
}
void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, uint8_t ack_nack)
void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, int cw_idx, uint8_t ack_nack)
{
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[harq_pid];
NR_UE_DL_HARQ_STATUS_t *current_harq = &mac->dl_harq_info[harq_pid][cw_idx];
if (current_harq->active) {
LOG_D(PHY,"Updating harq_status for harq_id %d, ack/nak %d\n", harq_pid, current_harq->ack);
+13
View File
@@ -84,6 +84,10 @@
#ifdef E2_AGENT
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
/* PHY simulators link RRC with E2 enabled, but do not link the RC service.
* Keep RC's real mutex when present and avoid a hard simulator dependency. */
extern void rc_teardown_lock(void) __attribute__((weak));
extern void rc_teardown_unlock(void) __attribute__((weak));
#define E2_AGENT_SIGNAL_DL_DCCH_RRC_MSG(BUF, LEN, ID) \
do { \
byte_array_t buffer_ba = {.len = LEN}; \
@@ -2734,7 +2738,16 @@ void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p)
nr_pdcp_remove_UE(UE->rrc_ue_id);
LOG_I(NR_RRC, "removed UE CU UE ID %u/RNTI %04x \n", UE->rrc_ue_id, UE->rnti);
rrc_delete_ue_data(UE);
#ifdef E2_AGENT
const bool rc_lock_available = rc_teardown_lock != NULL && rc_teardown_unlock != NULL;
if (rc_lock_available)
rc_teardown_lock(); /* block in-flight RC indications during teardown */
#endif
rrc_gNB_remove_ue_context(rrc, ue_context_p);
#ifdef E2_AGENT
if (rc_lock_available)
rc_teardown_unlock();
#endif
}
static void rrc_CU_process_ue_context_release_complete(MessageDef *msg_p)
+22
View File
@@ -227,6 +227,28 @@ void rxAddInput(c16_t **input_sig, cf_t *after_channel_sig, int rxAnt, channel_d
double Doppler_phase_cur = channelDesc->Doppler_phase_cur[rxAnt];
Doppler_phase_cur -= 2 * M_PI * round(Doppler_phase_cur / (2 * M_PI));
/* random_channel() defines AWGN as a one-tap identity matrix. The generic
* convolution below nevertheless scans every TX branch for every RX sample,
* turning an 8x8 RFsim link into 64 complex multiplies per sample. Select
* the sole non-zero branch directly. This is the exact same channel model;
* it only removes multiplication by the known zeros. */
if (channelDesc->modelid == AWGN && channelDesc->channel_length == 1
&& channelDesc->Doppler_phase_inc == 0.0) {
const int txAnt = rxAnt % nbTx;
const struct complexd channel = channelDesc->ch[rxAnt + (txAnt * channelDesc->nb_rx)][0];
const bool noise_rounds_to_zero = noise_per_sample < 1e-6;
for (int i = 0; i < nbSamples; i++) {
const struct complex16 tx = input_sig[txAnt][i];
const double rx_r = tx.r * channel.r - tx.i * channel.i;
const double rx_i = tx.i * channel.r + tx.r * channel.i;
after_channel_sig[i].r += rx_r * pathLossLinear
+ (noise_rounds_to_zero ? 0.0 : noise_per_sample * gaussZiggurat(0.0, 1.0));
after_channel_sig[i].i += rx_i * pathLossLinear
+ (noise_rounds_to_zero ? 0.0 : noise_per_sample * gaussZiggurat(0.0, 1.0));
}
return;
}
for (int i = 0; i < nbSamples; i++) {
cf_t *out_ptr = after_channel_sig + i;
struct complexd rx_tmp = {0};

Some files were not shown because too many files have changed in this diff Show More