5G-LENA nr-v4.1
The 5G/NR module for the ns-3 simulator
Loading...
Searching...
No Matches
nr-gnb-phy.cc
1// Copyright (c) 2019 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
2//
3// SPDX-License-Identifier: GPL-2.0-only
4
5#define NS_LOG_APPEND_CONTEXT \
6 do \
7 { \
8 std::clog << " [ CellId " << GetCellId() << ", bwpId " << GetBwpId() << "] "; \
9 } while (false);
10
11#include "nr-gnb-phy.h"
12
13#include "beam-manager.h"
14#include "nr-ch-access-manager.h"
15#include "nr-gnb-net-device.h"
16#include "nr-net-device.h"
17#include "nr-ue-net-device.h"
18#include "nr-ue-phy.h"
19
20#include "ns3/double.h"
21#include "ns3/enum.h"
22#include "ns3/log.h"
23#include "ns3/node-list.h"
24#include "ns3/node.h"
25#include "ns3/object-vector.h"
26#include "ns3/pointer.h"
27#include "ns3/uinteger.h"
28
29#include <algorithm>
30#include <functional>
31#include <random>
32#include <string>
33#include <unordered_set>
34#include <vector>
35
36namespace ns3
37{
38
39NS_LOG_COMPONENT_DEFINE("NrGnbPhy");
40
41NS_OBJECT_ENSURE_REGISTERED(NrGnbPhy);
42
44 : m_n0Delay(0),
45 m_n1Delay(4)
46{
47 NS_LOG_FUNCTION(this);
48 m_gnbCphySapProvider = new MemberNrGnbCphySapProvider<NrGnbPhy>(this);
50}
51
55
56void
58{
59 NS_LOG_FUNCTION(this);
60 delete m_gnbCphySapProvider;
61 delete m_nrFhPhySapUser;
62 m_nrFhPhySapUser = nullptr;
63 m_nrFhPhySapProvider = nullptr;
65}
66
67void
68NrGnbPhy::EnableCsiRs()
69{
70 m_enableCsiRs = true;
71}
72
73TypeId
75{
76 static TypeId tid =
77 TypeId("ns3::NrGnbPhy")
78 .SetParent<NrPhy>()
79 .AddConstructor<NrGnbPhy>()
80 .AddAttribute("RbOverhead",
81 "Overhead when calculating the usable RB number",
82 DoubleValue(0.04),
84 MakeDoubleChecker<double>(0, 0.5))
85 .AddAttribute("TxPower",
86 "Transmission power in dBm",
87 DoubleValue(4.0),
88 MakeDoubleAccessor(&NrGnbPhy::SetTxPower, &NrGnbPhy::GetTxPower),
89 MakeDoubleChecker<double>())
90 .AddAttribute(
91 "NoiseFigure",
92 "Loss (dB) in the Signal-to-Noise-Ratio due to non-idealities in the receiver."
93 " According to Wikipedia (http://en.wikipedia.org/wiki/Noise_figure), this is "
94 "\"the difference in decibels (dB) between"
95 " the noise output of the actual receiver to the noise output of an "
96 " ideal receiver with the same overall gain and bandwidth when the receivers "
97 " are connected to sources at the standard noise temperature T0.\" "
98 "In this model, we consider T0 = 290K.",
99 DoubleValue(5.0),
100 MakeDoubleAccessor(&NrPhy::SetNoiseFigure, &NrPhy::GetNoiseFigure),
101 MakeDoubleChecker<double>())
102 .AddAttribute(
103 "PowerAllocationType",
104 "Defines the type of the power allocation. Currently are supported "
105 "two types: \"UniformPowerAllocBw\", which is a uniform power allocation over all "
106 "bandwidth (over all RBs), and \"UniformPowerAllocUsed\", which is a uniform "
107 "power allocation over used (active) RBs. By default is set a uniform power "
108 "allocation over used RBs .",
109 EnumValue(NrSpectrumValueHelper::UNIFORM_POWER_ALLOCATION_USED),
110 MakeEnumAccessor<NrSpectrumValueHelper::PowerAllocationType>(
113 MakeEnumChecker(NrSpectrumValueHelper::UNIFORM_POWER_ALLOCATION_BW,
114 "UniformPowerAllocBw",
115 NrSpectrumValueHelper::UNIFORM_POWER_ALLOCATION_USED,
116 "UniformPowerAllocUsed"))
117 .AddAttribute("SpectrumPhy",
118 "The downlink NrSpectrumPhy associated to this NrPhy",
119 TypeId::ATTR_GET,
120 PointerValue(),
121 MakePointerAccessor(&NrPhy::GetSpectrumPhy),
122 MakePointerChecker<NrSpectrumPhy>())
123 .AddTraceSource("UlSinrTrace",
124 "UL SINR statistics.",
125 MakeTraceSourceAccessor(&NrGnbPhy::m_ulSinrTrace),
126 "ns3::UlSinr::TracedCallback")
127 .AddTraceSource("GnbPhyRxedCtrlMsgsTrace",
128 "Gnb PHY Rxed Control Messages Traces.",
129 MakeTraceSourceAccessor(&NrGnbPhy::m_phyRxedCtrlMsgsTrace),
130 "ns3::NrPhyRxTrace::RxedGnbPhyCtrlMsgsTracedCallback")
131 .AddTraceSource("GnbPhyTxedCtrlMsgsTrace",
132 "Gnb PHY Txed Control Messages Traces.",
133 MakeTraceSourceAccessor(&NrGnbPhy::m_phyTxedCtrlMsgsTrace),
134 "ns3::NrPhyRxTrace::TxedGnbPhyCtrlMsgsTracedCallback")
135 .AddAttribute("N0Delay",
136 "Minimum processing delay needed to decode DL DCI and decode DL data",
137 UintegerValue(0),
138 MakeUintegerAccessor(&NrGnbPhy::SetN0Delay, &NrGnbPhy::GetN0Delay),
139 MakeUintegerChecker<uint32_t>(0, 1))
140 .AddAttribute("N1Delay",
141 "Minimum processing delay (UE side) from the end of DL Data reception to "
142 "the earliest possible start of the corresponding ACK/NACK transmission",
143 UintegerValue(2),
144 MakeUintegerAccessor(&NrGnbPhy::SetN1Delay, &NrGnbPhy::GetN1Delay),
145 MakeUintegerChecker<uint32_t>(0, 4))
146 .AddAttribute("N2Delay",
147 "Minimum processing delay needed to decode UL DCI and prepare UL data",
148 UintegerValue(2),
149 MakeUintegerAccessor(&NrGnbPhy::SetN2Delay, &NrGnbPhy::GetN2Delay),
150 MakeUintegerChecker<uint32_t>(0, 4))
151 .AddAttribute("TbDecodeLatency",
152 "Transport block decode latency",
153 TimeValue(MicroSeconds(100)),
155 MakeTimeChecker())
156 .AddAttribute("Numerology",
157 "The 3GPP numerology to be used",
158 UintegerValue(0),
159 MakeUintegerAccessor(&NrPhy::SetNumerology, &NrPhy::GetNumerology),
160 MakeUintegerChecker<uint16_t>())
161 .AddAttribute(
162 "SymbolsPerSlot",
163 "Number of symbols in one slot",
164 UintegerValue(14),
165 MakeUintegerAccessor(&NrPhy::SetSymbolsPerSlot, &NrPhy::GetSymbolsPerSlot),
166 MakeUintegerChecker<uint16_t>())
167 .AddAttribute("Pattern",
168 "The slot pattern",
169 StringValue("F|F|F|F|F|F|F|F|F|F|"),
170 MakeStringAccessor(&NrGnbPhy::SetPattern, &NrGnbPhy::GetPattern),
171 MakeStringChecker())
172 .AddAttribute(
173 "CsiRsModel",
174 "Defines the type of the CSI-RS model to use. Currently the user can select "
175 "either: CsiRsPerUe or CsiRsPerBeam. CsiRsPerUe means that CSI-RS signals will be "
176 "transmitted towards a specific "
177 "UE periodically. CsiRsPerBeam means that the CSI-RS will be transmitted using a "
178 "predefined set of beams.",
179 EnumValue(NrGnbPhy::CSI_RS_PER_UE),
180 MakeEnumAccessor<NrGnbPhy::CsiRsModel>(&NrGnbPhy::SetCsiRsModel,
182 MakeEnumChecker(NrGnbPhy::CSI_RS_PER_UE,
183 "CsiRsPerUe",
185 "CsiRsPerBeam"))
186 .AddAttribute("CsiRsPeriodicity",
187 "Default CSI periodicity in the number of slots",
188 UintegerValue(10),
189 MakeUintegerAccessor(&NrGnbPhy::SetCsiRsPeriodicity,
191 MakeUintegerChecker<uint16_t>())
192 .AddTraceSource("SlotDataStats",
193 "Data statistics for the current slot: SfnSf, active UE, used RE, "
194 "used symbols, available RBs, available symbols, bwp ID, cell ID",
195 MakeTraceSourceAccessor(&NrGnbPhy::m_phySlotDataStats),
196 "ns3::NrGnbPhy::SlotStatsTracedCallback")
197 .AddTraceSource("SlotCtrlStats",
198 "Ctrl statistics for the current slot: SfnSf, active UE, used RE, "
199 "used symbols, available RBs, available symbols, bwp ID, cell ID",
200 MakeTraceSourceAccessor(&NrGnbPhy::m_phySlotCtrlStats),
201 "ns3::NrGnbPhy::SlotStatsTracedCallback")
202 .AddTraceSource(
203 "RBDataStats",
204 "Resource Block used for data: SfnSf, symbol, RB PHY map, bwp ID, cell ID",
205 MakeTraceSourceAccessor(&NrGnbPhy::m_rbStatistics),
206 "ns3::NrGnbPhy::RBStatsTracedCallback");
207 return tid;
208}
209
210uint32_t
212{
213 return m_phySapUser->GetNumRbPerRbg();
214}
215
216const SfnSf&
218{
219 return m_currentSlot;
220}
221
228static uint32_t
229modulo(int n, uint32_t m)
230{
231 if (n >= 0)
232 {
233 return static_cast<uint32_t>(n) % m;
234 }
235 else
236 {
237 while (n < 0)
238 {
239 n += m;
240 }
241 return static_cast<uint32_t>(n);
242 }
243}
244
258static int32_t
259ReturnHarqSlot(const std::vector<LteNrTddSlotType>& pattern, uint32_t pos, uint32_t n1)
260{
261 int32_t k1 = static_cast<int32_t>(n1);
262
263 uint32_t index = modulo(static_cast<int>(pos) + k1, static_cast<uint32_t>(pattern.size()));
264
265 while (pattern[index] < LteNrTddSlotType::S)
266 {
267 k1++;
268 index = modulo(static_cast<int>(pos) + k1, static_cast<uint32_t>(pattern.size()));
269 NS_ASSERT(index < pattern.size());
270 }
271
272 return k1;
273}
274
275struct DciKPair
276{
277 uint32_t indexDci{0};
278 uint32_t k{0};
279};
280
289static DciKPair
290ReturnDciSlot(const std::vector<LteNrTddSlotType>& pattern, uint32_t pos, uint32_t n)
291{
292 DciKPair ret;
293 ret.k = n;
294 ret.indexDci = modulo(static_cast<int>(pos) - static_cast<int>(ret.k),
295 static_cast<uint32_t>(pattern.size()));
296
297 while (pattern[ret.indexDci] > LteNrTddSlotType::F)
298 {
299 ret.k++;
300 ret.indexDci = modulo(static_cast<int>(pos) - static_cast<int>(ret.k),
301 static_cast<uint32_t>(pattern.size()));
302 NS_ASSERT(ret.indexDci < pattern.size());
303 }
304
305 return ret;
306}
307
318static void
319GenerateDciMaps(const std::vector<LteNrTddSlotType>& pattern,
320 std::map<uint32_t, std::vector<uint32_t>>* toSend,
321 std::map<uint32_t, std::vector<uint32_t>>* generate,
322 uint32_t pos,
323 uint32_t n,
324 uint32_t l1l2CtrlLatency)
325{
326 auto dciSlot = ReturnDciSlot(pattern, pos, n);
327 uint32_t indexGen =
328 modulo(static_cast<int>(dciSlot.indexDci) - static_cast<int>(l1l2CtrlLatency),
329 static_cast<uint32_t>(pattern.size()));
330 uint32_t kWithCtrlLatency = static_cast<uint32_t>(dciSlot.k) + l1l2CtrlLatency;
331
332 (*toSend)[dciSlot.indexDci].push_back(static_cast<uint32_t>(dciSlot.k));
333 (*generate)[indexGen].push_back(kWithCtrlLatency);
334}
335
336void
337NrGnbPhy::GenerateStructuresFromPattern(const std::vector<LteNrTddSlotType>& pattern,
338 std::map<uint32_t, std::vector<uint32_t>>* toSendDl,
339 std::map<uint32_t, std::vector<uint32_t>>* toSendUl,
340 std::map<uint32_t, std::vector<uint32_t>>* generateDl,
341 std::map<uint32_t, std::vector<uint32_t>>* generateUl,
342 std::map<uint32_t, uint32_t>* dlHarqfbPosition,
343 uint32_t n0,
344 uint32_t n2,
345 uint32_t n1,
346 uint32_t l1l2CtrlLatency)
347{
348 const uint32_t n = static_cast<uint32_t>(pattern.size());
349
350 // Create a pattern that is all F.
351 std::vector<LteNrTddSlotType> fddGenerationPattern;
352 fddGenerationPattern.resize(pattern.size(), LteNrTddSlotType::F);
353
354 /* if we have to generate structs for a TDD pattern, then use the input pattern.
355 * Otherwise, pass to the gen functions a pattern which is all F (therefore, the
356 * the function will think that they will be able to transmit or
357 * receive things following n0, n1, n2, that is what happen in FDD, just in
358 * another band..
359 */
360
361 const std::vector<LteNrTddSlotType>* generationPattern;
362
363 if (IsTdd(pattern))
364 {
365 generationPattern = &pattern;
366 }
367 else
368 {
369 generationPattern = &fddGenerationPattern;
370 }
371
372 for (uint32_t i = 0; i < n; i++)
373 {
374 if ((*generationPattern)[i] == LteNrTddSlotType::UL)
375 {
376 GenerateDciMaps(*generationPattern, toSendUl, generateUl, i, n2, l1l2CtrlLatency);
377 }
378 else if ((*generationPattern)[i] == LteNrTddSlotType::DL ||
379 (*generationPattern)[i] == LteNrTddSlotType::S)
380 {
381 GenerateDciMaps(*generationPattern, toSendDl, generateDl, i, n0, l1l2CtrlLatency);
382
383 int32_t k1 = ReturnHarqSlot(*generationPattern, i, n1);
384 (*dlHarqfbPosition).insert(std::make_pair(i, k1));
385 }
386 else if ((*generationPattern)[i] == LteNrTddSlotType::F)
387 {
388 GenerateDciMaps(*generationPattern, toSendDl, generateDl, i, n0, l1l2CtrlLatency);
389 GenerateDciMaps(*generationPattern, toSendUl, generateUl, i, n2, l1l2CtrlLatency);
390
391 int32_t k1 = ReturnHarqSlot(*generationPattern, i, n1);
392 (*dlHarqfbPosition).insert(std::make_pair(i, k1));
393 }
394 }
395
396 /*
397 * Now, if the input pattern is for FDD, remove the elements in the
398 * opposite generate* structures: in the end, we don't want to generate DL
399 * for a FDD-UL band, right?
400 *
401 * But.. maintain the toSend structures, as they will be used to send
402 * feedback or other messages, like DCI.
403 */
404
405 if (!IsTdd(pattern))
406 {
407 if (HasUlSlot(pattern))
408 {
409 generateDl->clear();
410 }
411 else
412 {
413 generateUl->clear();
414 }
415 }
416
417 for (auto& list : (*generateUl))
418 {
419 std::stable_sort(list.second.begin(), list.second.end());
420 }
421
422 for (auto& list : (*generateDl))
423 {
424 std::stable_sort(list.second.begin(), list.second.end());
425 }
426}
427
428void
429NrGnbPhy::PushDlAllocation(const SfnSf& sfnSf) const
430{
431 NS_LOG_FUNCTION(this);
432 NS_ASSERT(m_phySapUser);
433
434 auto dci = m_phySapUser->GetDlCtrlDci();
435 VarTtiAllocInfo dlCtrlVarTti(dci);
436
437 SlotAllocInfo slotAllocInfo = SlotAllocInfo(sfnSf);
438
439 slotAllocInfo.m_numSymAlloc = dlCtrlVarTti.m_dci->m_numSym;
440 slotAllocInfo.m_type = SlotAllocInfo::DL;
441 slotAllocInfo.m_varTtiAllocInfo.emplace_back(dlCtrlVarTti);
442
443 m_phySapProvider->SetSlotAllocInfo(slotAllocInfo);
444}
445
446void
447NrGnbPhy::PushUlAllocation(const SfnSf& sfnSf) const
448{
449 NS_LOG_FUNCTION(this);
450 NS_ASSERT(m_phySapUser);
451
452 auto dci = m_phySapUser->GetUlCtrlDci();
453 VarTtiAllocInfo ulCtrlVarTti(dci);
454
455 SlotAllocInfo slotAllocInfo = SlotAllocInfo(sfnSf);
456
457 slotAllocInfo.m_numSymAlloc = ulCtrlVarTti.m_dci->m_numSym;
458 slotAllocInfo.m_type = SlotAllocInfo::UL;
459 slotAllocInfo.m_varTtiAllocInfo.emplace_back(ulCtrlVarTti);
460
461 m_phySapProvider->SetSlotAllocInfo(slotAllocInfo);
462}
463
464void
465NrGnbPhy::SetTddPattern(const std::vector<LteNrTddSlotType>& pattern)
466{
467 NS_LOG_FUNCTION(this);
468
469 std::stringstream ss;
470
471 for (const auto& v : pattern)
472 {
473 ss << v << "|";
474 }
475 NS_LOG_INFO("Set pattern : " << ss.str());
476
477 m_tddPattern = pattern;
478
479 m_generateDl.clear();
480 m_generateUl.clear();
481 m_toSendDl.clear();
482 m_toSendUl.clear();
483 m_dlHarqfbPosition.clear();
484
485 GenerateStructuresFromPattern(pattern,
486 &m_toSendDl,
487 &m_toSendUl,
488 &m_generateDl,
489 &m_generateUl,
490 &m_dlHarqfbPosition,
491 0,
492 GetN2Delay(),
493 GetN1Delay(),
495}
496
497void
498NrGnbPhy::ScheduleStartEventLoop(uint32_t nodeId, uint16_t frame, uint8_t subframe, uint16_t slot)
499{
500 NS_LOG_FUNCTION(this);
501 Simulator::ScheduleWithContext(nodeId,
502 MilliSeconds(0),
503 &NrGnbPhy::StartEventLoop,
504 this,
505 frame,
506 subframe,
507 slot);
508}
509
510void
511NrGnbPhy::StartEventLoop(uint16_t frame, uint8_t subframe, uint16_t slot)
512{
513 NS_LOG_FUNCTION(this);
514 NS_LOG_DEBUG("PHY starting. Configuration: "
515 << std::endl
516 << "\t TxPower: " << m_txPower << " dBm" << std::endl
517 << "\t NoiseFigure: " << m_noiseFigure << std::endl
518 << "\t N0: " << m_n0Delay << std::endl
519 << "\t N1: " << m_n1Delay << std::endl
520 << "\t N2: " << m_n2Delay << std::endl
521 << "\t TbDecodeLatency: " << GetTbDecodeLatency().GetMicroSeconds() << " us "
522 << std::endl
523 << "\t Numerology: " << GetNumerology() << std::endl
524 << "\t SymbolsPerSlot: " << GetSymbolsPerSlot() << std::endl
525 << "\t Pattern: " << GetPattern() << std::endl
526 << "Attached to physical channel: " << std::endl
527 << "\t Channel bandwidth: " << GetChannelBandwidth() << " Hz" << std::endl
528 << "\t Channel central freq: " << GetCentralFrequency() << " Hz" << std::endl
529 << "\t Num. RB: " << GetRbNum());
530 SfnSf startSlot(frame, subframe, slot, GetNumerology());
532 StartSlot(startSlot);
533}
534
535void
537{
538 NS_LOG_FUNCTION(this);
539 m_gnbCphySapUser = s;
540}
541
544{
545 NS_LOG_FUNCTION(this);
546 return m_gnbCphySapProvider;
547}
548
549void
550NrGnbPhy::SetNrFhPhySapProvider(NrFhPhySapProvider* s)
551{
553}
554
555NrFhPhySapUser*
556NrGnbPhy::GetNrFhPhySapUser()
557{
558 return m_nrFhPhySapUser;
559}
560
561uint32_t
563{
564 return m_n0Delay;
565}
566
567uint32_t
569{
570 return m_n1Delay;
571}
572
573uint32_t
575{
576 return m_n2Delay;
577}
578
579void
580NrGnbPhy::SetN0Delay(uint32_t delay)
581{
582 m_n0Delay = delay;
583 SetTddPattern(m_tddPattern); // Update the generate/send structures
584}
585
586void
587NrGnbPhy::SetN1Delay(uint32_t delay)
588{
589 m_n1Delay = delay;
590 SetTddPattern(m_tddPattern); // Update the generate/send structures
591}
592
593void
594NrGnbPhy::SetN2Delay(uint32_t delay)
595{
596 m_n2Delay = delay;
597 SetTddPattern(m_tddPattern); // Update the generate/send structures
598}
599
600bool
601NrGnbPhy::DoesFhAllocationFit(uint16_t bwpId, uint32_t mcs, uint32_t nRegs, uint8_t dlRank) const
602{
603 NS_LOG_FUNCTION(this);
604 NS_ASSERT(m_nrFhPhySapProvider);
605 return m_nrFhPhySapProvider->DoesAllocationFit(bwpId, mcs, nRegs, dlRank);
606}
607
608BeamId
609NrGnbPhy::GetBeamId(uint16_t rnti) const
610{
611 NS_LOG_FUNCTION(this);
612
613 for (const auto& i : m_deviceMap)
614 {
615 Ptr<NrUeNetDevice> ueDev = DynamicCast<NrUeNetDevice>(i);
616 uint64_t ueRnti = (DynamicCast<NrUePhy>(ueDev->GetPhy(GetBwpId())))->GetRnti();
617
618 if (ueRnti == rnti && DynamicCast<UniformPlanarArray>(m_spectrumPhy->GetAntenna()))
619 {
620 NS_ASSERT(m_spectrumPhy->GetBeamManager());
621 return m_spectrumPhy->GetBeamManager()->GetBeamId(i);
622 }
623 }
624 return BeamId(0, 0);
625}
626
627void
628NrGnbPhy::SetCam(const Ptr<NrChAccessManager>& cam)
629{
630 NS_LOG_FUNCTION(this);
631 NS_ASSERT(cam != nullptr);
632 m_cam = cam;
633 m_cam->SetAccessGrantedCallback(
634 std::bind(&NrGnbPhy::ChannelAccessGranted, this, std::placeholders::_1));
635 m_cam->SetAccessDeniedCallback(std::bind(&NrGnbPhy::ChannelAccessLost, this));
636}
637
638Ptr<NrChAccessManager>
640{
641 NS_LOG_FUNCTION(this);
642 return m_cam;
643}
644
645void
647{
648 m_txPower = pow;
649}
650
651double
653{
654 return m_txPower;
655}
656
657void
658NrGnbPhy::SetSubChannels(const std::vector<int>& rbIndexVector, size_t nTotalAllocRbs)
659{
660 Ptr<SpectrumValue> txPsd = GetTxPowerSpectralDensity(rbIndexVector);
661 NS_ASSERT(txPsd);
662
663 // In case of UNIFORM_POWER_ALLOCATION_USED, the txPsd created by GetTxPowerSpectralDensity
664 // assumed that the transmit power would be split only among RBs allocated to this signal/UE.
665 // This assumption is false when there are concurrent transmissions on other RBs to other UEs
666 // (OFDMA DL). To correct this, use the combined number of used RBs to scale down txPsd.
667 if (GetPowerAllocationType() == NrSpectrumValueHelper::UNIFORM_POWER_ALLOCATION_USED)
668 {
669 auto scaling = double(rbIndexVector.size()) / double(nTotalAllocRbs);
670 for (auto it = txPsd->ValuesBegin(); it != txPsd->ValuesEnd(); it++)
671 {
672 *it *= scaling;
673 }
674 }
675 else
676 {
677 // UNIFORM_POWER_ALLOCATION_BW: no scaling required
678 }
679
680 m_spectrumPhy->SetTxPowerSpectralDensity(txPsd);
681}
682
683void
684NrGnbPhy::QueueMib()
685{
686 NS_LOG_FUNCTION(this);
689 mib.dlBandwidth = GetChannelBandwidth() / (1000 * 100);
690 mib.systemFrameNumber = 1;
691 Ptr<NrMibMessage> mibMsg = Create<NrMibMessage>();
692 mibMsg->SetSourceBwp(GetBwpId());
693 mibMsg->SetMib(mib);
694 EnqueueCtrlMsgNow(mibMsg);
695}
696
697void
698NrGnbPhy::QueueSib()
699{
700 NS_LOG_FUNCTION(this);
701 Ptr<NrSib1Message> msg = Create<NrSib1Message>();
702 msg->SetSib1(m_sib1);
703 msg->SetSourceBwp(GetBwpId());
705}
706
707void
708NrGnbPhy::CallMacForSlotIndication(const SfnSf& currentSlot)
709{
710 NS_LOG_FUNCTION(this);
711 NS_ASSERT(!m_generateDl.empty() || !m_generateUl.empty());
712
713 m_phySapUser->SetCurrentSfn(currentSlot);
714
715 uint64_t currentSlotN = currentSlot.Normalize() % m_tddPattern.size();
716
717 NS_LOG_DEBUG("Start Slot " << currentSlot << ". In position " << currentSlotN
718 << " there is a slot of type " << m_tddPattern[currentSlotN]);
719
720 for (const auto& k2WithLatency : m_generateUl[currentSlotN])
721 {
722 SfnSf targetSlot = currentSlot;
723 targetSlot.Add(k2WithLatency);
724
725 uint64_t pos = targetSlot.Normalize() % m_tddPattern.size();
726
727 NS_LOG_DEBUG(" in slot " << currentSlot << " generate UL for " << targetSlot
728 << " which is of type " << m_tddPattern[pos]);
729
730 m_phySapUser->SlotUlIndication(targetSlot, m_tddPattern[pos]);
731 }
732
733 for (const auto& k0WithLatency : m_generateDl[currentSlotN])
734 {
735 SfnSf targetSlot = currentSlot;
736 targetSlot.Add(k0WithLatency);
737
738 uint64_t pos = targetSlot.Normalize() % m_tddPattern.size();
739
740 NS_LOG_DEBUG(" in slot " << currentSlot << " generate DL for " << targetSlot
741 << " which is of type " << m_tddPattern[pos]);
742
743 m_phySapUser->SlotDlIndication(targetSlot, m_tddPattern[pos]);
744 }
745}
746
747void
748NrGnbPhy::StartSlot(const SfnSf& startSlot)
749{
750 NS_LOG_FUNCTION(this);
751 NS_ASSERT(m_channelStatus != TO_LOSE);
752
753 m_currentSlot = startSlot;
754 m_lastSlotStart = Simulator::Now();
755
756 Simulator::Schedule(GetSlotPeriod(), &NrGnbPhy::EndSlot, this);
757
758 // update the current slot allocation; if empty (e.g., at the beginning of simu)
759 // then insert a dummy allocation, without anything.
760 if (SlotAllocInfoExists(m_currentSlot))
761 {
763 }
764 else
765 {
766 NS_LOG_WARN("No allocation for the current slot. Using an empty one");
767 m_currSlotAllocInfo = SlotAllocInfo(m_currentSlot);
768 }
769
770 if (m_isPrimary)
771 {
772 if (m_currentSlot.GetSlot() == 0)
773 {
774 bool mibOrSib = false;
775 if (m_currentSlot.GetSubframe() == 0) // send MIB at the beginning of each frame
776 {
777 QueueMib();
778 mibOrSib = true;
779 }
780 else if (m_currentSlot.GetSubframe() == 5) // send SIB at beginning of second half-frame
781 {
782 QueueSib();
783 mibOrSib = true;
784 }
786 {
787 VarTtiAllocInfo dlCtrlSlot(m_phySapUser->GetDlCtrlDci());
788 m_currSlotAllocInfo.m_varTtiAllocInfo.push_front(dlCtrlSlot);
790 }
791 }
792 }
793
794 if (m_channelStatus == GRANTED)
795 {
796 NS_LOG_INFO("Channel granted");
797 CallMacForSlotIndication(m_currentSlot);
798 DoStartSlot();
799 }
800 else
801 {
802 bool hasUlDci = false;
803 SfnSf ulSfn = m_currentSlot;
804 ulSfn.Add(GetN2Delay());
805
806 if (GetN2Delay() > 0)
807 {
808 if (SlotAllocInfoExists(ulSfn))
809 {
810 SlotAllocInfo& ulSlot = PeekSlotAllocInfo(ulSfn);
811 hasUlDci = ulSlot.ContainsDataAllocation() || ulSlot.ContainsUlCtrlAllocation() ||
812 ulSlot.ContainsUlMsg3Allocation();
813 }
814 }
815 // If there is a DL CTRL, try to obtain the channel to transmit it;
816 // because, even if right now there isn't any message, maybe they
817 // will come from another BWP.
821 {
822 // Request the channel access
823 if (m_channelStatus == NONE)
824 {
825 NS_LOG_INFO("Channel not granted, request the channel");
826 m_channelStatus = REQUESTED; // This goes always before RequestAccess()
827 m_cam->RequestAccess();
828 if (m_channelStatus == GRANTED)
829 {
830 // Repetition but we can have a CAM that gives the channel
831 // instantaneously
832 NS_LOG_INFO("Channel granted; asking MAC for SlotIndication for the future and "
833 "then start the slot");
834 CallMacForSlotIndication(m_currentSlot);
835 DoStartSlot();
836 return; // Exit without calling anything else
837 }
838 }
839 // If the channel was not granted, queue back the allocation,
840 // without calling the MAC for a new slot
841 auto slotAllocCopy = m_currSlotAllocInfo;
842 auto newSfnSf = slotAllocCopy.m_sfnSf;
843 newSfnSf.Add(1);
844 NS_LOG_INFO("Queueing allocation in front for " << newSfnSf);
846 {
847 NS_LOG_INFO("Reason: Current slot allocation has data");
848 }
849 else
850 {
851 NS_LOG_INFO("Reason: CTRL message list is not empty");
852 }
853
854 PushFrontSlotAllocInfo(newSfnSf, slotAllocCopy);
855 }
856 else
857 {
858 // It's an empty slot; ask the MAC for a new one (maybe a new data will arrive..)
859 // and just let the current one go away
860 NS_LOG_INFO("Empty slot, but asking MAC for SlotIndication for the future, maybe there "
861 "will be data");
862 CallMacForSlotIndication(m_currentSlot);
863 }
864 // If we have the UL CTRL, then schedule it (we are listening, so
865 // we don't need the channel.
866
868 {
869 for (const auto& alloc : m_currSlotAllocInfo.m_varTtiAllocInfo)
870 {
871 if (alloc.m_dci->m_type == DciInfoElementTdma::CTRL &&
872 alloc.m_dci->m_format == DciInfoElementTdma::UL)
873 {
874 Time start = GetSymbolPeriod() * alloc.m_dci->m_symStart;
875 NS_LOG_INFO("Schedule UL CTRL at " << start);
876 Simulator::Schedule(start, &NrGnbPhy::UlCtrl, this, alloc.m_dci);
877 }
878 else if (alloc.m_dci->m_type == DciInfoElementTdma::SRS &&
879 alloc.m_dci->m_format == DciInfoElementTdma::UL)
880 {
881 Time start = GetSymbolPeriod() * alloc.m_dci->m_symStart;
882 NS_LOG_INFO("Schedule UL SRS at " << start);
883 Simulator::Schedule(start, &NrGnbPhy::UlSrs, this, alloc.m_dci);
884 }
885 }
886 }
887 }
888}
889
890void
891NrGnbPhy::DoCheckOrReleaseChannel()
892{
893 NS_LOG_FUNCTION(this);
894
895 NS_ASSERT(m_channelStatus == GRANTED);
896 // The channel is granted, we have to check if we maintain it for the next
897 // slot or we have to release it.
898
899 // Assuming the scheduler assign contiguous symbol
900 uint8_t lastDlSymbol = 0;
901 for (auto& dci : m_currSlotAllocInfo.m_varTtiAllocInfo)
902 {
903 if (dci.m_dci->m_type == DciInfoElementTdma::DATA &&
904 dci.m_dci->m_format == DciInfoElementTdma::DL)
905 {
906 lastDlSymbol =
907 std::max(lastDlSymbol,
908 static_cast<uint8_t>(dci.m_dci->m_symStart + dci.m_dci->m_numSym));
909 }
910 }
911
912 Time lastDataTime = GetSymbolPeriod() * lastDlSymbol;
913
914 if (GetSlotPeriod() - lastDataTime > MicroSeconds(25))
915 {
916 NS_LOG_LOGIC("Last symbol of data: " << +lastDlSymbol
917 << ", to the end of slot we still have "
918 << (GetSlotPeriod() - lastDataTime).GetMicroSeconds()
919 << " us, so we're going to lose the channel");
920 m_channelStatus = TO_LOSE;
921 }
922 else
923 {
924 NS_LOG_LOGIC("Last symbol of data: " << +lastDlSymbol
925 << ", to the end of slot we still have "
926 << (GetSlotPeriod() - lastDataTime).GetMicroSeconds()
927 << " us, so we're NOT going to lose the channel");
928 }
929}
930
931void
932NrGnbPhy::RetrievePrepareEncodeCtrlMsgs()
933{
934 NS_LOG_FUNCTION(this);
935 auto ctrlMsgs = PopCurrentSlotCtrlMsgs();
936 ctrlMsgs.sort();
937 ctrlMsgs.merge(RetrieveMsgsFromDCIs(m_currentSlot));
938
939 if (m_netDevice != nullptr)
940 {
941 DynamicCast<NrGnbNetDevice>(m_netDevice)->RouteOutgoingCtrlMsgs(ctrlMsgs, GetBwpId());
942 }
943 else
944 {
945 // No netDevice (that could happen in tests) so just redirect them to us
946 for (const auto& msg : ctrlMsgs)
947 {
948 EncodeCtrlMsg(msg);
949 }
950 }
951}
952
953void
954NrGnbPhy::GenerateAllocationStatistics(const SlotAllocInfo& allocInfo) const
955{
956 NS_LOG_FUNCTION(this);
957 std::unordered_set<uint16_t> activeUe;
958 uint32_t availRb = GetRbNum();
959 uint32_t dataReg = 0;
960 uint32_t ctrlReg = 0;
961 uint32_t dataSym = 0;
962 uint32_t ctrlSym = 0;
963
964 int lastSymStart = -1;
965 uint32_t symUsed = 0;
966
967 for (const auto& allocation : allocInfo.m_varTtiAllocInfo)
968 {
969 uint32_t rbg = std::count(allocation.m_dci->m_rbgBitmask.begin(),
970 allocation.m_dci->m_rbgBitmask.end(),
971 1);
972
973 // First: Store the RNTI of the UE in the active list
974 if (allocation.m_dci->m_rnti != 0)
975 {
976 activeUe.insert(allocation.m_dci->m_rnti);
977 }
978
979 NS_ASSERT(lastSymStart <= allocation.m_dci->m_symStart);
980
981 auto rbgUsed = (rbg * GetNumRbPerRbg()) * allocation.m_dci->m_numSym;
982 if (allocation.m_dci->m_type == DciInfoElementTdma::DATA ||
983 allocation.m_dci->m_type == DciInfoElementTdma::MSG3)
984 {
985 dataReg += rbgUsed;
986 }
987 else
988 {
989 ctrlReg += rbgUsed;
990 }
991
992 if (lastSymStart != allocation.m_dci->m_symStart)
993 {
994 symUsed += allocation.m_dci->m_numSym;
995
996 if (allocation.m_dci->m_type == DciInfoElementTdma::DATA ||
997 allocation.m_dci->m_type == DciInfoElementTdma::MSG3)
998 {
999 dataSym += allocation.m_dci->m_numSym;
1000 }
1001 else
1002 {
1003 ctrlSym += allocation.m_dci->m_numSym;
1004 }
1005 }
1006
1007 lastSymStart = allocation.m_dci->m_symStart;
1008 }
1009
1010 NS_ASSERT_MSG(symUsed == allocInfo.m_numSymAlloc,
1011 "Allocated " << +allocInfo.m_numSymAlloc << " but only " << symUsed
1012 << " written in stats");
1013
1014 m_phySlotDataStats(allocInfo.m_sfnSf,
1015 activeUe.size(),
1016 dataReg,
1017 dataSym,
1018 availRb,
1019 GetSymbolsPerSlot() - ctrlSym,
1020 GetBwpId(),
1021 GetCellId());
1022 m_phySlotCtrlStats(allocInfo.m_sfnSf,
1023 activeUe.size(),
1024 ctrlReg,
1025 ctrlSym,
1026 availRb,
1027 GetSymbolsPerSlot() - dataSym,
1028 GetBwpId(),
1029 GetCellId());
1030}
1031
1032void
1033NrGnbPhy::DoStartSlot()
1034{
1035 NS_LOG_FUNCTION(this);
1036 NS_ASSERT(m_ctrlMsgs.empty()); // This assert has to be re-evaluated for NR-U.
1037 // We can have messages before we weren't able to tx them before.
1038
1039 uint64_t currentSlotN = m_currentSlot.Normalize() % m_tddPattern.size();
1040
1041 NS_LOG_DEBUG("Start Slot " << m_currentSlot << " of type " << m_tddPattern[currentSlotN]);
1042
1043 GenerateAllocationStatistics(m_currSlotAllocInfo);
1044
1046 {
1047 return;
1048 }
1049
1050 NS_LOG_DEBUG("Allocations of the current slot: " << std::endl << m_currSlotAllocInfo);
1051
1052 DoCheckOrReleaseChannel();
1053
1054 RetrievePrepareEncodeCtrlMsgs();
1055
1056 PrepareRbgAllocationMap(m_currSlotAllocInfo.m_varTtiAllocInfo);
1057
1060 {
1061 HandleFhDropping();
1062 }
1063
1064 FillTheEvent();
1065}
1066
1067void
1068NrGnbPhy::PrepareRbgAllocationMap(const std::deque<VarTtiAllocInfo>& allocations)
1069{
1070 NS_LOG_FUNCTION(this);
1071
1072 // Start with a clean RBG allocation bitmask
1073 m_rbgAllocationPerSym.clear();
1074
1075 // Create RBG map to know where to put power in DL
1076 for (const auto& allocation : allocations)
1077 {
1078 if (allocation.m_dci->m_type != DciInfoElementTdma::CTRL)
1079 {
1080 if (allocation.m_dci->m_format == DciInfoElementTdma::DL)
1081 {
1082 // In m_rbgAllocationPerSym, store only the DL RBG set to 1:
1083 // these will used to put power
1084 StoreRBGAllocation(&m_rbgAllocationPerSym, allocation.m_dci);
1085 }
1086
1087 // For statistics, store UL/DL allocations
1088 StoreRBGAllocation(&m_rbgAllocationPerSymDataStat, allocation.m_dci);
1089 }
1090 }
1091
1092 for (const auto& s : m_rbgAllocationPerSymDataStat)
1093 {
1094 auto& rbgAllocation = s.second;
1095 m_rbStatistics(m_currentSlot,
1096 s.first,
1097 FromRBGBitmaskToRBAssignment(rbgAllocation),
1098 GetBwpId(),
1099 GetCellId());
1100 }
1101
1102 m_rbgAllocationPerSymDataStat.clear();
1103}
1104
1105void
1106NrGnbPhy::HandleFhDropping()
1107{
1108 NS_LOG_FUNCTION(this);
1109 NS_LOG_DEBUG("Dropping FH control messages that do not fit in the available FH BW");
1110 std::vector<size_t> indexesToDelete;
1111 std::vector<size_t> shuffledIndexes(m_currSlotAllocInfo.m_varTtiAllocInfo.size());
1112 std::iota(shuffledIndexes.begin(), shuffledIndexes.end(), 0); // Fill with 0, 1, …, n-1
1113 // Shuffle the indexes to randomize the order of processing
1114 auto rng = std::default_random_engine{};
1115 std::shuffle(shuffledIndexes.begin(), shuffledIndexes.end(), rng);
1116 // Example usage: Access elements using shuffled indexes
1117 for (size_t index : shuffledIndexes)
1118 {
1119 std::shared_ptr<DciInfoElementTdma> dci =
1120 m_currSlotAllocInfo.m_varTtiAllocInfo[index].m_dci; // Access by shuffled index
1121
1122 NS_ASSERT(dci != nullptr);
1123 if (dci->m_type == DciInfoElementTdma::DATA && dci->m_format == DciInfoElementTdma::DL)
1124 {
1125 NS_LOG_DEBUG("Checking DCI " << *dci << " for FH allocation fit");
1126 // Check if the DCI allocation fits in the FH BW
1127 // If it does not fit, mark it for deletion
1128 // If it fits, update traces based on dropped data
1129 long rbgAssigned = std::count(dci->m_rbgBitmask.begin(), dci->m_rbgBitmask.end(), 1);
1130
1132 dci->m_mcs,
1133 rbgAssigned * dci->m_numSym,
1134 dci->m_rank) == 0)
1135 {
1136 NS_LOG_DEBUG("Dropping DCI " << *dci << " because it does not fit in FH BW");
1137 indexesToDelete.push_back(index); // Add index to the list
1138 }
1139 else
1140 {
1141 m_nrFhPhySapProvider->UpdateTracesBasedOnDroppedData(GetBwpId(),
1142 dci->m_mcs,
1143 rbgAssigned,
1144 dci->m_numSym,
1145 dci->m_rank);
1146 }
1147 }
1148 else
1149 {
1150 NS_LOG_DEBUG("Skipping non-DL CTRL DCI " << *dci);
1151 continue; // Skip non-DL CTRL DCIs
1152 }
1153 }
1154
1155 // Sort indexesToDelete in ascending order
1156 std::sort(indexesToDelete.begin(), indexesToDelete.end());
1157
1158 // Delete elements in reverse order to avoid invalidating indexes
1159 for (auto it = indexesToDelete.rbegin(); it != indexesToDelete.rend(); ++it)
1160 {
1162 *it);
1163 }
1164}
1165
1166void
1167NrGnbPhy::FillTheEvent()
1168{
1169 NS_LOG_FUNCTION(this);
1170
1171 uint8_t lastSymStart = 0;
1172 for (const auto& allocation : m_currSlotAllocInfo.m_varTtiAllocInfo)
1173 {
1174 NS_ASSERT(lastSymStart <= allocation.m_dci->m_symStart);
1175
1176 auto varTtiStart = GetSymbolPeriod() * allocation.m_dci->m_symStart;
1177 Simulator::Schedule(varTtiStart, &NrGnbPhy::StartVarTti, this, allocation.m_dci);
1178 lastSymStart = allocation.m_dci->m_symStart;
1179
1180 NS_LOG_INFO("Scheduled allocation " << *(allocation.m_dci) << " at " << varTtiStart);
1181 }
1182
1184}
1185
1186void
1187NrGnbPhy::StoreRBGAllocation(std::unordered_map<uint8_t, std::vector<bool>>* map,
1188 const std::shared_ptr<DciInfoElementTdma>& dci) const
1189{
1190 NS_LOG_FUNCTION(this);
1191
1192 auto itAlloc = map->find(dci->m_symStart);
1193 if (itAlloc == map->end())
1194 {
1195 itAlloc = map->insert(std::make_pair(dci->m_symStart, dci->m_rbgBitmask)).first;
1196 }
1197 else
1198 {
1199 auto& existingRBGBitmask = itAlloc->second;
1200 NS_ASSERT(existingRBGBitmask.size() == dci->m_rbgBitmask.size());
1201 for (uint32_t i = 0; i < existingRBGBitmask.size(); ++i)
1202 {
1203 existingRBGBitmask.at(i) = existingRBGBitmask.at(i) || dci->m_rbgBitmask.at(i);
1204 }
1205 }
1206}
1207
1208std::list<Ptr<NrControlMessage>>
1209NrGnbPhy::RetrieveDciFromAllocation(const SlotAllocInfo& alloc,
1210 const DciInfoElementTdma::DciFormat& format,
1211 uint32_t kDelay,
1212 uint32_t k1Delay)
1213{
1214 NS_LOG_FUNCTION(this);
1215 std::list<Ptr<NrControlMessage>> ctrlMsgs;
1216
1217 if (!alloc.m_buildRarList.empty())
1218 {
1219 Ptr<NrRarMessage> ulMsg3DciMsg = Create<NrRarMessage>();
1220 for (const auto& rarIt : alloc.m_buildRarList)
1221 {
1222 NrRarMessage::Rar rar{};
1223 // RA preamble and RNTI should be set before by MAC/scheduler
1224 NS_ASSERT(rarIt.raPreambleId != 255);
1225 rar.rarPayload = rarIt;
1226 rar.rarPayload.k2Delay = kDelay;
1227 ulMsg3DciMsg->AddRar(rar);
1228
1229 NS_LOG_INFO("In slot " << m_currentSlot << " PHY retrieves the RAR message for RNTI "
1230 << rar.rarPayload.ulMsg3Dci->m_rnti << " RA preamble Id "
1231 << +rar.rarPayload.raPreambleId << " at:" << Simulator::Now()
1232 << " for slot:" << alloc.m_sfnSf << " kDelay:" << kDelay
1233 << "k1Delay:" << k1Delay);
1234 ulMsg3DciMsg->SetSourceBwp(GetBwpId());
1235 }
1236 if (kDelay != 0)
1237 {
1238 ctrlMsgs.push_back(ulMsg3DciMsg);
1239 }
1240 }
1241
1242 for (const auto& dlAlloc : alloc.m_varTtiAllocInfo)
1243 {
1244 if (dlAlloc.m_dci->m_type != DciInfoElementTdma::CTRL &&
1245 dlAlloc.m_dci->m_type != DciInfoElementTdma::MSG3 // we are sending MSG3 grant via RAR
1246 // message, we cannot also send UL DCI
1247 && dlAlloc.m_dci->m_format == format)
1248 {
1249 auto& dciElem = dlAlloc.m_dci;
1250 NS_ASSERT(dciElem->m_format == format);
1251 NS_ASSERT_MSG(dciElem->m_symStart + dciElem->m_numSym <= GetSymbolsPerSlot(),
1252 "symStart: " << static_cast<uint32_t>(dciElem->m_symStart)
1253 << " numSym: " << static_cast<uint32_t>(dciElem->m_numSym)
1254 << " symPerSlot: "
1255 << static_cast<uint32_t>(GetSymbolsPerSlot()));
1256
1257 NS_LOG_INFO("Send DCI to RNTI " << dciElem->m_rnti << " from sym "
1258 << +dciElem->m_symStart << " to "
1259 << +dciElem->m_symStart + dciElem->m_numSym);
1260
1261 Ptr<NrControlMessage> msg;
1262
1263 if (dciElem->m_format == DciInfoElementTdma::DL)
1264 {
1265 Ptr<NrDlDciMessage> dciMsg = Create<NrDlDciMessage>(dciElem);
1266
1267 dciMsg->SetSourceBwp(GetBwpId());
1268 dciMsg->SetKDelay(kDelay);
1269 dciMsg->SetK1Delay(k1Delay);
1270 msg = dciMsg;
1271 }
1272 else
1273 {
1274 Ptr<NrUlDciMessage> dciMsg = Create<NrUlDciMessage>(dciElem);
1275
1276 dciMsg->SetSourceBwp(GetBwpId());
1277 dciMsg->SetKDelay(kDelay);
1278 msg = dciMsg;
1279 }
1280
1281 ctrlMsgs.push_back(msg);
1282 }
1283 }
1284 ctrlMsgs.sort();
1285 return ctrlMsgs;
1286}
1287
1288std::list<Ptr<NrControlMessage>>
1289NrGnbPhy::RetrieveMsgsFromDCIs(const SfnSf& currentSlot)
1290{
1291 std::list<Ptr<NrControlMessage>> ctrlMsgs;
1292 uint64_t currentSlotN = currentSlot.Normalize() % m_tddPattern.size();
1293
1294 uint32_t k1delay = m_dlHarqfbPosition[currentSlotN];
1295
1296 // TODO: copy paste :(
1297 for (const auto& k0delay : m_toSendDl[currentSlotN])
1298 {
1299 SfnSf targetSlot = currentSlot;
1300
1301 targetSlot.Add(k0delay);
1302
1303 if (targetSlot == currentSlot)
1304 {
1305 NS_LOG_DEBUG(" in slot " << currentSlot << " send DL DCI for the same slot");
1306
1307 ctrlMsgs.merge(RetrieveDciFromAllocation(m_currSlotAllocInfo,
1309 k0delay,
1310 k1delay));
1311 }
1312 else if (SlotAllocInfoExists(targetSlot))
1313 {
1314 NS_LOG_DEBUG(" in slot " << currentSlot << " send DL DCI for " << targetSlot);
1315
1316 ctrlMsgs.merge(RetrieveDciFromAllocation(PeekSlotAllocInfo(targetSlot),
1318 k0delay,
1319 k1delay));
1320 }
1321 else
1322 {
1323 NS_LOG_DEBUG("No allocation found for slot " << targetSlot);
1324 }
1325 }
1326
1327 for (const auto& k2delay : m_toSendUl[currentSlotN])
1328 {
1329 SfnSf targetSlot = currentSlot;
1330
1331 targetSlot.Add(k2delay);
1332
1333 if (targetSlot == currentSlot)
1334 {
1335 NS_LOG_DEBUG(" in slot " << currentSlot << " send UL DCI for the same slot");
1336
1337 ctrlMsgs.merge(RetrieveDciFromAllocation(m_currSlotAllocInfo,
1339 k2delay,
1340 k1delay));
1341 }
1342 else if (SlotAllocInfoExists(targetSlot))
1343 {
1344 NS_LOG_DEBUG(" in slot " << currentSlot << " send UL DCI for " << targetSlot);
1345
1346 ctrlMsgs.merge(RetrieveDciFromAllocation(PeekSlotAllocInfo(targetSlot),
1348 k2delay,
1349 k1delay));
1350 }
1351 else
1352 {
1353 NS_LOG_DEBUG("No allocation found for slot " << targetSlot);
1354 }
1355 }
1356 ctrlMsgs.sort();
1357 return ctrlMsgs;
1358}
1359
1360Time
1361NrGnbPhy::DlCtrl(const std::shared_ptr<DciInfoElementTdma>& dci)
1362{
1363 NS_LOG_FUNCTION(this);
1364
1365 NS_LOG_DEBUG("Starting DL CTRL TTI at symbol " << +m_currSymStart << " to "
1366 << +m_currSymStart + dci->m_numSym);
1367
1368 // TX control period
1369 Time varTtiPeriod = GetSymbolPeriod() * dci->m_numSym;
1370
1371 bool transmitCsiRs = false;
1372 if (m_enableCsiRs)
1373 {
1374 // Check whether it is time to transmit CSI-RS
1375 uint16_t currentCsiRsOffset = m_currentSlot.Normalize() % m_csiRsPeriodicity;
1376 if (TimeToTransmitCsiRs(currentCsiRsOffset))
1377 {
1378 varTtiPeriod = ScheduleCsiRs(varTtiPeriod, currentCsiRsOffset);
1379 transmitCsiRs = true;
1380 }
1381 }
1382
1383 // The function that is filling m_ctrlMsgs is NrPhy::encodeCtrlMsgs
1384 if (!m_ctrlMsgs.empty() || transmitCsiRs)
1385 {
1386 NS_LOG_DEBUG("gNB TXing DL CTRL with "
1387 << m_ctrlMsgs.size() << " msgs, frame " << m_currentSlot << " symbols "
1388 << static_cast<uint32_t>(dci->m_symStart) << "-"
1389 << static_cast<uint32_t>(dci->m_symStart + dci->m_numSym - 1) << " start "
1390 << Simulator::Now() << " end "
1391 << Simulator::Now() + varTtiPeriod - NanoSeconds(1.0));
1392 for (auto& m_ctrlMsg : m_ctrlMsgs)
1393 {
1394 Ptr<NrControlMessage> msg = m_ctrlMsg;
1395 m_phyTxedCtrlMsgsTrace(m_currentSlot, GetCellId(), dci->m_rnti, GetBwpId(), msg);
1396 }
1397
1398 SendCtrlChannels(varTtiPeriod -
1399 NanoSeconds(1.0)); // -1 ns ensures control ends before data period
1400 }
1401 else
1402 {
1403 NS_LOG_DEBUG("No messages to send, skipping");
1404 }
1405
1406 return varTtiPeriod;
1407}
1408
1409bool
1410NrGnbPhy::TimeToTransmitCsiRs(uint16_t currentOffset) const
1411{
1412 if (!m_csiRsOffsetToUes.contains(currentOffset))
1413 {
1414 return false;
1415 }
1416 else
1417 {
1418 return !m_csiRsOffsetToUes.at(currentOffset).empty();
1419 }
1420}
1421
1422void
1423NrGnbPhy::TransmitCsiRsPerUe(Ptr<NrUeNetDevice> ueDev)
1424{
1425 NS_LOG_FUNCTION(this);
1427 uint64_t rnti = (DynamicCast<NrUePhy>(ueDev->GetPhy(GetBwpId())))->GetRnti();
1428
1429 NS_LOG_DEBUG("Transmitting CSI-RS towards UE with IMSI : " << ueDev->GetImsi() << " at slot:"
1430 << +m_currentSlot.Normalize());
1431 m_spectrumPhy->StartTxCsiRs(rnti, 0);
1432}
1433
1434Time
1435NrGnbPhy::ScheduleCsiRs(Time ctrlVarTti, uint16_t currentOffset)
1436{
1437 NS_ASSERT_MSG(!m_spectrumPhy->IsTransmitting(),
1438 "Should have finished transmission of CTRL already.");
1439
1440 if (m_csiRsModel == CSI_RS_PER_UE)
1441 {
1442 // CSI-RS is the duration of 1 nanosecond plus we want a
1443 // 1 nanosecond pause between the independent CSI-RS transmissions
1444 ctrlVarTti -= m_deviceMap.size() * NanoSeconds(2);
1445
1446 uint16_t ueCounter = 0;
1447 for (auto& i : m_csiRsOffsetToUes.at(currentOffset))
1448 {
1449 Ptr<NrUeNetDevice> ueDev = DynamicCast<NrUeNetDevice>(i);
1450 Simulator::Schedule(ctrlVarTti + NanoSeconds(2.0) * ueCounter,
1451 &NrGnbPhy::TransmitCsiRsPerUe,
1452 this,
1453 ueDev);
1454
1455 ueCounter++;
1456 }
1457 }
1458 return ctrlVarTti;
1459}
1460
1461Time
1462NrGnbPhy::UlCtrl(const std::shared_ptr<DciInfoElementTdma>& dci)
1463{
1464 NS_LOG_FUNCTION(this);
1465
1466 NS_LOG_DEBUG("Starting UL CTRL TTI at symbol " << +m_currSymStart << " to "
1467 << +m_currSymStart + dci->m_numSym);
1468
1469 Time varTtiPeriod = GetSymbolPeriod() * dci->m_numSym;
1470
1471 NS_LOG_DEBUG("gNB RXng UL CTRL frame "
1472 << m_currentSlot << " symbols " << static_cast<uint32_t>(dci->m_symStart) << "-"
1473 << static_cast<uint32_t>(dci->m_symStart + dci->m_numSym - 1) << " start "
1474 << Simulator::Now() << " end " << Simulator::Now() + varTtiPeriod);
1475 return varTtiPeriod;
1476}
1477
1478Time
1479NrGnbPhy::DlData(const std::shared_ptr<DciInfoElementTdma>& dci)
1480{
1481 NS_LOG_FUNCTION(this);
1482 NS_LOG_DEBUG("Starting DL DATA TTI at symbol " << +m_currSymStart << " to "
1483 << +m_currSymStart + dci->m_numSym << " for "
1484 << +dci->m_rnti);
1485
1486 Time varTtiPeriod = GetSymbolPeriod() * dci->m_numSym;
1487
1488 Ptr<PacketBurst> pktBurst = GetPacketBurst(m_currentSlot, dci->m_symStart, dci->m_rnti);
1489
1490 if (!pktBurst || pktBurst->GetNPackets() == 0)
1491 {
1492 // sometimes the UE will be scheduled when no data is queued.
1493 // In this case, don't send anything, don't put power... do nothing!
1494 return varTtiPeriod;
1495 }
1496
1497 NS_LOG_INFO("gNB TXing DL DATA frame "
1498 << m_currentSlot << " symbols " << static_cast<uint32_t>(dci->m_symStart) << "-"
1499 << static_cast<uint32_t>(dci->m_symStart + dci->m_numSym - 1) << " start "
1500 << Simulator::Now() + NanoSeconds(1) << " end "
1501 << Simulator::Now() + varTtiPeriod - NanoSeconds(2.0));
1502
1503 Simulator::Schedule(NanoSeconds(1.0),
1504 &NrGnbPhy::SendDataChannels,
1505 this,
1506 pktBurst,
1507 varTtiPeriod - NanoSeconds(2.0),
1508 dci);
1509
1510 return varTtiPeriod;
1511}
1512
1513Time
1514NrGnbPhy::UlData(const std::shared_ptr<DciInfoElementTdma>& dci)
1515{
1516 NS_LOG_FUNCTION(this);
1517
1518 NS_LOG_DEBUG("Starting UL DATA TTI at symbol " << +m_currSymStart << " to "
1519 << +m_currSymStart + dci->m_numSym);
1520
1521 Time varTtiPeriod = GetSymbolPeriod() * dci->m_numSym;
1522
1523 m_spectrumPhy->AddExpectedTb({dci->m_ndi,
1524 dci->m_tbSize,
1525 dci->m_mcs,
1526 dci->m_rank,
1527 dci->m_rnti,
1528 FromRBGBitmaskToRBAssignment(dci->m_rbgBitmask),
1529 dci->m_harqProcess,
1530 dci->m_rv,
1531 false,
1532 dci->m_symStart,
1533 dci->m_numSym,
1534 m_currentSlot});
1535
1536 bool found = false;
1537 for (auto& i : m_deviceMap)
1538 {
1539 Ptr<NrUeNetDevice> ueDev = DynamicCast<NrUeNetDevice>(i);
1540 uint64_t ueRnti = (DynamicCast<NrUePhy>(ueDev->GetPhy(GetBwpId())))->GetRnti();
1541 if (dci->m_rnti == ueRnti)
1542 {
1543 // Even if we change the beamforming vector, we hope that the scheduler
1544 // has scheduled UEs within the same beam (and, therefore, have the same
1545 // beamforming vector)
1546 // Beamforming vector should be available only when the node has a UPA antenna
1547 // device
1548 if (DynamicCast<UniformPlanarArray>(m_spectrumPhy->GetAntenna()))
1549 {
1550 ChangeBeamformingVector(i); // assume the control signal is omni
1551 }
1552 found = true;
1553 break;
1554 }
1555 }
1556 // In case UE was not attached via NrHelper::AttachToGnb(),
1557 // assume quasi omni beamforming until we have the opportunity to scan for a beam
1558 if (!found)
1559 {
1560 ChangeBeamformingVector(nullptr);
1561 }
1562
1563 NS_LOG_INFO("GNB RXing UL DATA frame "
1564 << m_currentSlot << " symbols " << static_cast<uint32_t>(dci->m_symStart) << "-"
1565 << static_cast<uint32_t>(dci->m_symStart + dci->m_numSym - 1) << " start "
1566 << Simulator::Now() << " end " << Simulator::Now() + varTtiPeriod);
1567 return varTtiPeriod;
1568}
1569
1570void
1572{
1573 auto beamManager = m_spectrumPhy->GetBeamManager();
1574 if (beamManager)
1575 {
1576 beamManager->ChangeBeamformingVector(dev);
1577 }
1578}
1579
1580void
1582{
1583 auto beamManager = m_spectrumPhy->GetBeamManager();
1584 if (beamManager)
1585 {
1586 beamManager->ChangeToQuasiOmniBeamformingVector();
1587 }
1588}
1589
1590Time
1591NrGnbPhy::UlSrs(const std::shared_ptr<DciInfoElementTdma>& dci)
1592{
1593 NS_LOG_FUNCTION(this);
1594
1595 NS_LOG_DEBUG("Starting UL SRS TTI at symbol " << +m_currSymStart << " to "
1596 << +m_currSymStart + dci->m_numSym);
1597
1598 Time varTtiPeriod = GetSymbolPeriod() * dci->m_numSym;
1599
1600 m_spectrumPhy->AddExpectedSrsRnti(dci->m_rnti);
1601
1602 bool found = false;
1603
1604 // if yes, and the rnti for the current SRS is not found in the list,
1605 // the code will not abort
1606 for (auto& i : m_deviceMap)
1607 {
1608 Ptr<NrUeNetDevice> ueDev = DynamicCast<NrUeNetDevice>(i);
1609 uint64_t ueRnti = (DynamicCast<NrUePhy>(ueDev->GetPhy(0)))->GetRnti();
1610 if (dci->m_rnti == ueRnti)
1611 {
1612 // Even if we change the beamforming vector, we hope that the scheduler
1613 // has scheduled UEs within the same beam (and, therefore, have the same
1614 // beamforming vector)
1615 // Beamforming vector should be available only when the node has a UPA antenna
1616 // device
1617 if (DynamicCast<UniformPlanarArray>(m_spectrumPhy->GetAntenna()))
1618 {
1619 ChangeBeamformingVector(i); // assume the control signal is omni
1620 }
1621 found = true;
1622 break;
1623 }
1624 }
1625
1626 // In case UE was not attached via NrHelper::AttachToGnb(),
1627 // assume quasi omni beamforming until we have the opportunity to scan for a beam
1628 if (!found)
1629 {
1630 ChangeBeamformingVector(nullptr);
1631 NS_LOG_WARN("The UE for which is scheduled this SRS does not have yet initialized RNTI. "
1632 "RAR message was not received yet.");
1633 }
1634
1635 NS_LOG_INFO("GNB RXing UL SRS frame "
1636 << m_currentSlot << " symbols " << static_cast<uint32_t>(dci->m_symStart) << "-"
1637 << static_cast<uint32_t>(dci->m_symStart + dci->m_numSym - 1) << " start "
1638 << Simulator::Now() << " end " << Simulator::Now() + varTtiPeriod);
1639 return varTtiPeriod;
1640}
1641
1642void
1643NrGnbPhy::StartVarTti(const std::shared_ptr<DciInfoElementTdma>& dci)
1644{
1645 NS_LOG_FUNCTION(this);
1646 if (DynamicCast<UniformPlanarArray>(m_spectrumPhy->GetAntenna()))
1647 {
1648 ChangeToQuasiOmniBeamformingVector(); // assume the control signal is omni
1649 }
1650 m_currSymStart = dci->m_symStart;
1651
1652 Time varTtiPeriod;
1653
1654 if (dci->m_type == DciInfoElementTdma::CTRL)
1655 {
1656 if (dci->m_format == DciInfoElementTdma::DL)
1657 {
1658 varTtiPeriod = DlCtrl(dci);
1659 }
1660 else if (dci->m_format == DciInfoElementTdma::UL)
1661 {
1662 varTtiPeriod = UlCtrl(dci);
1663 }
1664 }
1665 else if (dci->m_type == DciInfoElementTdma::DATA || dci->m_type == DciInfoElementTdma::MSG3)
1666 {
1667 if (dci->m_format == DciInfoElementTdma::DL)
1668 {
1669 varTtiPeriod = DlData(dci);
1670 }
1671 else if (dci->m_format == DciInfoElementTdma::UL)
1672 {
1673 varTtiPeriod = UlData(dci);
1674 }
1675 }
1676 else if (dci->m_type == DciInfoElementTdma::SRS)
1677 {
1678 NS_ASSERT(dci->m_format == DciInfoElementTdma::UL);
1679 varTtiPeriod = UlSrs(dci);
1680 }
1681
1682 Simulator::Schedule(varTtiPeriod, &NrGnbPhy::EndVarTti, this, dci);
1683}
1684
1685void
1686NrGnbPhy::EndVarTti(const std::shared_ptr<DciInfoElementTdma>& lastDci)
1687{
1688 NS_LOG_FUNCTION(this << Simulator::Now().GetSeconds());
1689
1690 NS_LOG_DEBUG("DCI started at symbol "
1691 << static_cast<uint32_t>(lastDci->m_symStart) << " which lasted for "
1692 << static_cast<uint32_t>(lastDci->m_numSym) << " symbols finished");
1693}
1694
1695void
1696NrGnbPhy::EndSlot()
1697{
1698 NS_LOG_FUNCTION(this);
1699
1700 Time slotStart = m_lastSlotStart + GetSlotPeriod() - Simulator::Now();
1701
1702 if (m_channelStatus == TO_LOSE)
1703 {
1704 NS_LOG_INFO("Release the channel because we did not have any data to maintain the grant");
1705 m_channelStatus = NONE;
1706 m_channelLostTimer.Cancel();
1707 }
1708
1709 NS_LOG_DEBUG("Slot started at " << m_lastSlotStart << " ended");
1710
1712 {
1713 NS_LOG_DEBUG("End slot notified from PHY"); // TODO: Add active UEs nad BWPs?
1714 m_nrFhPhySapProvider->NotifyEndSlot(GetBwpId(), m_currentSlot);
1715 }
1716
1717 m_currentSlot.Add(1);
1718 Simulator::Schedule(slotStart, &NrGnbPhy::StartSlot, this, m_currentSlot);
1719}
1720
1721void
1722NrGnbPhy::SendDataChannels(const Ptr<PacketBurst>& pb,
1723 const Time& varTtiPeriod,
1724 const std::shared_ptr<DciInfoElementTdma>& dci)
1725{
1726 NS_LOG_FUNCTION(this);
1727 // update beamforming vectors (currently supports 1 user only)
1728
1729 // In each time instance, there can only be a single BF vector. Only update BF vectors once
1730 // unless time has changed
1731 if (Simulator::Now() > m_lastBfChange)
1732 {
1733 NS_ASSERT_MSG(!m_spectrumPhy->IsTransmitting(),
1734 "Cannot change analog BF after TX has started");
1735 m_lastBfChange = Simulator::Now();
1736 bool found = false;
1737 for (auto& i : m_deviceMap)
1738 {
1739 Ptr<NrUeNetDevice> ueDev = DynamicCast<NrUeNetDevice>(i);
1740 uint64_t ueRnti = (DynamicCast<NrUePhy>(ueDev->GetPhy(GetBwpId())))->GetRnti();
1741 if (dci->m_rnti == ueRnti)
1742 {
1743 if (DynamicCast<UniformPlanarArray>(m_spectrumPhy->GetAntenna()))
1744 {
1746 }
1747
1748 found = true;
1749 break;
1750 }
1751 }
1752 // In case UE was not attached via NrHelper::AttachToGnb(),
1753 // assume quasi omni beamforming until we have the opportunity to scan for a beam
1754 if (!found)
1755 {
1756 ChangeBeamformingVector(nullptr);
1757 }
1758 }
1759
1760 // in the map we stored the RBG allocated by the MAC for this symbol.
1761 // If the transmission last n symbol (n > 1 && n < 12) the SetSubChannels
1762 // doesn't need to be called again. In fact, SendDataChannels will be
1763 // invoked only when the symStart changes.
1764 NS_ASSERT(m_rbgAllocationPerSym.find(dci->m_symStart) != m_rbgAllocationPerSym.end());
1765 auto nTotalAllocRbs =
1766 FromRBGBitmaskToRBAssignment(m_rbgAllocationPerSym.at(dci->m_symStart)).size();
1767 SetSubChannels(FromRBGBitmaskToRBAssignment(dci->m_rbgBitmask), nTotalAllocRbs);
1768
1769 std::list<Ptr<NrControlMessage>> ctrlMsgs;
1770 m_spectrumPhy->StartTxDataFrames(pb, ctrlMsgs, dci, varTtiPeriod);
1771}
1772
1773void
1774NrGnbPhy::SendCtrlChannels(const Time& varTtiPeriod)
1775{
1776 NS_LOG_FUNCTION(this << "Send Ctrl");
1777
1778 std::vector<int> fullBwRb(GetRbNum());
1779 // The first time set the right values for the phy
1780 for (uint32_t i = 0; i < fullBwRb.size(); ++i)
1781 {
1782 fullBwRb[i] = static_cast<int>(i);
1783 }
1784
1785 // Transmit power for the current signal is distributed over the full bandwidth. This is the
1786 // only signal, so the bandwidth occupied by all concurrent transmissions is also the full
1787 // bandwidth.
1788 SetSubChannels(fullBwRb, fullBwRb.size());
1789
1790 m_spectrumPhy->StartTxDlControlFrames(m_ctrlMsgs, varTtiPeriod);
1791 m_ctrlMsgs.clear();
1792}
1793
1794void
1795NrGnbPhy::AssignCsiRsOffset(const Ptr<NrUeNetDevice>& ueDevice)
1796{
1797 NS_LOG_FUNCTION(this);
1798
1799 if (m_csiRsOffsetToUes.empty())
1800 {
1801 NS_ABORT_MSG_UNLESS(m_csiRsPeriodicity % m_tddPattern.size() == 0,
1802 "CSI-RS periodicity should be a multiply of TDD pattern size");
1803 // how many patterns falls into the CSI periodicity
1804 uint8_t repetitions = m_csiRsPeriodicity / m_tddPattern.size();
1805
1806 for (uint8_t round = 0; round < repetitions; ++round)
1807 {
1808 // count available slots for the CSI-RS
1809 for (size_t index = 0; index < m_tddPattern.size(); index++)
1810 {
1811 if (m_tddPattern[index] != LteNrTddSlotType::UL)
1812 {
1813 m_csiRsOffsetToUes[m_tddPattern.size() * round + index] =
1814 std::set<Ptr<NrUeNetDevice>>();
1815 }
1816 }
1817 }
1818 }
1819
1820 size_t lastAssignedOffset = m_csiRsOffsetToUes.begin()->second.size();
1821
1822 // searching for the next available offset value
1823 for (auto& i : m_csiRsOffsetToUes)
1824 {
1825 if (i.second.size() < lastAssignedOffset)
1826 {
1827 i.second.emplace(ueDevice);
1828 NS_LOG_DEBUG("Assigning CSI-RS offset for UE with IMSI: " << ueDevice->GetImsi());
1829 return;
1830 }
1831 lastAssignedOffset = i.second.size();
1832 }
1833 // we are here because all the offset have the same number of users assigned so
1834 // the new user starts from the first offset value
1835 NS_LOG_DEBUG("Assigning CSI-RS offset for UE with IMSI: " << ueDevice->GetImsi());
1836 m_csiRsOffsetToUes.begin()->second.emplace(ueDevice);
1837}
1838
1839bool
1840NrGnbPhy::RegisterUe(uint64_t imsi, const Ptr<NrUeNetDevice>& ueDevice)
1841{
1842 NS_LOG_FUNCTION(this << imsi);
1843 std::set<uint64_t>::iterator it;
1844 it = m_ueAttached.find(imsi);
1845
1846 if (it == m_ueAttached.end())
1847 {
1848 m_ueAttached.insert(imsi);
1849 m_deviceMap.push_back(ueDevice);
1850
1851 if (m_enableCsiRs && HasDlSlot(m_tddPattern))
1852 {
1853 AssignCsiRsOffset(ueDevice);
1854 }
1855 return (true);
1856 }
1857 else
1858 {
1859 NS_LOG_ERROR("Programming error...UE already attached");
1860 return (false);
1861 }
1862}
1863
1864void
1866{
1867 Simulator::ScheduleWithContext(m_netDevice->GetNode()->GetId(),
1870 m_phySapUser,
1871 p);
1872}
1873
1874void
1875NrGnbPhy::GenerateDataCqiReport(const SpectrumValue& sinr)
1876{
1877 NS_LOG_FUNCTION(this << sinr);
1878
1879 Values::const_iterator it;
1881 ulcqi.m_ulCqi.m_type = UlCqiInfo::PUSCH;
1882 for (it = sinr.ConstValuesBegin(); it != sinr.ConstValuesEnd(); it++)
1883 {
1884 // double sinrdb = 10 * std::log10 ((*it));
1885 // NS_LOG_INFO ("ULCQI RB " << i << " value " << sinrdb);
1886 // convert from double to fixed point notaltion Sxxxxxxxxxxx.xxx
1887 // int16_t sinrFp = nr::FfConverter::double2fpS11dot3 (sinrdb);
1888 ulcqi.m_ulCqi.m_sinr.push_back(
1889 *it); // will be processed by NrMacSchedulerCQIManagement::UlSBCQIReported, it will
1890 // look into a map of assignment
1891 }
1892
1893 // here we use the start symbol index of the var tti in place of the var tti index because
1894 // the absolute UL var tti index is not known to the scheduler when m_allocationMap gets
1895 // populated
1896 ulcqi.m_sfnSf = m_currentSlot;
1897 ulcqi.m_symStart = m_currSymStart;
1898 SpectrumValue newSinr = sinr;
1899 m_ulSinrTrace(0, newSinr, newSinr);
1900 m_phySapUser->UlCqiReport(ulcqi);
1901}
1902
1903void
1904NrGnbPhy::PhyCtrlMessagesReceived(const Ptr<NrControlMessage>& msg)
1905{
1906 NS_LOG_FUNCTION(this);
1907
1908 if (msg->GetMessageType() == NrControlMessage::DL_CQI)
1909 {
1910 Ptr<NrDlCqiMessage> dlcqi = DynamicCast<NrDlCqiMessage>(msg);
1911 DlCqiInfo dlcqiLE = dlcqi->GetDlCqi();
1912 m_phyRxedCtrlMsgsTrace(m_currentSlot, GetCellId(), dlcqiLE.m_rnti, GetBwpId(), msg);
1913
1914 NS_LOG_INFO("Received DL_CQI for RNTI: " << dlcqiLE.m_rnti << " in slot " << m_currentSlot);
1915
1916 m_phySapUser->ReceiveControlMessage(msg);
1917 }
1918 else if (msg->GetMessageType() == NrControlMessage::RACH_PREAMBLE)
1919 {
1920 NS_LOG_INFO("received RACH_PREAMBLE");
1921
1922 Ptr<NrRachPreambleMessage> rachPreamble = DynamicCast<NrRachPreambleMessage>(msg);
1923 m_phyRxedCtrlMsgsTrace(m_currentSlot, GetCellId(), 0, GetBwpId(), msg);
1924 NS_LOG_INFO("Received RACH Preamble in slot " << m_currentSlot);
1925 m_phySapUser->ReceiveRachPreamble(rachPreamble->GetRapId());
1926 }
1927 else if (msg->GetMessageType() == NrControlMessage::DL_HARQ)
1928 {
1929 Ptr<NrDlHarqFeedbackMessage> dlharqMsg = DynamicCast<NrDlHarqFeedbackMessage>(msg);
1930 DlHarqInfo dlharq = dlharqMsg->GetDlHarqFeedback();
1931 if (m_ueAttachedRnti.find(dlharq.m_rnti) != m_ueAttachedRnti.end())
1932 {
1933 m_phyRxedCtrlMsgsTrace(m_currentSlot, GetCellId(), dlharq.m_rnti, GetBwpId(), msg);
1934
1935 NS_LOG_INFO("Received DL_HARQ for RNTI: " << dlharq.m_rnti << " in slot "
1936 << m_currentSlot);
1937 m_phySapUser->ReceiveControlMessage(msg);
1938 }
1939 }
1940 else
1941 {
1942 m_phyRxedCtrlMsgsTrace(m_currentSlot, GetCellId(), 0, GetBwpId(), msg);
1943 m_phySapUser->ReceiveControlMessage(msg);
1944 }
1945}
1946
1950
1951void
1952NrGnbPhy::DoSetBandwidth(uint16_t ulBandwidth, uint16_t dlBandwidth)
1953{
1954 NS_LOG_FUNCTION(this << +ulBandwidth << +dlBandwidth);
1955 NS_ASSERT(ulBandwidth == dlBandwidth);
1956 SetChannelBandwidth(dlBandwidth);
1957}
1958
1959void
1960NrGnbPhy::DoSetEarfcn(uint16_t ulEarfcn, uint16_t dlEarfcn)
1961{
1962 NS_LOG_FUNCTION(this << ulEarfcn << dlEarfcn);
1963}
1964
1965void
1966NrGnbPhy::DoAddUe([[maybe_unused]] uint16_t rnti)
1967{
1968 NS_LOG_FUNCTION(this << rnti);
1969 std::set<uint16_t>::iterator it;
1970 it = m_ueAttachedRnti.find(rnti);
1971 if (it == m_ueAttachedRnti.end())
1972 {
1973 m_ueAttachedRnti.insert(rnti);
1974 }
1975}
1976
1977void
1978NrGnbPhy::DoRemoveUe(uint16_t rnti)
1979{
1980 NS_LOG_FUNCTION(this << rnti);
1981
1982 std::set<uint16_t>::iterator it = m_ueAttachedRnti.find(rnti);
1983 if (it != m_ueAttachedRnti.end())
1984 {
1985 m_ueAttachedRnti.erase(it);
1986 }
1987 else
1988 {
1989 NS_FATAL_ERROR("Impossible to remove UE, not attached!");
1990 }
1991}
1992
1993void
1994NrGnbPhy::DoSetPa(uint16_t rnti, double pa)
1995{
1996 NS_LOG_FUNCTION(this << rnti << pa);
1997}
1998
1999void
2000NrGnbPhy::DoSetTransmissionMode(uint16_t rnti, uint8_t txMode)
2001{
2002 NS_LOG_FUNCTION(this << rnti << +txMode);
2003 // UL supports only SISO MODE
2004}
2005
2006void
2007NrGnbPhy::DoSetSrsConfigurationIndex(uint16_t rnti, uint16_t srcCi)
2008{
2009 NS_LOG_FUNCTION(this << rnti << srcCi);
2010}
2011
2012void
2013NrGnbPhy::DoSetMasterInformationBlock([[maybe_unused]] NrRrcSap::MasterInformationBlock mib)
2014{
2015 NS_LOG_FUNCTION(this);
2016}
2017
2018void
2019NrGnbPhy::DoSetSystemInformationBlockType1(NrRrcSap::SystemInformationBlockType1 sib1)
2020{
2021 NS_LOG_FUNCTION(this);
2022 m_sib1 = sib1;
2023}
2024
2025int8_t
2027{
2028 NS_LOG_FUNCTION(this);
2029 return static_cast<int8_t>(m_txPower);
2030}
2031
2032void
2034{
2035 m_phySapUser = ptr;
2036}
2037
2038void
2040{
2041 NS_LOG_FUNCTION(this);
2042 // forward to scheduler
2043 if (m_ueAttachedRnti.find(mes.m_rnti) != m_ueAttachedRnti.end())
2044 {
2045 NS_LOG_INFO("Received UL HARQ feedback " << mes.IsReceivedOk()
2046 << " and forwarding to the scheduler");
2047 m_phySapUser->UlHarqFeedback(mes);
2048 }
2049}
2050
2051void
2052NrGnbPhy::SetPattern(const std::string& pattern)
2053{
2054 NS_LOG_FUNCTION(this);
2055
2056 static std::unordered_map<std::string, LteNrTddSlotType> lookupTable = {
2057 {"DL", LteNrTddSlotType::DL},
2058 {"UL", LteNrTddSlotType::UL},
2059 {"S", LteNrTddSlotType::S},
2060 {"F", LteNrTddSlotType::F},
2061 };
2062
2063 std::vector<LteNrTddSlotType> vector;
2064 std::stringstream ss(pattern);
2065 std::string token;
2066 std::vector<std::string> extracted;
2067
2068 while (std::getline(ss, token, '|'))
2069 {
2070 extracted.push_back(token);
2071 }
2072
2073 for (const auto& v : extracted)
2074 {
2075 if (lookupTable.find(v) == lookupTable.end())
2076 {
2077 NS_FATAL_ERROR("Pattern type " << v << " not valid. Valid values are: DL UL F S");
2078 }
2079 vector.push_back(lookupTable[v]);
2080 }
2081
2082 SetTddPattern(vector);
2083}
2084
2085std::string
2087{
2089}
2090
2091void
2093{
2094 NS_LOG_FUNCTION(this);
2095 m_isPrimary = true;
2096}
2097
2098void
2100{
2101 m_csiRsModel = csiRsModel;
2102}
2103
2106{
2107 return m_csiRsModel;
2108}
2109
2110void
2111NrGnbPhy::SetCsiRsPeriodicity(uint16_t csiRsPeriodicity)
2112{
2113 m_csiRsPeriodicity = csiRsPeriodicity;
2114}
2115
2116uint16_t
2118{
2119 return m_csiRsPeriodicity;
2120}
2121
2122void
2123NrGnbPhy::ChannelAccessGranted(const Time& time)
2124{
2125 NS_LOG_FUNCTION(this);
2126
2127 if (time < GetSlotPeriod())
2128 {
2129 NS_LOG_INFO("Channel granted for less than the slot time. Ignoring the grant.");
2130 m_channelStatus = NONE;
2131 return;
2132 }
2133
2134 m_channelStatus = GRANTED;
2135
2136 Time toNextSlot = m_lastSlotStart + GetSlotPeriod() - Simulator::Now();
2137 Time grant = time - toNextSlot;
2138 int64_t slotGranted = grant.GetNanoSeconds() / GetSlotPeriod().GetNanoSeconds();
2139
2140 NS_LOG_INFO("Channel access granted for " << time << ", which corresponds to " << slotGranted
2141 << " slot in which each slot is " << GetSlotPeriod()
2142 << ". We lost " << toNextSlot);
2143 NS_ASSERT(!m_channelLostTimer.IsPending());
2144
2145 if (slotGranted < 1)
2146 {
2147 slotGranted = 1;
2148 }
2149 m_channelLostTimer = Simulator::Schedule(GetSlotPeriod() * slotGranted - NanoSeconds(1),
2150 &NrGnbPhy::ChannelAccessLost,
2151 this);
2152}
2153
2154void
2155NrGnbPhy::ChannelAccessLost()
2156{
2157 NS_LOG_FUNCTION(this);
2158 NS_LOG_INFO("Channel access lost");
2159 m_channelStatus = NONE;
2160}
2161
2162} // namespace ns3
Representation of a beam id.
Definition beam-id.h:26
Template for the implementation of the NrFhPhySapUser as a member of an owner class of type C to whic...
@ DL_HARQ
DL HARQ feedback.
@ RACH_PREAMBLE
Random Access Preamble.
@ Dropping
Drop DCI + DATA at the PHY Layer.
Service Access Point (SAP) offered by the FhControl instance to the gnb PHY instance.
void GenerateDataCqiReport(const SpectrumValue &sinr)
Generate a DL CQI report.
void SetN1Delay(uint32_t delay)
: Set the minimum processing delay (in slots) to decode DL Data and send Harq feedback.
NrGnbCphySapProvider * GetGnbCphySapProvider()
Get the C PHY SAP provider.
void ChangeToQuasiOmniBeamformingVector()
void ReportUlHarqFeedback(const UlHarqInfo &mes)
Get the HARQ feedback from NrSpectrumPhy and forward it to the scheduler.
int8_t DoGetReferenceSignalPower() const
Get the power of the gnb.
Ptr< NrChAccessManager > GetCam() const
Get the channel access manager for the PHY.
void DoDispose() override
DoDispose method inherited from Object.
Definition nr-gnb-phy.cc:57
bool DoesFhAllocationFit(uint16_t bwpId, uint32_t mcs, uint32_t nRegs, uint8_t dlRank) const
Returns a boolean indicating whether the current allocation can fit in the available FH bandwidth.
void SetPrimary()
Set this PHY as primary.
void SetN0Delay(uint32_t delay)
: Set the minimum processing delay (in slots) to decode DL DCI and decode DL data....
void ScheduleStartEventLoop(uint32_t nodeId, uint16_t frame, uint8_t subframe, uint16_t slot) override
Start the ue Event Loop.
~NrGnbPhy() override
~NrGnbPhy
Definition nr-gnb-phy.cc:52
const SfnSf & GetCurrentSfnSf() const override
Get the current SfnSf.
void AssignCsiRsOffset(const Ptr< NrUeNetDevice > &ueDevice)
void SetCsiRsModel(enum CsiRsModel csiRsModel)
Set the CSI-RS model.
void SetCam(const Ptr< NrChAccessManager > &s)
Set the channel access manager interface for this instance of the PHY.
CsiRsModel
CSI-RS model to be used.
Definition nr-gnb-phy.h:100
@ CSI_RS_PER_UE
CSI-RS per UE periodically.
Definition nr-gnb-phy.h:101
@ CSI_RS_PER_BEAM
CSI-RS per beam periodically.
Definition nr-gnb-phy.h:102
void SetSubChannels(const std::vector< int > &rbIndexVector, size_t nTotalAllocRbs)
Set the Tx power spectral density based on the RB index vector.
BeamId GetBeamId(uint16_t rnti) const override
Get the BeamId for the selected user.
void SetGnbCphySapUser(NrGnbCphySapUser *s)
Set the C PHY SAP user.
bool RegisterUe(uint64_t imsi, const Ptr< NrUeNetDevice > &ueDevice)
Add the UE to the list of this gnb UEs.
NrFhPhySapProvider * m_nrFhPhySapProvider
FH Control SAP provider.
Definition nr-gnb-phy.h:476
void SetCsiRsPeriodicity(uint16_t csiRsPeriodicity)
void SetPattern(const std::string &pattern)
Set the pattern that the gnb will utilize.
void SetN2Delay(uint32_t delay)
: Set the minimum processing delay (in slots) to decode UL DCI and prepare UL data.
NrGnbPhy()
NrGnbPhy constructor. Please use the other one.
Definition nr-gnb-phy.cc:43
uint32_t GetN2Delay() const
: Get the minimum processing delay (in slots) to decode UL DCI and prepare UL data
uint32_t GetN1Delay() const
: Get the minimum processing delay (in slots) to decode DL Data and send Harq feedback
void PhyDataPacketReceived(const Ptr< Packet > &p)
Receive a PHY data packet.
void PhyCtrlMessagesReceived(const Ptr< NrControlMessage > &msg)
Receive a list of CTRL messages.
void SetTxPower(double pow)
Set the transmission power for the UE.
uint16_t GetCsiRsPeriodicity() const
Retrieve CSI-RS periodicity.
enum CsiRsModel GetCsiRsModel() const
Gets the CSI-RS model in use.
double GetTxPower() const override
Retrieve the TX power of the gNB.
void SetPhySapUser(NrGnbPhySapUser *ptr)
Install the PHY SAP user (which is in this case the MAC)
NrFhPhySapUser * m_nrFhPhySapUser
FH Control SAP user.
Definition nr-gnb-phy.h:475
uint32_t GetN0Delay() const
: Get the minimum processing delay (in slots) to decode DL DCI and decode DL Data
uint32_t GetNumRbPerRbg() const override
Retrieve the number of RB per RBG.
std::string GetPattern() const
Retrieve the currently installed pattern.
void ChangeBeamformingVector(Ptr< NrNetDevice > dev)
static TypeId GetTypeId()
Get Type id.
Definition nr-gnb-phy.cc:74
SAP interface between the gNB PHY and the gNB MAC.
Definition nr-phy-sap.h:155
virtual void ReceiveControlMessage(Ptr< NrControlMessage > msg)=0
Receive SendNrControlMessage (PDCCH map, CQI feedbacks) using the ideal control channel.
virtual void SlotUlIndication(const SfnSf &sfn, LteNrTddSlotType slotType)=0
Trigger MAC layer to generate an UL slot for the SfnSf indicated.
virtual void ReceivePhyPdu(Ptr< Packet > p)=0
Notify the MAC of the reception of a new PHY-PDU.
virtual void UlCqiReport(NrMacSchedSapProvider::SchedUlCqiInfoReqParameters ulcqi)=0
Returns to MAC level the UL-CQI evaluated.
virtual void UlHarqFeedback(UlHarqInfo params)=0
Notify the HARQ on the UL transmission status.
virtual void SetCurrentSfn(const SfnSf &sfn)=0
Set the current Sfn. The state machine has advanced by one slot.
virtual uint32_t GetNumRbPerRbg() const =0
PHY requests information from MAC. While MAC normally act as user of PHY services,...
virtual uint8_t GetDlCtrlSymbols() const =0
Retrieve the DL CTRL symbols.
virtual void ReceiveRachPreamble(uint32_t raId)=0
Notify the reception of a RACH preamble on the PRACH.
virtual void SlotDlIndication(const SfnSf &sfn, LteNrTddSlotType slotType)=0
Trigger MAC layer to generate a DL slot for the SfnSf indicated.
virtual std::shared_ptr< DciInfoElementTdma > GetUlCtrlDci() const =0
Retrieve a dci for a UL CTRL allocation.
virtual std::shared_ptr< DciInfoElementTdma > GetDlCtrlDci() const =0
Retrieve a dci for a DL CTRL allocation.
The base class for gNb and UE physical layer.
Definition nr-phy.h:67
Time GetSymbolPeriod() const
Get SymbolPeriod.
Definition nr-phy.cc:858
Ptr< NrSpectrumPhy > m_spectrumPhy
Pointer to the (owned) spectrum phy.
Definition nr-phy.h:558
SlotAllocInfo RetrieveSlotAllocInfo()
Get the head for the slot allocation info, and delete it from the internal list.
Definition nr-phy.cc:788
uint16_t GetCellId() const
Definition nr-phy.cc:657
uint16_t GetNumerology() const
Get the configured numerology.
Definition nr-phy.cc:289
Ptr< NrSpectrumPhy > GetSpectrumPhy() const
Retrieve the SpectrumPhy pointer.
Definition nr-phy.cc:669
void EncodeCtrlMsg(const Ptr< NrControlMessage > &msg)
Take the control messages, and put it in a list that will be sent at the first occasion.
Definition nr-phy.cc:469
void SetNumerology(uint16_t numerology)
Set GNB or UE numerology.
Definition nr-phy.cc:261
virtual void SetTbDecodeLatency(const Time &us)
Configures TB decode latency.
Definition nr-phy.cc:882
std::list< Ptr< NrControlMessage > > m_ctrlMsgs
CTRL messages to be sent.
Definition nr-phy.h:571
void SetRbOverhead(double oh)
Set the bandwidth overhead for calculating the usable RB number.
Definition nr-phy.cc:303
Ptr< PacketBurst > GetPacketBurst(SfnSf sf, uint8_t sym, uint16_t rnti)
Retrieve the PacketBurst at the slot/symbol specified.
Definition nr-phy.cc:364
void SetPowerAllocationType(enum NrSpectrumValueHelper::PowerAllocationType powerAllocationType)
Set power allocation type. There are currently supported two types: one that distributes uniformly en...
Definition nr-phy.cc:432
static std::string GetPattern(const std::vector< LteNrTddSlotType > &pattern)
Get a string representation of a pattern.
Definition nr-phy.cc:413
Time GetSlotPeriod() const
Get the slot period.
Definition nr-phy.cc:321
virtual std::list< Ptr< NrControlMessage > > PopCurrentSlotCtrlMsgs()
Extract and return the message list that is at the beginning of the queue.
Definition nr-phy.cc:610
SlotAllocInfo m_currSlotAllocInfo
Current slot allocation.
Definition nr-phy.h:566
enum NrSpectrumValueHelper::PowerAllocationType GetPowerAllocationType() const
Get the power allocation type.
Definition nr-phy.cc:438
void EnqueueCtrlMsgNow(const Ptr< NrControlMessage > &msg)
Enqueue a CTRL message without considering L1L2CtrlLatency.
Definition nr-phy.cc:452
uint32_t GetChannelBandwidth() const
Retrieve the channel bandwidth, in Hz.
Definition nr-phy.cc:520
virtual Time GetTbDecodeLatency() const
Returns Transport Block decode latency.
Definition nr-phy.cc:888
SlotAllocInfo & PeekSlotAllocInfo(const SfnSf &sfnsf)
Peek the SlotAllocInfo at the SfnSf specified.
Definition nr-phy.cc:817
double GetCentralFrequency() const
Retrieve the frequency (in Hz) of this PHY's channel.
Definition nr-phy.cc:405
double m_noiseFigure
Noise figure (attribute)
Definition nr-phy.h:561
void SetSymbolsPerSlot(uint16_t symbolsPerSlot)
Set the number of symbol per slot.
Definition nr-phy.cc:295
double GetNoiseFigure() const
Get the NoiseFigure value.
Definition nr-phy.cc:876
void SetNoiseFigure(double d)
Set the NoiseFigure value.
Definition nr-phy.cc:865
double m_txPower
Transmission power (attribute)
Definition nr-phy.h:560
void InitializeMessageList()
Initialize the message list.
Definition nr-phy.cc:598
Ptr< SpectrumValue > GetTxPowerSpectralDensity(const std::vector< int > &rbIndexVector)
Definition nr-phy.cc:394
void SetChannelBandwidth(uint16_t bandwidth)
Function to set the channel bandwidth, used also by child classes, i.e., see functions DoSetDlBanwidt...
Definition nr-phy.cc:242
uint16_t GetBwpId() const
Definition nr-phy.cc:651
void PushFrontSlotAllocInfo(const SfnSf &newSfnSf, const SlotAllocInfo &slotAllocInfo)
Store the slot allocation info at the front.
Definition nr-phy.cc:717
Ptr< NrNetDevice > m_netDevice
Pointer to the owner netDevice.
Definition nr-phy.h:557
uint32_t GetL1L2CtrlLatency() const
Definition nr-phy.cc:663
bool HasDlSlot() const
Go through the current pattern and see if at least one slot is DL, F or S.
Definition nr-phy.cc:476
bool SlotAllocInfoExists(const SfnSf &sfnsf) const
Check if the SlotAllocationInfo for that slot exists.
Definition nr-phy.cc:773
uint32_t GetSymbolsPerSlot() const
Get the number of symbols in a slot.
Definition nr-phy.cc:315
std::vector< LteNrTddSlotType > m_tddPattern
Pattern.
Definition nr-phy.h:573
NrPhySapProvider * m_phySapProvider
Pointer to the MAC.
Definition nr-phy.h:568
double GetRbOverhead() const
Get the bandwidth overhead used when calculating the usable RB number.
Definition nr-phy.cc:309
bool HasUlSlot() const
Go through the current pattern and see if at least one slot is UL, F or S.
Definition nr-phy.cc:482
uint32_t GetRbNum() const
Get the number of Resource block configured.
Definition nr-phy.cc:514
std::vector< int > FromRBGBitmaskToRBAssignment(const std::vector< bool > rbgBitmask) const
Transform a MAC-made vector of RBG to a PHY-ready vector of SINR indices.
Definition nr-phy.cc:165
static bool IsTdd(const std::vector< LteNrTddSlotType > &pattern)
Check if a pattern is TDD.
Definition nr-phy.cc:571
void DoDispose() override
DoDispose method inherited from Object.
Definition nr-phy.cc:199
virtual void SetSlotAllocInfo(const SlotAllocInfo &slotAllocInfo)=0
Set a SlotAllocInfo inside the PHY allocations.
The SfnSf class.
Definition sfnsf.h:32
uint8_t GetSubframe() const
GetSubframe.
Definition sfnsf.cc:170
uint64_t Normalize() const
Normalize the SfnSf in slot number.
Definition sfnsf.cc:99
void Add(uint32_t slotN)
Add to this SfnSf a number of slot indicated by the first parameter.
Definition sfnsf.cc:117
uint8_t GetSlot() const
GetSlot.
Definition sfnsf.cc:176
@ F
DL CTRL + DL DATA + UL DATA + UL CTRL.
@ S
DL CTRL + DL DATA + UL CTRL.
@ DL
DL CTRL + DL DATA.
@ UL
UL DATA + UL CTRL.
@ CTRL
Used for DL/UL CTRL.
@ DATA
Used for DL/UL DATA.
@ SRS
Used for SRS (it would be like DCI format 2_3)
DciFormat
Format of the DCI.
The DlCqiInfo struct.
uint16_t m_rnti
The RNTI.
A struct that contains info for the DL HARQ.
uint16_t m_rnti
RNTI.
uint8_t m_symStart
Sym start of the transmission to which this CQI refers to.
MasterInformationBlock structure.
Definition nr-rrc-sap.h:627
uint16_t systemFrameNumber
system frame number
Definition nr-rrc-sap.h:630
uint16_t dlBandwidth
DL bandwidth.
Definition nr-rrc-sap.h:629
SfnSf m_sfnSf
SfnSf of this allocation.
bool ContainsDlCtrlAllocation() const
uint32_t m_numSymAlloc
Number of allocated symbols.
bool ContainsDataAllocation() const
Check if we have data allocations.
@ UL
UL Allocations.
@ DL
DL Allocations.
bool ContainsUlMsg3Allocation() const
Check if we have UL MSG3 allocations.
std::deque< VarTtiAllocInfo > m_varTtiAllocInfo
queue of allocations
A struct that contains info for the UL HARQ.
bool IsReceivedOk() const override