Skip to content

Commit 1820fde

Browse files
committed
Initial implementation of SetupHoldMeasurement
1 parent 5e27601 commit 1820fde

File tree

5 files changed

+427
-0
lines changed

5 files changed

+427
-0
lines changed

scopeprotocols/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ set(SCOPEPROTOCOLS_SOURCES
153153
SDCmdDecoder.cpp
154154
SDDataDecoder.cpp
155155
SDRAMDecoderBase.cpp
156+
SetupHoldMeasurement.cpp
156157
SNRFilter.cpp
157158
SParameterCascadeFilter.cpp
158159
SParameterDeEmbedFilter.cpp
Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
/***********************************************************************************************************************
2+
* *
3+
* libscopeprotocols *
4+
* *
5+
* Copyright (c) 2012-2024 Andrew D. Zonenberg and contributors *
6+
* All rights reserved. *
7+
* *
8+
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
9+
* following conditions are met: *
10+
* *
11+
* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the *
12+
* following disclaimer. *
13+
* *
14+
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the *
15+
* following disclaimer in the documentation and/or other materials provided with the distribution. *
16+
* *
17+
* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products *
18+
* derived from this software without specific prior written permission. *
19+
* *
20+
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
21+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
22+
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES *
23+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR *
24+
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
25+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
26+
* POSSIBILITY OF SUCH DAMAGE. *
27+
* *
28+
***********************************************************************************************************************/
29+
30+
/**
31+
@file
32+
@author Andrew D. Zonenberg
33+
@brief Implementation of SetupHoldMeasurement
34+
*/
35+
36+
#include "../scopehal/scopehal.h"
37+
#include "SetupHoldMeasurement.h"
38+
39+
using namespace std;
40+
41+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
42+
// Construction / destruction
43+
44+
SetupHoldMeasurement::SetupHoldMeasurement(const string& color)
45+
: Filter(color, CAT_MEASUREMENT)
46+
, m_vih(m_parameters["Vih"])
47+
, m_vil(m_parameters["Vil"])
48+
, m_edgemode(m_parameters["Clock Edge"])
49+
{
50+
AddStream(Unit(Unit::UNIT_FS), "tsetup", Stream::STREAM_TYPE_ANALOG_SCALAR);
51+
AddStream(Unit(Unit::UNIT_FS), "thold", Stream::STREAM_TYPE_ANALOG_SCALAR);
52+
53+
CreateInput("data");
54+
CreateInput("clock");
55+
56+
m_vih = FilterParameter(FilterParameter::TYPE_FLOAT, Unit(Unit::UNIT_VOLTS));
57+
m_vih.SetFloatVal(2.0);
58+
59+
m_vil = FilterParameter(FilterParameter::TYPE_FLOAT, Unit(Unit::UNIT_VOLTS));
60+
m_vil.SetFloatVal(1.3);
61+
62+
m_edgemode = FilterParameter(FilterParameter::TYPE_ENUM, Unit(Unit::UNIT_COUNTS));
63+
m_edgemode.AddEnumValue("Rising", EDGE_RISING);
64+
m_edgemode.AddEnumValue("Falling", EDGE_FALLING);
65+
m_edgemode.AddEnumValue("Both", EDGE_BOTH);
66+
}
67+
68+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
69+
// Factory methods
70+
71+
bool SetupHoldMeasurement::ValidateChannel(size_t i, StreamDescriptor stream)
72+
{
73+
if(stream.m_channel == NULL)
74+
return false;
75+
76+
if( (i < 2) && (stream.GetType() == Stream::STREAM_TYPE_ANALOG) )
77+
return true;
78+
79+
return false;
80+
}
81+
82+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
83+
// Accessors
84+
85+
void SetupHoldMeasurement::SetDefaultName()
86+
{
87+
char hwname[256];
88+
snprintf(hwname, sizeof(hwname), "SetupHold(%s, %s)",
89+
GetInputDisplayName(0).c_str(),
90+
GetInputDisplayName(1).c_str());
91+
m_hwname = hwname;
92+
m_displayname = m_hwname;
93+
}
94+
95+
string SetupHoldMeasurement::GetProtocolName()
96+
{
97+
return "Setup / Hold";
98+
}
99+
100+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
101+
// Actual decoder logic
102+
103+
void SetupHoldMeasurement::Refresh()
104+
{
105+
//Make sure we've got valid inputs
106+
if(!VerifyAllInputsOK())
107+
{
108+
m_streams[0].m_value = 0;
109+
m_streams[1].m_value = 0;
110+
return;
111+
}
112+
113+
float vih = m_vih.GetFloatVal();
114+
float vil = m_vil.GetFloatVal();
115+
auto mode = static_cast<EdgeMode>(m_edgemode.GetIntVal());
116+
117+
//Get the input data
118+
auto wdata = GetInputWaveform(0);
119+
auto wclk = GetInputWaveform(1);
120+
wdata->PrepareForCpuAccess();
121+
wclk->PrepareForCpuAccess();
122+
123+
//For now, assume inputs are always uniform
124+
auto udata = dynamic_cast<UniformAnalogWaveform*>(wdata);
125+
auto uclk = dynamic_cast<UniformAnalogWaveform*>(wclk);
126+
if(!udata || !uclk)
127+
{
128+
m_streams[0].m_value = 0;
129+
m_streams[1].m_value = 0;
130+
return;
131+
}
132+
133+
//Find the timestamps of clock and data edges
134+
bool clockMatchRising = (mode == EDGE_RISING) || (mode == EDGE_BOTH);
135+
bool clockMatchFalling = (mode == EDGE_FALLING) || (mode == EDGE_BOTH);
136+
auto clkedges = GetEdgeTimestamps(uclk, vil, vih, clockMatchRising, clockMatchFalling);
137+
auto datedges = GetEdgeTimestamps(udata, vil, vih, true, true);
138+
139+
//Loop over the clock edges and look for data edges before/after each
140+
size_t nclk = clkedges.size();
141+
size_t ndat = datedges.size();
142+
143+
int64_t minsetup = INT64_MAX;
144+
int64_t minhold = INT64_MAX;
145+
size_t idat = 0;
146+
for(size_t iclk = 0; iclk < nclk; iclk ++)
147+
{
148+
auto clockStart = clkedges[iclk].first;
149+
auto clockEnd = clkedges[iclk].second;
150+
151+
//Search forward to find the last data edge BEFORE our clock edge
152+
//(used for calculating setup time)
153+
bool dataFound = false;
154+
int64_t dataEnd = 0;
155+
for(; idat < ndat; idat ++)
156+
{
157+
//If the data edge ends after our current clock edge starts, stop searching
158+
auto dstart = datedges[idat].first;
159+
auto dend = datedges[idat].second;
160+
if(dend > clockStart)
161+
{
162+
//If the data and clock edges overlap, we have no margin at all!
163+
//More formally: if data start and/or end is between clock start and end, no margin
164+
if( ( (dstart >= clockStart) && (dstart <= clockEnd) ) ||
165+
( (dend >= clockStart) && (dend <= clockEnd) ) )
166+
{
167+
minsetup = 0;
168+
}
169+
break;
170+
}
171+
172+
//If it ends before our *previous* clock edge starts, it's too early, keep looking
173+
if( (iclk > 0) && (clkedges[iclk-1].first > dend) )
174+
continue;
175+
176+
//It's a hit, keep it
177+
dataFound = true;
178+
dataEnd = dend;
179+
}
180+
if(dataFound)
181+
{
182+
//Calculate setup time: data valid to clock invalid
183+
int64_t tsu = clockStart - dataEnd;
184+
/*LogDebug("Data valid at %s, clock invalid at %s, setup time = %s\n",
185+
Unit(Unit::UNIT_FS).PrettyPrint(dataEnd).c_str(),
186+
Unit(Unit::UNIT_FS).PrettyPrint(clockStart).c_str(),
187+
Unit(Unit::UNIT_FS).PrettyPrint(tsu).c_str());*/
188+
minsetup = min(tsu, minsetup);
189+
190+
//TODO: waveform output?
191+
}
192+
193+
//Continue searching forward to find the first data edge AFTER the clock edge
194+
dataFound = false;
195+
int64_t dataStart = 0;
196+
for(; idat < ndat; idat ++)
197+
{
198+
//If the data and clock edges overlap, we have no margin at all!
199+
//More formally: if data start and/or end is between clock start and end, no margin
200+
auto dstart = datedges[idat].first;
201+
auto dend = datedges[idat].second;
202+
if( ( (dstart >= clockStart) && (dstart <= clockEnd) ) ||
203+
( (dend >= clockStart) && (dend <= clockEnd) ) )
204+
{
205+
minhold = 0;
206+
break;
207+
}
208+
209+
//If the data edge starts after our current clock edge ends, stop searching
210+
if(dstart > clockEnd)
211+
{
212+
//If the data edge starts after the *next* clock edge starts, it's outside our UI
213+
//Ignore it
214+
if(iclk+1 < nclk)
215+
{
216+
if(dstart > clkedges[iclk+1].first)
217+
break;
218+
}
219+
220+
dataFound = true;
221+
dataStart = dstart;
222+
break;
223+
}
224+
}
225+
226+
if(dataFound)
227+
{
228+
//Calculate hold time: clock valid to data invalid
229+
int64_t th = dataStart - clockEnd;
230+
/*LogDebug("Clock valid at %s, data invalid at %s, hold time = %s\n",
231+
Unit(Unit::UNIT_FS).PrettyPrint(clockEnd).c_str(),
232+
Unit(Unit::UNIT_FS).PrettyPrint(dataStart).c_str(),
233+
Unit(Unit::UNIT_FS).PrettyPrint(th).c_str());*/
234+
minhold = min(th, minhold);
235+
236+
//TODO: waveform output?
237+
}
238+
239+
}
240+
241+
m_streams[0].m_value = minsetup;
242+
m_streams[1].m_value = minhold;
243+
}
244+
245+
/**
246+
@brief Returns a vector of (edge start, edge end) timestamps
247+
248+
@param wfm Input signal
249+
@param vil Logic low threshold
250+
@param vih Logic high threshold
251+
@param matchRising True to match rising edges
252+
@param matchFalling True to match falling edges
253+
*/
254+
vector< pair<int64_t, int64_t> > SetupHoldMeasurement::GetEdgeTimestamps(
255+
UniformAnalogWaveform* wfm,
256+
float vil,
257+
float vih,
258+
bool matchRising,
259+
bool matchFalling)
260+
{
261+
vector< pair<int64_t, int64_t> > ret;
262+
263+
enum bitstate_t
264+
{
265+
STATE_UNKNOWN_WAS_LOW,
266+
STATE_UNKNOWN_WAS_HIGH,
267+
STATE_LOW,
268+
STATE_HIGH,
269+
};
270+
271+
//Assign the first state
272+
bitstate_t state;
273+
if(wfm->m_samples[0] < vil)
274+
state = STATE_LOW;
275+
else if(wfm->m_samples[0] > vih)
276+
state = STATE_HIGH;
277+
else
278+
state = STATE_UNKNOWN_WAS_LOW;
279+
280+
//Main loop looking for edges
281+
int64_t edgestart = 0;
282+
auto size = wfm->size();
283+
for(size_t i = 1; i < size; i ++)
284+
{
285+
float vin = wfm->m_samples[i];
286+
int64_t tstamp = GetOffsetScaled(wfm, i);
287+
288+
switch(state)
289+
{
290+
//Look for rising edges
291+
case STATE_UNKNOWN_WAS_LOW:
292+
293+
//TODO: interpolate Vih level crossing?
294+
if(vin > vih)
295+
{
296+
if(matchRising)
297+
ret.push_back( pair<int64_t, int64_t>(edgestart, tstamp));
298+
299+
state = STATE_HIGH;
300+
}
301+
302+
break;
303+
304+
case STATE_UNKNOWN_WAS_HIGH:
305+
306+
//TODO: interpolate Vil level crossing?
307+
if(vin < vil)
308+
{
309+
if(matchFalling)
310+
ret.push_back( pair<int64_t, int64_t>(edgestart, tstamp));
311+
312+
state = STATE_LOW;
313+
}
314+
315+
break;
316+
317+
case STATE_LOW:
318+
319+
//Look for Vil level crossing
320+
if(vin > vil)
321+
{
322+
state = STATE_UNKNOWN_WAS_LOW;
323+
edgestart = tstamp;
324+
}
325+
326+
break;
327+
328+
case STATE_HIGH:
329+
330+
//Look for Vih level crossing
331+
if(vin < vih)
332+
{
333+
state = STATE_UNKNOWN_WAS_HIGH;
334+
edgestart = tstamp;
335+
}
336+
337+
break;
338+
}
339+
}
340+
341+
return ret;
342+
}

0 commit comments

Comments
 (0)