diff --git a/scopehal/CMakeLists.txt b/scopehal/CMakeLists.txt index b7e758e1..b03b61fd 100644 --- a/scopehal/CMakeLists.txt +++ b/scopehal/CMakeLists.txt @@ -224,6 +224,7 @@ set(SCOPEHAL_SOURCES SParameterFilter.cpp TestWaveformSource.cpp + TestDigitalWaveformSource.cpp ComputePipeline.cpp FilterGraphExecutor.cpp diff --git a/scopehal/DemoOscilloscope.cpp b/scopehal/DemoOscilloscope.cpp index 1a74700b..d4f1cb13 100644 --- a/scopehal/DemoOscilloscope.cpp +++ b/scopehal/DemoOscilloscope.cpp @@ -62,6 +62,8 @@ DemoOscilloscope::DemoOscilloscope(SCPITransport* transport) m_source[i] = new TestWaveformSource(*m_rng[i]); } + m_digitalSource = new TestDigitalWaveformSource(); + m_model = "Oscilloscope Simulator"; m_vendor = "Antikernel Labs"; m_serial = "12345"; @@ -93,6 +95,22 @@ DemoOscilloscope::DemoOscilloscope(SCPITransport* transport) m_channelModes[i] = CHANNEL_MODE_NOISE_LPF; } + char chn[32]; + for(size_t i=0; i<16; i++) + { + snprintf(chn, sizeof(chn), "D%zu", i); + auto chan = new OscilloscopeChannel( + this, + chn, + GetDefaultChannelColor(m_channels.size()), + Unit(Unit::UNIT_FS), + Unit(Unit::UNIT_COUNTS), + Stream::STREAM_TYPE_DIGITAL, + m_channels.size()); + m_channels.push_back(chan); + m_digitalChannels.push_back(chan); + } + m_sweepFreq = 1e9; //Default sampling configuration @@ -104,6 +122,24 @@ DemoOscilloscope::DemoOscilloscope(SCPITransport* transport) m_channels[2]->SetDisplayName("PRBS31"); m_channels[3]->SetDisplayName("8B10B"); + m_channels[4]->SetDisplayName("SPI-CS"); + m_channels[5]->SetDisplayName("SPI-SCLK"); + m_channels[6]->SetDisplayName("SPI-MOSI"); + m_channels[7]->SetDisplayName("UART-0"); + m_channels[8]->SetDisplayName("UART-0-Clk"); + m_channels[9]->SetDisplayName("UART-1"); + m_channels[10]->SetDisplayName("UART-1-Clk"); + m_channels[11]->SetDisplayName("Parallel-Clk"); + + m_channels[12]->SetDisplayName("Parallel-0"); + m_channels[13]->SetDisplayName("Parallel-1"); + m_channels[14]->SetDisplayName("Parallel-2"); + m_channels[15]->SetDisplayName("Parallel-3"); + m_channels[16]->SetDisplayName("Parallel-4"); + m_channels[17]->SetDisplayName("Parallel-5"); + m_channels[18]->SetDisplayName("Parallel-6"); + m_channels[19]->SetDisplayName("Parallel-7"); + //Create Vulkan objects for the waveform conversion for(int i=0; i<4; i++) { @@ -137,6 +173,24 @@ DemoOscilloscope::DemoOscilloscope(SCPITransport* transport) } } +vector DemoOscilloscope::GetDigitalBanks() +{ + vector banks; + + for(size_t n = 0; n < 2; n++) + { + DigitalBank bank; + + for(size_t i = 0; i < 8; i++) + bank.push_back(m_digitalChannels[i + n * 8]); + + banks.push_back(bank); + } + + return banks; +} + + DemoOscilloscope::~DemoOscilloscope() { LogTrace("Shutting down demo scope\n"); @@ -146,6 +200,7 @@ DemoOscilloscope::~DemoOscilloscope() delete m_source[i]; delete m_rng[i]; } + delete m_digitalSource; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -538,7 +593,7 @@ bool DemoOscilloscope::AcquireData() auto depth = GetSampleDepth(); int64_t sampleperiod = FS_PER_SECOND / m_rate; - WaveformBase* waveforms[4] = {nullptr}; + WaveformBase* waveforms[4+16] = {nullptr}; for(int i=0; i<4; i++) { if(!m_channelsEnabled[i]) @@ -591,8 +646,122 @@ bool DemoOscilloscope::AcquireData() ChannelsDownloadStatusUpdate(i, InstrumentChannel::DownloadState::DOWNLOAD_FINISHED, 1.0); } + bool spiEnabled = m_channelsEnabled[4]||m_channelsEnabled[5]||m_channelsEnabled[6]; + bool parallelEnabled = false; + for(int i=11; i<=19; i++) + { + if(m_channelsEnabled[i]) + { + parallelEnabled = true; + break; + } + } + + + // Prepare SPI data + SparseDigitalWaveform* cs = nullptr; + SparseDigitalWaveform* sclk = nullptr; + SparseDigitalWaveform* mosi = nullptr; + if(spiEnabled) + { + cs = AllocateDigitalWaveform("CS"); + sclk = AllocateDigitalWaveform("SCLK"); + mosi = AllocateDigitalWaveform("MOSI"); + m_digitalSource->GenerateSPI(cs,sclk,mosi,sampleperiod, depth); + } + + std::vector parallelWfms; + if(parallelEnabled) + { // Prepare Parallel bus data + auto wfClk = AllocateDigitalWaveform("Parallel-Clk"); + parallelWfms.push_back(wfClk); + // Parallel lines waveforms + for(int i = 0 ; i < 8 ; i++) + { + auto wf = AllocateDigitalWaveform("Parallel-"+to_string(i)); + parallelWfms.push_back(wf); + } + m_digitalSource->GenerateParallel(parallelWfms,sampleperiod,depth); + } + + for(int i=4; i<(4+16); i++) + { + if(!m_channelsEnabled[i]) + { + switch(i) + { + case 4: + if(spiEnabled) delete cs; + break; + case 5: + if(spiEnabled) delete sclk; + break; + case 6: + if(spiEnabled) delete mosi; + break; + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + // Paralle bus + if(parallelEnabled) delete parallelWfms[i-11]; + break; + } + + continue; + } + + // Lambda passed to generate waveform methods to update "download" percentage + ChannelsDownloadStatusUpdate(i, InstrumentChannel::DownloadState::DOWNLOAD_IN_PROGRESS, 0.0); + switch(i) + { + case 4: + waveforms[i] = cs; + break; + case 5: + waveforms[i] = sclk; + break; + case 6: + waveforms[i] = mosi; + break; + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + // Paralle bus + waveforms[i] = parallelWfms[i-11]; + break; + default: + if(i%2 == 1) + { + auto wfm = AllocateDigitalWaveform("UART"); + waveforms[i] = wfm; + m_digitalSource->GenerateUART(wfm, sampleperiod, depth); + } + else + { + auto wfm = AllocateDigitalWaveform("UART-Clk"); + waveforms[i] = wfm; + m_digitalSource->GenerateUARTClock(wfm, sampleperiod, depth); + } + break; + } + + ChannelsDownloadStatusUpdate(i, InstrumentChannel::DownloadState::DOWNLOAD_FINISHED, 1.0); + } + SequenceSet s; - for(int i=0; i<4; i++) + for(int i=0; i<(4+16); i++) { s[GetOscilloscopeChannel(i)] = waveforms[i]; } diff --git a/scopehal/DemoOscilloscope.h b/scopehal/DemoOscilloscope.h index 04c686e3..dfa94c62 100644 --- a/scopehal/DemoOscilloscope.h +++ b/scopehal/DemoOscilloscope.h @@ -38,6 +38,7 @@ #define DemoOscilloscope_h #include "TestWaveformSource.h" +#include "TestDigitalWaveformSource.h" #include /** @@ -119,6 +120,9 @@ class DemoOscilloscope : public virtual SCPIOscilloscope virtual unsigned int GetInstrumentTypes() const override; virtual void LoadConfiguration(int version, const YAML::Node& node, IDTable& idmap) override; + virtual std::vector GetDigitalBanks() override; + + protected: ///@brief External trigger @@ -145,6 +149,10 @@ class DemoOscilloscope : public virtual SCPIOscilloscope ///@brief Map of channel ID to ADC mode std::map m_channelModes; + ///@brief Digital channels + std::vector m_digitalChannels; + + ///@brief ADC mode selectors (used to select the simulated channel) enum ChannelModes { @@ -186,6 +194,8 @@ class DemoOscilloscope : public virtual SCPIOscilloscope */ TestWaveformSource* m_source[4]; + TestDigitalWaveformSource* m_digitalSource; + ///@brief Vulkan queue for ISI channel std::shared_ptr m_queue[4]; diff --git a/scopehal/TestDigitalWaveformSource.cpp b/scopehal/TestDigitalWaveformSource.cpp new file mode 100644 index 00000000..c589f247 --- /dev/null +++ b/scopehal/TestDigitalWaveformSource.cpp @@ -0,0 +1,329 @@ +/*********************************************************************************************************************** +* * +* libscopehal * +* * +* Copyright (c) 2012-2026 Andrew D. Zonenberg and contributors * +* 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 Frederic BORRY + @brief Implementation of TestDigitalWaveformSource + + @ingroup core + */ +#include "scopehal.h" +#include "TestDigitalWaveformSource.h" +#include + +using namespace std; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Construction / destruction + +/** + @brief Initializes a TestDigitalWaveformSource + */ +TestDigitalWaveformSource::TestDigitalWaveformSource() +{ +} + +TestDigitalWaveformSource::~TestDigitalWaveformSource() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Signal generation + +/** + @brief Generates a UART waveform + + @param wfm Waveform to fill + @param sampleperiod Interval between samples, in femtoseconds + @param depth Total number of samples to generate + @param baudrate The baudrate of the UART link + */ +void TestDigitalWaveformSource::GenerateUART( + SparseDigitalWaveform* wfm, + int64_t sampleperiod, + size_t depth, + int64_t baudrate) +{ + wfm->PrepareForCpuAccess(); + wfm->m_timescale = sampleperiod; + + int64_t timeWindow = depth * sampleperiod; + + const int64_t bitPeriodFs = FS_PER_SECOND / baudrate; + const int64_t bitPeriod = bitPeriodFs/sampleperiod; + + int64_t numBits = timeWindow / bitPeriodFs; + + wfm->Resize(numBits+1); + + int64_t currentTime = 0; + + int64_t bitCount = 0; + + auto push = [&bitCount,bitPeriod,numBits,depth,¤tTime] (SparseDigitalWaveform* w, bool v) -> bool + { + w->m_offsets.push_back(currentTime); + int64_t duration = currentTime + bitPeriod < (int64_t)depth ? bitPeriod : depth - currentTime; + w->m_durations.push_back(duration); + w->m_samples.push_back(v); + currentTime += duration; + bitCount++; + return bitCount > numBits+1; + }; + + // Idle initial + push(wfm, true); + + string msg = "Hello World from ngscopeclient UART !\n"; + + while(bitCount < numBits) + { + for(uint8_t c : msg) + { + // Start bit + if(push(wfm, false)) break; + + // Data bits (LSB first) + for(int i = 0; i < 8; i++) + { + bool bit = (c >> i) & 1; + if(push(wfm, bit)) break; + } + + // Stop bit + if(push(wfm, true)) break; + } + } + + // Idle final + wfm->m_offsets.push_back(currentTime); + wfm->m_durations.push_back(1); + wfm->m_samples.push_back(true); + + wfm->MarkSamplesModifiedFromCpu(); + wfm->MarkTimestampsModifiedFromCpu(); +} + +/** + @brief Generates a UART clock waveform + + @param wfm Waveform to fill + @param sampleperiod Interval between samples, in femtoseconds + @param depth Total number of samples to generate + @param baudrate The baudrate of the UART link +*/ +void TestDigitalWaveformSource::GenerateUARTClock( + SparseDigitalWaveform* wfm, + int64_t sampleperiod, + size_t depth, + int64_t baudrate) +{ + wfm->PrepareForCpuAccess(); + wfm->m_timescale = sampleperiod; + + int64_t timeWindow = depth * sampleperiod; + + const int64_t bitPeriodFs = (FS_PER_SECOND / baudrate)/2; + const int64_t bitPeriod = bitPeriodFs/sampleperiod; + + int64_t numBits = timeWindow / bitPeriodFs; + + wfm->Resize(numBits+1); + + int64_t currentTime = 0; + + int64_t bitCount = 0; + + auto push = [&bitCount,bitPeriod,depth,¤tTime](SparseDigitalWaveform* w, bool v) + { + w->m_offsets.push_back(currentTime); + int64_t duration = currentTime + bitPeriod < (int64_t)depth ? bitPeriod : depth - currentTime; + w->m_durations.push_back(duration); + w->m_samples.push_back(v); + currentTime+=duration; + bitCount++; + }; + + while(bitCount < numBits) + { + push(wfm, false); + push(wfm, true); + } + + // Idle final + wfm->m_offsets.push_back(depth); + wfm->m_durations.push_back(1); + wfm->m_samples.push_back(true); + + wfm->MarkSamplesModifiedFromCpu(); + wfm->MarkTimestampsModifiedFromCpu(); +} + +void TestDigitalWaveformSource::GenerateSPI(SparseDigitalWaveform* cs, SparseDigitalWaveform* sclk, SparseDigitalWaveform* mosi, int64_t sampleperiod, size_t depth) +{ + cs->PrepareForCpuAccess(); + cs->m_timescale = sampleperiod; + sclk->PrepareForCpuAccess(); + sclk->m_timescale = sampleperiod; + mosi->PrepareForCpuAccess(); + mosi->m_timescale = sampleperiod; + + string msg = "Hello ngscopeclient from SPI !\n"; + + int64_t t = 0; + int64_t numBits = msg.size()*8 + 4; + const int64_t bitPeriod = depth/numBits; + const int64_t half = bitPeriod / 2; + + cs->Resize(numBits+1); + sclk->Resize(numBits*2+1); + mosi->Resize(numBits+1); + + auto push = [](SparseDigitalWaveform* w, int64_t time, int64_t duration, bool v) + { + w->m_offsets.push_back(time); + w->m_durations.push_back(duration); + w->m_samples.push_back(v); + }; + + // Idle state + push(cs, t, 3*bitPeriod, true); + push(sclk, t, 3*bitPeriod, false); + push(mosi, t, 3*bitPeriod, false); + + t += (3*bitPeriod); + + // Assert CS + push(cs, t, bitPeriod*msg.size(), false); + + for(uint8_t c : msg) + { + for(int i = 7; i >= 0; i--) + { + bool bit = (c >> i) & 1; + + // Data setup + push(mosi, t, bitPeriod, bit); + push(sclk, t, half, false); + + t += half; + + // Clock high (sampling edge) + push(sclk, t, half, true); + + t += half; + } + } + + // Deassert CS + push(cs, t, bitPeriod, true); + push(sclk, t, bitPeriod, false); + push(mosi, t, bitPeriod, false); + t += bitPeriod; + // Finale Sample + push(cs, t, 1, true); + push(sclk, t, 1, false); + push(mosi, t, 1, false); + + cs->MarkSamplesModifiedFromCpu(); + cs->MarkTimestampsModifiedFromCpu(); + sclk->MarkSamplesModifiedFromCpu(); + sclk->MarkTimestampsModifiedFromCpu(); + mosi->MarkSamplesModifiedFromCpu(); + mosi->MarkTimestampsModifiedFromCpu(); +} + +void TestDigitalWaveformSource::GenerateParallel(std::vector &waveforms, int64_t sampleperiod, size_t depth) +{ + if(waveforms.size() != 9) + { + LogError("Invalid waveforms size: exected 9, found %zu.\n",waveforms.size()); + return; + } + string msg = "\x01\x02\x04\x08\x10\x20\x40\x80\xFFHello ngscopeclient from UART !"; + + int64_t t = 0; + int64_t numBits = msg.size(); + const int64_t bitPeriod = depth/numBits; + const int64_t half = bitPeriod / 2; + + // Clock WF + auto wfClk = waveforms[0]; + wfClk->PrepareForCpuAccess(); + wfClk->m_timescale = sampleperiod; + wfClk->Resize(2*numBits+1); + // Parallel lines waveforms + for(int i = 0 ; i < 8 ; i++) + { + auto wf = waveforms[i+1]; + wf->PrepareForCpuAccess(); + wf->m_timescale = sampleperiod; + wf->Resize(numBits+1); + } + + auto push = [](SparseDigitalWaveform* w, int64_t time, int64_t duration, bool v) + { + w->m_offsets.push_back(time); + w->m_durations.push_back(duration); + w->m_samples.push_back(v); + }; + + for(uint8_t c : msg) + { + for(int i = 0; i < 8; i++) + { + bool bit = (c >> i) & 1; + // Push data lines + push(waveforms[i+1], t, bitPeriod, bit); + } + push(wfClk, t, half, false); + + t += half; + + // Clock high (sampling edge) + push(wfClk, t, half, true); + + t += half; + } + + // Final sample + for(int i = 0; i < 8; i++) + { + push(waveforms[i+1], t, 1, false); + } + push(wfClk, t, 1, false); + + for(int i = 0 ; i < 9 ; i++) + { + auto wf = waveforms[i]; + wf->MarkSamplesModifiedFromCpu(); + wf->MarkTimestampsModifiedFromCpu(); + } +} diff --git a/scopehal/TestDigitalWaveformSource.h b/scopehal/TestDigitalWaveformSource.h new file mode 100644 index 00000000..4de8ff8d --- /dev/null +++ b/scopehal/TestDigitalWaveformSource.h @@ -0,0 +1,83 @@ +/*********************************************************************************************************************** +* * +* libscopehal * +* * +* Copyright (c) 2012-2026 Andrew D. Zonenberg and contributors * +* 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 Frederic BORRY + @brief Declaration of TestDigitalWaveformSource + @ingroup core + */ + +#ifndef TestDigitalWaveformSource_h +#define TestDigitalWaveformSource_h + +/** + @brief Helper class for generating test digital waveforms + + Used by DemoOscilloscope + + @ingroup core + */ +class TestDigitalWaveformSource +{ +public: + TestDigitalWaveformSource(); + virtual ~TestDigitalWaveformSource(); + + TestDigitalWaveformSource(const TestDigitalWaveformSource&) =delete; + TestDigitalWaveformSource& operator=(const TestDigitalWaveformSource&) =delete; + + void GenerateUART( + SparseDigitalWaveform* wfm, + int64_t sampleperiod, + size_t depth, + int64_t baudrate = 115200); + + void GenerateUARTClock( + SparseDigitalWaveform* wfm, + int64_t sampleperiod, + size_t depth, + int64_t baudrate = 115200); + + void GenerateSPI( + SparseDigitalWaveform* cs, + SparseDigitalWaveform* sclk, + SparseDigitalWaveform* mosi, + int64_t sampleperiod, + size_t depth); + + void GenerateParallel(std::vector &waveforms, + int64_t sampleperiod, + size_t depth); + +protected: + +}; + +#endif