Skip to content

Commit

Permalink
Add phase correction for generation of 5G NR time domain signals
Browse files Browse the repository at this point in the history
  • Loading branch information
danielschaeufele committed Jul 9, 2024
1 parent 2deba0c commit 63e1885
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 2 deletions.
14 changes: 14 additions & 0 deletions sionna/nr/carrier_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,20 @@ def frame_number(self, value):
assert value in range(0,1024), "frame_number must be in [0, 1023]"
self._frame_number = value

#---carrier_frequency---#
@property
def carrier_frequency(self):
r"""
float: Carrier frequency :math:`f_0` in Hz
"""
self._ifndef("carrier_frequency", 0.)
return self._carrier_frequency

@carrier_frequency.setter
def carrier_frequency(self, value):
assert value > 0, "carrier_frequency must be positive"
self._carrier_frequency = value

#--------------------------#
#---Read-only parameters---#
#--------------------------#
Expand Down
16 changes: 15 additions & 1 deletion sionna/nr/pusch_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,19 @@ def tb_size(self):

return tb_size

@property
def phase_correction_sequence(self):
r"""
np.ndarray[float], read-only: Vector of phase rotations (implicitly)
defined in Section 5.4 [3GPP38211]_.
"""
symbol_durations = (self.carrier.cyclic_prefix_length +
self.fft_size / self.sample_rate)
# Reference point is start of first symbol, so ignore first cyclic prefix
symbol_durations[0] = 0.
symbol_start_times = np.cumsum(symbol_durations)
return np.exp(1j*2*np.pi*self.carrier.carrier_frequency*symbol_start_times)

#-------------------#
#---Class methods---#
#-------------------#
Expand Down Expand Up @@ -1170,7 +1183,8 @@ def check_pusch_configs(pusch_configs):
"tb_size" : pc.tb_size,
"dmrs_length" : pc.dmrs.length,
"dmrs_additional_position" : pc.dmrs.additional_position,
"num_cdm_groups_without_data" : pc.dmrs.num_cdm_groups_without_data
"num_cdm_groups_without_data" : pc.dmrs.num_cdm_groups_without_data,
"phase_correction_sequence" : pc.phase_correction_sequence,
}
params["bandwidth"] = params["num_subcarriers"]*params["subcarrier_spacing"]
params["cyclic_prefix_length"] = np.ceil(carrier.cyclic_prefix_length *
Expand Down
4 changes: 4 additions & 0 deletions sionna/nr/pusch_receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ def __init__(self,
l_min=self._l_min,
cyclic_prefix_length=pusch_transmitter._cyclic_prefix_length)

self._phase_correction_sequence = pusch_transmitter._phase_correction_sequence

# Use or create default ChannelEstimator
self._perfect_csi = False
self._w = None
Expand Down Expand Up @@ -246,6 +248,8 @@ def call(self, inputs):
# (Optional) OFDM Demodulation
if self._input_domain=="time":
y = self._ofdm_demodulator(y)
y *= tf.expand_dims(tf.cast(
self._phase_correction_sequence, y.dtype), -1)

# Channel estimation
if self._perfect_csi:
Expand Down
4 changes: 3 additions & 1 deletion sionna/nr/pusch_transmitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ def call(self, inputs):

# (Optionally) apply OFDM modulation
if self._output_domain=="time":
x = self._ofdm_modulator(x_pre)
x = x_pre / tf.expand_dims(tf.cast(
self._phase_correction_sequence, x_pre.dtype), -1)
x = self._ofdm_modulator(x)
else:
x = x_pre

Expand Down
30 changes: 30 additions & 0 deletions test/unit/nr/test_pusch_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,33 @@ def test_cyclic_prefix_length(self):
np.testing.assert_array_almost_equal(carrier_config.cyclic_prefix_length * 1e6, [0.81] + [0.29] * 13, decimal=2)
carrier_config = CarrierConfig(subcarrier_spacing=240, slot_number=1)
np.testing.assert_array_almost_equal(carrier_config.cyclic_prefix_length * 1e6, [0.29] * 14, decimal=2)


class TestPUSCHConfig(unittest.TestCase):
"""Tests for the PUSCHConfig class"""

def test_phase_correction_sequence(self):
"""Generate carrier signal for upconversion and compare phase shift at
the start of each symbol with the generated phase correction sequence"""
for subcarrier_spacing in [15, 30, 60, 120, 240]:
pusch_config = PUSCHConfig(subcarrier_spacing=subcarrier_spacing,
sample_rate="standard")
for carrier_frequency in np.arange(1e9, 10e9, .5e9):
pusch_config.carrier.carrier_frequency = carrier_frequency

fft_size = pusch_config.fft_size
cp_lengths = np.round(pusch_config.carrier.cyclic_prefix_length *
pusch_config.sample_rate).astype(int)

sample_idx = np.arange(-cp_lengths[0], np.sum(cp_lengths[1:])
+ fft_size * len(cp_lengths))
upconversion_vector = np.exp(2.j*np.pi*carrier_frequency*
sample_idx/pusch_config.sample_rate)

symbol_end_idx = np.cumsum(cp_lengths + fft_size)
phase_rotations = []
for idx in symbol_end_idx:
phase_rotations.append(upconversion_vector[idx - fft_size])

np.testing.assert_array_almost_equal(phase_rotations,
pusch_config.phase_correction_sequence)

0 comments on commit 63e1885

Please sign in to comment.