PUCCH signals in 5G
The article presents a system for generating and processing uplink signals in the 5G standard, namely– the PUCCH (Physical Uplink Control Channel) control channel. This example allows us to visually explore the differences in signal processing chains for each format, comparing their structural complexity, the methods of modulation, encoding and spectrum expansion used. PUCCH (Physical Uplink Control Channel) is a physical uplink channel in 5G NR designed exclusively for transmitting UCI (Uplink Control Information) control information from user equipment (UE) to the base station (gNB).
PUCCH provides transmission of three main types of control information (UCI):
- HARQ-ACK — confirmation of incoming data reception (HARQ-ACK/NACK).
- CSI (Channel State Information) — Radio channel status reports (CQI, RI, PMI).
- SR (Scheduling Request) — Request to allocate resources for data transmission.
Unlike
the PUSCH data channel, PUCCH is optimized for transmitting small amounts of service information with minimal latency and high reliability. In 5G NR, the channel is implemented with increased flexibility compared to LTE: support for multiple formats (0-4), configurable duration (1-14 OFDM characters), dynamic resource allocation and frequency/time multiplexing.
The model implements and compares the PUCCH 1-4 formats (PUCCH 0 is not implemented because it is designed for 1-2 bits with an ultra-short duration). Each format is optimized for transmitting certain types of control information (UCI – Uplink Control Information).
function run_model( name_model)
Path = (@__DIR__) * "/" * name_model * ".engee"
if name_model in [m.name for m in engee.get_all_models()]
model = engee.open( name_model )
model_output = engee.run( model, verbose=true );
else
model = engee.load( Path, force=true )
model_output = engee.run( model, verbose=true );
engee.close( name_model, force=true );
end
sleep(0.1)
return model_output
end
run_model("PUCCH_model")
Comparative analysis of PUCCH formats implemented in the model:
Technical characteristics of PUCCH formats
| Format | Purpose | Modulation | Transform Precoding | Features |
|---|---|---|---|---|
| PUCCH1 | Short UCI (1-2 bits) | Low PAPR | Yes | OCC |
| PUCCH2 | CSI | QPSK | No | - |
| PUCCH3 | HARQ-ACK, SR | QPSK + DFT | Yes | - |
| PUCCH4 | High Capacity | QPSK+Spreading | Yes | OCC+ CDM |
using EngeeDSP
using Statistics, LinearAlgebra
using FFTW
using StatsBase
function unwrap(phase::Vector{Float64})
unwrapped = copy(phase)
for i in 2:length(phase)
diff = phase[i] - phase[i-1]
if diff > π
unwrapped[i] -= 2π
elseif diff < -π
unwrapped[i] += 2π
end
end
return unwrapped
end
function compute_papr(signal::AbstractArray{<:Complex})
power = abs2.(signal)
papr_db = 10 * log10(maximum(power) / mean(power))
return round(papr_db, digits=2)
end
function compute_spectrum(signal::AbstractArray{<:Complex}, fs=1.0)
N = length(signal)
fft_result = fft(signal)
freqs = fftfreq(N, fs)
pwr = abs2.(fft_result) / N
return freqs, pwr
end
function analyze_constellation(signal::AbstractArray{<:Complex}, title)
p = scatter(real.(signal), imag.(signal),
title=title, xlabel="In-phase (I)", ylabel="Quadrature (Q)",
legend=false, markersize=4, markerstrokewidth=0, alpha=0.7,
aspect_ratio=:equal)
return p
end
function compute_signal_stats(signal::AbstractArray{<:Complex}, name)
println("\n" * "═"^50)
println("statistics: $name")
println("═"^50)
println(" Data type: $(eltype(signal))")
println(" Size: $(size(signal))")
println(" PAPR: $(compute_papr(signal)) dB")
println(" Energy: $(round(sum(abs2.(signal)), digits=2))")
println(" Average amplitude: $(round(mean(abs.(signal)), digits=4))")
println(" Average phase: $(round(mean(angle.(signal)), digits=4)) glad")
end
PUCCH Format 1: Simulates the transmission of short control information (1-2 bits) using sequences with a low PAPR (peak factor). Includes operations:
-
Generation of low-PAPR sequences (
nrLowPAPRS). -
External parameter management via custom blocks (
Engee Function). -
The use of Orthogonal Cover Codes (OCC – Orthogonal Cover Codes) through the blocks
Spreading Formatto separate users.
y = collect(simout["PUCCH_model/PUCCH1/# Parse and validate inputs.y"]).value[end]
oSeq1 = collect(simout["PUCCH_model/PUCCH1/Spreading Format-1.1"]).value[end]
final = collect(simout["PUCCH_model/PUCCH1/# Get the PUCCH format 1 sequence.Seq"]).value[end]
compute_signal_stats(y, "Signal y (after nrPUCCH1)")
compute_signal_stats(oSeq1, "Orthogonal sequence 1")
compute_signal_stats(final, "The final sequence")
p1 = analyze_constellation(y, "PUCCH1: The constellation of the 'y' signal")
p2 = analyze_constellation(final, "PUCCH1: The Final Constellation")
p3 = plot(abs.(final), title="PUCCH1: The amplitude envelope",
xlabel="The counts", ylabel="The amplitude", legend=false, grid=true)
display(plot(p1, p2, p3, layout=(1,3), size=(1200, 400)))
savefig("pucch1_analysis.png")
PUCCH Format 2: Is intended for transmission of CSI (Channel State Information) of longer length. The processing chain includes:
-
Scrambling using a pseudorandom sequence generator (
PUCCH PRBS). -
QPSK modulation (
Symbol Modulate). -
Relatively simple structure compared to other formats.
prbs_bits = collect(simout["PUCCH_model/PUCCH2/PUCCH PRBS-1.1"]).value[end]
modulated = collect(simout["PUCCH_model/PUCCH2/Symbol Modulate-1.1"]).value[end]
println("Scrambled Bits (PRBS):")
println(" Number of bits: $(length(prbs_bits))")
println(" Balance (1/0): $(sum(prbs_bits))/$(sum(.!prbs_bits))")
compute_signal_stats(modulated, "QPSK modulated characters")
unique_symbols = unique(round.(modulated, digits=6))
println("\Punic points of the QPSK constellation:")
for sym in unique_symbols
println(" I=$(round(real(sym), digits=4)), Q=$(round(imag(sym), digits=4))")
end
phase = angle.(modulated)
instant_freq = diff(unwrap(phase))
p1 = histogram(instant_freq, bins=30, title="PUCCH2: Instantaneous frequency",
xlabel="Frequency", ylabel="Frequency of occurrence", legend=false)
p2 = analyze_constellation(modulated, "PUCCH2: The QPSK Constellation")
display(plot(p1, p2, layout=(1,2), size=(1200, 400)))
savefig("pucch2_analysis.png")
PUCCH Format 3: Is used to transmit HARQ-ACK and SR medium capacity. Features:
-
Converting logical bits to integer values.
-
Symbolic Modulation (QPSK).
-
Transform Precoding, using DFT (Discrete Fourier Transform) before OFDM modulation, which is typical for subcarriers with DFT-s-OFDM (SC-FDMA) in the uplink.
converted = collect(simout["PUCCH_model/PUCCH3/Convert in Int.1"]).value[end]
final = collect(simout["PUCCH_model/PUCCH3/Transform Precoding-1.1"]).value[end]
println("\Converted integer data:")
println(" Range: $(minimum(converted)) - $(maximum(converted))")
println(" Average: $(round(mean(converted), digits=2))")
compute_signal_stats(final, "The signal after Transform Precoding")
N = length(final)
fs = 1.0
fft_result = fft(final)
freqs = fftfreq(N, fs)
pwr = abs2.(fft_result) / N
p1 = plot(freqs, pwr, title="PUCCH3: Power spectral Density",
xlabel="Frequency", ylabel="Power (dB)", legend=false, grid=true,
xlim=(-0.5, 0.5))
p2 = analyze_constellation(final, "PUCCH3: Constellation after DFT")
max_pwr = maximum(pwr)
half_pwr = max_pwr / 2
indices = findall(pwr .> half_pwr)
if !isempty(indices)
min_freq = minimum(freqs[indices])
max_freq = maximum(freqs[indices])
bandwidth = max_freq - min_freq
println("\Lane width mark-up:")
println(" Bandwidth at 3 dB level: $(round(bandwidth, digits=4))")
else
println("We have not been able to estimate the bandwidth at the 3 dB level")
end
display(plot(p1, p2, layout=(1,2), size=(900, 400)))
savefig("pucch3_analysis.png")
PUCCH Format 4: Is designed to transmit high-capacity control information for a single user or with code division multiplexing for multiple users. It has the most complex structure:
-
Sequential scrambling, QPSK modulation.
-
Block-wise Spreading implemented in a separate subsystem
Spreading. This process multiplies modulated symbols by orthogonal cover codes (OCCs) to create multiple copies of the signal, which increases reliability and/or allows multiplexing of users. -
Transform precoding.
prbs = collect(simout["PUCCH_model/PUCCH4/PUCCH PRBS.1"]).value[end]
modulated = collect(simout["PUCCH_model/PUCCH4/Symbol Modulate.1"]).value[end]
after_spreading = collect(simout["PUCCH_model/PUCCH4/Spreading.Out1"]).value[end]
final = collect(simout["PUCCH_model/PUCCH4/Transform Precoding.1"]).value[end]
compute_signal_stats(modulated, "Before Block-wise Spreading")
compute_signal_stats(after_spreading, "After Spreading")
compute_signal_stats(final, "After Transform Precoding")
gain_factor = mean(abs2.(after_spreading)) / mean(abs2.(modulated))
println("Efficiency of Block-wise Spreading:")
println(" Power gain: $(round(gain_factor, digits=2))")
plots = []
push!(plots, analyze_constellation(modulated, "Before Spreading"))
push!(plots, analyze_constellation(after_spreading, "After Spreading"))
push!(plots, analyze_constellation(final, "The final signal"))
p4 = plot(abs.(modulated), label="Before Spreading", linewidth=1.5, alpha=0.8)
plot!(p4, abs.(after_spreading), label="After Spreading", linewidth=1, alpha=0.7)
plot!(p4, abs.(final), label="After Precoding", linewidth=1, alpha=0.7,
title="PUCCH4: Comparing amplitudes", xlabel="The counts", ylabel="The amplitude", grid=true)
push!(plots, p4)
display(plot(plots..., layout=(2,2), size=(1000, 800)))
savefig("pucch4_analysis.png")
, Key modeling results
📈 PAPR (peak factor)
- PUCCH1: 0.0 dB — low-PAPR sequences, ideal for UE energy efficiency
- PUCCH2: 0.0 dB — QPSK with constant envelope in the model
- PUCCH3: 13.8 dB — high PAPR is typical for DFT-s-OFDM
- PUCCH4: 8.73 dB — balance between capacity and power consumption
🔧 Specific features
- PUCCH1: low-PAPR+ OCC — for short ACK/NACK and SR
- PUCCH2: Simple QPSK chain — for periodic CSI reports
- PUCCH3: DFT-s-OFDM — for HARQ-ACK+SR medium capacity
- PUCCH4: Block-wise Spreading + CDM — for high capacity and multi-user scenarios
💾 Amount of data
- PUCCH1: 84 characters (low efficiency, ~0.024 bits/character)
- PUCCH2: 48 characters (1.0 bit/character)
- PUCCH3: 48 characters (1.0 bit/character)
- PUCCH4: 96 characters after expansion (1.0 bit/character)
⚡ Comparison with theory
All results comply with 3GPP specifications:
- PUCCH1: Low PAPR as designed for IoT devices
- PUCCH3: high PAPR (13.8 dB) expected for DFT-s-OFDM
- PUCCH4: spreading gives 1.0 power gain — orthogonal codes to separate users, not gain
- Resource efficiency increases from PUCCH1 to PUCCH4 in proportion to the complexity of processing
Conclusion
All four PUCCH formats are correctly implemented in the model, and their characteristics comply with 3GPP 5G NR specifications.
| Format | Purpose | Advantages |
|---|---|---|
| PUCCH1 | Short control information | Minimum power consumption, low PAPR |
| PUCCH2 | CSI transmission (Channel status information) | Easy to implement, stable operation |
| PUCCH3 | HARQ-ACK and scheduling requests | Balance of capacity and reliability |
| PUCCH4 | High capacity, multi-user access | CDM support, extended functionality |
Key conclusions on the implementation of PUCCH formats in the 5G NR model:
-
Each of the four PUCCH formats implements a well-defined compromise between capacity, latency, and energy efficiency, which allows adaptive configuration to meet the specific requirements of a network scenario.
-
The model clearly demonstrates the direct relationship between the type of control information transmitted (UCI) and the complexity of the signal path — from the minimalistic PUCCH 0 for short confirmations to the high-capacity PUCCH 4 with CDM support for multi-user operation.
-
All critical components of the physical layer — scrambling, modulation, transform precoding and spectrum expansion — are implemented in strict accordance with 3GPP TS 38.211 specifications, ensuring full compatibility of the model with the standard.
-
The proposed implementation serves not only as an educational tool, but also as a valid basis for prototyping, testing and verifying real-world PUCCH signal processing algorithms in fifth-generation communication systems.