From 70488f826261e81052a4c19dff95c41d2160b29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Mon, 24 Oct 2022 00:08:10 +0200 Subject: [PATCH] eye diagram thread rework + uninitialized variables fix --- .../LibreVNA-GUI/Traces/eyediagramplot.cpp | 263 +++++++++++++++++- .../LibreVNA-GUI/Traces/eyediagramplot.h | 21 +- .../Traces/sparamtraceselector.cpp | 1 + .../LibreVNA-GUI/Traces/trace.cpp | 4 + .../LibreVNA-GUI/Traces/tracecsvexport.cpp | 1 + .../Traces/tracetouchstoneexport.cpp | 4 + .../LibreVNA-GUI/Traces/tracewidget.cpp | 1 + .../VNA/Deembedding/deembedding.cpp | 5 +- .../VNA/Deembedding/portextension.cpp | 1 + .../LibreVNA-GUI/VNA/Deembedding/twothru.cpp | 2 + 10 files changed, 289 insertions(+), 14 deletions(-) diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp index 942a430..f7f7eb7 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.cpp @@ -51,6 +51,10 @@ EyeDiagramPlot::EyeDiagramPlot(TraceModel &model, QWidget *parent) yAxis.set(YAxis::Type::Real, false, true, -1, 1, 1); initializeTraceInfo(); + destructing = false; + thread = new EyeThread(*this); + thread->start(EyeThread::Priority::LowestPriority); + connect(tdr, &Math::TDR::outputSamplesChanged, this, &EyeDiagramPlot::triggerUpdate); replot(); @@ -58,9 +62,11 @@ EyeDiagramPlot::EyeDiagramPlot(TraceModel &model, QWidget *parent) EyeDiagramPlot::~EyeDiagramPlot() { - while(updating) { - std::this_thread::sleep_for(20ms); - } + // tell thread to exit + destructing = true; + semphr.release(); + thread->wait(); + delete thread; delete tdr; } @@ -859,11 +865,11 @@ void EyeDiagramPlot::updateThread(unsigned int xSamples) double transitionTime = -10; // assume that we start with a settled input, last transition was "long" ago for(unsigned int i=0;i= transitionTime) { // currently within a bit transition double edgeTime = 0; - double expTimeConstant; + double expTimeConstant = 0.0; if(currentSignal < nextSignal) { edgeTime = risetime; } else if(currentSignal > nextSignal) { @@ -952,13 +958,8 @@ void EyeDiagramPlot::updateThread(unsigned int xSamples) void EyeDiagramPlot::triggerUpdate() { - if(updating) { - // already updating, can't start again, schedule for later - updateScheduled = true; - } else { - updating = true; - new std::thread(&EyeDiagramPlot::updateThread, this, xSamples); - } + // trigger the thread + semphr.release(); } void EyeDiagramPlot::setStatus(QString s) @@ -983,3 +984,241 @@ double EyeDiagramPlot::maxDisplayVoltage() auto eyeRange = highlevel - lowlevel; return highlevel + eyeRange * yOverrange; } + +void EyeThread::run() +{ + while(1) { + eye.semphr.acquire(); + std::lock_guard calc(eye.calcMutex); + // clear possible additional semaphores + eye.semphr.tryAcquire(eye.semphr.available()); + if(eye.destructing) { + // Eye diagram object about to be deleted, exit thread + qDebug() << "Eye thread exiting"; + return; + } + eye.setStatus("Starting calculation..."); + if(!eye.trace) { + eye.setStatus("No trace assigned"); + continue; + } + + qDebug() << "Starting eye diagram calculation"; + + // sanity check values + if(eye.datarate >= eye.trace->getSample(eye.trace->numSamples() - 1).x) { + eye.setStatus("Data rate too high"); + continue; + } + if(eye.datarate <= 0) { + eye.setStatus("Data rate too low"); + continue; + } + if(eye.risetime > 0.3 * 1.0 / eye.datarate) { + eye.setStatus("Rise time too high"); + continue; + } + if(eye.falltime > 0.3 * 1.0 / eye.datarate) { + eye.setStatus("Fall time too high"); + continue; + } + if(eye.jitter > 0.3 * 1.0 / eye.datarate) { + eye.setStatus("Jitter too high"); + continue; + } + + qDebug() << "Eye calculation: input values okay"; + + // calculate timestep + double timestep = eye.calculatedTime() / eye.xSamples; + // reserve vector for input data + std::vector> inVec(eye.xSamples * (eye.cycles + 1), 0.0); // needs to calculate one more cycle than required for the display (settling) + + // resize working buffer + qDebug() << "Clearing old eye data, calcData:" << eye.calcData; + eye.calcData->clear(); + eye.calcData->resize(eye.xSamples); + for(auto& s : *eye.calcData) { + s.y.resize(eye.cycles, 0.0); + } + + eye.setStatus("Extracting impulse response..."); + + // calculate impulse response of trace + double eyeTimeShift = 0; + std::vector> impulseVec; + // determine how long the impulse response is + auto samples = eye.tdr->numSamples(); + if(samples == 0) { + // TDR calculation not yet done, unable to update + eye.updating = false; + eye.setStatus("No time-domain data from trace"); + continue; + } + auto length = eye.tdr->getSample(samples - 1).x; + + // determine average delay + auto total_step = eye.tdr->getStepResponse(samples - 1); + for(unsigned int i=0;igetStepResponse(i); + if(abs(total_step - step) <= abs(step)) { + // mid point reached + eyeTimeShift = eye.tdr->getSample(i).x; + break; + } + } + + unsigned long convolutedSize = length / timestep; + if(convolutedSize > inVec.size()) { + // impulse response is longer than what we display, truncate + convolutedSize = inVec.size(); + } + impulseVec.resize(convolutedSize); + /* + * we can't use the impulse response directly because we most likely need samples inbetween + * the calculated values. Interpolation is available but if our sample spacing here is much + * wider than the impulse response data, we might miss peaks (or severely miscalculate their + * amplitude. + * Instead, the step response is interpolated and the impulse response determined by deriving + * it from the interpolated step response data. As the step response is the integrated imulse + * response data, we can't miss narrow peaks that way. + */ + double lastStepResponse = 0.0; + for(unsigned long i=0;igetInterpolatedStepResponse(x); + impulseVec[i] = step - lastStepResponse; + lastStepResponse = step; + } + + eyeTimeShift += (eye.risetime + eye.falltime) * 1.25 / 4; + eyeTimeShift += 0.5 / eye.datarate; + int eyeXshift = eyeTimeShift / timestep; + + qDebug() << "Eye calculation: TDR calculation done"; + + eye.setStatus("Generating PRBS sequence..."); + + auto prbs = PRBS(eye.patternbits); + + auto getNextLevel = [&]() -> unsigned int { + unsigned int level = 0; + for(unsigned int i=0;i double { + unsigned int maxLevel = (0x01 << eye.bitsPerSymbol) - 1; + return Util::Scale((double) level, 0.0, (double) maxLevel, eye.lowlevel, eye.highlevel); + }; + + unsigned int currentSignal = getNextLevel(); + unsigned int nextSignal = getNextLevel(); + + // initialize random generator + std::random_device rd1; + std::mt19937 mt_noise(rd1()); + std::normal_distribution<> dist_noise(0, eye.noise); + + std::random_device rd2; + std::mt19937 mt_jitter(rd2()); + std::normal_distribution<> dist_jitter(0, eye.jitter); + + unsigned int bitcnt = 1; + double transitionTime = -10; // assume that we start with a settled input, last transition was "long" ago + for(unsigned int i=0;i= transitionTime) { + // currently within a bit transition + double edgeTime = 0; + double expTimeConstant; + if(currentSignal < nextSignal) { + edgeTime = eye.risetime; + } else if(currentSignal > nextSignal) { + edgeTime = eye.falltime; + } + if(eye.linearEdge) { + // edge is modeled as linear rise/fall + // increase slightly to account for typical 10/90% fall/rise time + edgeTime *= 1.25; + } else { + // edge is modeled as exponential rise/fall. Adjust time constant to match + // selected rise/fall time (with 10-90% signal rise/fall within specified time) + expTimeConstant = edgeTime / 2.197224577; + edgeTime = 6 * expTimeConstant; // after six time constants, 99.7% of signal movement has happened + } + if(time >= transitionTime + edgeTime) { + // bit transition settled + voltage = levelToVoltage(nextSignal); + // move on to the next bit + currentSignal = nextSignal; + nextSignal = getNextLevel(); + transitionTime = bitcnt * 1.0 / eye.datarate + dist_jitter(mt_jitter); + bitcnt++; + } else { + // still within rise or fall time + double timeSinceEdge = time - transitionTime; + double from = levelToVoltage(currentSignal); + double to = levelToVoltage(nextSignal); + if(eye.linearEdge) { + double edgeRatio = timeSinceEdge / edgeTime; + voltage = from * (1.0 - edgeRatio) + to * edgeRatio; + } else { + voltage = from + (1.0 - exp(-timeSinceEdge/expTimeConstant)) * (to - from); + } + } + } else { + // still before the next edge + voltage = levelToVoltage(currentSignal); + } + voltage += dist_noise(mt_noise); + inVec[i] = voltage; + } + + // input voltage vector fully assembled + qDebug() << "Eye calculation: input data generated"; + + eye.setStatus("Performing convolution..."); + + qDebug() << "Convolve via FFT start"; + std::vector> outVec; + impulseVec.resize(inVec.size(), 0.0); + outVec.resize(inVec.size()); + Fft::convolve(inVec, impulseVec, outVec); + qDebug() << "Convolve via FFT stop"; + + // fill data from outVec + for(unsigned int i=0;i guard(eye.bufferSwitchMutex); + // switch buffers + qDebug() << "Switching diplay buffers, calcData:" << eye.calcData; + auto buf = eye.displayData; + eye.displayData = eye.calcData; + eye.calcData = buf; + if((*eye.displayData)[0].y[0] == 0.0 && (*eye.displayData)[0].y[1] == 0.0) { + qDebug() << "detected null after eye calculation"; + } + qDebug() << "Buffer switch complete, displayData:" << eye.displayData; + } + + eye.setStatus("Eye calculation complete"); + eye.replot(); + } +} diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h index d6c0be6..b02f703 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h +++ b/Software/PC_Application/LibreVNA-GUI/Traces/eyediagramplot.h @@ -6,12 +6,27 @@ #include "Traces/Math/tdr.h" #include +#include +#include #include +class EyeDiagramPlot; + +class EyeThread : public QThread +{ + Q_OBJECT +public: + EyeThread(EyeDiagramPlot &eye) : eye(eye) {} + ~EyeThread(){} +private: + void run() override; + EyeDiagramPlot &eye; +}; + class EyeDiagramPlot : public TracePlot { -// friend class WaterfallAxisDialog; + friend class EyeThread; Q_OBJECT public: EyeDiagramPlot(TraceModel &model, QWidget *parent = 0); @@ -99,6 +114,10 @@ private: std::mutex bufferSwitchMutex; std::mutex calcMutex; + + EyeThread *thread; + bool destructing; + QSemaphore semphr; }; #endif // EYEDIAGRAMPLOT_H diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/sparamtraceselector.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/sparamtraceselector.cpp index b8c4f9e..5dbf75d 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/sparamtraceselector.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/sparamtraceselector.cpp @@ -67,6 +67,7 @@ void SparamTraceSelector::setInitialChoices() } boxes[i]->blockSignals(false); } + minFreq = maxFreq = 0; points = 0; valid = empty_allowed; } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp index 6f96bcb..b9fb90e 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/trace.cpp @@ -26,6 +26,9 @@ Trace::Trace(QString name, QColor color, QString live) JSONskipHash(false), _liveType(LivedataType::Overwrite), liveParam(live), + fileParameter(0), + mathUpdateBegin(0), + mathUpdateEnd(0), vFactor(0.66), reflection(true), visible(true), @@ -34,6 +37,7 @@ Trace::Trace(QString name, QColor color, QString live) domain(DataType::Frequency), lastMath(nullptr) { + settings.valid = false; MathInfo self = {.math = this, .enabled = true}; mathOps.push_back(self); updateLastMath(mathOps.rbegin()); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp index d58b31c..bb808c6 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracecsvexport.cpp @@ -127,6 +127,7 @@ TraceCSVModel::TraceCSVModel(std::vector traces, QObject *parent) save.resize(traces.size(), false); enabled.resize(traces.size(), true); points = 0; + minX = maxX = 0; updateEnabledTraces(); } diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.cpp index 9f0c44c..55f40e1 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracetouchstoneexport.cpp @@ -12,6 +12,10 @@ TraceTouchstoneExport::TraceTouchstoneExport(TraceModel &model, QWidget *parent) ui(new Ui::TraceTouchstoneExport), model(model), ports(0), + points(0), + lowerFreq(0), + upperFreq(0), + referenceImpedance(50.0), freqsSet(false) { ui->setupUi(this); diff --git a/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp b/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp index eb3f47d..974ef69 100644 --- a/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Traces/tracewidget.cpp @@ -20,6 +20,7 @@ TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) : QWidget(parent), SCPINode("TRACe"), + dragTrace(nullptr), ui(new Ui::TraceWidget), model(model) { diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp index 1f64b1c..12c7ffe 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp @@ -70,8 +70,11 @@ void Deembedding::startMeasurementDialog(DeembeddingOption *option) } Deembedding::Deembedding(TraceModel &tm) - : tm(tm), + : measuringOption(nullptr), + tm(tm), measuring(false), + measurementDialog(nullptr), + measurementUI(nullptr), sweepPoints(0) { diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/portextension.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/portextension.cpp index 9c119f3..29d0345 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/portextension.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/portextension.cpp @@ -21,6 +21,7 @@ PortExtension::PortExtension() port = 1; isIdeal = true; + isOpen = true; kit = nullptr; ui = nullptr; diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/twothru.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/twothru.cpp index 5c5fdaf..68be693 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/twothru.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/twothru.cpp @@ -15,6 +15,8 @@ TwoThru::TwoThru() Z0 = 50.0; port1 = 1; port2 = 2; + measuring2xthru = false; + measuringDUT = false; } std::set TwoThru::getAffectedPorts()