Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67717ed5c1 | ||
|
|
c50fe1af53 | ||
|
|
04ee4ea8e5 | ||
|
|
08349f246b | ||
|
|
bfb177c38e | ||
|
|
434cd8ca3d | ||
|
|
38167e50f8 | ||
|
|
d1129d1f90 | ||
|
|
6a6f200790 | ||
|
|
1c26f372aa | ||
|
|
0da440017e | ||
|
|
e0a2b4837c | ||
|
|
11dc86f59c | ||
|
|
547f683ebe |
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
Executable
+48
@@ -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
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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"]
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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.
@@ -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 "$@"
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
|
||||
@@ -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**));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
Submodule openair2/E2AP/flexric updated: 340c36bc83...25353bb973
@@ -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();
|
||||
|
||||
@@ -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 } }, \
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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--;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user