Using pyAerial for PUSCH decoding on Aerial Data Lake data#
This example shows how to use the pyAerial bindings to run cuPHY GPU accelerated PUSCH decoding for 5G NR PUSCH. The 5G NR PUSCH data is read from an example over the air captured PUSCH dataset collected and stored using Aerial Data Lake. A complete PUSCH receiver using individual separate Python function calls to individual PUSCH receiver components is used so that channel estimates can be shown.
In this setup there are two cells:
Cell 51 is an active cell being used with the OAI L2+ using slot pattern DDDSU and commercial UEs.
Cell 41 is a passive “listener” cell using the same slot pattern as cell 51 but is being driven by testmac to request IQ samples for every uplink slot.
There are multiple UEs connected to cell 51, as is shown in the nUEs
field of the fapi table. The first two plots show the power in all of the resource elements for the given slot on both cells. The plots after that show just the resource elements scheduled for a given UE (RNTI) across both cells, as well pre- and post- equalized samples, then channel estimates, for IQ samples from each cell, followed by text indicating whether the transmission decoded successfully.
Note: This example requires that the clickhouse server is running and that the example data has been stored in the database. Refer to the Aerial Data Lake documentation on how to do this. https://docs.nvidia.com/aerial/cuda-accelerated-ran/aerial_data_lake/index.html#clickhouse-client
Imports#
[1]:
import math
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
import numpy as np
import pandas as pd
from IPython.display import Markdown
from IPython.display import display
# Connecting to clickhouse on remote server
import clickhouse_connect
# Plotting with Matplotlib.
import matplotlib.pyplot as plt
from matplotlib import dates as mdates
# pyAerial imports
from aerial.phy5g.config import PuschConfig
from aerial.phy5g.config import PuschUeConfig
from aerial.phy5g.algorithms import ChannelEstimator
from aerial.phy5g.algorithms import ChannelEqualizer
from aerial.phy5g.algorithms import NoiseIntfEstimator
from aerial.phy5g.ldpc import LdpcDeRateMatch
from aerial.phy5g.ldpc import LdpcDecoder
from aerial.phy5g.ldpc import CrcChecker
from aerial.util.cuda import get_cuda_stream
from aerial.util.fapi import dmrs_fapi_to_bit_array
# Hide log10(10) warning
_ = np.seterr(divide='ignore', invalid='ignore')
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)
np.set_printoptions(threshold=100) # Control the number of elements to display
np.set_printoptions(edgeitems=100) # Control the number of edge items to display
np.set_printoptions(linewidth=200) # Control the width of the display
plt.rcParams['figure.figsize'] = [10, 4]
Create the PUSCH pipelines#
This is a PUSCH receiver pipeline made up of separately called pyAerial PUSCH receiver components.
[2]:
# Whether to plot intermediate results within the PUSCH pipeline, such as channel estimates and equalized symbols.
plot_figures = True
num_ues = 1
num_tx_ant = 2 # UE antennas
num_rx_ant = 4 # gNB antennas
enable_pusch_tdi = 0 # Enable time interpolation for equalizer coefficients
eq_coeff_algo = 1 # Equalizer algorithm
# The PUSCH receiver chain built from separately called pyAerial Python components is defined here.
class PuschRxSeparate:
"""PUSCH receiver class.
This class encapsulates the whole PUSCH receiver chain built using
pyAerial components.
"""
def __init__(self,
num_rx_ant,
enable_pusch_tdi,
eq_coeff_algo,
plot_figures):
"""Initialize the PUSCH receiver."""
self.cuda_stream = get_cuda_stream()
# Build the components of the receiver.
self.channel_estimator = ChannelEstimator(
num_rx_ant=num_rx_ant,
cuda_stream=self.cuda_stream)
self.channel_equalizer = ChannelEqualizer(
num_rx_ant=num_rx_ant,
enable_pusch_tdi=enable_pusch_tdi,
eq_coeff_algo=eq_coeff_algo,
cuda_stream=self.cuda_stream)
self.noise_intf_estimator = NoiseIntfEstimator(
num_rx_ant=num_rx_ant,
eq_coeff_algo=eq_coeff_algo,
cuda_stream=self.cuda_stream)
self.derate_match = LdpcDeRateMatch(
enable_scrambling=True,
cuda_stream=self.cuda_stream)
self.decoder = LdpcDecoder(cuda_stream=self.cuda_stream)
self.crc_checker = CrcChecker(cuda_stream=self.cuda_stream)
# Whether to plot the intermediate results.
self.plot_figures = plot_figures
def run(
self,
rx_slot,
slot,
pusch_configs,
cell_num
):
"""Run the receiver."""
# Channel estimation.
ch_est = self.channel_estimator.estimate(
rx_slot=rx_slot,
slot=slot,
pusch_configs=pusch_configs
)
# Noise and interference estimation.
lw_inv, noise_var_pre_eq = self.noise_intf_estimator.estimate(
rx_slot=rx_slot,
channel_est=ch_est,
slot=slot,
pusch_configs=pusch_configs
)
# Channel equalization and soft demapping. The first return value are the LLRs,
# second are the equalized symbols. We only want the LLRs now.
llrs, sym = self.channel_equalizer.equalize(
rx_slot=rx_slot,
channel_est=ch_est,
lw_inv=lw_inv,
noise_var_pre_eq=noise_var_pre_eq,
pusch_configs=pusch_configs
)
if self.plot_figures:
fig, axs = plt.subplots(1,4)
for ant in range(4):
axs[ant].imshow(10*np.log10(np.abs(rx_slot[:, :, ant]**2)), aspect='auto')
axs[ant].set_ylim([(pusch_record.rbStart + pusch_record.rbSize) * 12,pusch_record.rbStart * 12])
axs[ant].set_title('Ant ' + str(ant))
axs[ant].set(xlabel='Symbol', ylabel='Resource Element')
axs[ant].label_outer()
fig.suptitle('Power in PUSCH REs cell {} for RNTI {}'.format(cell_num,pusch_record.rnti))
fig, axs = plt.subplots(1,2)
axs[0].scatter(rx_slot.reshape(-1).real, rx_slot.reshape(-1).imag)
axs[0].set_title("Pre-Equalized samples")
axs[0].set_aspect('equal')
axs[1].scatter(np.array(sym).reshape(-1).real, np.array(sym).reshape(-1).imag)
axs[1].set_title("Post-Equalized samples")
axs[1].set_aspect('equal')
fig, axs = plt.subplots(1)
axs.set_title("Channel estimates from the PUSCH pipeline")
for ant in range(4):
axs.plot(np.abs(ch_est[0][ant, 0, :, 0]))
axs.legend(["Rx antenna 0, estimate",
"Rx antenna 1, estimate",
"Rx antenna 2, estimate",
"Rx antenna 3, estimate"])
axs.grid(True)
plt.show()
coded_blocks = self.derate_match.derate_match(
input_llrs=llrs,
pusch_configs=pusch_configs
)
code_blocks = self.decoder.decode(
input_llrs=coded_blocks,
pusch_configs=pusch_configs
)
decoded_tbs, _ = self.crc_checker.check_crc(
input_bits=code_blocks,
pusch_configs=pusch_configs
)
return decoded_tbs
pusch_rx_separate = PuschRxSeparate(
num_rx_ant=num_rx_ant,
enable_pusch_tdi=enable_pusch_tdi,
eq_coeff_algo=eq_coeff_algo,
plot_figures=plot_figures
)
Querying the database#
Below shows how to connect to the clickhouse database and querying the data from it.
[3]:
# Connect to the local database
import clickhouse_connect
client = clickhouse_connect.get_client(host='localhost')
# Pick a packet from the database,
pusch_records = client.query_df('select * from fapi order by TsTaiNs,rbStart')
#print(pusch_records[['TsTaiNs','SFN','Slot','nUEs','rnti','rbStart','rbSize','StartSymbolIndex','NrOfSymbols','CQI']])
Extract the PUSCH parameters and run the pipelines#
In this section we use the timestamp of the start of a slot to query the IQ sample database for both cells and demonstrate that the transmission can be decoded in the IQ samples of both cells.
[4]:
runReceiver = True
#Only show the full slot pattern the first time through:
shownTsTaiNs = -1
pusch_records = client.query_df('select * from fapi order by TsTaiNs,rbStart limit 8')
print(pusch_records[['TsTaiNs','SFN','Slot','CellId','nUEs','rnti','rbStart','rbSize','StartSymbolIndex','NrOfSymbols','nrOfLayers','TBSize','CQI']])
for index, pusch_record in pusch_records.iterrows():
query = f"""select TsTaiNs,CellId,fhData from fh where TsTaiNs == toDateTime64(\'{pusch_record.TsTaiNs.timestamp()}\',9)"""
fh = client.query_df(query)
#Skip RNTI 20000 as we know it won't decode
if pusch_record.rnti == 20000:
continue
# Extract all the needed parameters from the PUSCH record and create the PuschConfig.
pusch_ue_config = PuschUeConfig(
scid=int(pusch_record.SCID),
layers=pusch_record.nrOfLayers,
dmrs_ports=pusch_record.dmrsPorts,
rnti=pusch_record.rnti,
data_scid=pusch_record.dataScramblingId,
mcs_table=pusch_record.mcsTable,
mcs_index=pusch_record.mcsIndex,
code_rate=pusch_record.targetCodeRate,
mod_order=pusch_record.qamModOrder,
tb_size=pusch_record.TBSize
)
slot = int(pusch_record.Slot)
tb_input = np.array(pusch_record.pduData)
# Note that this is a list. One UE group only in this case.
pusch_configs = [PuschConfig(
ue_configs=[pusch_ue_config],
num_dmrs_cdm_grps_no_data=pusch_record.numDmrsCdmGrpsNoData,
dmrs_scrm_id=pusch_record.ulDmrsScramblingId,
start_prb=pusch_record.rbStart,
num_prbs=pusch_record.rbSize,
dmrs_syms=dmrs_fapi_to_bit_array(int(pusch_record.ulDmrsSymbPos)),
dmrs_max_len=1,
dmrs_add_ln_pos=2,
start_sym=pusch_record.StartSymbolIndex,
num_symbols=pusch_record.NrOfSymbols
)]
numCells = fh.index.size
if shownTsTaiNs != pusch_record.TsTaiNs.timestamp():
shownTsTaiNs = pusch_record.TsTaiNs.timestamp()
display(Markdown("### SFN.Slot {}.{}, RNTI {} at time {}"
.format(pusch_record.SFN, pusch_record.Slot, pusch_record.rnti,pusch_record.TsTaiNs
)))
fig=plt.figure(constrained_layout=True)
if numCells > 1:
outer = fig.subfigures(1,numCells)
for cellNum, cellPlot in enumerate(outer.flat):
fh_samp = (np.array(fh['fhData'].iloc[cellNum], dtype=np.int16).view(np.float16)).astype(np.float32)
rx_slot = np.swapaxes(fh_samp.view(np.complex64).reshape(4, 14, 273 * 12), 2, 0)
axs = cellPlot.subplots(1,4)
for ant, ax in enumerate(axs.flat):
ax.imshow(10*np.log10(np.abs(rx_slot[:, :, ant]**2)), aspect='auto')
ax.set_title('Ant ' + str(ant))
ax.set(xlabel='Symbol', ylabel='Resource Element')
ax.label_outer()
cellPlot.suptitle('Power in RU Antennas Cell '+str(fh['CellId'].iloc[cellNum]))
plt.show()
else:
cellNum = 0
fig, axs = plt.subplots(1,4)
fh_samp = (np.array(fh['fhData'].iloc[cellNum], dtype=np.int16).view(np.float16)).astype(np.float32)
rx_slot = np.swapaxes(fh_samp.view(np.complex64).reshape(4, 14, 273 * 12), 2, 0)
for ant in range(4):
axs[ant].imshow(10*np.log10(np.abs(rx_slot[:, :, ant]**2)), aspect='auto')
axs[ant].set_ylim([pusch_record.rbStart * 12, (pusch_record.rbStart+pusch_record.rbSize) * 12])
axs[ant].set_title('Ant ' + str(ant))
axs[ant].set(xlabel='Symbol', ylabel='Resource Element')
axs[ant].label_outer()
fig.suptitle('Power in RU Antennas')
fig, axs = plt.subplots(1,2)
axs[0].scatter(rx_slot.reshape(-1).real, rx_slot.reshape(-1).imag)
axs[0].set_title("Pre-Equalized samples")
axs[0].set_aspect('equal')
axs[1].scatter(np.array(sym).reshape(-1).real, np.array(sym).reshape(-1).imag)
axs[1].set_title("Post-Equalized samples")
axs[1].set_aspect('equal')
fig, axs = plt.subplots(1)
axs.set_title("Channel estimates from the PUSCH pipeline")
for ant in range(4):
axs.plot(np.abs(ch_est[0][ant, 0, :, 0]))
axs.legend(["Rx antenna 0, estimate",
"Rx antenna 1, estimate",
"Rx antenna 2, estimate",
"Rx antenna 3, estimate"])
axs.grid(True)
plt.show()
if runReceiver:
for cellNum in range(0,numCells):
cellId = fh['CellId'].iloc[cellNum]
#fh_samp = np.array(fh['fhData'].iloc[cellNum], dtype=np.float32)
fh_samp = (np.array(fh['fhData'].iloc[cellNum], dtype=np.int16).view(np.float16)).astype(np.float32)
rx_slot = np.swapaxes(fh_samp.view(np.complex64).reshape(4, 14, 273 * 12), 2, 0)
tbs = pusch_rx_separate.run(
rx_slot=rx_slot,
slot=slot,
pusch_configs=pusch_configs,
cell_num=cellId
)
if np.array_equal(tbs[0][:tb_input.size], tb_input):
display(Markdown("**PUSCH decoding success** for SFN.Slot {}.{} RNTI {} from cell {} "
.format(pusch_record.SFN, pusch_record.Slot, pusch_record.rnti, cellId)))
else:
display(Markdown("**PUSCH decoding failure**"))
print("Output bytes:")
print(tbs[0][:tb_input.size])
print("Expected output:")
print(tb_input)
# Run the receiver using the second cell data
cell_num = fh['CellId'].iloc[1]
fh_samp = (np.array(fh['fhData'].iloc[1], dtype=np.int16).view(np.float16)).astype(np.float32)
rx_slot = np.swapaxes(fh_samp.view(np.complex64).reshape(4, 14, 273 * 12), 2, 0)
tbs = pusch_rx_separate.run(
rx_slot=rx_slot,
slot=slot,
pusch_configs=pusch_configs,
cell_num=cell_num
)
if np.array_equal(tbs[0][:tb_input.size], tb_input):
display(Markdown("**PUSCH decoding success** for SFN.Slot {}.{} RNTI {} from cell {} "
.format(pusch_record.SFN, pusch_record.Slot, pusch_record.rnti, cell_num)))
else:
display(Markdown("**PUSCH decoding failure**"))
print("Output bytes:")
print(tbs[0][:tb_input.size])
print("Expected output:")
print(tb_input)
TsTaiNs SFN Slot CellId nUEs rnti rbStart rbSize StartSymbolIndex NrOfSymbols nrOfLayers TBSize CQI
0 2024-07-19 10:42:46.272 391 4 41 7 20000 0 8 0 10 1 63 -7.352562
1 2024-07-19 10:42:46.272 391 4 51 7 62290 0 5 0 13 1 185 31.755341
2 2024-07-19 10:42:46.272 391 4 51 7 53137 5 5 0 13 1 185 30.275444
3 2024-07-19 10:42:46.272 391 4 51 7 1624 10 5 0 13 1 185 31.334328
4 2024-07-19 10:42:46.272 391 4 51 7 47905 15 5 0 13 1 185 30.117304
5 2024-07-19 10:42:46.272 391 4 51 7 57375 20 5 0 13 1 185 29.439499
6 2024-07-19 10:42:46.272 391 4 51 7 20216 25 248 0 13 1 19985 25.331459
7 2024-07-19 10:42:47.292 493 4 41 6 20000 0 8 0 10 1 63 -7.845479
SFN.Slot 391.4, RNTI 62290 at time 2024-07-19 10:42:46.272000#




PUSCH decoding success for SFN.Slot 391.4 RNTI 62290 from cell 41



PUSCH decoding success for SFN.Slot 391.4 RNTI 62290 from cell 51



PUSCH decoding success for SFN.Slot 391.4 RNTI 62290 from cell 51



PUSCH decoding success for SFN.Slot 391.4 RNTI 53137 from cell 41



PUSCH decoding success for SFN.Slot 391.4 RNTI 53137 from cell 51



PUSCH decoding success for SFN.Slot 391.4 RNTI 53137 from cell 51



PUSCH decoding failure
Output bytes:
[ 10 48 209 157 5 8 230 133 18 7 201 217 29 224 195 211 75 82 203 164 42 142 110 189 231 69 70 88 148 156 122 210 236 205 149 1 102 101 17 214 64 76 192 196 211 223 93 129 42
233 190 143 210 13 147 97 98 229 82 113 100 198 129 100 95 179 133 51 17 125 165 57 245 57 122 193 19 201 180 182 145 112 250 78 205 147 95 32 160 193 175 111 193 83 242 204 90 0
212 53 41 107 67 85 225 209 202 11 84 211 52 217 30 144 187 50 195 11 137 227 178 151 55 252 154 113 95 89 183 132 145 63 166 125 202 148 178 113 98 240 131 126 87 31 114 83 219
160 51 215 185 137 117 136 150 17 57 218 125 214 24 59 132 79 104 77 219 137 213 200 97 13 55 7 65 250 239 146 219 35 250 17 4 69 36]
Expected output:
[61 0 57 63 51 63 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33]



PUSCH decoding success for SFN.Slot 391.4 RNTI 1624 from cell 51



PUSCH decoding success for SFN.Slot 391.4 RNTI 1624 from cell 51



PUSCH decoding failure
Output bytes:
[191 131 138 184 120 175 5 126 179 48 203 46 204 202 78 207 17 10 250 235 190 59 104 12 73 128 35 212 70 147 142 156 143 28 189 255 224 80 7 239 67 103 113 195 28 97 168 251 18
207 117 170 102 35 17 226 165 243 249 216 239 31 57 8 28 244 146 107 2 89 180 202 164 70 140 134 94 10 138 214 228 137 36 179 154 136 94 187 165 204 70 127 33 96 176 125 119 246
24 81 21 227 134 227 188 8 151 72 1 250 48 60 176 216 113 141 11 87 168 246 182 217 8 182 99 26 161 187 161 23 160 8 86 167 64 86 54 217 151 141 208 133 91 79 156 7 96
252 211 183 115 214 194 111 75 79 70 114 139 39 203 54 24 91 232 7 106 253 47 105 54 123 122 9 27 104 151 76 176 133 55 95 18 177 105]
Expected output:
[61 0 57 63 51 63 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33]



PUSCH decoding success for SFN.Slot 391.4 RNTI 47905 from cell 51



PUSCH decoding success for SFN.Slot 391.4 RNTI 47905 from cell 51



PUSCH decoding failure
Output bytes:
[229 153 165 35 61 137 241 89 85 2 26 167 119 112 238 106 195 165 32 49 195 131 94 248 170 70 176 178 213 122 191 133 189 72 62 12 62 11 54 80 81 114 219 8 27 199 181 168 139
25 220 25 76 151 114 88 140 103 152 101 78 6 146 8 148 167 82 177 13 137 203 186 129 73 219 218 215 51 97 194 97 174 50 174 190 94 61 30 2 136 108 127 225 180 84 201 100 196
27 241 9 55 58 27 112 113 232 151 34 242 250 250 135 102 143 243 105 60 198 38 70 79 40 151 168 108 165 106 194 102 189 123 231 127 10 76 80 250 44 190 162 77 12 110 70 179 101
105 208 22 69 171 184 187 195 190 169 227 92 218 36 253 29 89 246 121 36 32 191 58 235 74 82 66 153 105 84 127 59 227 172 46 197 9 116]
Expected output:
[ 4 3 0 0 80 61 0 57 63 51 63 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33
33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33]



PUSCH decoding success for SFN.Slot 391.4 RNTI 57375 from cell 51



PUSCH decoding success for SFN.Slot 391.4 RNTI 57375 from cell 51



PUSCH decoding failure
Output bytes:
[132 39 255 193 116 103 251 170 83 195 178 86 92 32 202 220 21 123 13 225 204 129 100 223 92 167 215 25 184 20 118 92 62 88 99 18 204 218 199 180 10 128 62 53 87 85 125 144 155
161 193 213 192 181 140 135 163 168 247 53 91 209 99 199 194 119 21 153 220 32 232 11 190 91 253 94 65 99 190 192 34 80 44 35 149 5 53 50 104 221 210 21 162 108 96 226 46 87
166 142 ... 65 143 205 214 1 154 247 25 253 233 43 62 132 213 83 14 79 187 110 205 230 73 100 128 175 9 135 78 192 143 155 124 230 205 99 142 180 61 20 116 55 148 237 89 49 191
229 142 125 136 194 179 126 125 212 176 237 155 248 16 253 252 23 178 255 194 177 132 114 46 193 255 136 121 138 210 216 79 81 62 25 43 164 14 202 113 43 36 145 153 160 233 44 15 24
242 118 81 5 225]
Expected output:
[ 68 1 133 160 13 151 4 71 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52
53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51
52 53 ... 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54
55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53 54 55 56 57 48 49 50 51 52 53
54 55 56 57 48]



PUSCH decoding success for SFN.Slot 391.4 RNTI 20216 from cell 51



PUSCH decoding success for SFN.Slot 391.4 RNTI 20216 from cell 51