Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ngscopeclient/scopehal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 793895bbe928
Choose a base ref
...
head repository: ngscopeclient/scopehal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: b4142b49b14d
Choose a head ref
  • 4 commits
  • 7 files changed
  • 1 contributor

Commits on Apr 9, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    54ad0df View commit details
  2. Copy the full SHA
    b6f40fd View commit details
  3. Copy the full SHA
    b2a7962 View commit details
  4. Copy the full SHA
    b4142b4 View commit details
1 change: 1 addition & 0 deletions scopeprotocols/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ set(SCOPEPROTOCOLS_SOURCES
DPhyDataDecoder.cpp
DPhyHSClockRecoveryFilter.cpp
DPhySymbolDecoder.cpp
DramClockFilter.cpp
DramRefreshActivateMeasurement.cpp
DramRowColumnLatencyMeasurement.cpp
DSIFrameDecoder.cpp
308 changes: 308 additions & 0 deletions scopeprotocols/DramClockFilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
/***********************************************************************************************************************
* *
* ANTIKERNEL v0.1 *
* *
* Copyright (c) 2012-2021 Andrew D. Zonenberg *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
* following conditions are met: *
* *
* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the *
* following disclaimer. *
* *
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the *
* following disclaimer in the documentation and/or other materials provided with the distribution. *
* *
* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products *
* derived from this software without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
* POSSIBILITY OF SUCH DAMAGE. *
* *
***********************************************************************************************************************/

#include "scopeprotocols.h"
#include "DramClockFilter.h"

using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Construction / destruction

DramClockFilter::DramClockFilter(const string& color)
: Filter(OscilloscopeChannel::CHANNEL_TYPE_DIGITAL, color, CAT_CLOCK)
{
//Set up channels
ClearStreams();
AddStream("RD");
AddStream("WR");

//Set up channels
CreateInput("CMD");
CreateInput("CLK");
CreateInput("DQS");

m_dqsthreshname = "DQS Threshold";
m_parameters[m_dqsthreshname] = FilterParameter(FilterParameter::TYPE_FLOAT, Unit(Unit::UNIT_VOLTS));
m_parameters[m_dqsthreshname].SetFloatVal(1.6);

m_burstname = "Burst Length";
m_parameters[m_burstname] = FilterParameter(FilterParameter::TYPE_ENUM, Unit(Unit::UNIT_COUNTS));
m_parameters[m_burstname].AddEnumValue("2", 2);
m_parameters[m_burstname].AddEnumValue("4", 4);
m_parameters[m_burstname].AddEnumValue("8", 8);
m_parameters[m_burstname].SetIntVal(8);

m_casname = "CAS# Latency";
m_parameters[m_casname] = FilterParameter(FilterParameter::TYPE_FLOAT, Unit(Unit::UNIT_COUNTS));
m_parameters[m_casname].SetFloatVal(2);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Factory methods

bool DramClockFilter::ValidateChannel(size_t i, StreamDescriptor stream)
{
if(stream.m_channel == NULL)
return false;

if( (i == 0) && (dynamic_cast<SDRAMWaveform*>(stream.m_channel->GetData(stream.m_stream)) != NULL ) )
return true;
if( (i == 1) && (dynamic_cast<DigitalWaveform*>(stream.m_channel->GetData(stream.m_stream)) != NULL ) )
return true;
if( (i == 2) && (dynamic_cast<AnalogWaveform*>(stream.m_channel->GetData(stream.m_stream)) != NULL ) )
return true;

return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Accessors

void DramClockFilter::SetDefaultName()
{
char hwname[256];
snprintf(hwname, sizeof(hwname), "GatedClk(%s)", GetInputDisplayName(0).c_str());
m_hwname = hwname;
m_displayname = m_hwname;
}

string DramClockFilter::GetProtocolName()
{
return "DRAM Clocks";
}

bool DramClockFilter::IsOverlay()
{
return false;
}

bool DramClockFilter::NeedsConfig()
{
return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Actual decoder logic

void DramClockFilter::Refresh()
{
if(!VerifyAllInputsOK())
{
SetData(NULL, 0);
return;
}

//Get the input data
auto cmd = dynamic_cast<SDRAMWaveform*>(GetInputWaveform(0));
auto clk = GetDigitalInputWaveform(1);
auto dqs = GetAnalogInputWaveform(2);

//Find edges in the DQS signal (double rate so we want both polarity)
//TODO: support differential DQS for DDR2/3
vector<int64_t> edges;
float thresh = m_parameters[m_dqsthreshname].GetFloatVal();
FindZeroCrossings(dqs, thresh, edges);

//Find edges in the CLK signal
//TODO: support analog clock too?
vector<int64_t> clkedges;
FindZeroCrossings(clk, clkedges);

//Create output waveforms
auto rdclk = new DigitalWaveform;
auto wrclk = new DigitalWaveform;
rdclk->m_timescale = 1;
wrclk->m_timescale = 1;
SetData(rdclk, 0);
SetData(wrclk, 1);

//Copy timestamps
rdclk->m_startTimestamp = dqs->m_startTimestamp;
wrclk->m_startTimestamp = dqs->m_startTimestamp;
rdclk->m_startFemtoseconds = dqs->m_startFemtoseconds;
wrclk->m_startFemtoseconds = dqs->m_startFemtoseconds;

//Create initial all-zero samples at start of both clocks
wrclk->m_samples.push_back(false);
wrclk->m_durations.push_back(1);
wrclk->m_offsets.push_back(0);
rdclk->m_samples.push_back(false);
rdclk->m_durations.push_back(1);
rdclk->m_offsets.push_back(0);

//Extract some parameters
int bl = m_parameters[m_burstname].GetIntVal();
float tcas_cycles = m_parameters[m_casname].GetFloatVal();
int tcas_halfcycles = round(tcas_cycles * 2);

int64_t tdqs = 0;
size_t idqs = 0;
size_t dqslen = edges.size();

int64_t tclk = 0;
size_t iclk = 0;
size_t clklen = clkedges.size();

Unit fs(Unit::UNIT_FS);

//Loop over the command bus transactions and find the corresponding DQS pulses for each read/write burst
size_t len = cmd->m_samples.size();
for(size_t i=0; i<len; i++)
{
int64_t tnow = cmd->m_offsets[i] * cmd->m_timescale + cmd->m_triggerPhase;
auto s = cmd->m_samples[i];
switch(s.m_stype)
{
//Writes
case SDRAMSymbol::TYPE_WR:
case SDRAMSymbol::TYPE_WRA:
{
//Find the first DQS edge after the clock edge
while(idqs < dqslen)
{
tdqs = edges[idqs];

if(tdqs > tnow)
break;
else
idqs ++;
}

//Now to add the burst.
//Create samples for each DQS pulse
for(int j=0; j<bl; j++)
{
//Extend the last sample to our start point
size_t last = wrclk->m_samples.size() - 1;
wrclk->m_durations[last] = tdqs - wrclk->m_offsets[last];

//LogDebug("Write clock pulse at tdqs=%s\n", fs.PrettyPrint(tdqs).c_str());

//Create a new sample for this pulse
wrclk->m_samples.push_back(j & 1 ? false : true);
wrclk->m_durations.push_back(1);
wrclk->m_offsets.push_back(tdqs);

//Advance to the next DQS edge
idqs ++;
if(idqs >= dqslen)
break;
tdqs = edges[idqs];
}
}
break;

//Reads
case SDRAMSymbol::TYPE_RD:
case SDRAMSymbol::TYPE_RDA:
{
//Throw away CLK edges until we're lined up with the beginning of the read burst
while(iclk < clklen)
{
tclk = clkedges[iclk];

if(tclk >= tnow)
break;
else
iclk ++;
}

//Move forward by the CAS latency
iclk += tcas_halfcycles;
if(iclk >= clklen)
break;
tclk = clkedges[iclk];

//Find the first DQS edge after the clock edge
//TODO: is this actually correct?
while(idqs < dqslen)
{
tdqs = edges[idqs];

if(tdqs > tclk)
break;
else
idqs ++;
}
if(idqs >= dqslen)
break;

//Now to add the burst.
//Create samples for each DQS pulse
for(int j=0; j<bl; j++)
{
//Extend the last sample to our start point
size_t last = rdclk->m_samples.size() - 1;
rdclk->m_durations[last] = tdqs - rdclk->m_offsets[last];

//LogDebug("Read clock pulse at tdqs=%s\n", fs.PrettyPrint(tdqs).c_str());

//Create a new sample for this pulse
rdclk->m_samples.push_back(j & 1 ? false : true);
rdclk->m_durations.push_back(1);
rdclk->m_offsets.push_back(tdqs);

//Advance to the next DQS edge
idqs ++;
if(idqs >= dqslen)
break;
tdqs = edges[idqs];
}
}
break;

//Ignore anything else
default:
break;
}
}

//Stretch last zero sample to end of capture
size_t ilast = dqs->m_samples.size() - 1;
size_t tlast = (dqs->m_offsets[ilast] + dqs->m_durations[ilast])*dqs->m_timescale + dqs->m_triggerPhase;
size_t last = wrclk->m_samples.size() - 1;
wrclk->m_durations[last] = tlast - wrclk->m_offsets[last];
last = rdclk->m_samples.size() - 1;
rdclk->m_durations[last] = tlast - rdclk->m_offsets[last];

//Add a bunch of 1fs zero samples to pad end of capture
for(size_t i=0; i<5; i++)
{
last = wrclk->m_samples.size() - 1;
wrclk->m_samples.push_back(0);
wrclk->m_durations.push_back(1);
wrclk->m_offsets.push_back(wrclk->m_offsets[last] + 1);

last = rdclk->m_samples.size() - 1;
rdclk->m_samples.push_back(0);
rdclk->m_durations.push_back(1);
rdclk->m_offsets.push_back(rdclk->m_offsets[last] + 1);
}
}
61 changes: 61 additions & 0 deletions scopeprotocols/DramClockFilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/***********************************************************************************************************************
* *
* ANTIKERNEL v0.1 *
* *
* Copyright (c) 2012-2021 Andrew D. Zonenberg *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
* following conditions are met: *
* *
* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the *
* following disclaimer. *
* *
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the *
* following disclaimer in the documentation and/or other materials provided with the distribution. *
* *
* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products *
* derived from this software without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
* POSSIBILITY OF SUCH DAMAGE. *
* *
***********************************************************************************************************************/

/**
@file
@author Andrew D. Zonenberg
@brief Declaration of DramClockFilter
*/
#ifndef DramClockFilter_h
#define DramClockFilter_h

class DramClockFilter : public Filter
{
public:
DramClockFilter(const std::string& color);

virtual void Refresh();

virtual bool NeedsConfig();
virtual bool IsOverlay();

static std::string GetProtocolName();
virtual void SetDefaultName();

virtual bool ValidateChannel(size_t i, StreamDescriptor stream);

PROTOCOL_DECODER_INITPROC(DramClockFilter)

protected:
std::string m_dqsthreshname;
std::string m_burstname;
std::string m_casname;
};

#endif
67 changes: 64 additions & 3 deletions scopeprotocols/EyePattern.cpp
Original file line number Diff line number Diff line change
@@ -113,6 +113,8 @@ EyePattern::EyePattern(const string& color)
, m_vmodeName("Vertical Scale Mode")
, m_rangeName("Vertical Range")
, m_clockAlignName("Clock Alignment")
, m_rateModeName("Bit Rate Mode")
, m_rateName("Bit Rate")
{
//Set up channels
CreateInput("din");
@@ -146,6 +148,14 @@ EyePattern::EyePattern(const string& color)
m_parameters[m_clockAlignName].AddEnumValue("Center", ALIGN_CENTER);
m_parameters[m_clockAlignName].AddEnumValue("Edge", ALIGN_EDGE);
m_parameters[m_clockAlignName].SetIntVal(ALIGN_CENTER);

m_parameters[m_rateModeName] = FilterParameter(FilterParameter::TYPE_ENUM, Unit(Unit::UNIT_COUNTS));
m_parameters[m_rateModeName].AddEnumValue("Auto", MODE_AUTO);
m_parameters[m_rateModeName].AddEnumValue("Fixed", MODE_FIXED);
m_parameters[m_rateModeName].SetIntVal(MODE_AUTO);

m_parameters[m_rateName] = FilterParameter(FilterParameter::TYPE_INT, Unit(Unit::UNIT_BITRATE));
m_parameters[m_rateName].SetIntVal(1250000000);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -429,11 +439,15 @@ void EyePattern::Refresh()
break;
}

//If no clock edges, don't change anything
if(clock_edges.empty())
return;

//Calculate the nominal UI width
if(cap->m_uiWidth < FLT_EPSILON)
RecalculateUIWidth();

//Shift the clock by half a UI if it's edge aligned
//Shift the clock leby half a UI if it's edge aligned
//All of the eye creation logic assumes a center aligned clock.
if(clock_align == ALIGN_EDGE)
{
@@ -463,9 +477,9 @@ void EyePattern::Refresh()
//We can assume m_offsets[i] = i and m_durations[i] = 0 for all input
if(waveform->m_densePacked)
{
if(g_hasAvx2)
/*if(g_hasAvx2)
DensePackedInnerLoopAVX2(waveform, clock_edges, data, wend, cend, xmax, ymax, xtimescale, yscale, yoff);
else
else*/
DensePackedInnerLoop(waveform, clock_edges, data, wend, cend, xmax, ymax, xtimescale, yscale, yoff);
}

@@ -514,6 +528,10 @@ void EyePattern::DensePackedInnerLoopAVX2(
float yoff
)
{
EyeWaveform* cap = dynamic_cast<EyeWaveform*>(GetData(0));
int64_t width = cap->GetUIWidth();
int64_t halfwidth = width/2;

size_t iclock = 0;

size_t wend_rounded = wend - (wend % 8);
@@ -559,6 +577,12 @@ void EyePattern::DensePackedInnerLoopAVX2(
//Figure out the offset to the next edge
offset[j] = tstart - tnext;
}

//Drop anything past half a UI if the next clock edge is a long ways out
//(this is needed for irregularly sampled data like DDR RAM)
int64_t ttnext = tnext - tstart;
if( (offset[j] > halfwidth) && (ttnext > width) )
offset[j] = -INT_MAX;
}

//Interpolate X position
@@ -647,6 +671,12 @@ void EyePattern::DensePackedInnerLoopAVX2(
if(pixel_x_round > xmax)
continue;

//Drop anything past half a UI if the next clock edge is a long ways out
//(this is needed for irregularly sampled data like DDR RAM)
int64_t ttnext = tnext - tstart;
if( (offset > halfwidth) && (ttnext > width) )
continue;

//Interpolate voltage, early out if clipping
float dv = waveform->m_samples[i+1] - waveform->m_samples[i];
float nominal_voltage = waveform->m_samples[i] + dv*dx_frac;
@@ -679,6 +709,10 @@ void EyePattern::DensePackedInnerLoop(
float yoff
)
{
EyeWaveform* cap = dynamic_cast<EyeWaveform*>(GetData(0));
int64_t width = cap->GetUIWidth();
int64_t halfwidth = width/2;

size_t iclock = 0;
for(size_t i=0; i<wend && iclock < cend; i++)
{
@@ -706,6 +740,12 @@ void EyePattern::DensePackedInnerLoop(
float pixel_x_fround = floor(pixel_x_f);
float dx_frac = (pixel_x_f - pixel_x_fround ) / xtimescale;

//Drop anything past half a UI if the next clock edge is a long ways out
//(this is needed for irregularly sampled data like DDR RAM)
int64_t ttnext = tnext - tstart;
if( (offset > halfwidth) && (ttnext > width) )
continue;

//Early out if off end of plot
int32_t pixel_x_round = floor(pixel_x_f);
if(pixel_x_round > xmax)
@@ -743,6 +783,10 @@ void EyePattern::SparsePackedInnerLoop(
float yoff
)
{
EyeWaveform* cap = dynamic_cast<EyeWaveform*>(GetData(0));
int64_t width = cap->GetUIWidth();
int64_t halfwidth = width/2;

size_t iclock = 0;
for(size_t i=0; i<wend && iclock < cend; i++)
{
@@ -765,6 +809,12 @@ void EyePattern::SparsePackedInnerLoop(
offset = tstart - tnext;
}

//Drop anything past half a UI if the next clock edge is a long ways out
//(this is needed for irregularly sampled data like DDR RAM)
int64_t ttnext = tnext - tstart;
if( (offset > halfwidth) && (ttnext > width) )
continue;

//Interpolate position
int64_t dt = waveform->m_offsets[i+1] - waveform->m_offsets[i];
float pixel_x_f = (offset - m_xoff) * m_xscale;
@@ -809,6 +859,13 @@ void EyePattern::RecalculateUIWidth()
if(!cap)
cap = ReallocateWaveform();

//If manual override, don't look at anything else
if(m_parameters[m_rateModeName].GetIntVal() == MODE_FIXED)
{
cap->m_uiWidth = FS_PER_SECOND * 1.0 / m_parameters[m_rateName].GetIntVal();
return;
}

auto clock = GetDigitalInputWaveform(1);
if(!clock)
return;
@@ -830,6 +887,10 @@ void EyePattern::RecalculateUIWidth()
break;
}

//If no clock edges, don't change anything
if(clock_edges.empty())
return;

//Find width of each UI
vector<int64_t> ui_widths;
for(size_t i=0; i<clock_edges.size()-1; i++)
8 changes: 8 additions & 0 deletions scopeprotocols/EyePattern.h
Original file line number Diff line number Diff line change
@@ -166,6 +166,12 @@ class EyePattern : public Filter
ALIGN_EDGE
};

enum UIMode
{
MODE_AUTO,
MODE_FIXED
};

PROTOCOL_DECODER_INITPROC(EyePattern)

protected:
@@ -224,6 +230,8 @@ class EyePattern : public Filter
std::string m_vmodeName;
std::string m_rangeName;
std::string m_clockAlignName;
std::string m_rateModeName;
std::string m_rateName;

EyeMask m_mask;
};
1 change: 1 addition & 0 deletions scopeprotocols/scopeprotocols.cpp
Original file line number Diff line number Diff line change
@@ -61,6 +61,7 @@ void ScopeProtocolStaticInit()
AddDecoderClass(DPhyDataDecoder);
AddDecoderClass(DPhyHSClockRecoveryFilter);
AddDecoderClass(DPhySymbolDecoder);
AddDecoderClass(DramClockFilter);
AddDecoderClass(DramRefreshActivateMeasurement);
AddDecoderClass(DramRowColumnLatencyMeasurement);
AddDecoderClass(DSIFrameDecoder);
1 change: 1 addition & 0 deletions scopeprotocols/scopeprotocols.h
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@
#include "DPhyDataDecoder.h"
#include "DPhyHSClockRecoveryFilter.h"
#include "DPhySymbolDecoder.h"
#include "DramClockFilter.h"
#include "DramRefreshActivateMeasurement.h"
#include "DramRowColumnLatencyMeasurement.h"
#include "DSIFrameDecoder.h"