summaryrefslogtreecommitdiffstats
path: root/dpd/README.md
blob: fc84bad46c691380a8a7e429077089ed8c299c01 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
Digital Predistortion Computation Engine for ODR-DabMod
=======================================================

This folder contains a digital predistortion prototype.
It was only tested in a laboratory system, and is not ready
for production usage.

Concept
-------

ODR-DabMod makes outgoing TX samples and feedback RX samples available to an
external tool. This external tool can request a buffer of samples for analysis,
can calculate coefficients for the predistorter in ODR-DabMod and load the new
coefficients using the remote control.

The external tool is called the Digital Predistortion Computation Engine (DPDCE).
The DPDCE is written in python, and makes use of the numpy library for
efficient computation. Its sources reside in the *dpd* folder.

The predistorter in ODR-DabMod supports two modes: polynomial and lookup table.
In the DPDCE, only the polynomial model is implemented at the moment.

The *dpd/main.py* script is the entry point for the *DPD Computation Engine*
into which these features will be implemented. The tool uses modules from the
*dpd/src/* folder:

- Sample transfer and time alignment with subsample accuracy is done by *Measure.py*
- Estimating the effects of the PA using some model and calculation of the updated
  polynomial coefficients is done in *Model.py* and other specific *Model_XXX.py* files
- Finally, *Adapt.py* updates the ODR-DabMod predistortion setting and digital gain

These modules themselves use additional helper scripts in the *dpd/src/* folder.

Requirements
------------

- USRP B200.
- Power amplifier.
- A feedback connection from the power amplifier output, such that the average power level at
  the USRP RX port is at -45dBm or lower.
  Usually this is done with a directional coupler and additional attenuators.
- ODR-DabMod with enabled *dpd_port*, and with a samplerate of 8192000 samples per second.
- Synchronous=1 so that the USRP has the timestamping set properly, internal refclk and pps
  are sufficient (not GPSDO necessary).
- A live mux source with TIST enabled.

See dpd/dpd.ini for an example.

The DPD server port can be tested with the *dpd/show_spectrum.py* helper tool, which can also display
a constellation diagram.

Hardware Setup
--------------

![setup diagram](img/setup_diagram.svg)
![setup photo](img/setup_photo.svg)

Our setup is depicted in the Figure above. We used components with the following properties:
 1. USRP TX (max +20dBm)
 2. Band III Filter (Mini-Circuits RBP-220W+, 190-250MHz, -3.5dB)
 3. Power amplifier (Mini-Circuits, max +15dBm output, +10 dB gain at 200MHz)
 4. Directional coupler (approx. -25dB @ 223MHz)
 5. Attenuator (-20 dB)
 6. Attenuator (-30 dB)
 7. USRP RX (max -15dBm input allowed, max -45dBm desired)
 8. Spectrum analyzer (max +30dBm allowed)

It is important to make sure that the USRP RX port does not receive too much
power. Otherwise the USRP will break. Here is an example of how we calculated
the maximal USRP RX input power for our case. As this is only a rough
calculation to protect the port, the predistortion software will later
automatically apply a normalization for the RX input by adapting the USRP RX
gain.

    TX Power + PA Gain - Coupling Factor - Attenuation = 20dBm + 10dB -25dB -50dB = -45dBm

Thus we have a margin of about 30dB for the input power of the USRP RX port.
Keep in mind we need to calculate using peak power, not average power, and it is
essential that there is no nonlinearity in the RX path!

Software Setup
--------------

We assume that you already installed *ODR-DabMux* and *ODR-DabMod*.
You should install the required python dependencies for the DPDCE using
distribution packages. You will need at least scipy, matplotlib and
python-zeromq.

Use the predistortion
----------------------

Make sure you have a ODR-DabMux running with a TCP output on port 9200.

Then run the modulator, with the example dpd configuration file.

```
./odr-dabmod dpd/dpd.ini
```

This configuration file is different from usual defaults in several respects:

 * logging to /tmp/dabmod.log
 * 4x oversampling: 8192000 sample rate
 * a very small digital gain, which will be overridden by the DPDCE
 * predistorter enabled
 * UHD output with a rather low TX gain, which will be overridden by DPDCE

The DPDCE uses automatic gain control for both TX and RX gain to get both a
high quantisation quality for the most frequent amplitude regions and a high
enough back-off so the peaks are also quantised correctly. This means that the
output power will stay at the same level, but the DPDCE may change TX gain to
trade it with digital gain and also change RX gain. This also implies that you
should *not modify txgain, rxgain, digital gain or coefficient settings manually!*
When the DPDCE is used, it controls these settings, and there are command line
options for you to define initial values.

To verify that the communication between the DPDCE and ODR-DabMod is ok,
you can use the status and reset options:

```
cd dpd
python main.py --status
python main.py --reset
```

The reset option sets all DPD-related settings to the defaults (as shown in the
`--help` usage screen) and stops.

When neither `--status` nor `--reset` is given, the DPDCE will run the predistortion
algorithm. As a first test you should run the DPDCE with the `--plot`
parameter. It preserves the output power and generates all available
visualisation plots in the newly created logging directory
`/tmp/dpd_<time_stamp>`. As the predistortion should increase the peak to
shoulder ratio, you should select a *txgain* in the ODR-DabMod configuration
file such that the initial peak-to-soulder ratio visible on your spectrum
analyser. This way, you will be able to see a the
change.

```
cd dpd
python main.py --plot
```

The DPDCE now does 10 iterations, and tries to improve the predistortion effectiveness.
In each step the learning rate is decreased. The learning rate is the factor
with which new coefficients are weighted in a weighted mean with the old
coefficients. Moreover the nuber of measurements increases in each iteration.
You find more information about that in *Heuristic.py*.

Each plot is stored to the logging directory under a filename containing its
time stamp and its label. Following plots are generated chronologically:

 - ExtractStatistic: Extracted information from one or multiple measurements.
 - Model\_AM: Fitted function for the amplitudes of the power amplifier against the TX amplitude.
 - Model\_PM: Fitted function for the phase difference of the power amplifier against the TX amplitude.
 - adapt.pkl: Contains all settings for the predistortion.
   You can load them again without doing measurements with the `apply_adapt_dumps.py` script.
 - MER: Constellation diagram used to calculate the modulation error rate.

After the run you should be able to observe that the peak-to-shoulder
difference has increased on your spectrum analyzer, similar to the figure below.

Without digital predistortion:

![shoulder_measurement_before](img/shoulder_measurement_before.png)

With digital predistortion, computed by the DPDCE:

![shoulder_measurement_after](img/shoulder_measurement_after.png)

Now see what happens if you apply the predistortions for different TX gains.
You can either set the TX gain before you start the predistortion or using the
command line option `--txgain gain`. You can also try to adjust other
parameters. To see their documentation run `python main.py --help`.

File format for coefficients
----------------------------
The coef file contains the polynomial coefficients used in the predistorter.
The file format is very similar to the filtertaps file used in the FIR filter.
It is a text-based format that can easily be inspected and edited in a text
editor.

The first line contains an integer that defines the predistorter to be used:
1 for polynomial, 2 for lookup table.

For the polynomial, the subsequent line contains the number of coefficients
as an integer. The second and third lines contain the real, respectively the
imaginary parts of the first coefficient. Fourth and fifth lines give the
second coefficient, and so on. The file therefore contains 1 + 1 + 2xN lines if
it contains N coefficients.

For the lookup table, the subsequent line contains a float scalefactor that is
applied to the samples in order to bring them into the range of 32-bit unsigned
integer. Then, the next pair of lines contains real and imaginary part of the first
lookup-table entry, which is multiplied to samples in first range. Then it's
followed by 31 other pairs. The entries are complex values close to 1 + 0j.
The file therefore contains 1 + 1 + 2xN lines if it contains N coefficients.

TODO
----

 - Understand and fix occasional ODR-DabMod crashes when using DPDCE.
 - Make the predistortion more robust. At the moment the shoulders sometimes
   increase instead of decrease after applying newly calculated predistortion
   parameters. Can this behaviour be predicted from the measurement? This would
   make it possible to filter out bad predistortion settings.
 - Find a better measurement for the quality of the predistortion. The USRP
   might not be good enough to measure large peak-to-shoulder ratios, because
   the ADC has 12 bits and DAB signals have a large crest factor.
 - Implement a Volterra polynomial to model the PA. Compared to the current
   model this would also capture the time dependent behaviour of the PA (memory
   effects).
 - Continuously observe DAB signal in frequency domain and make sure the power
   stays the same. At the moment only the power in the time domain is kept the
   same.
 - At the moment we assume that the USRP RX gain has to be larger than 30dB and
   the received signal should have a median absolute value of 0.05 in order to
   have a high quality quantization. Do measurements to support or improve
   this heuristic.
 - Check if we need to measure MER differently (average over more symbols?)
 - Is -45dBm the best RX feedback power level?

REFERENCES
----------

Some papers:

The paper Raich, Qian, Zhou, "Orthogonal Polynomials for Power Amplifier
Modeling and Predistorter Design" proposes other base polynomials that have
less numerical instability.

Aladrén, Garcia, Carro, de Mingo, and Sanchez-Perez, "Digital Predistortion
Based on Zernike Polynomial Functions for RF Nonlinear Power Amplifiers".

Jiang and Wilford, "Digital predistortion for power amplifiers using separable functions"

Changsoo Eun and Edward J. Powers, "A New Volterra Predistorter Based on the Indirect Learning Architecture"

Raviv Raich, Hua Qian, and G. Tong Zhou, "Orthogonal Polynomials for Power Amplifier Modeling and Predistorter Design"


Models without memory:

Complex polynomial: y[i] = a1 x[i] + a2 x[i]^2 + a3 x[i]^3 + ...

The complex polynomial corresponds to the input/output relationship that
applies to the PA in passband (real-valued signal). According to several
sources, this gets transformed to another representation if we consider complex
baseband instead. In the following, all variables are complex.

Odd-order baseband: y[i] = (b1 + b2 abs(x[i])^2 + b3 abs(x[i])^4) + ...) x[i]

Complete baseband: y[i] = (b1 + b2 abs(x[i]) + b3 abs(x[i])^2) + ...) x[i]

with
    b_k = 2^{1-k} \binom{k}{(k-1)/2} a_k


Models with memory:

 - Hammerstein model: Nonlinearity followed by LTI filter
 - Wiener model: LTI filter followed by NL
 - Parallel Wiener: input goes to N delays, each delay goes to a NL, all NL outputs summed.

Taken from slide 36 of [ECE218C Lecture 15](http://www.ece.ucsb.edu/Faculty/rodwell/Classes/ece218c/notes/Lecture15_Digital%20Predistortion_and_Future%20Challenges.pdf)