5G/NR - Carrier Aggregation

 

 

 

Carrier Aggregation

The basic concept of carrier aggregation and the process of establishing carrier aggregaion in NR is almost same as LTE carrier aggregation. So I would not explain much of the basic concept on carrier aggregation in this note, please refer to LTE carrier aggregation page for the concept and process of establishing CA(carrier aggregation). In this page, I will only put some examples of establishing carrier aggregation in NR.

 

If you are already familiar with the concept of Carrier aggregation from LTE, the only thing I want you to refresh your memory is following sequence. This is how CA is established in protocol sequence and I will try to provide several examples of this establishment process in this page.

 

How to read this diagram. The four vertical lines are not four boxes. eNBPCC and eNBSCC are the primary and secondary carriers of the same eNB, and UEPCC and UESCC are the same UE's receive chains for those two carriers. They are drawn separately only so you can see which carrier each message travels on - and the answer, for every signalling message in the diagram, is the primary carrier. The secondary line stays silent until the very last step.

The three pictures down the right hand side are the part people skip, and they are the most important part. Each one is a snapshot of the radio situation and the TP (throughput) curve at that moment :

  • Top, after step (1) - only the PCC is beaming at the phone. The throughput line is flat and low : one carrier.
  • Middle, after step (6) - both eNB carriers are now drawn beaming at the phone, because the SCC has been configured. But look at the throughput line : it has still barely moved.
  • Bottom, after step (8) - same two beams, but now the throughput curve steps up.

That difference between the middle and the bottom picture is the whole point of the diagram. Configuring an SCell buys you nothing at all. The throughput only moves when the SCell is activated and actually carries a PDSCH - steps (7) and (8). This is exactly the "configured is not the same as activated" idea that the NR logs further down this page show as raw messages.

Step by step

Step What happens Why it is there

(1)

RRC Connection Reconfiguration
eNB -> UE, on the PCC

The message that carries the SCell configuration. Note that it is sent on the PCC - the UE is not listening to the SCC yet, so it could not possibly arrive any other way.

(2)

HARQ ACK
UE -> eNB

A physical layer acknowledgement only. It says "I received the bits", not "I applied the configuration". Confusing these two is a classic mistake when reading a log.

(3)

NW Reconfig / UE Reconfig
internal, both sides

No message is sent. Both ends apply the new configuration internally. The UE now knows the SCell's frequency, bandwidth and reference signals - but it is not yet receiving anything there.

(4)

SR (Scheduling Request)
UE -> eNB

The UE has a message to send back but no uplink grant, so it must ask for one first.

(5)

DCI 0
eNB -> UE

The uplink grant that answers the SR. Steps (4) and (5) are pure plumbing and are usually left out of textbook diagrams - they are drawn here because they explain the delay between (3) and (6).

(6)

RRC Connection Reconfiguration Complete
UE -> eNB

This is the real acknowledgement - the UE confirming it applied the configuration. The SCell is now configured but still dormant, which is why the middle throughput curve is flat.

(7)

MAC CE (Activation)
eNB -> UE, dashed

Drawn as a dashed line because it is a MAC control element, not an RRC message. One byte, and it switches the SCell on. Everything before this was preparation.

(8)

PDSCH
eNB -> UE, on both carriers

The only step where the SCC line is used. Data now arrives on both carriers at once, and the throughput curve finally steps up. Carrier aggregation is running.

NOTE : there is no measurement report anywhere in this diagram. The network simply decides to add the SCell. That makes this sequence the direct counterpart of Example 01 below. Example 02 is the same sequence with a measurement configuration and a measurement report inserted in front of step (1).

The same steps in NR terms

The diagram is drawn with LTE names because that is where most of us learned CA, and the sequence is genuinely almost unchanged in NR. Only the vocabulary moves, so here is the translation you will need to follow the logs below.

LTE, in the diagram NR, in the logs below Where to find it

PCC / SCC

PCell / SCell

sCellToAddModList, sCellIndex 1

RRC Connection Reconfiguration

RRCReconfiguration

Ex 01 step [1], Ex 02 step [4]

RRC Connection Reconfiguration Complete

RRCReconfigurationComplete

Ex 01 step [2], Ex 02 step [5]

MAC CE (Activation)

SCell Activation/Deactivation MAC CE

Ex 01 step [3], Ex 02 step [6]

DCI 0

DCI format 0_0 / 0_1

not captured in these logs

PDSCH on both carriers

PDSCH on PCell and on SCell, acknowledged by one PUCCH on the PCell

Ex 01 steps [8] to [11], Ex 02 steps [11] to [14]

One thing the diagram does not show, and that the NR logs make very visible : the uplink. In the diagram the SCC line carries a downlink arrow at step (8) and nothing else, and that is exactly right. The SCell has no uplink of its own, so every acknowledgement for it travels back on the PCell - which is why step (8) has arrows going out on both carriers but no arrow coming back on the SCC.

Before the examples : what to look for in a CA log

Both examples on this page are real logs, and a real log is mostly noise until you know what you are looking for. So before jumping into the messages, let me put down the handful of ideas that make a CA log readable. If you keep these five points in your head, almost every line in the logs below will explain itself.

(1) Adding an SCell is not a handover. This is the first thing that surprises people coming from a handover background. When the network adds an SCell, it sends an RRCReconfiguration that contains sCellToAddModList, and that is it. There is no reconfigurationWithSync, no random access to the new cell, no new C-RNTI, no security key change. The UE simply learns "there is another downlink over there, here is how it is configured". The RRC connection itself never moves. This is why the whole procedure can be so short - two RRC messages and one MAC CE.

(2) One cell owns the uplink control. In a downlink CA configuration like the ones below, the SCell carries downlink data only. Every HARQ ACK for the SCell's PDSCH, and every CSI report about the SCell, is sent on the PCell's PUCCH. That is why in both examples you will see the RRCReconfiguration adding a PUCCH resource and a CSI report configuration to the PCell at the same moment it adds the SCell. If you ever wonder why an "SCell addition" message spends half of its content configuring the PCell, this is the reason.

(3) Configured is not the same as activated. RRC only configures the SCell. It stays dormant until MAC turns it on with the SCell Activation/Deactivation MAC CE. This split exists because RRC signalling is slow and expensive while a MAC CE is one byte, so the network can configure an SCell once and then switch it on and off with the traffic load. In the logs below you can see both halves clearly : an RRC message, then later a tiny MAC CE.

(4) The SCell is not useful the instant it is activated. After the MAC CE arrives, the UE still has to settle its AGC on the new carrier, lock its fine time and frequency tracking, and measure a CSI-RS before it can report a meaningful CQI. This is exactly what the TRS (the CSI-RS resource set with trs-Info true) and the CSI-RS / CSI-IM resources in the SCell configuration are for. Until the first CSI report comes back, the scheduler has to guess the MCS on the SCell - and in Example 02 you can actually watch it guessing.

(5) Two DCIs, two PDSCHs, one PUCCH. Once CA is running, a downlink slot looks like this : one DCI and one PDSCH on the PCell, one DCI and one PDSCH on the SCell, and then a single PUCCH on the PCell carrying both HARQ ACK bits. The mechanism that keeps the UE and the gNB agreeing on how many ACK bits belong in that PUCCH is the DAI field in the DCI, and it is the most satisfying thing to decode in these logs.

The two carriers used in both examples

Both examples use the same pair of carriers, so it is worth working out what they actually are once. For band n78 the NR-ARFCN maps to frequency as F = 3000 MHz + 0.015 MHz x (N - 600000).

  PCell  ssbFrequency 621216  -> 3000 + 0.015 x (621216 - 600000) = 3318.24 MHz
  SCell  ssbFrequency 627264  -> 3000 + 0.015 x (627264 - 600000) = 3408.96 MHz
                                                       separation = 90.72 MHz

  SCell absoluteFrequencyPointA 626688 -> 3400.32 MHz   (reference point of the SCell carrier)
  carrierBandwidth 51 RB @ 30 kHz SCS  -> 51 x 12 x 30 kHz = 18.36 MHz  ("20 MHz" channel)
  locationAndBandwidth 13750           -> RIV for 51 RBs starting at RB 0  (275 x (51-1) + 0)

So both carriers sit inside band n78 (3300 - 3800 MHz) but they are about 90 MHz apart, which is far more than the 18.36 MHz that either carrier occupies. In other words this is intra-band non-contiguous CA. That single fact explains something that appears in Example 02 and looks odd at first sight : the network gives the UE a measurement gap to measure the other carrier. The two carriers are in the same band, but they are nowhere near each other, so before the SCell exists the UE cannot see the second carrier without retuning - and retuning means it cannot receive the PCell at that moment, which is precisely what a measurement gap is for.

  PCell SCell

PCI

500

501

Band / SCS

n78 / 30 kHz

n78 / 30 kHz

SSB frequency

621216 (3318.24 MHz)

627264 (3408.96 MHz)

Bandwidth

51 RB = 18.36 MHz

51 RB = 18.36 MHz

Serving cell index

0 (implicit)

sCellIndex 1

Uplink

PUCCH + PUSCH

none - downlink only

Timing advance group

tag-Id 0

tag-Id 0 (same TAG)

Scheduling

own PDCCH

own PDCCH (self-scheduling)

NOTE : both cells are in the same timing advance group (tag-Id 0). That is the normal choice when the two carriers come out of the same radio unit at the same site, as they do in a callbox setup - the propagation delay is identical, so one timing advance covers both. If the SCell were on a remote radio head at a different distance, it would need its own TAG and its own timing advance loop.

The Amarisoft log fields you will keep seeing

The physical layer lines in these logs are terse, so here is a translation table you can refer back to. Nothing here is CA specific - it is just what the Amarisoft trace calls things.

Field Meaning

ss_id / cce_index / al

Which search space the PDCCH was sent in, which CCE it started at, and the aggregation level. al=2 means the DCI was spread over 2 CCEs.

dci=1_1

DCI format 1_1, the UE specific downlink assignment. Format 1_0 is the fallback format and cannot do CA properly, so seeing 1_1 is a precondition for any of this to work.

rb_alloc / prb

The frequency domain resource allocation carried in the DCI, and the resulting PRB position at the PHY.

mcs1 / mod / cr

The MCS index in the DCI, and what it resolved to : mod is bits per symbol (2 = QPSK, 4 = 16QAM, 6 = 64QAM, 8 = 256QAM) and cr is the code rate.

ndi1 / rv_idx1 / retx

New data indicator, redundancy version and retransmission counter. ndi toggled and rv_idx=0 with retx=0 means this is a fresh transport block, not a HARQ retransmission.

dai

The 4 bit Downlink Assignment Index of DCI 1_1. The 2 MSBs are the counter DAI (which PDSCH is this in the bundle) and the 2 LSBs are the total DAI (how many PDSCHs the UE should expect to acknowledge). This is the field that keeps the HARQ codebook in sync across carriers.

harq_feedback_timing / k1

The DCI field points at an entry of dl-DataToUL-ACK ; k1 in the PDSCH line is the resulting offset in slots from the PDSCH to the PUCCH that carries its ACK.

tb_len

Transport block size in bytes. This is the size of the MAC PDU, so it is the number you use to check that a MAC PDU you decoded actually fits.

symb=1:13

The PDSCH occupies symbols 1 to 13 of the slot. This comes straight from the SLIV in pdsch-TimeDomainAllocationList, as shown in Example 01 step [1].

ack=11

The HARQ ACK bits the gNB decoded from PUCCH. "1" is one ACK bit, "11" is two ACK bits - so the number of characters tells you how big the HARQ codebook was in that slot.

Example 01 > NR SA 2CC CA : n78 (20Mhz) + n78(20 Mhz)

This example is from the log captured by Amarisoft Callbox(gNB) and UE Simulator. Overall procedure captured in this log is as follows.

Step Message Layer / cell Dir What happens

Establishment - configure, then activate

[1]

[1] RrcReconfiguration for Scell Addition

RRC / PCell

DL

Describes the SCell, and at the same time prepares the PCell to carry the SCell's PUCCH and CSI

[2]

[2] RrcReconfigurationComplete

RRC / PCell

UL

Empty message. Its presence is the proof that the UE accepted the whole configuration

[3]

[3] MAC CE to activate the Scell

MAC / PCell

DL

One octet bitmap, 0x02, so C1 = 1 : switch on the SCell with sCellIndex 1

[4]

[4] DCI to schedule PDSCH carrying the MAC CE

PHY / PDCCH / PCell

DL

The assignment for step [5]. It has to be on the PCell, because the SCell is still off

[5]

[5] PDSCH carrying the MAC CE

PHY / PDSCH / PCell

DL

tb_len=84 bytes - exactly the size of the MAC PDU in step [3]

[6]

[6] PUCCH (HARQ ACK) for PDSCH at step [5]

PHY / PUCCH / PCell

UL

The MAC CE is acknowledged. From this moment the SCell is active

Traffic - what one slot looks like with two carriers running

[7]

[7] DCI (PDSCH) scheduling PDSCH in PCell

PHY / PDCCH / PCell

DL

dai=1 : counter DAI 1, total DAI 2 - "expect two ACK bits"

[8]

[8] PDSCH in PCell

PHY / PDSCH / PCell

DL

92 bytes, 64QAM, k1=6

[9]

[9] DCI(PDSCH) scheduling PDSCH in SCell

PHY / PDCCH / SCell

DL

Sent on the SCell itself - self-scheduling. dai=5 : counter DAI 2 of 2

[10]

[10] PDSCH in SCell

PHY / PDSCH / SCell

DL

101 bytes, 256QAM, k1=6 - the same uplink slot as step [8]

[11]

[11] HARQ Ack for PDSCH in both PCell and SCell

PHY / PUCCH / PCell

UL

ack=11 - both carriers acknowledged in a single PUCCH on the PCell

Read that table once more and notice what is not in it. There is no measurement configuration and no measurement report. The network simply decides on its own that the UE should have an SCell and adds it. This is the shortest possible version of CA establishment, which makes it the right place to start : steps [1] to [6] are the establishment, and steps [7] to [11] are what normal traffic looks like afterwards. Example 02 then adds the measurement part on top of exactly this skeleton.

 

[1] RrcReconfiguration for Scell Addition

This is the big one, and it is the only message in the whole example that really needs studying. It is long, but it has only three jobs, and once you split it that way it becomes easy :

  • Job A - prepare the PCell to carry the SCell's feedback. Everything under spCellConfig is about the PCell, not the SCell. It adds a PUCCH resource and a CSI report configuration.
  • Job B - describe the SCell. Everything under sCellToAddModList. This is essentially a compressed version of the SCell's system information plus a dedicated configuration, handed to the UE over the PCell so that the UE never has to read SIB on the SCell.
  • Job C - carry an unrelated NAS message. The dedicatedNAS-MessageList at the bottom is just a passenger. The network had a NAS PDU to deliver and piggybacked it here. It has nothing to do with CA.

Job A : what changes on the PCell. Two things are added to spCellConfigDedicated :

  pucch-Config -> resourceToAddModList -> pucch-ResourceId 18
      startingPRB 49, secondHopPRB 1, intraSlotFrequencyHopping enabled
      format2 : nrofPRBs 1, nrofSymbols 2, startingSymbolIndex 2

  csi-MeasConfig -> csi-ReportConfigToAddModList -> reportConfigId 1
      carrier 1                                <-- this report is about serving cell #1 = the SCell
      resourcesForChannelMeasurement 0, csi-IM-ResourcesForInterference 1
      reportConfigType periodic : reportSlotConfig slots80: 18
      pucch-CSI-ResourceList : uplinkBandwidthPartId 0, pucch-Resource 18
      reportQuantity cri-RI-PMI-CQI, cqi-Table table2 (256QAM table)

The single most important line in this whole message is carrier 1. A csi-ReportConfig normally reports on the cell it lives in ; here it explicitly says the measurement subject is serving cell index 1, which is the SCell. And the resource it reports on is pucch-Resource 18, which lives on the PCell's uplink BWP. So this handful of lines is the whole "SCell reports through the PCell" story, written out. The report is periodic every 80 slots, which at 30 kHz SCS is 80 x 0.5 ms = 40 ms, at slot offset 18.

Note also that PUCCH resource 18 is a format 2 resource, 2 symbols long, 1 PRB, hopping from PRB 49 to PRB 1. Format 2 exists because a CSI report is far too big for the 1 or 2 bits that format 0 or format 1 can carry. Keep this in mind for step [6] : the HARQ ACK PUCCH you will see there is a different resource with a different format, so do not expect to see resource 18 in the HARQ lines.

You do not have to take that on trust - resource 18 is visible in the second screenshot of Example 02, and every field of the configuration above can be read straight off it :

  configured in RRC                          seen in the PHY log
  ---------------------------------------    ------------------------------------------
  format2                                    format=2
  startingPRB 49                             prb=49
  secondHopPRB 1  (freq hopping enabled)     prb2=1
  startingSymbolIndex 2, nrofSymbols 2       symb=2:2
  reportQuantity cri-RI-PMI-CQI              csi=1011   <-- the report itself

  and the two reports in that screenshot are at SFN 816.9 and SFN 820.9
     4 radio frames apart = 40 ms  =  slots80 at 30 kHz SCS  (80 x 0.5 ms)

So the periodic CSI report for the SCell really does come back on the PCell's uplink, on a format 2 resource, exactly 40 ms apart. That single line in the log is the whole of point (2) at the top of this page, made visible.

Job B : what the UE is told about the SCell. The sCellToAddModList entry has an sCellIndex 1 - remember this number, because it is what the MAC CE in step [3] will refer to. Inside it, sCellConfigCommon is the "system information" part :

  physCellId 501
  absoluteFrequencySSB 627264      -> 3408.96 MHz
  absoluteFrequencyPointA 626688   -> 3400.32 MHz
  carrierBandwidth 51 RB @ kHz30   -> 18.36 MHz
  ssb-PositionsInBurst mediumBitmap '80'H  -> 1000 0000 : only SSB index 0 is transmitted
  ssb-periodicityServingCell ms20
  ss-PBCH-BlockPower -28

  tdd-UL-DL-ConfigurationCommon : periodicity ms5,
      7 DL slots + 6 DL symbols | 4 UL symbols + 2 UL slots
      -> at 30 kHz, 5 ms = 10 slots : D D D D D D D S U U
      -> the special slot S has 6 DL symbols, 4 guard symbols, 4 UL symbols

  pdsch-TimeDomainAllocationList :
      startSymbolAndLength 40 -> SLIV 40 -> S=1, L=13 -> symbols 1..13
      startSymbolAndLength 57 -> SLIV 57 -> S=1, L=5  -> symbols 1..5

That SLIV is worth pausing on, because it is a rare case where you can check a configuration against a physical layer log with no ambiguity. SLIV 40 decodes to start symbol 1, length 13. Now look ahead at steps [5], [8] and [10] : every PDSCH line in this log says symb=1:13. The configuration and the trace agree exactly. When you are debugging someone else's log this kind of cross-check is how you build confidence that you are reading the right cell.

Then comes sCellConfigDedicated, the part that makes the SCell actually usable :

  pdcch-Config -> searchSpacesToAddModList -> searchSpaceId 2
      controlResourceSetId 1, monitoringSlotPeriodicityAndOffset sl1 (every slot)
      searchSpaceType ue-Specific : dci-Formats formats0-1-And-1-1
                                     <-- the SCell has its own PDCCH : self-scheduling

  pdsch-Config -> tci-StatesToAddModList -> tci-StateId 0, qcl ssb:0, qcl-Type typeD
      mcs-Table qam256, resourceAllocation type1, prb-BundlingType wideband

  pdsch-ServingCellConfig -> nrofHARQ-ProcessesForPDSCH n16
  firstActiveDownlinkBWP-Id 0
  tag-Id 0                          <-- same timing advance group as the PCell

The search space entry tells you the scheduling mode. The SCell has its own UE specific search space monitored in every slot, so the gNB will send the SCell's DCI on the SCell itself. That is called self-scheduling, and it is what you will see happening in step [9]. The alternative, cross-carrier scheduling, would have a crossCarrierSchedulingConfig here instead - and there is none, so this log is a self-scheduling log.

Also notice what is absent : there is no uplinkConfig anywhere inside sCellConfigDedicated. No PUCCH, no PUSCH, no SRS, no PRACH. This SCell is downlink only, which is the normal arrangement for a first CA deployment and the reason why point (2) at the top of this page matters so much.

Job B, continued : the measurement machinery inside the SCell. The last big chunk of the SCell configuration is csi-MeasConfig, and it looks intimidating until you realise it is four separate things serving one purpose - letting the UE produce a CQI for the SCell.

  (a) NZP-CSI-RS for channel measurement
      nzp-CSI-RS-ResourceId 0 : row2 '800'H, 1 port, symbol 4, density one
                               periodicityAndOffset slots80: 1   (= 40 ms)
      -> grouped into nzp-CSI-RS-ResourceSetId 0

  (b) CSI-IM for interference measurement
      csi-IM-ResourceId 0 : pattern1, subcarrier s8, symbol 8
                            periodicityAndOffset slots80: 1      (= same 40 ms, same offset)

  (c) ZP-CSI-RS for rate matching
      zp-CSI-RS-ResourceId 0 : row4 '100'B, 4 ports, symbol 8
                               periodicityAndOffset slots80: 1

  (d) TRS - the tracking reference signal
      nzp-CSI-RS-ResourceId 1 : symbol 4, slots40: 11
      nzp-CSI-RS-ResourceId 2 : symbol 8, slots40: 11
      nzp-CSI-RS-ResourceId 3 : symbol 4, slots40: 12
      nzp-CSI-RS-ResourceId 4 : symbol 8, slots40: 12
      -> grouped into nzp-CSI-RS-ResourceSetId 1 with trs-Info true

Read them as a chain. (a) is the signal the UE measures to work out how good the SCell's channel is. (b) is a set of resource elements where the gNB deliberately sends nothing, so whatever the UE receives there is by definition interference and noise - measure (a) against (b) and you have an SINR, and from an SINR you get a CQI. (c) exists because of (b) : if the gNB is going to keep those resource elements empty, the PDSCH must be told to skip them, and a zero power CSI-RS is how PDSCH is told to rate match around something. Notice that (a), (b) and (c) all use slots80: 1 - the same period and the same offset - because channel and interference have to be measured in the same instant for the comparison to mean anything.

(d) is a different animal. Four resources in two consecutive slots (11 and 12 of every 40 slots), two symbols apart in each slot, one port, density three, tagged trs-Info true. That is the textbook NR TRS burst. The two symbols within a slot give the UE a frequency offset estimate ; the two slots 1 slot apart give it a time offset estimate. It repeats every 40 slots = 20 ms. The UE needs this before its CSI measurements mean anything, because it has just started receiving a carrier 90 MHz away from the one its oscillator was tracking. This is the concrete answer to point (4) at the top of the page : the reason an SCell is not instantly useful is that the UE has to wait for a TRS burst.

NOTE : there is one small omission here compared with Example 02. This SCell configuration has no servingCellMO. That field links a serving cell to a measObject so that the UE reports it in measResultServingMOList. Example 01 has no measConfig at all, so there is nothing to link to and the field is simply not needed. In Example 02 you will see servingCellMO 2 appear in exactly this position.

{
  message c1: rrcReconfiguration: {
    rrc-TransactionIdentifier 0,
    criticalExtensions rrcReconfiguration: {
      nonCriticalExtension {
        masterCellGroup {
          cellGroupId 0,
          mac-CellGroupConfig {
            phr-Config setup: {
              phr-PeriodicTimer sf500,
              phr-ProhibitTimer sf200,
              phr-Tx-PowerFactorChange dB3,
              multiplePHR FALSE,
              dummy FALSE,
              phr-Type2OtherCell FALSE,
              phr-ModeOtherCG real
            },
            skipUplinkTxDynamic FALSE
          },
          spCellConfig {
            spCellConfigDedicated {
              initialDownlinkBWP {
                pdsch-Config setup: {
                  resourceAllocation resourceAllocationType1,
                  rbg-Size config1,
                  mcs-Table qam256,
                  prb-BundlingType staticBundling: {
                    bundleSize wideband
                  }
                }
              },
              uplinkConfig {
                initialUplinkBWP {
                  pucch-Config setup: {
                    resourceToAddModList {
                      {
                        pucch-ResourceId 18,
                        startingPRB 49,
                        intraSlotFrequencyHopping enabled,
                        secondHopPRB 1,
                        format format2: {
                          nrofPRBs 1,
                          nrofSymbols 2,
                          startingSymbolIndex 2
                        }
                      }
                    }
                  },
                  pusch-Config setup: {
                    txConfig codebook,
                    resourceAllocation resourceAllocationType1,
                    mcs-Table qam256,
                    mcs-TableTransformPrecoder qam256,
                    codebookSubset nonCoherent,
                    maxRank 1
                  }
                }
              },
              csi-MeasConfig setup: {
                csi-ReportConfigToAddModList {
                  {
                    reportConfigId 1,
                    carrier 1,
                    resourcesForChannelMeasurement 0,
                    csi-IM-ResourcesForInterference 1,
                    reportConfigType periodic: {
                      reportSlotConfig slots80: 18,
                      pucch-CSI-ResourceList {
                        {
                          uplinkBandwidthPartId 0,
                          pucch-Resource 18
                        }
                      }
                    },
                    reportQuantity cri-RI-PMI-CQI: NULL,
                    reportFreqConfiguration {
                      cqi-FormatIndicator widebandCQI
                    },
                    timeRestrictionForChannelMeasurements notConfigured,
                    timeRestrictionForInterferenceMeasurements notConfigured,
                    groupBasedBeamReporting disabled: {
                    },
                    cqi-Table table2,
                    subbandSize value1
                  }
                }
              },
              tag-Id 0
            }
          },
          sCellToAddModList {
            {
              sCellIndex 1,
              sCellConfigCommon {
                physCellId 501,
                downlinkConfigCommon {
                  frequencyInfoDL {
                    absoluteFrequencySSB 627264,
                    frequencyBandList {
                      78
                    },
                    absoluteFrequencyPointA 626688,
                    scs-SpecificCarrierList {
                      {
                        offsetToCarrier 0,
                        subcarrierSpacing kHz30,
                        carrierBandwidth 51
                      }
                    }
                  },
                  initialDownlinkBWP {
                    genericParameters {
                      locationAndBandwidth 13750,
                      subcarrierSpacing kHz30
                    },
                    pdcch-ConfigCommon setup: {
                      controlResourceSetZero 10,
                      commonControlResourceSet {
                        controlResourceSetId 1,
                        frequencyDomainResources '011111110000000000000000000000000000000000000'B,
                        duration 1,
                        cce-REG-MappingType nonInterleaved: NULL,
                        precoderGranularity sameAsREG-bundle
                      },
                      searchSpaceZero 0,
                      commonSearchSpaceList {
                        {
                          searchSpaceId 1,
                          controlResourceSetId 0,
                          monitoringSlotPeriodicityAndOffset sl1: NULL,
                          monitoringSymbolsWithinSlot '10000000000000'B,
                          nrofCandidates {
                            aggregationLevel1 n0,
                            aggregationLevel2 n0,
                            aggregationLevel4 n4,
                            aggregationLevel8 n0,
                            aggregationLevel16 n0
                          },
                          searchSpaceType common: {
                            dci-Format0-0-AndFormat1-0 {
                            }
                          }
                        }
                      },
                      searchSpaceSIB1 0,
                      searchSpaceOtherSystemInformation 1,
                      pagingSearchSpace 1,
                      ra-SearchSpace 1
                    },
                    pdsch-ConfigCommon setup: {
                      pdsch-TimeDomainAllocationList {
                        {
                          mappingType typeA,
                          startSymbolAndLength 40
                        },
                        {
                          mappingType typeA,
                          startSymbolAndLength 57
                        }
                      }
                    }
                  }
                },
                ssb-PositionsInBurst mediumBitmap: '80'H,
                ssb-periodicityServingCell ms20,
                dmrs-TypeA-Position pos2,
                ssbSubcarrierSpacing kHz30,
                tdd-UL-DL-ConfigurationCommon {
                  referenceSubcarrierSpacing kHz30,
                  pattern1 {
                    dl-UL-TransmissionPeriodicity ms5,
                    nrofDownlinkSlots 7,
                    nrofDownlinkSymbols 6,
                    nrofUplinkSlots 2,
                    nrofUplinkSymbols 4
                  }
                },
                ss-PBCH-BlockPower -28
              },
              sCellConfigDedicated {
                initialDownlinkBWP {
                  pdcch-Config setup: {
                    searchSpacesToAddModList {
                      {
                        searchSpaceId 2,
                        controlResourceSetId 1,
                        monitoringSlotPeriodicityAndOffset sl1: NULL,
                        monitoringSymbolsWithinSlot '10000000000000'B,
                        nrofCandidates {
                          aggregationLevel1 n0,
                          aggregationLevel2 n2,
                          aggregationLevel4 n1,
                          aggregationLevel8 n0,
                          aggregationLevel16 n0
                        },
                        searchSpaceType ue-Specific: {
                          dci-Formats formats0-1-And-1-1
                        }
                      }
                    }
                  },
                  pdsch-Config setup: {
                    dmrs-DownlinkForPDSCH-MappingTypeA setup: {
                      dmrs-AdditionalPosition pos1
                    },
                    tci-StatesToAddModList {
                      {
                        tci-StateId 0,
                        qcl-Type1 {
                          referenceSignal ssb: 0,
                          qcl-Type typeD
                        }
                      }
                    },
                    resourceAllocation resourceAllocationType1,
                    rbg-Size config1,
                    mcs-Table qam256,
                    prb-BundlingType staticBundling: {
                      bundleSize wideband
                    },
                    zp-CSI-RS-ResourceToAddModList {
                      {
                        zp-CSI-RS-ResourceId 0,
                        resourceMapping {
                          frequencyDomainAllocation row4: '100'B,
                          nrofPorts p4,
                          firstOFDMSymbolInTimeDomain 8,
                          cdm-Type fd-CDM2,
                          density one: NULL,
                          freqBand {
                            startingRB 0,
                            nrofRBs 52
                          }
                        },
                        periodicityAndOffset slots80: 1
                      }
                    },
                    p-ZP-CSI-RS-ResourceSet setup: {
                      zp-CSI-RS-ResourceSetId 0,
                      zp-CSI-RS-ResourceIdList {
                        0
                      }
                    }
                  }
                },
                firstActiveDownlinkBWP-Id 0,
                pdcch-ServingCellConfig setup: {
                },
                pdsch-ServingCellConfig setup: {
                  nrofHARQ-ProcessesForPDSCH n16
                },
                csi-MeasConfig setup: {
                  nzp-CSI-RS-ResourceToAddModList {
                    {
                      nzp-CSI-RS-ResourceId 0,
                      resourceMapping {
                        frequencyDomainAllocation row2: '800'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 4,
                        cdm-Type noCDM,
                        density one: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots80: 1,
                      qcl-InfoPeriodicCSI-RS 0
                    },
                    {
                      nzp-CSI-RS-ResourceId 1,
                      resourceMapping {
                        frequencyDomainAllocation row1: '1'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 4,
                        cdm-Type noCDM,
                        density three: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots40: 11,
                      qcl-InfoPeriodicCSI-RS 0
                    },
                    {
                      nzp-CSI-RS-ResourceId 2,
                      resourceMapping {
                        frequencyDomainAllocation row1: '1'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 8,
                        cdm-Type noCDM,
                        density three: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots40: 11,
                      qcl-InfoPeriodicCSI-RS 0
                    },
                    {
                      nzp-CSI-RS-ResourceId 3,
                      resourceMapping {
                        frequencyDomainAllocation row1: '1'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 4,
                        cdm-Type noCDM,
                        density three: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots40: 12,
                      qcl-InfoPeriodicCSI-RS 0
                    },
                    {
                      nzp-CSI-RS-ResourceId 4,
                      resourceMapping {
                        frequencyDomainAllocation row1: '1'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 8,
                        cdm-Type noCDM,
                        density three: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots40: 12,
                      qcl-InfoPeriodicCSI-RS 0
                    }
                  },
                  nzp-CSI-RS-ResourceSetToAddModList {
                    {
                      nzp-CSI-ResourceSetId 0,
                      nzp-CSI-RS-Resources {
                        0
                      }
                    },
                    {
                      nzp-CSI-ResourceSetId 1,
                      nzp-CSI-RS-Resources {
                        1,
                        2,
                        3,
                        4
                      },
                      trs-Info true
                    }
                  },
                  csi-IM-ResourceToAddModList {
                    {
                      csi-IM-ResourceId 0,
                      csi-IM-ResourceElementPattern pattern1: {
                        subcarrierLocation-p1 s8,
                        symbolLocation-p1 8
                      },
                      freqBand {
                        startingRB 0,
                        nrofRBs 52
                      },
                      periodicityAndOffset slots80: 1
                    }
                  },
                  csi-IM-ResourceSetToAddModList {
                    {
                      csi-IM-ResourceSetId 0,
                      csi-IM-Resources {
                        0
                      }
                    }
                  },
                  csi-ResourceConfigToAddModList {
                    {
                      csi-ResourceConfigId 0,
                      csi-RS-ResourceSetList nzp-CSI-RS-SSB: {
                        nzp-CSI-RS-ResourceSetList {
                          0
                        }
                      },
                      bwp-Id 0,
                      resourceType periodic
                    },
                    {
                      csi-ResourceConfigId 1,
                      csi-RS-ResourceSetList csi-IM-ResourceSetList: {
                        0
                      },
                      bwp-Id 0,
                      resourceType periodic
                    },
                    {
                      csi-ResourceConfigId 2,
                      csi-RS-ResourceSetList nzp-CSI-RS-SSB: {
                        nzp-CSI-RS-ResourceSetList {
                          1
                        }
                      },
                      bwp-Id 0,
                      resourceType periodic
                    }
                  }
                },
                tag-Id 0
              }
            }
          }
        },
        dedicatedNAS-MessageList {
          '7E026FF0BE84017E0042010977000BF200F....'H
        }
      }
    }
  }
}

[2] RrcReconfigurationComplete

Four lines, no content, and that is the point. RRCReconfigurationComplete never echoes back what was configured. It only says "transaction 0 applied successfully". If the UE could not apply something in the reconfiguration - an unsupported band combination, a configuration it considers invalid - it would not send a failed version of this message. It would declare a reconfiguration failure and fall back, which in SA means going to RRC_IDLE and starting over. So in practice this empty message is a pass/fail bit : if you see it, the UE has accepted the entire SCell configuration.

Match the rrc-TransactionIdentifier 0 against the one in step [1] to be sure you are looking at the response to the right message. In a busy log there are often several reconfigurations in flight.

{
  message c1: rrcReconfigurationComplete: {
    rrc-TransactionIdentifier 0,
    criticalExtensions rrcReconfigurationComplete: {
    }
  }
}

[3] MAC CE to activate the Scell

At this moment the UE knows everything about the SCell but is not receiving anything on it. This one line changes that. It is the whole MAC PDU, and it contains three subPDUs :

  Message: Scell AD:02  LCID:1 len=3  PAD: len=76

  Scell AD:02   SCell Activation/Deactivation MAC CE, payload = 0x02
  LCID:1        3 bytes of SRB1 data travelling in the same PDU (this is the RRC
                message of a different procedure - MAC happily multiplexes them)
  PAD: len=76   76 bytes of padding, because the transport block was bigger than needed

Decoding the 0x02. The one octet SCell Activation/Deactivation MAC CE is a bitmap. Reading from the most significant bit down, the fields are C7 C6 C5 C4 C3 C2 C1 R :

  0x02  =  0 0 0 0 0 1 0
           | | | | | | | |
           | | | | | | | +-- R  = 0   (reserved)
           | | | | | | +---- C1 = 1  -> activate the SCell with sCellIndex 1
           | | | | | +------ C2 = 0
           | | | | +-------- C3 = 0
           | | | +---------- C4 = 0
           | | +------------ C5 = 0
           | +-------------- C6 = 0
           +---------------- C7 = 0

C1 = 1 refers to sCellIndex 1, which is exactly the index the RRCReconfiguration in step [1] gave to this SCell. That is the link between the RRC world and the MAC world, and it is the reason sCellIndex exists at all : RRC needs a short handle that a one byte MAC CE can point at.

NOTE : because it is a bitmap, one MAC CE can activate and deactivate several SCells at once, and it is absolute rather than incremental. A later MAC CE with value 0x00 would deactivate this SCell again. This is why an SCell can be switched on and off with almost no cost, and why networks often deactivate an SCell after a few hundred milliseconds of idle traffic - an activated SCell costs the UE real battery, because it has to keep receiving PDCCH on it in every slot.

Message: Scell AD:02 LCID:1 len=3 PAD: len=76

[4] DCI to schedule PDSCH carrying the MAC CE

A MAC CE is not magic - it travels in a normal transport block on a normal PDSCH, which needs a normal DCI. This DCI is sent on the PCell, because at this instant the SCell is still switched off and the UE is not monitoring its PDCCH yet. There is a nice little chicken-and-egg quality to it : the message that turns the SCell on can only be delivered over the PCell.

The fields worth reading here :

  • ss_id=2, cce_index=0, al=2 - a UE specific search space, aggregation level 2. Normal.
  • mcs1=17, ndi1=1, rv_idx1=0, harq_process=0 - a fresh transport block on HARQ process 0.
  • dai=0 - binary 0000, so counter DAI = 00 and total DAI = 00. Both mean "the first, and there is one in total". Keep this next to the dai values in steps [7] and [9] and the DAI mechanism will become obvious.
  • harq_feedback_timing=5 - the index into dl-DataToUL-ACK. Step [5] shows what it resolved to.
Message: ss_id=2 cce_index=0 al=2 dci=1_1

Data:
    rb_alloc=0x32
    time_domain_rsc=0
    mcs1=17
    ndi1=1
    rv_idx1=0
    harq_process=0
    dai=0
    tpc_command=1
    pucch_rsc=0
    harq_feedback_timing=5
    antenna_ports=0
    srs_request=0
    dmrs_seq_init=0

[5] PDSCH carrying the MAC CE

This is the transport block scheduled by step [4], and it contains the MAC PDU from step [3]. The reason to look at it is that you can verify that claim arithmetically. The log says tb_len=84 bytes, so add up the MAC PDU from step [3] :

  SCell Act/Deact MAC CE   subheader 1 byte + payload 1 byte    =  2 bytes
  LCID 1 subPDU           subheader 2 bytes + payload 3 bytes   =  5 bytes
  Padding                 subheader 1 byte + 76 bytes           = 77 bytes
                                                                 --------
                                                                  84 bytes  = tb_len

It matches exactly. This is the kind of check that turns a log from a wall of text into something you can trust : the MAC line and the PHY line are describing the same object from two different layers.

The other field to notice is k1=12. Twelve slots is a long wait for a HARQ ACK - at 30 kHz SCS that is 6 ms. But look back at the TDD pattern in step [1] : D D D D D D D S U U repeating every 5 ms. Uplink slots only come around twice per 5 ms period, so a PDSCH landing early in the downlink run has no choice but to wait for the next uplink opportunity. In TDD, k1 is dictated by the pattern far more than by any processing capability.

Finally, mod=6 means 64QAM at a code rate of 0.80. The gNB is being reasonably aggressive here because this is the PCell, where it has had CSI reports for a long time. Compare this with the very first PDSCH on the SCell in Example 02 step [13].

Message: harq=0 prb=50 symb=1:13 k1=12 CW0: tb_len=84 mod=6 rv_idx=0 cr=0.80 retx=0

[6] PUCCH (HARQ ACK) for PDSCH at step [5]

The UE acknowledges the transport block, and with that the SCell is on. Reading the line :

  • format=1 - PUCCH format 1, the long format for 1 or 2 HARQ bits. Note that this is not pucch-Resource 18 from step [1] ; resource 18 is a format 2 resource reserved for the SCell's CSI report. HARQ feedback and CSI reporting use different PUCCH resources, and mixing them up is a common source of confusion when reading a CA log.
  • prb=50, prb2=0, symb=0:14 - 14 symbols long, hopping from PRB 50 to PRB 0. Long format plus frequency hopping is the standard choice at cell edge, where the uplink is the weak direction.
  • snr=37.4, epre=-88.0 - the received quality at the gNB. 37 dB SNR is a cabled or very short range lab setup ; over the air you would rarely see above the mid twenties.

NOTE : the ack=11 here shows two ACK bits, even though the DCI at step [4] carried dai=0 which on its own would suggest a codebook of one bit. The second screenshot at the top of this example has the answer, and it is worth tracing because it is a complete worked example of the DAI mechanism. Between the MAC CE PDSCH and this PUCCH there is a second PDSCH that the text log above does not include :

  from the screenshot, all within SFN 3.175 :

    slot 6   PDCCH  ss_id=2 cce_index=0 al=2 dci=1_1        (step [4])
    slot 6   PDSCH  harq=0 prb=50 symb=1:13 k1=12 tb_len=84   (step [5], the MAC CE)
                                                  6 + 12 = 18
    slot 7   PDCCH  ss_id=2 cce_index=6 al=2 dci=1_1
    slot 7   PDSCH  harq=1 prb=43:8 symb=1:5 k1=11 tb_len=253  (a second RRCReconfiguration)
                                                  7 + 11 = 18
    slot 18  PUCCH  format=1 ... ack=11                     (step [6])

Both PDSCHs were deliberately aimed at the same uplink slot - one from slot 6 with k1=12, the other from slot 7 with k1=11 - so both are acknowledged in one PUCCH, and the codebook is 2 bits. The dai=0 in step [4]'s DCI is not a contradiction either : at that moment only one PDSCH was planned, and the total DAI in the later DCI is what finally sets the codebook size. This is exactly the mechanism described in steps [7] and [9], happening here a few slots earlier than you would expect.

Message: format=1 prb=50 prb2=0 symb=0:14 cs=0 occ=0 ack=11 snr=37.4 epre=-88.0

 

Everything from here on is no longer establishment. The SCell is active, and steps [7] to [11] are simply what a single downlink slot looks like when two carriers are running. Read them as one group of five rather than five separate events, because that is what they are : two DCIs, two PDSCHs, one PUCCH.

[7] DCI (PDSCH) scheduling PDSCH in PCell

The first of the two downlink assignments. Nothing about it is CA specific except one field :

  dai=1  =  binary 00 01
                   |  |
                   |  +-- total DAI   = 01 -> expect 2 PDSCHs in this feedback window
                   +----- counter DAI = 00 -> this is the 1st of them

The gNB is telling the UE, in the very first DCI of the slot, that two acknowledgements will be needed. This matters because the UE has to build the HARQ codebook before it knows what else will be scheduled, and because DCIs get missed. If the UE had missed this DCI entirely, the total DAI in the next one would still tell it the codebook is 2 bits long, so it would send a NACK in the right bit position rather than sending a 1 bit codebook that the gNB cannot interpret. That robustness is the entire reason the DAI field exists.

Also harq_feedback_timing=2 here, against 5 in step [4] - the scheduler picked a different entry of dl-DataToUL-ACK because this PDSCH lands at a different place in the TDD pattern.

Message: ss_id=2 cce_index=6 al=2 dci=1_1

Data:
    rb_alloc=0x0
    time_domain_rsc=0
    mcs1=19
    ndi1=0
    rv_idx1=0
    harq_process=0
    dai=1
    tpc_command=1
    pucch_rsc=0
    harq_feedback_timing=2
    antenna_ports=0
    srs_request=0
    dmrs_seq_init=0

[8] PDSCH in PCell

92 bytes at 64QAM, code rate 0.87, symbols 1:13 as always, first transmission. This is the PCell's contribution to the slot. The only thing to note is k1=6, which together with step [10]'s k1=6 is what makes step [11] possible - both PDSCHs point at the same uplink slot, so both ACKs can travel in the same PUCCH.

Message: harq=0 prb=0 symb=1:13 k1=6 CW0: tb_len=92 mod=6 rv_idx=0 cr=0.87 retx=0

[9] DCI(PDSCH) scheduling PDSCH in SCell

Here is the moment CA actually shows itself. This DCI is transmitted on the SCell, in search space 2 that was configured in step [1] - self-scheduling, exactly as the configuration promised. There is no carrier indicator field in play, because with self-scheduling none is needed : a DCI found on the SCell schedules the SCell.

  dai=5  =  binary 01 01
                   |  |
                   |  +-- total DAI   = 01 -> still 2 PDSCHs in this window
                   +----- counter DAI = 01 -> this is the 2nd of them

Put steps [7] and [9] side by side and the whole mechanism is visible in two numbers. The total DAI stayed at 01 in both, so the size of the codebook never changed. The counter DAI went 00 -> 01, so the UE knows the PCell PDSCH occupies bit 0 and the SCell PDSCH occupies bit 1. The UE now knows how to build a 2 bit codebook and where each bit goes, without ever needing to know which carrier is which - the ordering comes out of the DAI counting, not out of the cell index.

One more detail : cce_index=0 here while step [7] had cce_index=6. They do not collide because they are in different cells, on different frequencies. Each carrier has its own CORESET and its own CCE space.

If you have the screenshot rather than the text log, none of this needs inferring. The Amarisoft trace has a Cell column, and the third screenshot at the top of this example shows it directly - steps [7] and [8] on Cell 1, steps [9] and [10] on Cell 2, and step [11] back on Cell 1. The timing closes too : both PDSCHs are in SFN 3.566 slot 3 with k1=6, and the PUCCH carrying both ACKs is in SFN 3.566 slot 9. Three plus six is nine, on the PCell, exactly as configured.

Message: ss_id=2 cce_index=0 al=2 dci=1_1

Data:
    rb_alloc=0x0
    time_domain_rsc=0
    mcs1=21
    ndi1=0
    rv_idx1=0
    harq_process=0
    dai=5
    tpc_command=1
    pucch_rsc=0
    harq_feedback_timing=2
    antenna_ports=0
    srs_request=0
    dmrs_seq_init=0

[10] PDSCH in SCell

101 bytes at mod=8, which is 256QAM, code rate 0.71. The SCell is now running at a higher modulation order than the PCell was in step [8], which tells you the SCell has been active long enough for its CSI reports to arrive and for the scheduler to trust them. Remember that the SCell's pdsch-Config in step [1] set mcs-Table qam256 - without that, 256QAM would not have been available at all.

This is what carrier aggregation buys you, expressed in the smallest possible way : in this one slot the UE received 92 bytes on one carrier and 101 bytes on the other, and the two arrived independently on two separate 20 MHz channels.

Message: harq=0 prb=0 symb=1:13 k1=6 CW0: tb_len=101 mod=8 rv_idx=0 cr=0.71 retx=0

[11] HARQ Ack for PDSCH in both PCell and SCell

And here is the payoff. ack=11 - two ACK bits, one PUCCH, and it is on the PCell. Two carriers were received, one uplink transmission acknowledged them both.

Trace the chain backwards one last time, because it is the cleanest illustration of how CA holds together :

  [7] DCI on PCell   dai=1 -> counter 1 of 2   \
  [8] PDSCH on PCell k1=6                       |
                                                |--> both point at the same uplink slot
  [9] DCI on SCell   dai=5 -> counter 2 of 2    |
  [10] PDSCH on SCell k1=6                     /

  [11] one PUCCH on the PCell, ack=11  -> bit0 = PCell PDSCH, bit1 = SCell PDSCH

The SCell never transmitted anything. It has no uplink configuration at all, as we saw in step [1]. Every bit of feedback for it went out over the PCell, which is the arrangement the network prepared for back at the very beginning of this example when it added pucch-Resource 18 and the csi-ReportConfig with carrier 1 to the PCell.

Message: format=1 prb=50 prb2=0 symb=0:14 cs=0 occ=0 ack=11 snr=36.9 epre=-88.2

Example 02 > NR SA 2CC CA : n78 (20Mhz) + n78(20 Mhz) with Measurement Report

This example is from the log captured by Amarisoft Callbox(gNB) and UE Simulator. Overall procedure captured in this log is as follows.

Step Message Layer / cell Dir What happens

Measurement - the part Example 01 does not have

[1]

[1] RrcReconfiguration for Scell Measurement

RRC / PCell

DL

No SCell is added yet. Sets up measObject 2 + event A4, and a 6 ms measurement gap every 40 ms

[2]

[2] RrcReconfigurationComplete

RRC / PCell

UL

The UE starts using the gaps and evaluating A4 on the other carrier

[3]

[3] Measurement Report

RRC / PCell

UL

measId 1 fired. PCI 501 at about -90 dBm, SINR about 21 dB - good enough to add

Establishment - identical to Example 01 steps [1] to [6]

[4]

[4] RrcReconfiguration for Scell Addition

RRC / PCell

DL

Adds the SCell, and re-points measId 1 from event A4 to event A2 - from "should I add it" to "should I drop it"

[5]

[5] RrcReconfigurationComplete

RRC / PCell

UL

The SCell configuration is accepted

[6]

[6] MAC CE to activate the Scell

MAC / PCell

DL

Same 0x02 bitmap as Example 01. Only the padding length differs

[7]

[7] DCI to schedule PDSCH carrying the MAC CE

PHY / PDCCH / PCell

DL

dai=0 : one PDSCH in this window, so a 1 bit HARQ codebook

[8]

[8] PDSCH carrying the MAC CE

PHY / PDSCH / PCell

DL

tb_len=92 bytes = 2 + 5 + 85, the MAC PDU of step [6]

[9]

[9] PUCCH (HARQ ACK) for PDSCH at step [8]

PHY / PUCCH / PCell

UL

ack=1 - one serving cell, one ACK bit. Compare with step [14]

Traffic - identical to Example 01 steps [7] to [11]

[10]

[10] DCI (PDSCH) scheduling PDSCH in PCell

PHY / PDCCH / PCell

DL

dai=1 : the codebook has grown to 2 bits since step [7]

[11]

[11] PDSCH in PCell

PHY / PDSCH / PCell

DL

92 bytes, 64QAM, k1=5

[12]

[12] DCI(PDSCH) scheduling PDSCH in SCell

PHY / PDCCH / SCell

DL

dai=5 : counter DAI 2 of 2. But mcs1=7, far below the PCell's 19

[13]

[13] PDSCH in SCell

PHY / PDSCH / SCell

DL

Only 34 bytes at 16QAM - the scheduler has no CSI from the SCell yet

[14]

[14] HARQ Ack for PDSCH in both PCell and SCell

PHY / PUCCH / PCell

UL

ack=11 - two ACK bits. This is the proof that CA is really running

Before any of this, the first screenshot above shows something that is easy to scroll past but is the precondition for the entire example : UE capability enquiry, UE capability information, and then a line reading NR band combinations. That is the moment the gNB learns this UE can do n78 + n78 at all. No amount of measurement reporting will produce an SCell if that band combination is absent - which is why "the UE's reported band combination does not support this pair" appears in the troubleshooting table at the end of this page. Worth knowing where in a log to find it.

Compare this step table against Example 01's and you will see that steps [4] to [14] here are the same eleven steps as [1] to [11] there. Everything that is new is at the front : three extra messages that turn "the network decided to add an SCell" into "the network found out that adding an SCell was a good idea, and then added it". That is the difference between a lab configuration and how a real network behaves.

The build-up is worth stating before we look at the messages :

  • [1] the network tells the UE to go and look at the other carrier, and under what condition to report back
  • [2] the UE confirms it will
  • [3] the UE finds the other carrier is good enough and reports it
  • [4] only now does the network add the SCell - and while it is at it, it changes the meaning of the measurement it had configured in [1]

 

 

 

 

[1] RrcReconfiguration for Scell Measurement

No sCellToAddModList anywhere in this message. Nothing is being added yet. This message only sets up measurements, and it does it in the four-part structure that every NR measConfig uses :

  measObjectToAddModList   what to measure    (a frequency)
  reportConfigToAddModList when to report     (an event and its threshold)
  measIdToAddModList       which pairs of the above are actually active
  quantityConfig           how to filter the raw measurements
  measGapConfig            when the UE is allowed to stop receiving and go look

What to measure. Two measObjects are configured :

  measObjectId 1 : ssbFrequency 621216 (3318.24 MHz) - this is the PCell's own frequency
  measObjectId 2 : ssbFrequency 627264 (3408.96 MHz) - the candidate SCell frequency

  both : ssbSubcarrierSpacing kHz30, freqBandIndicatorNR 78
         smtc1 periodicityAndOffset sf20: 0, duration sf1

The smtc1 is the SSB Measurement Timing Configuration - a 1 ms window every 20 ms, at offset 0. It has to match the SSB periodicity of the target cell, and indeed in Example 01 we saw the SCell configured with ssb-periodicityServingCell ms20. A mismatch here is one of the classic reasons a UE reports nothing at all : it is looking in a window where no SSB is being transmitted.

When to report. Two report configurations, and the choice of events is the interesting part :

  reportConfigId 1 : eventA2 - serving becomes worse than threshold
      a2-Threshold rsrp 16  -> RSRP_LEV 16 = about -141 dBm
      hysteresis 0, timeToTrigger ms0, reportAmount r1

  reportConfigId 2 : eventA4 - neighbour becomes better than threshold
      a4-Threshold rsrp 36  -> RSRP_LEV 36 = about -121 dBm
      hysteresis 0, timeToTrigger ms0, reportAmount r1
      useWhiteCellList FALSE

  both : rsType ssb, reportInterval ms120, maxReportCells 1,
         reportQuantityCell { rsrp, rsrq, sinr }, includeBeamMeasurements FALSE

The RSRP values are reported as RSRP-Range indices, not dBm. The mapping is RSRP_LEV n corresponds to roughly (n - 157) dBm, so 16 is about -141 dBm and 36 is about -121 dBm.

A4 is the event that adds an SCell. A4 means "a neighbour has become better than an absolute threshold", and it does not compare against the serving cell at all. That is exactly what you want for CA : the question is not "is the other carrier better than my PCell", it is "is the other carrier good enough to be worth using as well". A3, the event you would use for handover, would be the wrong question entirely here.

And A2 is the event that removes one. A2 means "the serving cell has become worse than a threshold". Right now it is configured but not linked to anything, which is the setup for the trick you will see in step [4].

NOTE : hysteresis 0 and timeToTrigger ms0 are lab settings. They make the event fire the instant the condition is true, which is convenient when you are testing but would cause constant ping-ponging in the field, where a UE walking past a lamp post would trigger and untrigger repeatedly. In a real network you would expect a few dB of hysteresis and a timeToTrigger of a few hundred milliseconds. Also reportAmount r1 means the UE sends one report and then stops - it does not keep reporting every 120 ms.

Which pairs are active. This is where the measurement actually gets switched on, and there is only one entry :

  measIdToAddModList :
      measId 1  =  measObjectId 2  +  reportConfigId 2
                        (the other carrier)   (event A4)

So of everything configured, only one measurement is running : watch frequency 627264 and tell me when a cell there exceeds about -121 dBm. measObjectId 1 (the PCell's own frequency) and reportConfigId 1 (event A2) are configured but idle. Networks routinely pre-load configurations like this so that later they can switch behaviour with a tiny delta reconfiguration instead of resending everything - and that is precisely what happens in step [4].

The measurement gap. The last piece is the one that catches people out :

  measGapConfig -> gapUE setup :
      gapOffset 16, mgl ms6, mgrp ms40, mgta ms0
      -> a 6 ms gap every 40 ms, starting at subframe offset 16

Why does an intra-band measurement need a gap at all? Because, as we worked out at the top of this page, these two n78 carriers are 90.72 MHz apart while the UE's configured carrier is only 18.36 MHz wide. The second carrier is nowhere near the UE's current receive bandwidth, so to measure it the UE must retune - and while it is retuned it cannot receive the PCell. That is the definition of a measurement gap. Note that it is a gapUE, a per-UE gap covering all frequencies, rather than a per-FR gap.

The cost is real and worth knowing : 6 ms out of every 40 ms is 15% of the UE's receive time given up. The network is deliberately sacrificing 15% of the current throughput for a few hundred milliseconds in order to find out whether it can double the throughput afterwards. Once the SCell is configured, the UE receives that carrier continuously and the gap is no longer needed for it.

{
  message c1: rrcReconfiguration: {
    rrc-TransactionIdentifier 0,
    criticalExtensions rrcReconfiguration: {
      measConfig {
        measObjectToAddModList {
          {
            measObjectId 1,
            measObject measObjectNR: {
              ssbFrequency 621216,
              ssbSubcarrierSpacing kHz30,
              smtc1 {
                periodicityAndOffset sf20: 0,
                duration sf1
              },
              referenceSignalConfig {
                ssb-ConfigMobility {
                  deriveSSB-IndexFromCell FALSE
                }
              },
              quantityConfigIndex 1,
              offsetMO {
              },
              freqBandIndicatorNR 78
            }
          },
          {
            measObjectId 2,
            measObject measObjectNR: {
              ssbFrequency 627264,
              ssbSubcarrierSpacing kHz30,
              smtc1 {
                periodicityAndOffset sf20: 0,
                duration sf1
              },
              referenceSignalConfig {
                ssb-ConfigMobility {
                  deriveSSB-IndexFromCell FALSE
                }
              },
              quantityConfigIndex 1,
              offsetMO {
              },
              freqBandIndicatorNR 78
            }
          }
        },
        reportConfigToAddModList {
          {
            reportConfigId 1,
            reportConfig reportConfigNR: {
              reportType eventTriggered: {
                eventId eventA2: {
                  a2-Threshold rsrp: 16,
                  reportOnLeave FALSE,
                  hysteresis 0,
                  timeToTrigger ms0
                },
                rsType ssb,
                reportInterval ms120,
                reportAmount r1,
                reportQuantityCell {
                  rsrp TRUE,
                  rsrq TRUE,
                  sinr TRUE
                },
                maxReportCells 1,
                includeBeamMeasurements FALSE
              }
            }
          },
          {
            reportConfigId 2,
            reportConfig reportConfigNR: {
              reportType eventTriggered: {
                eventId eventA4: {
                  a4-Threshold rsrp: 36,
                  reportOnLeave FALSE,
                  hysteresis 0,
                  timeToTrigger ms0,
                  useWhiteCellList FALSE
                },
                rsType ssb,
                reportInterval ms120,
                reportAmount r1,
                reportQuantityCell {
                  rsrp TRUE,
                  rsrq TRUE,
                  sinr TRUE
                },
                maxReportCells 1,
                includeBeamMeasurements FALSE
              }
            }
          }
        },
        measIdToAddModList {
          {
            measId 1,
            measObjectId 2,
            reportConfigId 2
          }
        },
        quantityConfig {
          quantityConfigNR-List {
            {
              quantityConfigCell {
                ssb-FilterConfig {
                },
                csi-RS-FilterConfig {
                }
              }
            }
          }
        },
        measGapConfig {
          gapUE setup: {
            gapOffset 16,
            mgl ms6,
            mgrp ms40,
            mgta ms0
          }
        }
      },
      nonCriticalExtension {
        masterCellGroup {
          cellGroupId 0,
          spCellConfig {
            spCellConfigDedicated {
              initialDownlinkBWP {
                pdsch-Config setup: {
                  resourceAllocation resourceAllocationType1,
                  rbg-Size config1,
                  mcs-Table qam256,
                  prb-BundlingType staticBundling: {
                    bundleSize wideband
                  }
                }
              },
              uplinkConfig {
                initialUplinkBWP {
                  pusch-Config setup: {
                    txConfig codebook,
                    resourceAllocation resourceAllocationType1,
                    mcs-Table qam256,
                    mcs-TableTransformPrecoder qam256,
                    codebookSubset nonCoherent,
                    maxRank 1
                  }
                }
              },
              tag-Id 0
            }
          }
        },
        dedicatedNAS-MessageList {
          '7E022CCB5D1C017E0042010977000B...'H
        }
      }
    }
  }
}

[2] RrcReconfigurationComplete

The same empty acknowledgement as Example 01 step [2]. From this moment the UE starts using the measurement gaps and evaluating event A4 on frequency 627264.

{
  message c1: rrcReconfigurationComplete: {
    rrc-TransactionIdentifier 0,
    criticalExtensions rrcReconfigurationComplete: {
    }
  }
}

[3] Measurement Report

The A4 condition was met and the UE reports. This is the message that triggers everything that follows. Note that the log here is truncated part way through the neighbour cell entry, but everything that matters is visible :

  measId 1                          <-- this is the A4 measurement configured in step [1]

  measResultServingMOList :
      servCellId 0, physCellId 500       (the PCell)
          rsrp 70  -> about -87 dBm
          rsrq 65  -> about -10.5 dB
          sinr 84  -> about 19 dB

  measResultNeighCells measResultListNR :
      physCellId 501                     (the candidate SCell)
          rsrp 67  -> about -90 dBm
          rsrq 65  -> about -10.5 dB
          sinr 88  -> about 21 dB

The first thing to check is measId 1. A measurement report carries no event name - it only carries the measId, and you have to look back at the measIdToAddModList in step [1] to find out what it means. measId 1 was measObject 2 plus reportConfig 2, so this is the A4 report on frequency 627264. When you are handed a log with a dozen measIds configured, this lookup is always the first thing to do.

Then the numbers. The neighbour, PCI 501, is at about -90 dBm, comfortably above the A4 threshold of about -121 dBm, so the event fired. It is 3 dB weaker than the serving cell but that is irrelevant for A4 - and it is a good illustration of why A4 rather than A3 is the right event here. Under A3 the network would be asking "should I hand over to 501", the answer would be no, and no CA would ever be configured. Under A4 the question is "is 501 good enough to also use", and the answer is clearly yes.

The SINR of about 21 dB is what really matters for CA, because it predicts what MCS the SCell will support. At 21 dB the SCell should eventually reach 256QAM, and in Example 01 step [10] we saw a settled SCell doing exactly that.

NOTE : includeBeamMeasurements FALSE in step [1] means this report only carries cell level results, with no per-SSB beam detail. In FR1 with a single SSB beam - remember ssb-PositionsInBurst was '80'H, one beam - there is nothing useful to add. In FR2 you would normally set it TRUE, because the network needs to know which beam of the candidate cell was good.

{
  message c1: measurementReport: {
    criticalExtensions measurementReport: {
      measResults {
        measId 1,
        measResultServingMOList {
          {
            servCellId 0,
            measResultServingCell {
              physCellId 500,
              measResult {
                cellResults {
                  resultsSSB-Cell {
                    rsrp 70,
                    rsrq 65,
                    sinr 84
                  }
                }
              }
            }
          }
        },
        measResultNeighCells measResultListNR: {
          {
            physCellId 501,
            measResult {
              cellResults {
                resultsSSB-Cell {
                  rsrp 67,
                  rsrq 65,
                  sinr 88
                }
              }

[4] RrcReconfiguration for Scell Addition

The network has its answer and now adds the SCell. The bulk of this message - sCellToAddModList with sCellIndex 1, physCellId 501, the TDD pattern, the search space, the TRS, the CSI-RS and CSI-IM - is the same content that was explained in detail in Example 01 step [1], so I will not repeat it. What is worth studying here are the three things that are different.

Difference 1 : it is a delta, not a full configuration. Look at what is in spCellConfigDedicated this time :

  Example 01 step [1], spCellConfigDedicated contained :
      initialDownlinkBWP (pdsch-Config), uplinkConfig (pucch-Config and pusch-Config),
      csi-MeasConfig, tag-Id

  Example 02 step [4], spCellConfigDedicated contains :
      uplinkConfig -> pucch-Config only  (pucch-ResourceId 18, same as before)
      csi-MeasConfig  (reportConfigId 1, carrier 1, pucch-Resource 18)
      tag-Id 0

The pdsch-Config and pusch-Config are missing because they were already set correctly by an earlier reconfiguration and nothing about them needs to change. RRC configuration in NR is delta based - what you do not mention stays as it was. This is why you can never read a single RRC message and claim to know the UE's full configuration ; you have to accumulate every reconfiguration since the connection was set up. It is also why the two additions that are here stand out so clearly : pucch-Resource 18 and the csi-ReportConfig with carrier 1. Those are the two things the PCell genuinely needs before it can host the SCell's feedback, and here they are, isolated, with everything else stripped away.

Difference 2 : the measId is repurposed. This is my favourite detail in the whole page. The measConfig in this message contains exactly one thing :

  measIdToAddModList :
      measId 1  =  measObjectId 2  +  reportConfigId 1

  compare with step [1] :
      measId 1  =  measObjectId 2  +  reportConfigId 2

  same measId, same measObject, different reportConfig
       reportConfigId 2 = eventA4 (neighbour becomes better) -> "should I add this carrier?"
       reportConfigId 1 = eventA2 (serving becomes worse)    -> "should I drop this carrier?"

Read it in plain English. Before this message, frequency 627264 was a neighbour and the interesting question was whether it was good enough to add - so measId 1 was watching for A4. After this message, frequency 627264 is a serving cell, and the interesting question has become the opposite one : has it gone bad enough that I should remove it? So the network points the very same measId at event A2 instead.

This is why reportConfigId 1 was configured back in step [1] and then left unused. The network pre-loaded both halves of the SCell's life cycle, and switching between them costs one small measIdToAddModList entry instead of a full measurement reconfiguration. And it explains the A2 threshold that looked so strange in step [1] : about -141 dBm is essentially "never, unless the carrier disappears completely". In a lab you do not want the SCell dropping itself ; in a real network that threshold would be set somewhere sensible like -110 dBm so the SCell is released when it stops being worth its overhead.

Difference 3 : servingCellMO appears. At the very bottom of the SCell configuration, after tag-Id, there is one extra line that Example 01 did not have :

  sCellConfigDedicated :
      ...
      tag-Id 0,
      servingCellMO 2          <-- links this SCell to measObjectId 2

This ties the SCell to measObjectId 2, the measObject for frequency 627264. Without it, the UE would have no way to know that the cell it is now serving on that frequency is the same thing as the measObject it has been measuring. With it, measurements on the SCell are reported under measResultServingMOList as a serving cell rather than as a neighbour - which is exactly what event A2 needs in order to evaluate "the serving cell has become worse than". Example 01 had no measConfig at all, so it had nothing to link to and the field was correctly absent.

NOTE : these three differences together are a good summary of what makes a real network's CA setup different from a callbox script. It measures before it acts, it only signals what changed, and it arranges in advance for the SCell to be released again when it stops being useful.

{
  message c1: rrcReconfiguration: {
    rrc-TransactionIdentifier 0,
    criticalExtensions rrcReconfiguration: {
      measConfig {
        measIdToAddModList {
          {
            measId 1,
            measObjectId 2,
            reportConfigId 1
          }
        }
      },
      nonCriticalExtension {
        masterCellGroup {
          cellGroupId 0,
          mac-CellGroupConfig {
            phr-Config setup: {
              phr-PeriodicTimer sf500,
              phr-ProhibitTimer sf200,
              phr-Tx-PowerFactorChange dB3,
              multiplePHR FALSE,
              dummy FALSE,
              phr-Type2OtherCell FALSE,
              phr-ModeOtherCG real
            },
            skipUplinkTxDynamic FALSE
          },
          spCellConfig {
            spCellConfigDedicated {
              uplinkConfig {
                initialUplinkBWP {
                  pucch-Config setup: {
                    resourceToAddModList {
                      {
                        pucch-ResourceId 18,
                        startingPRB 49,
                        intraSlotFrequencyHopping enabled,
                        secondHopPRB 1,
                        format format2: {
                          nrofPRBs 1,
                          nrofSymbols 2,
                          startingSymbolIndex 2
                        }
                      }
                    }
                  }
                }
              },
              csi-MeasConfig setup: {
                csi-ReportConfigToAddModList {
                  {
                    reportConfigId 1,
                    carrier 1,
                    resourcesForChannelMeasurement 0,
                    csi-IM-ResourcesForInterference 1,
                    reportConfigType periodic: {
                      reportSlotConfig slots80: 18,
                      pucch-CSI-ResourceList {
                        {
                          uplinkBandwidthPartId 0,
                          pucch-Resource 18
                        }
                      }
                    },
                    reportQuantity cri-RI-PMI-CQI: NULL,
                    reportFreqConfiguration {
                      cqi-FormatIndicator widebandCQI
                    },
                    timeRestrictionForChannelMeasurements notConfigured,
                    timeRestrictionForInterferenceMeasurements notConfigured,
                    groupBasedBeamReporting disabled: {
                    },
                    cqi-Table table2,
                    subbandSize value1
                  }
                }
              },
              tag-Id 0
            }
          },
          sCellToAddModList {
            {
              sCellIndex 1,
              sCellConfigCommon {
                physCellId 501,
                downlinkConfigCommon {
                  frequencyInfoDL {
                    absoluteFrequencySSB 627264,
                    frequencyBandList {
                      78
                    },
                    absoluteFrequencyPointA 626688,
                    scs-SpecificCarrierList {
                      {
                        offsetToCarrier 0,
                        subcarrierSpacing kHz30,
                        carrierBandwidth 51
                      }
                    }
                  },
                  initialDownlinkBWP {
                    genericParameters {
                      locationAndBandwidth 13750,
                      subcarrierSpacing kHz30
                    },
                    pdcch-ConfigCommon setup: {
                      controlResourceSetZero 10,
                      commonControlResourceSet {
                        controlResourceSetId 1,
                        frequencyDomainResources '011111110000000000000000000000000000000000000'B,
                        duration 1,
                        cce-REG-MappingType nonInterleaved: NULL,
                        precoderGranularity sameAsREG-bundle
                      },
                      searchSpaceZero 0,
                      commonSearchSpaceList {
                        {
                          searchSpaceId 1,
                          controlResourceSetId 0,
                          monitoringSlotPeriodicityAndOffset sl1: NULL,
                          monitoringSymbolsWithinSlot '10000000000000'B,
                          nrofCandidates {
                            aggregationLevel1 n0,
                            aggregationLevel2 n0,
                            aggregationLevel4 n4,
                            aggregationLevel8 n0,
                            aggregationLevel16 n0
                          },
                          searchSpaceType common: {
                            dci-Format0-0-AndFormat1-0 {
                            }
                          }
                        }
                      },
                      searchSpaceSIB1 0,
                      searchSpaceOtherSystemInformation 1,
                      pagingSearchSpace 1,
                      ra-SearchSpace 1
                    },
                    pdsch-ConfigCommon setup: {
                      pdsch-TimeDomainAllocationList {
                        {
                          mappingType typeA,
                          startSymbolAndLength 40
                        },
                        {
                          mappingType typeA,
                          startSymbolAndLength 57
                        }
                      }
                    }
                  }
                },
                ssb-PositionsInBurst mediumBitmap: '80'H,
                ssb-periodicityServingCell ms20,
                dmrs-TypeA-Position pos2,
                ssbSubcarrierSpacing kHz30,
                tdd-UL-DL-ConfigurationCommon {
                  referenceSubcarrierSpacing kHz30,
                  pattern1 {
                    dl-UL-TransmissionPeriodicity ms5,
                    nrofDownlinkSlots 7,
                    nrofDownlinkSymbols 6,
                    nrofUplinkSlots 2,
                    nrofUplinkSymbols 4
                  }
                },
                ss-PBCH-BlockPower -28
              },
              sCellConfigDedicated {
                initialDownlinkBWP {
                  pdcch-Config setup: {
                    searchSpacesToAddModList {
                      {
                        searchSpaceId 2,
                        controlResourceSetId 1,
                        monitoringSlotPeriodicityAndOffset sl1: NULL,
                        monitoringSymbolsWithinSlot '10000000000000'B,
                        nrofCandidates {
                          aggregationLevel1 n0,
                          aggregationLevel2 n2,
                          aggregationLevel4 n1,
                          aggregationLevel8 n0,
                          aggregationLevel16 n0
                        },
                        searchSpaceType ue-Specific: {
                          dci-Formats formats0-1-And-1-1
                        }
                      }
                    }
                  },
                  pdsch-Config setup: {
                    dmrs-DownlinkForPDSCH-MappingTypeA setup: {
                      dmrs-AdditionalPosition pos1
                    },
                    tci-StatesToAddModList {
                      {
                        tci-StateId 0,
                        qcl-Type1 {
                          referenceSignal ssb: 0,
                          qcl-Type typeD
                        }
                      }
                    },
                    resourceAllocation resourceAllocationType1,
                    rbg-Size config1,
                    mcs-Table qam256,
                    prb-BundlingType staticBundling: {
                      bundleSize wideband
                    },
                    zp-CSI-RS-ResourceToAddModList {
                      {
                        zp-CSI-RS-ResourceId 0,
                        resourceMapping {
                          frequencyDomainAllocation row4: '100'B,
                          nrofPorts p4,
                          firstOFDMSymbolInTimeDomain 8,
                          cdm-Type fd-CDM2,
                          density one: NULL,
                          freqBand {
                            startingRB 0,
                            nrofRBs 52
                          }
                        },
                        periodicityAndOffset slots80: 1
                      }
                    },
                    p-ZP-CSI-RS-ResourceSet setup: {
                      zp-CSI-RS-ResourceSetId 0,
                      zp-CSI-RS-ResourceIdList {
                        0
                      }
                    }
                  }
                },
                firstActiveDownlinkBWP-Id 0,
                pdcch-ServingCellConfig setup: {
                },
                pdsch-ServingCellConfig setup: {
                  nrofHARQ-ProcessesForPDSCH n16
                },
                csi-MeasConfig setup: {
                  nzp-CSI-RS-ResourceToAddModList {
                    {
                      nzp-CSI-RS-ResourceId 0,
                      resourceMapping {
                        frequencyDomainAllocation row2: '800'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 4,
                        cdm-Type noCDM,
                        density one: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots80: 1,
                      qcl-InfoPeriodicCSI-RS 0
                    },
                    {
                      nzp-CSI-RS-ResourceId 1,
                      resourceMapping {
                        frequencyDomainAllocation row1: '1'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 4,
                        cdm-Type noCDM,
                        density three: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots40: 11,
                      qcl-InfoPeriodicCSI-RS 0
                    },
                    {
                      nzp-CSI-RS-ResourceId 2,
                      resourceMapping {
                        frequencyDomainAllocation row1: '1'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 8,
                        cdm-Type noCDM,
                        density three: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots40: 11,
                      qcl-InfoPeriodicCSI-RS 0
                    },
                    {
                      nzp-CSI-RS-ResourceId 3,
                      resourceMapping {
                        frequencyDomainAllocation row1: '1'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 4,
                        cdm-Type noCDM,
                        density three: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots40: 12,
                      qcl-InfoPeriodicCSI-RS 0
                    },
                    {
                      nzp-CSI-RS-ResourceId 4,
                      resourceMapping {
                        frequencyDomainAllocation row1: '1'H,
                        nrofPorts p1,
                        firstOFDMSymbolInTimeDomain 8,
                        cdm-Type noCDM,
                        density three: NULL,
                        freqBand {
                          startingRB 0,
                          nrofRBs 52
                        }
                      },
                      powerControlOffset 0,
                      powerControlOffsetSS db0,
                      scramblingID 501,
                      periodicityAndOffset slots40: 12,
                      qcl-InfoPeriodicCSI-RS 0
                    }
                  },
                  nzp-CSI-RS-ResourceSetToAddModList {
                    {
                      nzp-CSI-ResourceSetId 0,
                      nzp-CSI-RS-Resources {
                        0
                      }
                    },
                    {
                      nzp-CSI-ResourceSetId 1,
                      nzp-CSI-RS-Resources {
                        1,
                        2,
                        3,
                        4
                      },
                      trs-Info true
                    }
                  },
                  csi-IM-ResourceToAddModList {
                    {
                      csi-IM-ResourceId 0,
                      csi-IM-ResourceElementPattern pattern1: {
                        subcarrierLocation-p1 s8,
                        symbolLocation-p1 8
                      },
                      freqBand {
                        startingRB 0,
                        nrofRBs 52
                      },
                      periodicityAndOffset slots80: 1
                    }
                  },
                  csi-IM-ResourceSetToAddModList {
                    {
                      csi-IM-ResourceSetId 0,
                      csi-IM-Resources {
                        0
                      }
                    }
                  },
                  csi-ResourceConfigToAddModList {
                    {
                      csi-ResourceConfigId 0,
                      csi-RS-ResourceSetList nzp-CSI-RS-SSB: {
                        nzp-CSI-RS-ResourceSetList {
                          0
                        }
                      },
                      bwp-Id 0,
                      resourceType periodic
                    },
                    {
                      csi-ResourceConfigId 1,
                      csi-RS-ResourceSetList csi-IM-ResourceSetList: {
                        0
                      },
                      bwp-Id 0,
                      resourceType periodic
                    },
                    {
                      csi-ResourceConfigId 2,
                      csi-RS-ResourceSetList nzp-CSI-RS-SSB: {
                        nzp-CSI-RS-ResourceSetList {
                          1
                        }
                      },
                      bwp-Id 0,
                      resourceType periodic
                    }
                  }
                },
                tag-Id 0,
                servingCellMO 2
              }
            }
          }
        }
      }
    }
  }

[5] RrcReconfigurationComplete

The SCell configuration is accepted. From here to the end of the example, everything follows Example 01 exactly - so rather than repeat the explanations, I will point out only where the numbers differ and why.

{
  message c1: rrcReconfigurationComplete: {
    rrc-TransactionIdentifier 0,
    criticalExtensions rrcReconfigurationComplete: {
    }
  }
}

[6] MAC CE to activate the Scell

The same SCell Activation/Deactivation MAC CE as Example 01 step [3] : Scell AD:02, so C1 = 1, activating sCellIndex 1. The only difference is the padding, 84 bytes here against 76 in Example 01, because the transport block the scheduler happened to pick was a different size.

Message: Scell AD:02 LCID:1 len=3 PAD: len=84

[7] DCI to schedule PDSCH carrying the MAC CE

On the PCell, as it must be, since the SCell is not on yet. dai=0, binary 0000, so counter DAI = 1 and total DAI = 1 - a single PDSCH in this feedback window and therefore a 1 bit HARQ codebook. Watch what step [9] does with that.

Message: ss_id=2 cce_index=0 al=2 dci=1_1

Data:
    rb_alloc=0x32
    time_domain_rsc=0
    mcs1=19
    ndi1=1
    rv_idx1=0
    harq_process=0
    dai=0
    tpc_command=1
    pucch_rsc=0
    harq_feedback_timing=3
    antenna_ports=0
    srs_request=0
    dmrs_seq_init=0

[8] PDSCH carrying the MAC CE

tb_len=92 bytes, and the MAC PDU from step [6] accounts for all of it : 2 bytes for the SCell Act/Deact CE, 5 bytes for the 3 byte LCID 1 subPDU, and 1 + 84 = 85 bytes of padding. 2 + 5 + 85 = 92. Same arithmetic as Example 01 step [5], different numbers.

Message: harq=0 prb=50 symb=1:13 k1=5 CW0: tb_len=92 mod=6 rv_idx=0 cr=0.87 retx=0

[9] PUCCH (HARQ ACK) for PDSCH at step [8]

ack=1 - a single ACK bit, exactly as the dai=0 in step [7] predicted. This is the clean version of the moment that Example 01 step [6] showed with an inconsistent ack=11 because of log filtering. One PDSCH scheduled, one bit of feedback.

Keep this line in mind, because in five more steps it becomes ack=11 and that transition is the whole point of the example.

Message: format=1 prb=50 prb2=0 symb=0:14 cs=0 occ=0 ack=1 snr=37.1 epre=-87.9

[10] DCI (PDSCH) scheduling PDSCH in PCell

dai=1, binary 00 01 : counter DAI = 1st, total DAI = 2 expected. The codebook has grown from 1 bit to 2 bits between step [7] and this DCI, which is the observable consequence of the SCell having been activated in between. Nothing else in the DCI changed - same search space, same mcs, same feedback timing. The DAI is the only field that knows CA is now running.

Message: ss_id=2 cce_index=0 al=2 dci=1_1

Data:
    rb_alloc=0x32
    time_domain_rsc=0
    mcs1=19
    ndi1=0
    rv_idx1=0
    harq_process=0
    dai=1
    tpc_command=1
    pucch_rsc=0
    harq_feedback_timing=3
    antenna_ports=0
    srs_request=0
    dmrs_seq_init=0

[11] PDSCH in PCell

92 bytes at 64QAM, code rate 0.87, symbols 1:13, k1=5. Business as usual on the PCell.

Message: harq=0 prb=50 symb=1:13 k1=5 CW0: tb_len=92 mod=6 rv_idx=0 cr=0.87 retx=0

[12] DCI(PDSCH) scheduling PDSCH in SCell

dai=5, binary 01 01 : counter DAI = 2nd, total DAI = 2. Together with step [10] this completes the 2 bit codebook, in exactly the same way as Example 01 steps [7] and [9].

But look at the MCS : mcs1=7, and ndi1=1 meaning this is a fresh transport block. Compare that with the PCell's mcs1=19 in the same slot. The scheduler is being deliberately timid on the SCell, and step [13] shows why that is the right call.

Message: ss_id=2 cce_index=0 al=2 dci=1_1

Data:
    rb_alloc=0x32
    time_domain_rsc=0
    mcs1=7
    ndi1=1
    rv_idx1=0
    harq_process=0
    dai=5
    tpc_command=1
    pucch_rsc=0
    harq_feedback_timing=3
    antenna_ports=0
    srs_request=0
    dmrs_seq_init=0

[13] PDSCH in SCell

This is the most instructive single line in Example 02. tb_len=34, mod=4, cr=0.50 - 34 bytes at 16QAM with a half rate code. Now put it next to Example 01 step [10], which was the same situation but on an SCell that had been running for a while :

  Ex 02 step [13]
SCell just activated
Ex 01 step [10]
SCell settled

MCS

7

21

Modulation

mod=4 (16QAM)

mod=8 (256QAM)

Code rate

0.50

0.71

Transport block

34 bytes

101 bytes

Three times the data on the same 20 MHz carrier, at the same measured radio quality. The difference is not the radio - the measurement report in step [3] said the SCell had about 21 dB SINR, which is plenty for 256QAM. The difference is information. At step [13] the scheduler has no CSI report from the SCell yet. The UE has only just been told to start receiving that carrier ; it has not necessarily caught a TRS burst yet, and the periodic CSI report configured in step [4] only fires every 80 slots, which is 40 ms. So the gNB picks a conservative MCS that will almost certainly decode, and waits.

This is exactly what point (4) at the top of this page was about, now visible as a number in a log. If you are ever asked why a newly activated SCell "does not give the throughput it should", this is usually the answer, and the fix is in the configuration : a shorter CSI reporting period, or an aperiodic CSI request right after activation, gets the scheduler its information sooner.

Message: harq=0 prb=50 symb=1:13 k1=5 CW0: tb_len=34 mod=4 rv_idx=0 cr=0.50 retx=0

[14] HARQ Ack for PDSCH in both PCell and SCell

ack=11. Two carriers acknowledged in one PUCCH on the PCell, and carrier aggregation is fully running.

The single most useful thing in Example 02 is the transition you can now see across the whole log with one field :

  step [9]   ack=1     one serving cell  -> 1 bit codebook   (SCell configured, not yet active)
  step [14]  ack=11    two serving cells -> 2 bit codebook   (SCell active and scheduled)

If you only had time to look at two lines in a CA log to decide whether CA is actually working, these would be the two. An RRCReconfiguration with sCellToAddModList only proves the SCell was configured. A MAC CE only proves it was activated. A PUCCH carrying two ACK bits proves that data really was delivered on both carriers and that the UE really received it.

Message: format=1 prb=50 prb2=0 symb=0:14 cs=0 occ=0 ack=11 snr=37.1 epre=-87.9

When CA does not work : where to look first

Having gone through both logs, the failure modes almost sort themselves. Here they are in the order I would check them, because that order is roughly from most common to least.

Symptom in the log Most likely cause Where to look

No measurement report ever arrives

The smtc window does not match the target cell's SSB periodicity or offset, or the A4 threshold is set too high, or no measurement gap was given for a carrier the UE cannot reach without retuning.

measObject smtc1, a4-Threshold, measGapConfig - Ex 02 step [1]

Measurement report arrives but no SCell is added

The UE's reported band combination does not support this pair, or the gNB has no CA licence or capacity configured for the second cell. Nothing in the air interface is wrong.

UE Capability, gNB cell configuration

RRCReconfiguration sent, no RRCReconfigurationComplete

The UE could not apply the configuration and declared a reconfiguration failure. In SA this drops to idle - so also look for an RRC re-establishment or a fresh setup right after.

the message after the missing Complete

SCell configured, MAC CE sent, but no PDSCH ever appears on the SCell

The scheduler has no data to send, or no CSI has come back so the SCell is never selected. Check whether the CSI report configured with carrier = the SCell index is actually arriving on the PCell's PUCCH.

csi-ReportConfig carrier field and pucch-CSI-ResourceList - Ex 01 step [1]

PDSCH on the SCell but throughput barely improves

The SCell is stuck at a low MCS because CSI is slow or missing. See Ex 02 step [13]. Shorten the CSI period or add an aperiodic CSI trigger after activation.

reportSlotConfig, TRS configuration

HARQ ACK is never more than 1 bit

The SCell is never actually scheduled in the same feedback window, or the DCI is format 1_0 rather than 1_1 - format 1_0 has no DAI and cannot carry a multi-cell codebook.

dci= field and dai field in the DCI lines

Frequent NACKs on the SCell only

If both cells share a TAG but are not actually co-located, the SCell's timing is wrong. Check whether the SCell should have had its own tag-Id.

tag-Id in sCellConfigDedicated