Commit 1dac4f1a authored by Kirill Smelkov's avatar Kirill Smelkov

nrarfcn: Fix dl2ssb for bands with exceptions and non-contiguous allowed GSCN

Joanne reports that for N79 and DL NR ARFCN 720000 dl2ssb returns that
same 720000 and a phone cannot connect to the base station. In contrast
Amarisoft lteenb chooses SSB NR ARFCN = 720288 for the above setup and
the phone connects ok.

The problem turns out to be that in b8065120 (nrarfcn: New package to do
computations with NR bands, frequencies and NR-ARFCN numbers.) I missed
that the table for Applicable SS raster entries besides GSCN range for
each band also specifies conditions for which particular GSCNs are
allowed to be used inside that range. For example for N79 it is only
GSCNs that are multiple of 16:

https://github.com/blevic/nrarfcn/blob/7ece6a11/nrarfcn/tables/applicable_ss_raster_fr1.py#L60

Besides missing the "multiple of" condition, I also missed exceptional
note conditions where instead of providing a range TS 38.104 specifies
particular set of allowed GSCNs. For example for N39 and SCS=15 kHz only
the following set of GSCNs is allowed to be used for SSB:

    {4707, 4715, 4718, 4729, 4732, 4743, 4747, 4754, 4761, 4768, 4772, 4782, 4786, 4793}

https://github.com/blevic/nrarfcn/blob/7ece6a11/nrarfcn/tables/applicable_ss_raster_fr1.py#L39
https://github.com/blevic/nrarfcn/blob/7ece6a11/nrarfcn/tables/applicable_ss_raster_fr1.py#L13

-> Fix SSB calculation logic to take all that into account. This fixes
N79 case and also highlights previous problems in our tests for
above-mentioned n39 and for "multiple of" and "exceptional" cases in FR2.

Also add explicit test for N77 because we use that band in the ORS.

/reported-by @jhuge
/reported-on nexedi/slapos!1729 (comment 227244)
parent 598588a3
# Copyright (C) 2023 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
# Copyright (C) 2023-2025 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
......@@ -129,21 +129,21 @@ def dl2ssb(dl_nr_arfcn, band): # -> ssb_nr_arfcn, max_ssb_scs_khz
f = frequency(nrarfcn=dl_nr_arfcn)
_trace('f %.16g' % f)
# query all SSB SCS available in this band
# query all SS raster entries available in this band
if isinstance(band, int):
band = 'n%d' % band
tab_fr1 = nr.tables.applicable_ss_raster_fr1.table_applicable_ss_raster_fr1()
tab_fr2 = nr.tables.applicable_ss_raster_fr2.table_applicable_ss_raster_fr2()
scs_v = []
try_v = [] # of _SSRasterTabEntry
for tab in (tab_fr1, tab_fr2):
for row in tab.data:
if tab.get_cell(row, 'band') == band:
scs_v.append( tab.get_cell(row, 'scs') )
try_v.append( _SSRasterTabEntry(tab, row) )
# for each scs↓ try to find suitable sync point
for scs_khz in sorted(scs_v, reverse=True):
_trace('trying scs %r' % scs_khz)
scs = scs_khz / 1000 # khz -> mhz
for t in sorted(try_v, key=lambda _: _.scs, reverse=True):
_trace('trying %s' % t)
scs = t.scs / 1000 # khz -> mhz
# locate nearby point on global sync raster and further search around it
# until sync point aligns to be multiple of scs
......@@ -157,14 +157,52 @@ def dl2ssb(dl_nr_arfcn, band): # -> ssb_nr_arfcn, max_ssb_scs_khz
δf = f_sync - f
q, r = divmod(δf, scs)
r_scs = r / scs
_trace('gscn %d\tf_sync %.16g (%d) δf %+.3f //scs %d %%scs %.16g·scs' % (gscn, f_sync, nr.get_nrarfcn(f_sync), δf, q, r_scs))
if abs(r_scs - round(r_scs)) < 1e-5:
_trace('-> %d %d' % (f_sync_arfcn, scs_khz))
return f_sync_arfcn, scs_khz
gscn += (+1 if δf > 0 else -1)
# if f_sync is good we can yield it only if gscn ∈ allowed set
if t.has_gscn(gscn):
_trace('gscn %d\tf_sync %.16g (%d) δf %+.3f //scs %d %%scs %.16g·scs' % (gscn, f_sync, nr.get_nrarfcn(f_sync), δf, q, r_scs))
if abs(r_scs - round(r_scs)) < 1e-5:
_trace('-> %d %d' % (f_sync_arfcn, t.scs))
return f_sync_arfcn, t.scs
gscn += (+1 if δf >= 0 else -1)
raise KeyError('dl2ssb %r %s: cannot find SSB frequency that is both on GSR and aligns from dl modulo SSB SCS of the given band' % (dl_nr_arfcn, band))
# _SSRasterTabEntry serves dl2ssb by representing one entry from FR1 or FR2 SS raster entries table.
class _SSRasterTabEntry:
__slots__ = ('band', 'scs', 'block_pattern', 'gscn_first', 'step_size', 'gscn_last', 'note')
def __init__(tabe, tab, row):
for key in tabe.__slots__:
setattr(tabe, key, tab.get_cell(row, key))
assert type(tabe.note) is set or tabe.note == {}, repr(tabe)
if len(tabe.note) > 0:
assert tabe.gscn_first == tabe.gscn_last == tabe.step_size == 0, repr(tabe)
else:
assert tabe.gscn_first != 0, repr(tabe)
assert tabe.gscn_last != 0, repr(tabe)
assert tabe.step_size != 0, repr(tabe)
# has_gscn returns whether table entry covers given gscn.
def has_gscn(tabe, gscn):
if len(tabe.note) > 0:
ok = tabe.note
else:
ok = range(tabe.gscn_first, tabe.gscn_last+1, tabe.step_size)
return (gscn in ok)
def __str__(tabe):
s = '%s %s·scs %s ' % (tabe.band, tabe.scs, tabe.block_pattern)
if len(tabe.note) > 0:
s += '%r' % (tabe.note,)
else:
s += '%d-<%d>-%d' % (tabe.gscn_first, tabe.step_size, tabe.gscn_last)
return s
def __repr__(tabe):
return '_SSRasterTabEntry(' + \
', '.join('%s=%r' % (k, getattr(tabe, k)) for k in tabe.__slots__) + \
')'
# frequency returns frequency corresponding to DL or UL NR-ARFCN.
def frequency(nrarfcn): # -> freq (MHz)
......
# Copyright (C) 2023 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
# Copyright (C) 2023-2025 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
......@@ -68,22 +68,24 @@ def test_nrarfcn():
_( 7, 526000, 502000, 2630, 2510, 'FDD', 526090, 15)
_( 29, 144500, None, 722.5, None, 'SDL', 144530, 15)
_( 39, 378000, 378000, 1890, 1890, 'TDD', 378030, 30) # % 30khz = 0
_( 39, 378003, 378003, 1890.015, 1890.015, 'TDD', 378030, 15) # % 15khz = 0 % 30khz ≠ 0
_( 39, 378003, 378003, 1890.015, 1890.015, 'TDD', 379470, 15) # % 15khz = 0 % 30khz ≠ 0 gscn ∈ note4
_( 38, 520000, 520000, 2600, 2600, 'TDD', 520090, 30)
_( 41, 523020, 523020, 2615.1, 2615.1, 'TDD', 522990, 30) # % 30khz = 0
_( 41, 523023, 523023, 2615.115, 2615.115, 'TDD', 522990, 15) # % 15khz = 0 % 30khz ≠ 0
_( 66, 431000, 351000, 2155, 1755, 'FDD', 431090, 30)
_( 66, 437000, None, 2185, None, 'FDD', 437090, 30) # NOTE in n66 range(dl) > range(ul)
_( 77, 660000, 660000, 3900, 3900, 'TDD', 660000, 30)
_( 78, 632628, 632628, 3489.42, 3489.42, 'TDD', 632640, 30)
_( 79, 720000, 720000, 4800, 4800, 'TDD', 720288, 30) # NOTE gscn % 16 = 0
_( 91, 285900, 166900, 1429.5, 834.5, 'FDD', 285870, 15)
_( 91, None, 172400, None, 862, 'FDD', None, None) # NOTE in n91 range(dl) < range(ul)
_( 80, None, 342000, None, 1710, 'SUL', None, None)
_(257, 2079167, 2079167, 28000.08, 28000.08, 'TDD', 2079163, 240) # FR2-1
_(257, 2079167, 2079167, 28000.08, 28000.08, 'TDD', 2078875, 240) # FR2-1 gscn % 2 = 0
_(257, 2079169, 2079169, 28000.20, 28000.20, 'TDD', 2079163, 120) # FR2-1 % 240khz ≠ 0
_(263, 2680027, 2680027, 64051.68, 64051.68, 'TDD', 2679931, 960) # FR2-2
_(263, 2680003, 2680003, 64050.24, 64050.24, 'TDD', 2679931, 480) # FR2-2 % 960khz ≠ 0
_(263, 2679991, 2679991, 64049.52, 64049.52, 'TDD', 2679931, 120) # FR2-2 % 480khz ≠ 0
_(263, 2680027, 2680027, 64051.68, 64051.68, 'TDD', 2679643, 960) # FR2-2 gscn % 6 = 0
_(263, 2680003, 2680003, 64050.24, 64050.24, 'TDD', 2679643, 480) # FR2-2 % 960khz ≠ 0 gscn ∈ Table 5.4.3.3-3
_(263, 2679991, 2679991, 64049.52, 64049.52, 'TDD', 2679643, 120) # FR2-2 % 480khz ≠ 0 gscn ∈ Table 5.4.3.3-3
# some dl points not on ΔFraster -> ssb cannot be found
_( 78, 632629, 632629, 3489.435, 3489.435, 'TDD', KeyError, None)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment