#include "eyediagramplot.h" #include "ui_eyediagrameditdialog.h" #include "unit.h" #include "Util/prbs.h" #include "Util/util.h" #include "fftcomplex.h" #include "preferences.h" #include "appwindow.h" #include #include #include #include #include #include using namespace std::chrono_literals; EyeDiagramPlot::EyeDiagramPlot(TraceModel &model, QWidget *parent) : TracePlot(model, parent), trace(nullptr), updating(false), updateScheduled(false), xSamples(200), datarate(100000000), highlevel(1.0), lowlevel(0.0), bitsPerSymbol(1), risetime(0.000000001), falltime(0.000000001), noise(0.01), jitter(0.0000000001), linearEdge(true), patternbits(9), cycles(200) { plotAreaTop = 0; plotAreaLeft = 0; plotAreaWidth = 0; plotAreaBottom = 0; tdr = new Math::TDR; calcData = &data[0]; displayData = &data[1]; xAxis.set(XAxis::Type::Time, false, true, 0, 0.000001, 1); yAxis.set(YAxis::Type::Real, false, true, -1, 1, 1); initializeTraceInfo(); connect(tdr, &Math::TDR::outputSamplesChanged, this, &EyeDiagramPlot::triggerUpdate); replot(); } EyeDiagramPlot::~EyeDiagramPlot() { while(updating) { std::this_thread::sleep_for(20ms); } delete tdr; } void EyeDiagramPlot::enableTrace(Trace *t, bool enabled) { if(enabled) { // only one trace at a time is allowed, disable all others for(auto t : traces) { if(t.second) { enableTrace(t.first, false); break; } } } TracePlot::enableTrace(t, enabled); if(enabled) { trace = t; tdr->assignInput(trace); } else { if(trace) { tdr->removeInput(); while(updating) { std::this_thread::sleep_for(20ms); } displayData->clear(); calcData->clear(); } trace = nullptr; } } void EyeDiagramPlot::replot() { if(xAxis.getAutorange()) { xAxis.set(xAxis.getType(), false, true, 0, calculatedTime(), 8); } if(yAxis.getAutorange()) { yAxis.set(yAxis.getType(), false, true, minDisplayVoltage(), maxDisplayVoltage(), 8); } TracePlot::replot(); } void EyeDiagramPlot::move(const QPoint &vect) { if(!xAxis.getLog()) { // can only move axis in linear mode // calculate amount of movement double distance = xAxis.inverseTransform(vect.x(), 0, plotAreaWidth) - xAxis.getRangeMin(); xAxis.set(xAxis.getType(), false, false, xAxis.getRangeMin() - distance, xAxis.getRangeMax() - distance, xAxis.getRangeDiv()); } if(!yAxis.getLog()) { // can only move axis in linear mode // calculate amount of movement double distance = yAxis.inverseTransform(vect.y(), 0, plotAreaTop - plotAreaBottom) - yAxis.getRangeMin(); yAxis.set(yAxis.getType(), false, false, yAxis.getRangeMin() - distance, yAxis.getRangeMax() - distance, yAxis.getRangeDiv()); } replot(); } void EyeDiagramPlot::zoom(const QPoint ¢er, double factor, bool horizontally, bool vertically) { if(horizontally && !xAxis.getLog()) { // can only zoom axis in linear mode // calculate center point double cp = xAxis.inverseTransform(center.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth); double min = ((xAxis.getRangeMin() - cp) * factor) + cp; double max = ((xAxis.getRangeMax() - cp) * factor) + cp; xAxis.set(xAxis.getType(), false, false, min, max, xAxis.getRangeDiv() * factor); } if(vertically) { // can only move axis in linear mode // calculate center point double cp = yAxis.inverseTransform(center.y(), plotAreaBottom, plotAreaTop); double min = ((yAxis.getRangeMin() - cp) * factor) + cp; double max = ((yAxis.getRangeMax() - cp) * factor) + cp; yAxis.set(yAxis.getType(), false, false, min, max, yAxis.getRangeDiv() * factor); } replot(); } void EyeDiagramPlot::setAuto(bool horizontally, bool vertically) { if(horizontally) { xAxis.set(xAxis.getType(), xAxis.getLog(), true, xAxis.getRangeMin(), xAxis.getRangeMax(), xAxis.getRangeDiv()); } if(vertically) { yAxis.set(yAxis.getType(), yAxis.getLog(), true, yAxis.getRangeMin(), yAxis.getRangeMax(), yAxis.getRangeDiv()); } replot(); } void EyeDiagramPlot::fromJSON(nlohmann::json j) { auto jX = j["XAxis"]; bool xAuto = jX.value("autorange", xAxis.getAutorange()); double xMin = jX.value("min", xAxis.getRangeMin()); double xMax = jX.value("max", xAxis.getRangeMax()); double xDivs = jX.value("div", xAxis.getRangeDiv()); xAxis.set(xAxis.getType(), false, xAuto, xMin, xMax, xDivs); auto jY = j["YAxis"]; bool yAuto = jY.value("autorange", yAxis.getAutorange()); double yMin = jY.value("min", yAxis.getRangeMin()); double yMax = jY.value("max", yAxis.getRangeMax()); double yDivs = jY.value("div", yAxis.getRangeDiv()); yAxis.set(yAxis.getType(), false, yAuto, yMin, yMax, yDivs); datarate = j.value("datarate", datarate); risetime = j.value("risetime", risetime); falltime = j.value("falltime", falltime); linearEdge = j.value("linearEdge", linearEdge); highlevel = j.value("highlevel", highlevel); lowlevel = j.value("lowlevel", lowlevel); bitsPerSymbol = j.value("bitPerSymbol", bitsPerSymbol); noise = j.value("noise", noise); jitter = j.value("jitter", jitter); patternbits = j.value("patternBits", patternbits); cycles = j.value("cycles", cycles); xSamples = j.value("xSamples", xSamples); for(unsigned int hash : j["traces"]) { // attempt to find the traces with this hash bool found = false; for(auto t : model.getTraces()) { if(t->toHash() == hash) { enableTrace(t, true); found = true; break; } } if(!found) { qWarning() << "Unable to find trace with hash" << hash; } } } nlohmann::json EyeDiagramPlot::toJSON() { nlohmann::json j; nlohmann::json jX; jX["autorange"] = yAxis.getAutorange(); jX["min"] = xAxis.getRangeMin(); jX["max"] = xAxis.getRangeMax(); jX["div"] = xAxis.getRangeDiv(); j["XAxis"] = jX; nlohmann::json jY; jY["autorange"] = yAxis.getAutorange(); jY["min"] = yAxis.getRangeMin(); jY["max"] = yAxis.getRangeMax(); jY["div"] = yAxis.getRangeDiv(); j["YAxis"] = jY; nlohmann::json jtraces; for(auto t : traces) { if(t.second) { jtraces.push_back(t.first->toHash()); } } j["traces"] = jtraces; j["datarate"] = datarate; j["risetime"] = risetime; j["falltime"] = falltime; j["linearEdge"] = linearEdge; j["highlevel"] = highlevel; j["lowlevel"] = lowlevel; j["bitPerSymbol"] = bitsPerSymbol; j["noise"] = noise; j["jitter"] = jitter; j["patternBits"] = patternbits; j["cycles"] = cycles; j["xSamples"] = xSamples; return j; } void EyeDiagramPlot::axisSetupDialog() { auto d = new QDialog(this); d->setAttribute(Qt::WA_DeleteOnClose); auto ui = new Ui::EyeDiagramEditDialog; ui->setupUi(d); ui->datarate->setUnit("bps"); ui->datarate->setPrefixes(" kMG"); ui->datarate->setPrecision(3); ui->risetime->setUnit("s"); ui->risetime->setPrefixes("pnum "); ui->risetime->setPrecision(3); ui->falltime->setUnit("s"); ui->falltime->setPrefixes("pnum "); ui->falltime->setPrecision(3); ui->highLevel->setUnit("V"); ui->highLevel->setPrefixes("m "); ui->highLevel->setPrecision(3); ui->lowLevel->setUnit("V"); ui->lowLevel->setPrefixes("m "); ui->lowLevel->setPrecision(3); ui->noise->setUnit("V"); ui->noise->setPrefixes("um "); ui->noise->setPrecision(3); ui->jitter->setUnit("s"); ui->jitter->setPrefixes("pnum "); ui->jitter->setPrecision(3); ui->Xmin->setUnit("s"); ui->Xmin->setPrefixes("pnum "); ui->Xmin->setPrecision(5); ui->Xmax->setUnit("s"); ui->Xmax->setPrefixes("pnum "); ui->Xmax->setPrecision(5); ui->Xdivs->setUnit("s"); ui->Xdivs->setPrefixes("pnum "); ui->Xdivs->setPrecision(3); ui->Ymin->setUnit("V"); ui->Ymin->setPrefixes("um "); ui->Ymin->setPrecision(4); ui->Ymax->setUnit("V"); ui->Ymax->setPrefixes("um "); ui->Ymax->setPrecision(4); ui->Ydivs->setUnit("V"); ui->Ydivs->setPrefixes("um "); ui->Ydivs->setPrecision(3); // set initial values ui->datarate->setValue(datarate); ui->risetime->setValue(risetime); ui->falltime->setValue(falltime); ui->highLevel->setValue(highlevel); ui->lowLevel->setValue(lowlevel); ui->noise->setValue(noise); ui->jitter->setValue(jitter); ui->signalLevels->setCurrentIndex(bitsPerSymbol - 1); ui->patternLength->setCurrentIndex(patternbits - 2); ui->fallrisetype->setCurrentIndex(linearEdge ? 0 : 1); ui->displayedCycles->setValue(cycles); ui->pointsPerCycle->setValue(xSamples); connect(ui->Xauto, &QCheckBox::toggled, [=](bool checked) { ui->Xmin->setEnabled(!checked); ui->Xmax->setEnabled(!checked); ui->Xdivs->setEnabled(!checked); }); ui->Xauto->setChecked(xAxis.getAutorange()); ui->Xmin->setValue(xAxis.getRangeMin()); ui->Xmax->setValue(xAxis.getRangeMax()); ui->Xdivs->setValue(xAxis.getRangeDiv()); connect(ui->Yauto, &QCheckBox::toggled, [=](bool checked) { ui->Ymin->setEnabled(!checked); ui->Ymax->setEnabled(!checked); ui->Ydivs->setEnabled(!checked); }); ui->Yauto->setChecked(yAxis.getAutorange()); ui->Ymin->setValue(yAxis.getRangeMin()); ui->Ymax->setValue(yAxis.getRangeMax()); ui->Ydivs->setValue(yAxis.getRangeDiv()); auto updateValues = [=](){ std::lock_guard guard(calcMutex); datarate = ui->datarate->value(); risetime = ui->risetime->value(); falltime = ui->falltime->value(); highlevel = ui->highLevel->value(); lowlevel = ui->lowLevel->value(); noise = ui->noise->value(); jitter = ui->jitter->value(); bitsPerSymbol = ui->signalLevels->currentIndex() + 1; patternbits = ui->patternLength->currentIndex() + 2; linearEdge = ui->fallrisetype->currentIndex() == 0; cycles = ui->displayedCycles->value(); xSamples = ui->pointsPerCycle->value(); xAxis.set(xAxis.getType(), false, ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value()); yAxis.set(yAxis.getType(), false, ui->Yauto->isChecked(), ui->Ymin->value(), ui->Ymax->value(), ui->Ydivs->value()); }; connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [=](){ updateValues(); }); connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, [=](){ updateValues(); }); if(AppWindow::showGUI()) { d->show(); } } void EyeDiagramPlot::updateContextMenu() { contextmenu->clear(); auto setup = new QAction("Setup...", contextmenu); connect(setup, &QAction::triggered, this, &EyeDiagramPlot::axisSetupDialog); contextmenu->addAction(setup); contextmenu->addSeparator(); auto image = new QAction("Save image...", contextmenu); contextmenu->addAction(image); connect(image, &QAction::triggered, [=]() { auto filename = QFileDialog::getSaveFileName(nullptr, "Save plot image", "", "PNG image files (*.png)", nullptr, QFileDialog::DontUseNativeDialog); if(filename.isEmpty()) { // aborted selection return; } if(filename.endsWith(".png")) { filename.chop(4); } filename += ".png"; grab().save(filename); }); contextmenu->addSection("Traces"); // Populate context menu for(auto t : orderedTraces()) { if(!supported(t)) { continue; } auto action = new QAction(t->name(), contextmenu); action->setCheckable(true); if(traces[t]) { action->setChecked(true); } connect(action, &QAction::toggled, [=](bool active) { enableTrace(t, active); }); contextmenu->addAction(action); } finishContextMenu(); } bool EyeDiagramPlot::positionWithinGraphArea(const QPoint &p) { return p.x() >= plotAreaLeft && p.x() <= plotAreaLeft + plotAreaWidth && p.y() >= plotAreaTop && p.y() <= plotAreaBottom; } void EyeDiagramPlot::draw(QPainter &p) { auto &pref = Preferences::getInstance(); auto w = p.window(); auto yAxisSpace = pref.Graphs.fontSizeAxis * 5.5; auto xAxisSpace = pref.Graphs.fontSizeAxis * 3; plotAreaLeft = yAxisSpace; plotAreaWidth = w.width() - plotAreaLeft - 10; plotAreaTop = 10; plotAreaBottom = w.height() - xAxisSpace; p.setBackground(QBrush(pref.Graphs.Color.background)); p.fillRect(0, 0, width(), height(), QBrush(pref.Graphs.Color.background)); auto pen = QPen(pref.Graphs.Color.axis, 0); pen.setCosmetic(true); p.setPen(pen); auto plotRect = QRect(plotAreaLeft, plotAreaTop, plotAreaWidth + 1, plotAreaBottom - plotAreaTop + 1); p.drawRect(plotRect); // Y axis QString labelY = "Voltage"; auto font = p.font(); font.setPixelSize(pref.Graphs.fontSizeAxis); p.setFont(font); p.setPen(QPen(pref.Graphs.Color.axis, 1)); p.save(); p.translate(0, w.height()-xAxisSpace); p.rotate(-90); p.drawText(QRect(0, 0, w.height()-xAxisSpace, pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, labelY); p.restore(); // draw ticks if(yAxis.getTicks().size() > 0) { // this only works for evenly distributed ticks: auto max = qMax(abs(yAxis.getTicks().front()), abs(yAxis.getTicks().back())); double step; if(yAxis.getTicks().size() >= 2) { step = abs(yAxis.getTicks()[0] - yAxis.getTicks()[1]); } else { // only one tick, set arbitrary number of digits step = max / 1000; } int significantDigits = floor(log10(max)) - floor(log10(step)) + 1; for(unsigned int j = 0; j < yAxis.getTicks().size(); j++) { auto yCoord = yAxis.transform(yAxis.getTicks()[j], plotAreaBottom, plotAreaTop); p.setPen(QPen(pref.Graphs.Color.axis, 1)); // draw tickmark on axis auto tickStart = plotAreaLeft; auto tickLen = -2; p.drawLine(tickStart, yCoord, tickStart + tickLen, yCoord); QString unit = ""; QString prefix = " "; if(pref.Graphs.showUnits) { unit = "V"; prefix = "um "; } auto tickValue = Unit::ToString(yAxis.getTicks()[j], unit, prefix, significantDigits); p.drawText(QRectF(0, yCoord - pref.Graphs.fontSizeAxis/2 - 2, tickStart + 2 * tickLen, pref.Graphs.fontSizeAxis), Qt::AlignRight, tickValue); // tick lines if(yCoord == plotAreaTop || yCoord == plotAreaBottom) { // skip tick lines right on the plot borders continue; } // only draw tick lines for primary axis if (pref.Graphs.Color.Ticks.Background.enabled) { if (j%2) { int yCoordTop = yAxis.transform(yAxis.getTicks()[j], plotAreaTop, plotAreaBottom); int yCoordBot = yAxis.transform(yAxis.getTicks()[j-1], plotAreaTop, plotAreaBottom); if(yCoordTop > yCoordBot) { auto buf = yCoordBot; yCoordBot = yCoordTop; yCoordTop = buf; } p.setBrush(pref.Graphs.Color.Ticks.Background.background); p.setPen(pref.Graphs.Color.Ticks.Background.background); auto rect = QRect(plotAreaLeft+1, yCoordTop+1, plotAreaWidth-2, yCoordBot-yCoordTop-2); p.drawRect(rect); } } p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine)); p.drawLine(plotAreaLeft, yCoord, plotAreaLeft + plotAreaWidth, yCoord); } } // X axis name p.setPen(QPen(pref.Graphs.Color.axis, 1)); p.drawText(QRect(plotAreaLeft, w.height()-pref.Graphs.fontSizeAxis*1.5, plotAreaWidth, pref.Graphs.fontSizeAxis*1.5), Qt::AlignHCenter, "Time"); // draw X axis ticks if(xAxis.getTicks().size() >= 1) { // draw X ticks int significantDigits; // this only works for evenly distributed ticks: auto max = qMax(abs(xAxis.getTicks().front()), abs(xAxis.getTicks().back())); double step; if(xAxis.getTicks().size() >= 2) { step = abs(xAxis.getTicks()[0] - xAxis.getTicks()[1]); } else { // only one tick, set arbitrary number of digits step = max / 1000; } significantDigits = floor(log10(max)) - floor(log10(step)) + 1; QString prefixes = "fpnum kMG"; QString unit = ""; if(pref.Graphs.showUnits) { unit = xAxis.Unit(); } int lastTickLabelEnd = 0; for(auto t : xAxis.getTicks()) { auto xCoord = xAxis.transform(t, plotAreaLeft, plotAreaLeft + plotAreaWidth); p.setPen(QPen(pref.Graphs.Color.axis, 1)); p.drawLine(xCoord, plotAreaBottom, xCoord, plotAreaBottom + 2); if(xCoord != plotAreaLeft && xCoord != plotAreaLeft + plotAreaWidth) { p.setPen(QPen(pref.Graphs.Color.Ticks.divisions, 0.5, Qt::DashLine)); p.drawLine(xCoord, plotAreaTop, xCoord, plotAreaBottom); } if(xCoord - 40 <= lastTickLabelEnd) { // would overlap previous tick label, skip continue; } auto tickValue = Unit::ToString(t, unit, prefixes, significantDigits); p.setPen(QPen(pref.Graphs.Color.axis, 1)); QRect bounding; p.drawText(QRect(xCoord - pref.Graphs.fontSizeAxis*2, plotAreaBottom + 5, pref.Graphs.fontSizeAxis*4, pref.Graphs.fontSizeAxis), Qt::AlignHCenter, tickValue, &bounding); lastTickLabelEnd = bounding.x() + bounding.width(); } } if(displayData->size() >= 2) { std::lock_guard guard(bufferSwitchMutex); unsigned int pxWidth = plotAreaWidth; unsigned int pxHeight = plotAreaBottom - plotAreaTop; std::vector> bitmap; bitmap.resize(pxWidth); for(auto &y : bitmap) { y.resize(pxHeight, 0); } unsigned int highestIntensity = 0; unsigned int numTraces = (*displayData)[0].y.size(); auto addLine = [&](int x0, int y0, int x1, int y1, bool skipFirst = true) { bool first = true; auto putpixel = [&](int x, int y) { if(skipFirst && first) { first = false; return; } if(x < 0 || x >= (int) pxWidth || y < 0 || y >= (int) pxHeight) { return; } auto &bin = bitmap[x][y]; bin++; if(bin > highestIntensity) { highestIntensity = bin; } }; int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1; int dy = -abs (y1 - y0), sy = y0 < y1 ? 1 : -1; int err = dx + dy, e2; /* error value e_xy */ for (;;){ /* loop */ putpixel (x0,y0); if (x0 == x1 && y0 == y1) break; e2 = 2 * err; if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */ if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */ } }; // Assemble the bitmap for(unsigned int i=1;i= (int) pxWidth && x1 >= (int) pxWidth)) { // completely out of the frame continue; } for(unsigned int j=0;j 1); } } // draw the bitmap pen = QPen(); pen.setCosmetic(true); for(unsigned int i=1;i 0) { double value = (double) bitmap[i][j] / highestIntensity; pen.setColor(Util::getIntensityGradeColor(value)); p.setPen(pen); p.drawPoint(plotAreaLeft + i + 1, plotAreaTop + j + 1); } } } } if(dropPending) { p.setOpacity(0.5); p.setBrush(Qt::white); p.setPen(Qt::white); // show drop area over whole plot p.drawRect(plotRect); auto font = p.font(); font.setPixelSize(20); p.setFont(font); p.setOpacity(1.0); p.setPen(Qt::white); auto text = "Drop here to add\n" + dropTrace->name() + "\nto waterfall plot"; p.drawText(plotRect, Qt::AlignCenter, text); } } bool EyeDiagramPlot::supported(Trace *t) { if(t->getDataType() != Trace::DataType::Frequency) { // wrong domain return false; } if(t->isReflection()) { // can't work with reflection measurements return false; } return true; } QString EyeDiagramPlot::mouseText(QPoint pos) { QString ret; if(positionWithinGraphArea(pos)) { // cursor within plot area QPointF coords = pixelToPlotValue(pos); int significantDigits = floor(log10(abs(xAxis.getRangeMax()))) - floor(log10((abs(xAxis.getRangeMax() - xAxis.getRangeMin())) / 1000.0)) + 1; ret += Unit::ToString(coords.x(), xAxis.Unit(), "fpnum kMG", significantDigits) + "\n"; auto max = qMax(abs(yAxis.getRangeMax()), abs(yAxis.getRangeMin())); auto step = abs(yAxis.getRangeMax() - yAxis.getRangeMin()) / 1000.0; significantDigits = floor(log10(max)) - floor(log10(step)) + 1; ret += Unit::ToString(coords.y(), "V", yAxis.Prefixes(), significantDigits) + "\n"; } return ret; } QPoint EyeDiagramPlot::plotValueToPixel(QPointF plotValue) { QPoint p; p.setX(xAxis.transform(plotValue.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth)); p.setY(yAxis.transform(plotValue.y(), plotAreaBottom, plotAreaTop)); return p; } QPointF EyeDiagramPlot::pixelToPlotValue(QPoint pixel) { QPointF p; p.setX(xAxis.inverseTransform(pixel.x(), plotAreaLeft, plotAreaLeft + plotAreaWidth)); p.setY(yAxis.inverseTransform(pixel.y(), plotAreaBottom, plotAreaTop)); return p; } void EyeDiagramPlot::updateThread(unsigned int xSamples) { std::lock_guard calc(calcMutex); do { updateScheduled = false; setStatus("Starting calculation..."); if(!trace) { setStatus("No trace assigned"); continue; } qDebug() << "Starting eye diagram calculation"; // sanity check values if(datarate >= trace->getSample(trace->numSamples() - 1).x) { setStatus("Data rate too high"); continue; } if(datarate <= 0) { setStatus("Data rate too low"); continue; } if(risetime > 0.3 * 1.0 / datarate) { setStatus("Rise time too high"); continue; } if(falltime > 0.3 * 1.0 / datarate) { setStatus("Fall time too high"); continue; } if(jitter > 0.3 * 1.0 / datarate) { setStatus("Jitter too high"); continue; } qDebug() << "Eye calculation: input values okay"; // calculate timestep double timestep = calculatedTime() / xSamples; // reserve vector for input data std::vector> inVec(xSamples * (cycles + 1), 0.0); // needs to calculate one more cycle than required for the display (settling) // resize working buffer calcData->clear(); calcData->resize(xSamples); for(auto& s : *calcData) { s.y.resize(cycles, 0.0); } setStatus("Extracting impulse response..."); // calculate impulse response of trace double eyeTimeShift = 0; std::vector> impulseVec; // determine how long the impulse response is auto samples = tdr->numSamples(); if(samples == 0) { // TDR calculation not yet done, unable to update updating = false; setStatus("No time-domain data from trace"); return; } auto length = tdr->getSample(samples - 1).x; // determine average delay auto total_step = tdr->getStepResponse(samples - 1); for(unsigned int i=0;igetStepResponse(i); if(abs(total_step - step) <= abs(step)) { // mid point reached eyeTimeShift = 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 += (risetime + falltime) * 1.25 / 4; eyeTimeShift += 0.5 / datarate; int eyeXshift = eyeTimeShift / timestep; qDebug() << "Eye calculation: TDR calculation done"; setStatus("Generating PRBS sequence..."); auto prbs = new PRBS(patternbits); auto getNextLevel = [&]() -> unsigned int { unsigned int level = 0; for(unsigned int i=0;inext()) { level |= 0x01; } } return level; }; auto levelToVoltage = [=](unsigned int level) -> double { unsigned int maxLevel = (0x01 << bitsPerSymbol) - 1; return Util::Scale((double) level, 0.0, (double) maxLevel, lowlevel, 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, noise); std::random_device rd2; std::mt19937 mt_jitter(rd2()); std::normal_distribution<> dist_jitter(0, 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 = risetime; } else if(currentSignal > nextSignal) { edgeTime = falltime; } if(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 / 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(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"; 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(bufferSwitchMutex); // switch buffers auto buf = displayData; displayData = calcData; calcData = buf; } setStatus("Eye calculation complete"); replot(); } while (updateScheduled); updating = false; } 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); } } void EyeDiagramPlot::setStatus(QString s) { status = s; emit statusChanged(s); } double EyeDiagramPlot::calculatedTime() { return 2.0 / datarate; } double EyeDiagramPlot::minDisplayVoltage() { auto eyeRange = highlevel - lowlevel; return lowlevel - eyeRange * yOverrange; } double EyeDiagramPlot::maxDisplayVoltage() { auto eyeRange = highlevel - lowlevel; return highlevel + eyeRange * yOverrange; }