aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2017-09-13 18:55:39 +0200
committerMatthias P. Braendli <matthias.braendli@mpb.li>2017-09-13 18:55:39 +0200
commit1ca5368f547c429bf0d86dac78162310e1d2b032 (patch)
tree6c6949b3ecf235ab18a6d21af6d3ad6105190d67 /src
parent4f9372c130960559a0bba13828a810eb57e30123 (diff)
downloaddabmod-1ca5368f547c429bf0d86dac78162310e1d2b032.tar.gz
dabmod-1ca5368f547c429bf0d86dac78162310e1d2b032.tar.bz2
dabmod-1ca5368f547c429bf0d86dac78162310e1d2b032.zip
Add LUT predistorter
Diffstat (limited to 'src')
-rw-r--r--src/MemlessPoly.cpp189
-rw-r--r--src/MemlessPoly.h22
2 files changed, 169 insertions, 42 deletions
diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp
index d7f9a96..f223d34 100644
--- a/src/MemlessPoly.cpp
+++ b/src/MemlessPoly.cpp
@@ -8,7 +8,8 @@
http://opendigitalradio.org
- This block implements a memoryless polynom for digital predistortion.
+ This block implements both a memoryless polynom for digital predistortion,
+ and a lookup table predistorter.
For better performance, multiplying is done in another thread, leading
to a pipeline delay of two calls to MemlessPoly::process
*/
@@ -67,7 +68,7 @@ MemlessPoly::MemlessPoly(const std::string& coefs_file, unsigned int num_threads
if (num_threads == 0) {
const unsigned int hw_concurrency = std::thread::hardware_concurrency();
- etiLog.level(info) << "Polynomial Predistorter will use " <<
+ etiLog.level(info) << "Digital Predistorter will use " <<
hw_concurrency << " threads (auto detected)";
for (size_t i = 0; i < hw_concurrency; i++) {
@@ -80,7 +81,7 @@ MemlessPoly::MemlessPoly(const std::string& coefs_file, unsigned int num_threads
}
}
else {
- etiLog.level(info) << "Polynomial Predistorter will use " <<
+ etiLog.level(info) << "Digital Predistorter will use " <<
num_threads << " threads (set in config file)";
for (size_t i = 0; i < num_threads; i++) {
@@ -100,54 +101,93 @@ MemlessPoly::MemlessPoly(const std::string& coefs_file, unsigned int num_threads
void MemlessPoly::load_coefficients(const std::string &coefFile)
{
- std::vector<float> coefs_am;
- std::vector<float> coefs_pm;
std::ifstream coef_fstream(coefFile.c_str());
if (!coef_fstream) {
throw std::runtime_error("MemlessPoly: Could not open file with coefs!");
}
- int n_coefs;
- coef_fstream >> n_coefs;
- if (n_coefs <= 0) {
- throw std::runtime_error("MemlessPoly: coefs file has invalid format.");
- }
- else if (n_coefs != NUM_COEFS) {
- throw std::runtime_error("MemlessPoly: invalid number of coefs: " +
- std::to_string(n_coefs) + " expected " + std::to_string(NUM_COEFS));
- }
+ uint32_t file_format_indicator;
+ const uint8_t file_format_odd_poly = 1;
+ const uint8_t file_format_lut = 2;
+ coef_fstream >> file_format_indicator;
- const int n_entries = 2 * n_coefs;
+ if (file_format_indicator == file_format_odd_poly) {
+ int n_coefs;
+ coef_fstream >> n_coefs;
+
+ if (n_coefs <= 0) {
+ throw std::runtime_error("MemlessPoly: coefs file has invalid format.");
+ }
+ else if (n_coefs != NUM_COEFS) {
+ throw std::runtime_error("MemlessPoly: invalid number of coefs: " +
+ std::to_string(n_coefs) + " expected " + std::to_string(NUM_COEFS));
+ }
- etiLog.log(debug, "MemlessPoly: Reading %d coefs...", n_entries);
+ const int n_entries = 2 * n_coefs;
- coefs_am.resize(n_coefs);
- coefs_pm.resize(n_coefs);
+ std::vector<float> coefs_am;
+ std::vector<float> coefs_pm;
+ coefs_am.resize(n_coefs);
+ coefs_pm.resize(n_coefs);
- for (int n = 0; n < n_entries; n++) {
- float a;
- coef_fstream >> a;
+ for (int n = 0; n < n_entries; n++) {
+ float a;
+ coef_fstream >> a;
- if (n < n_coefs) {
- coefs_am[n] = a;
- }
- else {
- coefs_pm[n - n_coefs] = a;
+ if (n < n_coefs) {
+ coefs_am[n] = a;
+ }
+ else {
+ coefs_pm[n - n_coefs] = a;
+ }
+
+ if (coef_fstream.eof()) {
+ etiLog.log(error, "MemlessPoly: file %s should contains %d coefs, "
+ "but EOF reached after %d coefs !",
+ coefFile.c_str(), n_entries, n);
+ throw std::runtime_error("MemlessPoly: coefs file invalid !");
+ }
}
- if (coef_fstream.eof()) {
- etiLog.log(error, "MemlessPoly: file %s should contains %d coefs, "
- "but EOF reached after %d coefs !",
- coefFile.c_str(), n_entries, n);
- throw std::runtime_error("MemlessPoly: coefs file invalid !");
+ {
+ std::lock_guard<std::mutex> lock(m_coefs_mutex);
+
+ m_dpd_type = dpd_type_t::odd_only_poly;
+ m_coefs_am = coefs_am;
+ m_coefs_pm = coefs_pm;
+ m_dpd_settings_valid = true;
}
+ etiLog.log(info, "MemlessPoly loaded %zu poly coefs",
+ m_coefs_am.size() + m_coefs_pm.size());
}
+ else if (file_format_indicator == file_format_lut) {
+ float scalefactor;
+ coef_fstream >> scalefactor;
- {
- std::lock_guard<std::mutex> lock(m_coefs_mutex);
+ std::array<complexf, lut_entries> lut;
- m_coefs_am = coefs_am;
- m_coefs_pm = coefs_pm;
+ for (size_t n = 0; n < lut_entries; n++) {
+ float a;
+ coef_fstream >> a;
+
+ lut[n] = a;
+ }
+
+ {
+ std::lock_guard<std::mutex> lock(m_coefs_mutex);
+
+ m_dpd_type = dpd_type_t::lookup_table;
+ m_lut_scalefactor = scalefactor;
+ m_lut = lut;
+ m_dpd_settings_valid = true;
+ }
+
+ etiLog.log(info, "MemlessPoly loaded %zu LUT entries", m_lut.size());
+ }
+ else {
+ etiLog.log(error, "MemlessPoly: coef file has unknown format %d",
+ file_format_indicator);
+ m_dpd_settings_valid = false;
}
}
@@ -195,6 +235,39 @@ static void apply_coeff(
}
}
+static void apply_lut(
+ const complexf *__restrict lut, const float scalefactor,
+ const complexf *__restrict in,
+ size_t start, size_t stop, complexf *__restrict out)
+{
+ for (size_t i = start; i < stop; i++) {
+ const float in_mag = std::abs(in[i]);
+
+ // The scalefactor is chosen so as to map the input magnitude
+ // to the range of uint32_t
+ const uint32_t scaled_in = lrintf(in_mag * scalefactor);
+
+ // lut_ix contains the number of leading 0-bits of the
+ // scaled value, starting at the most significant bit position.
+ //
+ // This partitions the range 0 -- 0xFFFFFFFF into 32 bins.
+ //
+ // 0x00000000 to 0x07FFFFFF go into bin 0
+ // 0x08000000 to 0x0FFFFFFF go into bin 1
+ // 0x10000000 to 0x17FFFFFF go into bin 2
+ // ...
+ // 0xF0000000 to 0xF7FFFFFF go into bin 30
+ // 0xF8000000 to 0xFFFFFFFF go into bin 31
+ //
+ // The high 5 bits are therefore used as index.
+ const uint8_t lut_ix = (scaled_in >> 27);
+
+ // The LUT contains a complex correction factor that is close to
+ // 1 + 0j
+ out[i] = in[i] * lut[lut_ix];
+ }
+}
+
void MemlessPoly::worker_thread(MemlessPoly::worker_t *workerdata)
{
while (true) {
@@ -205,9 +278,18 @@ void MemlessPoly::worker_thread(MemlessPoly::worker_t *workerdata)
break;
}
- apply_coeff(in_data.coefs_am, in_data.coefs_pm,
- in_data.in, in_data.start, in_data.stop,
- in_data.out);
+ switch (in_data.dpd_type) {
+ case dpd_type_t::odd_only_poly:
+ apply_coeff(in_data.coefs_am, in_data.coefs_pm,
+ in_data.in, in_data.start, in_data.stop,
+ in_data.out);
+ break;
+ case dpd_type_t::lookup_table:
+ apply_lut(in_data.lut, in_data.lut_scalefactor,
+ in_data.in, in_data.start, in_data.stop,
+ in_data.out);
+ break;
+ }
workerdata->out_queue.push(1);
}
@@ -221,6 +303,7 @@ int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut)
complexf* out = reinterpret_cast<complexf*>(dataOut->getData());
size_t sizeOut = dataOut->getLength() / sizeof(complexf);
+ if (m_dpd_settings_valid)
{
std::lock_guard<std::mutex> lock(m_coefs_mutex);
const size_t num_threads = m_workers.size();
@@ -232,6 +315,9 @@ int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut)
for (auto& worker : m_workers) {
worker_t::input_data_t dat;
dat.terminate = false;
+ dat.dpd_type = m_dpd_type;
+ dat.lut_scalefactor = m_lut_scalefactor;
+ dat.lut = m_lut.data();
dat.coefs_am = m_coefs_am.data();
dat.coefs_pm = m_coefs_pm.data();
dat.in = in;
@@ -245,8 +331,16 @@ int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut)
}
// Do the last in this thread
- apply_coeff(m_coefs_am.data(), m_coefs_pm.data(),
- in, start, sizeOut, out);
+ switch (m_dpd_type) {
+ case dpd_type_t::odd_only_poly:
+ apply_coeff(m_coefs_am.data(), m_coefs_pm.data(),
+ in, start, sizeOut, out);
+ break;
+ case dpd_type_t::lookup_table:
+ apply_lut(m_lut.data(), m_lut_scalefactor,
+ in, start, sizeOut, out);
+ break;
+ }
// Wait for completion of the tasks
for (auto& worker : m_workers) {
@@ -255,10 +349,21 @@ int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut)
}
}
else {
- apply_coeff(m_coefs_am.data(), m_coefs_pm.data(),
- in, 0, sizeOut, out);
+ switch (m_dpd_type) {
+ case dpd_type_t::odd_only_poly:
+ apply_coeff(m_coefs_am.data(), m_coefs_pm.data(),
+ in, 0, sizeOut, out);
+ break;
+ case dpd_type_t::lookup_table:
+ apply_lut(m_lut.data(), m_lut_scalefactor,
+ in, 0, sizeOut, out);
+ break;
+ }
}
}
+ else {
+ memcpy(dataOut->getData(), dataIn->getData(), sizeOut);
+ }
return dataOut->getLength();
}
diff --git a/src/MemlessPoly.h b/src/MemlessPoly.h
index 57c0924..612934f 100644
--- a/src/MemlessPoly.h
+++ b/src/MemlessPoly.h
@@ -49,6 +49,12 @@
typedef std::complex<float> complexf;
+enum class dpd_type_t {
+ odd_only_poly,
+ lookup_table
+};
+
+
class MemlessPoly : public PipelinedModCodec, public RemoteControllable
{
public:
@@ -71,8 +77,16 @@ private:
struct input_data_t {
bool terminate = false;
+ dpd_type_t dpd_type;
+
+ // Valid for polynomial types
const float *coefs_am = nullptr;
const float *coefs_pm = nullptr;
+
+ // Valid for LUT
+ float lut_scalefactor = 0.0f;
+ const complexf *lut = nullptr;
+
const complexf *in = nullptr;
size_t start = 0;
size_t stop = 0;
@@ -112,8 +126,16 @@ private:
static void worker_thread(worker_t *workerdata);
+ bool m_dpd_settings_valid = false;
+ dpd_type_t m_dpd_type;
std::vector<float> m_coefs_am; // AM/AM coefficients
std::vector<float> m_coefs_pm; // AM/PM coefficients
+
+ float m_lut_scalefactor; // Scale value applied before looking up in LUT
+
+ static constexpr size_t lut_entries = 32;
+ std::array<complexf, lut_entries> m_lut; // Lookup table correction factors
+
std::string m_coefs_file;
mutable std::mutex m_coefs_mutex;
};