From: Soeren Apel Date: Wed, 14 Jun 2017 06:30:43 +0000 (+0200) Subject: Merge DecoderStack into DecodeSignal X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=477472187338948c83bea5d790ead66034008296;p=pulseview.git Merge DecoderStack into DecodeSignal Several changes make up this commit, which unfortunately can't be separated: 1) Move decoder stack management from DecoderStack to DecodeSignal, thereby making DecoderStack unnecessary 2) Change the decoder stack from std::list to an std::vector for direct decoder access 3) Introduce logic_mux_thread which will take care of muxing the individual SignalBases' logic data into (cached) logic data that libsigrokdecode expects. This is necessary as we can no longer do simple bit mapping within a single logic data segment's logic data as we now may feed from multiple logic data segments at once 4) Refactored the creation of decode traces, making it more streamlined and flexible while simplifying the class interface 5) Refactored the auto-assignment of channels 6) Refactored is_decode_signal() 7) Reworked decode signal save/restore, allowing proper handling and with the decoder stack now being part of the signal, easier save/restore of the stack and its settings --- diff --git a/CMakeLists.txt b/CMakeLists.txt index b51753b6..39f0c07f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -335,7 +335,6 @@ if(ENABLE_DECODE) list(APPEND pulseview_SOURCES pv/binding/decoder.cpp pv/data/decodesignal.cpp - pv/data/decoderstack.cpp pv/data/decode/annotation.cpp pv/data/decode/decoder.cpp pv/data/decode/row.cpp @@ -347,7 +346,6 @@ if(ENABLE_DECODE) list(APPEND pulseview_HEADERS pv/data/decodesignal.hpp - pv/data/decoderstack.hpp pv/views/trace/decodetrace.hpp pv/widgets/decodergroupbox.hpp pv/widgets/decodermenu.hpp diff --git a/pv/data/decoderstack.cpp b/pv/data/decoderstack.cpp deleted file mode 100644 index c7504f58..00000000 --- a/pv/data/decoderstack.cpp +++ /dev/null @@ -1,468 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#include - -#include - -#include - -#include "decoderstack.hpp" - -#include -#include -#include -#include -#include -#include - -using std::lock_guard; -using std::mutex; -using std::unique_lock; -using std::deque; -using std::make_pair; -using std::max; -using std::min; -using std::list; -using std::shared_ptr; -using std::make_shared; -using std::vector; - -using boost::optional; - -using namespace pv::data::decode; - -namespace pv { -namespace data { - -const double DecoderStack::DecodeMargin = 1.0; -const double DecoderStack::DecodeThreshold = 0.2; -const int64_t DecoderStack::DecodeChunkLength = 10 * 1024 * 1024; -const unsigned int DecoderStack::DecodeNotifyPeriod = 1024; - -mutex DecoderStack::global_srd_mutex_; - -DecoderStack::DecoderStack(pv::Session &session, - const srd_decoder *const dec) : - session_(session), - start_time_(0), - samplerate_(0), - sample_count_(0), - frame_complete_(false), - samples_decoded_(0) -{ - connect(&session_, SIGNAL(frame_began()), - this, SLOT(on_new_frame())); - connect(&session_, SIGNAL(data_received()), - this, SLOT(on_data_received())); - connect(&session_, SIGNAL(frame_ended()), - this, SLOT(on_frame_ended())); - - stack_.push_back(make_shared(dec)); -} - -DecoderStack::~DecoderStack() -{ - if (decode_thread_.joinable()) { - interrupt_ = true; - input_cond_.notify_one(); - decode_thread_.join(); - } -} - -const list< shared_ptr >& DecoderStack::stack() const -{ - return stack_; -} - -void DecoderStack::push(shared_ptr decoder) -{ - assert(decoder); - stack_.push_back(decoder); -} - -void DecoderStack::remove(int index) -{ - assert(index >= 0); - assert(index < (int)stack_.size()); - - // Find the decoder in the stack - auto iter = stack_.begin(); - for (int i = 0; i < index; i++, iter++) - assert(iter != stack_.end()); - - // Delete the element - stack_.erase(iter); -} - -double DecoderStack::samplerate() const -{ - return samplerate_; -} - -const pv::util::Timestamp& DecoderStack::start_time() const -{ - return start_time_; -} - -int64_t DecoderStack::samples_decoded() const -{ - lock_guard decode_lock(output_mutex_); - return samples_decoded_; -} - -vector DecoderStack::get_visible_rows() const -{ - lock_guard lock(output_mutex_); - - vector rows; - - for (const shared_ptr &dec : stack_) { - assert(dec); - if (!dec->shown()) - continue; - - const srd_decoder *const decc = dec->decoder(); - assert(dec->decoder()); - - // Add a row for the decoder if it doesn't have a row list - if (!decc->annotation_rows) - rows.emplace_back(decc); - - // Add the decoder rows - for (const GSList *l = decc->annotation_rows; l; l = l->next) { - const srd_decoder_annotation_row *const ann_row = - (srd_decoder_annotation_row *)l->data; - assert(ann_row); - rows.emplace_back(decc, ann_row); - } - } - - return rows; -} - -uint64_t DecoderStack::inc_annotation_count() -{ - return (annotation_count_++); -} - -void DecoderStack::get_annotation_subset( - vector &dest, - const Row &row, uint64_t start_sample, - uint64_t end_sample) const -{ - lock_guard lock(output_mutex_); - - const auto iter = rows_.find(row); - if (iter != rows_.end()) - (*iter).second.get_annotation_subset(dest, - start_sample, end_sample); -} - -QString DecoderStack::error_message() -{ - lock_guard lock(output_mutex_); - return error_message_; -} - -void DecoderStack::clear() -{ - sample_count_ = 0; - annotation_count_ = 0; - frame_complete_ = false; - samples_decoded_ = 0; - error_message_ = QString(); - rows_.clear(); - class_rows_.clear(); -} - -void DecoderStack::begin_decode() -{ - if (decode_thread_.joinable()) { - interrupt_ = true; - input_cond_.notify_one(); - decode_thread_.join(); - } - - clear(); - - // Check that all decoders have the required channels - for (const shared_ptr &dec : stack_) - if (!dec->have_required_channels()) { - error_message_ = tr("One or more required channels " - "have not been specified"); - return; - } - - // Add classes - for (const shared_ptr &dec : stack_) { - assert(dec); - const srd_decoder *const decc = dec->decoder(); - assert(dec->decoder()); - - // Add a row for the decoder if it doesn't have a row list - if (!decc->annotation_rows) - rows_[Row(decc)] = decode::RowData(); - - // Add the decoder rows - for (const GSList *l = decc->annotation_rows; l; l = l->next) { - const srd_decoder_annotation_row *const ann_row = - (srd_decoder_annotation_row *)l->data; - assert(ann_row); - - const Row row(decc, ann_row); - - // Add a new empty row data object - rows_[row] = decode::RowData(); - - // Map out all the classes - for (const GSList *ll = ann_row->ann_classes; - ll; ll = ll->next) - class_rows_[make_pair(decc, - GPOINTER_TO_INT(ll->data))] = row; - } - } - - // We get the logic data of the first channel in the list. - // This works because we are currently assuming all - // logic signals have the same data/segment - pv::data::SignalBase *signalbase; - pv::data::Logic *data = nullptr; - - for (const shared_ptr &dec : stack_) - if (dec && !dec->channels().empty() && - ((signalbase = (*dec->channels().begin()).second.get())) && - ((data = signalbase->logic_data().get()))) - break; - - if (!data) - return; - - // Check we have a segment of data - const deque< shared_ptr > &segments = - data->logic_segments(); - if (segments.empty()) - return; - segment_ = segments.front(); - - // Get the samplerate and start time - start_time_ = segment_->start_time(); - samplerate_ = segment_->samplerate(); - if (samplerate_ == 0.0) - samplerate_ = 1.0; - - interrupt_ = false; - decode_thread_ = std::thread(&DecoderStack::decode_proc, this); -} - -uint64_t DecoderStack::max_sample_count() const -{ - uint64_t max_sample_count = 0; - - for (const auto& row : rows_) - max_sample_count = max(max_sample_count, - row.second.get_max_sample()); - - return max_sample_count; -} - -optional DecoderStack::wait_for_data() const -{ - unique_lock input_lock(input_mutex_); - - // Do wait if we decoded all samples but we're still capturing - // Do not wait if we're done capturing - while (!interrupt_ && !frame_complete_ && - (samples_decoded_ >= sample_count_) && - (session_.get_capture_state() != Session::Stopped)) { - - input_cond_.wait(input_lock); - } - - // Return value is valid if we're not aborting the decode, - return boost::make_optional(!interrupt_ && - // and there's more work to do... - (samples_decoded_ < sample_count_ || !frame_complete_) && - // and if the end of the data hasn't been reached yet - (!((samples_decoded_ >= sample_count_) && (session_.get_capture_state() == Session::Stopped))), - sample_count_); -} - -void DecoderStack::decode_data( - const int64_t abs_start_samplenum, const int64_t sample_count, const unsigned int unit_size, - srd_session *const session) -{ - const unsigned int chunk_sample_count = - DecodeChunkLength / segment_->unit_size(); - - for (int64_t i = abs_start_samplenum; !interrupt_ && i < sample_count; - i += chunk_sample_count) { - - const int64_t chunk_end = min( - i + chunk_sample_count, sample_count); - const uint8_t* chunk = segment_->get_samples(i, chunk_end); - - if (srd_session_send(session, i, chunk_end, chunk, - (chunk_end - i) * unit_size, unit_size) != SRD_OK) { - error_message_ = tr("Decoder reported an error"); - delete[] chunk; - break; - } - delete[] chunk; - - { - lock_guard lock(output_mutex_); - samples_decoded_ = chunk_end; - } - } -} - -void DecoderStack::decode_proc() -{ - optional sample_count; - srd_session *session; - srd_decoder_inst *prev_di = nullptr; - - assert(segment_); - - // Prevent any other decode threads from accessing libsigrokdecode - lock_guard srd_lock(global_srd_mutex_); - - // Create the session - srd_session_new(&session); - assert(session); - - // Create the decoders - const unsigned int unit_size = segment_->unit_size(); - - for (const shared_ptr &dec : stack_) { - srd_decoder_inst *const di = dec->create_decoder_inst(session); - - if (!di) { - error_message_ = tr("Failed to create decoder instance"); - srd_session_destroy(session); - return; - } - - if (prev_di) - srd_inst_stack (session, prev_di, di); - - prev_di = di; - } - - // Get the intial sample count - { - unique_lock input_lock(input_mutex_); - sample_count = sample_count_ = segment_->get_sample_count(); - } - - // Start the session - srd_session_metadata_set(session, SRD_CONF_SAMPLERATE, - g_variant_new_uint64((uint64_t)samplerate_)); - - srd_pd_output_callback_add(session, SRD_OUTPUT_ANN, - DecoderStack::annotation_callback, this); - - srd_session_start(session); - - int64_t abs_start_samplenum = 0; - do { - decode_data(abs_start_samplenum, *sample_count, unit_size, session); - abs_start_samplenum = *sample_count; - } while (error_message_.isEmpty() && (sample_count = wait_for_data())); - - // Make sure all annotations are known to the frontend - new_annotations(); - - // Destroy the session - srd_session_destroy(session); -} - -void DecoderStack::annotation_callback(srd_proto_data *pdata, void *decoder_stack) -{ - assert(pdata); - assert(decoder); - - DecoderStack *const ds = (DecoderStack*)decoder_stack; - assert(ds); - - lock_guard lock(ds->output_mutex_); - - const Annotation a(pdata); - - // Find the row - assert(pdata->pdo); - assert(pdata->pdo->di); - const srd_decoder *const decc = pdata->pdo->di->decoder; - assert(decc); - - auto row_iter = ds->rows_.end(); - - // Try looking up the sub-row of this class - const auto r = ds->class_rows_.find(make_pair(decc, a.format())); - if (r != ds->class_rows_.end()) - row_iter = ds->rows_.find((*r).second); - else { - // Failing that, use the decoder as a key - row_iter = ds->rows_.find(Row(decc)); - } - - assert(row_iter != ds->rows_.end()); - if (row_iter == ds->rows_.end()) { - qDebug() << "Unexpected annotation: decoder = " << decc << - ", format = " << a.format(); - assert(false); - return; - } - - // Add the annotation - (*row_iter).second.push_annotation(a); - - // Notify the frontend every DecodeNotifyPeriod annotations - if (ds->inc_annotation_count() % DecodeNotifyPeriod == 0) - ds->new_annotations(); -} - -void DecoderStack::on_new_frame() -{ - begin_decode(); -} - -void DecoderStack::on_data_received() -{ - { - unique_lock lock(input_mutex_); - if (segment_) - sample_count_ = segment_->get_sample_count(); - } - input_cond_.notify_one(); -} - -void DecoderStack::on_frame_ended() -{ - { - unique_lock lock(input_mutex_); - if (segment_) - frame_complete_ = true; - } - input_cond_.notify_one(); -} - -} // namespace data -} // namespace pv diff --git a/pv/data/decoderstack.hpp b/pv/data/decoderstack.hpp deleted file mode 100644 index 37e4888f..00000000 --- a/pv/data/decoderstack.hpp +++ /dev/null @@ -1,190 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2012 Joel Holdsworth - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - */ - -#ifndef PULSEVIEW_PV_DATA_DECODERSTACK_HPP -#define PULSEVIEW_PV_DATA_DECODERSTACK_HPP - -#include "signaldata.hpp" - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include - -using std::atomic; -using std::condition_variable; -using std::list; -using std::map; -using std::mutex; -using std::pair; -using std::shared_ptr; -using std::vector; - -struct srd_decoder; -struct srd_decoder_annotation_row; -struct srd_channel; -struct srd_proto_data; -struct srd_session; - -namespace DecoderStackTest { -struct TwoDecoderStack; -} - -namespace pv { - -class Session; - -namespace view { -class LogicSignal; -} - -namespace data { - -class LogicSegment; - -namespace decode { -class Annotation; -class Decoder; -} - -class Logic; - -class DecoderStack : public QObject -{ - Q_OBJECT - -private: - static const double DecodeMargin; - static const double DecodeThreshold; - static const int64_t DecodeChunkLength; - static const unsigned int DecodeNotifyPeriod; - -public: - DecoderStack(pv::Session &session, const srd_decoder *const dec); - - virtual ~DecoderStack(); - - const list< shared_ptr >& stack() const; - void push(shared_ptr decoder); - void remove(int index); - - double samplerate() const; - - const pv::util::Timestamp& start_time() const; - - int64_t samples_decoded() const; - - vector get_visible_rows() const; - - /** - * Helper function for static annotation_callback(), - * must be public so the function can access it. - * Don't use from outside this class. - */ - uint64_t inc_annotation_count(); - - /** - * Extracts sorted annotations between two period into a vector. - */ - void get_annotation_subset( - vector &dest, - const decode::Row &row, uint64_t start_sample, - uint64_t end_sample) const; - - QString error_message(); - - void clear(); - - uint64_t max_sample_count() const; - - void begin_decode(); - -private: - boost::optional wait_for_data() const; - - void decode_data(const int64_t abs_start_samplenum, const int64_t sample_count, - const unsigned int unit_size, srd_session *const session); - - void decode_proc(); - - static void annotation_callback(srd_proto_data *pdata, void *decoder_stack); - -private Q_SLOTS: - void on_new_frame(); - - void on_data_received(); - - void on_frame_ended(); - -Q_SIGNALS: - void new_annotations(); - -private: - pv::Session &session_; - - pv::util::Timestamp start_time_; - double samplerate_; - - /** - * This mutex prevents more than one thread from accessing - * libsigrokdecode concurrently. - * @todo A proper solution should be implemented to allow multiple - * decode operations in parallel. - */ - static mutex global_srd_mutex_; - - list< shared_ptr > stack_; - - shared_ptr segment_; - - mutable mutex input_mutex_; - mutable condition_variable input_cond_; - int64_t sample_count_, annotation_count_; - bool frame_complete_; - - mutable mutex output_mutex_; - int64_t samples_decoded_; - - map rows_; - - map, decode::Row> class_rows_; - - QString error_message_; - - std::thread decode_thread_; - atomic interrupt_; - - friend struct DecoderStackTest::TwoDecoderStack; -}; - -} // namespace data -} // namespace pv - -#endif // PULSEVIEW_PV_DATA_DECODERSTACK_HPP diff --git a/pv/data/decodesignal.cpp b/pv/data/decodesignal.cpp index 4061ef27..14bef5d4 100644 --- a/pv/data/decodesignal.cpp +++ b/pv/data/decodesignal.cpp @@ -19,6 +19,8 @@ #include +#include + #include "logic.hpp" #include "logicsegment.hpp" #include "decodesignal.hpp" @@ -27,79 +29,110 @@ #include #include #include -#include #include +using boost::optional; +using std::lock_guard; +using std::make_pair; using std::make_shared; using std::min; using std::shared_ptr; +using std::unique_lock; +using pv::data::decode::Annotation; using pv::data::decode::Decoder; using pv::data::decode::Row; namespace pv { namespace data { -DecodeSignal::DecodeSignal(shared_ptr decoder_stack, - const unordered_set< shared_ptr > &all_signals) : - SignalBase(nullptr, SignalBase::DecodeChannel), - decoder_stack_(decoder_stack), - all_signals_(all_signals) -{ - set_name(QString::fromUtf8(decoder_stack_->stack().front()->decoder()->name)); +const double DecodeSignal::DecodeMargin = 1.0; +const double DecodeSignal::DecodeThreshold = 0.2; +const int64_t DecodeSignal::DecodeChunkLength = 10 * 1024 * 1024; +const unsigned int DecodeSignal::DecodeNotifyPeriod = 1024; - update_channel_list(); - auto_assign_signals(); +mutex DecodeSignal::global_srd_mutex_; - connect(decoder_stack_.get(), SIGNAL(new_annotations()), - this, SLOT(on_new_annotations())); -} -DecodeSignal::~DecodeSignal() +DecodeSignal::DecodeSignal(pv::Session &session) : + SignalBase(nullptr, SignalBase::DecodeChannel), + session_(session), + logic_mux_data_invalid_(false), + start_time_(0), + samplerate_(0), + sample_count_(0), + annotation_count_(0), + samples_decoded_(0), + frame_complete_(false) { + connect(&session_, SIGNAL(capture_state_changed(int)), + this, SLOT(on_capture_state_changed(int))); + connect(&session_, SIGNAL(data_received()), + this, SLOT(on_data_received())); + connect(&session_, SIGNAL(frame_ended()), + this, SLOT(on_frame_ended())); + + set_name(tr("Empty decoder signal")); } -bool DecodeSignal::is_decode_signal() const +DecodeSignal::~DecodeSignal() { - return true; -} + if (decode_thread_.joinable()) { + decode_interrupt_ = true; + decode_input_cond_.notify_one(); + decode_thread_.join(); + } -shared_ptr DecodeSignal::decoder_stack() const -{ - return decoder_stack_; + if (logic_mux_thread_.joinable()) { + logic_mux_interrupt_ = true; + logic_mux_cond_.notify_one(); + logic_mux_thread_.join(); + } } -const list< shared_ptr >& DecodeSignal::decoder_stack_list() const +const vector< shared_ptr >& DecodeSignal::decoder_stack() const { - return decoder_stack_->stack(); + return stack_; } void DecodeSignal::stack_decoder(srd_decoder *decoder) { assert(decoder); - assert(decoder_stack); - decoder_stack_->push(make_shared(decoder)); + stack_.push_back(make_shared(decoder)); + + // Set name if this decoder is the first in the list + if (stack_.size() == 1) + set_name(QString::fromUtf8(decoder->name)); // Include the newly created decode channels in the channel list update_channel_list(); auto_assign_signals(); - decoder_stack_->begin_decode(); + begin_decode(); } void DecodeSignal::remove_decoder(int index) { - decoder_stack_->remove(index); + assert(index >= 0); + assert(index < (int)stack_.size()); + + // Find the decoder in the stack + auto iter = stack_.begin(); + for (int i = 0; i < index; i++, iter++) + assert(iter != stack_.end()); + + // Delete the element + stack_.erase(iter); + + // Update channels and decoded data update_channel_list(); - decoder_stack_->begin_decode(); + begin_decode(); } bool DecodeSignal::toggle_decoder_visibility(int index) { - const list< shared_ptr > stack(decoder_stack_->stack()); - - auto iter = stack.cbegin(); + auto iter = stack_.cbegin(); for (int i = 0; i < index; i++, iter++) - assert(iter != stack.end()); + assert(iter != stack_.end()); shared_ptr dec = *iter; @@ -113,43 +146,127 @@ bool DecodeSignal::toggle_decoder_visibility(int index) return state; } +void DecodeSignal::reset_decode() +{ + sample_count_ = 0; + annotation_count_ = 0; + frame_complete_ = false; + samples_decoded_ = 0; + error_message_ = QString(); + rows_.clear(); + class_rows_.clear(); +} + void DecodeSignal::begin_decode() { - decoder_stack_->begin_decode(); + if (decode_thread_.joinable()) { + decode_interrupt_ = true; + decode_input_cond_.notify_one(); + decode_thread_.join(); + } + + if (logic_mux_thread_.joinable()) { + logic_mux_interrupt_ = true; + logic_mux_cond_.notify_one(); + logic_mux_thread_.join(); + } + + reset_decode(); + + // Check that all decoders have the required channels + for (const shared_ptr &dec : stack_) + if (!dec->have_required_channels()) { + error_message_ = tr("One or more required channels " + "have not been specified"); + return; + } + + // Add annotation classes + for (const shared_ptr &dec : stack_) { + assert(dec); + const srd_decoder *const decc = dec->decoder(); + assert(dec->decoder()); + + // Add a row for the decoder if it doesn't have a row list + if (!decc->annotation_rows) + rows_[Row(decc)] = decode::RowData(); + + // Add the decoder rows + for (const GSList *l = decc->annotation_rows; l; l = l->next) { + const srd_decoder_annotation_row *const ann_row = + (srd_decoder_annotation_row *)l->data; + assert(ann_row); + + const Row row(decc, ann_row); + + // Add a new empty row data object + rows_[row] = decode::RowData(); + + // Map out all the classes + for (const GSList *ll = ann_row->ann_classes; + ll; ll = ll->next) + class_rows_[make_pair(decc, + GPOINTER_TO_INT(ll->data))] = row; + } + } + + // Make sure the logic output data is complete and up-to-date + logic_mux_thread_ = std::thread(&DecodeSignal::logic_mux_proc, this); + + // Update the samplerate and start time + start_time_ = segment_->start_time(); + samplerate_ = segment_->samplerate(); + if (samplerate_ == 0.0) + samplerate_ = 1.0; + + decode_interrupt_ = false; + decode_thread_ = std::thread(&DecodeSignal::decode_proc, this); } QString DecodeSignal::error_message() const { - return decoder_stack_->error_message(); + lock_guard lock(output_mutex_); + return error_message_; } -const list DecodeSignal::get_channels() const +const vector DecodeSignal::get_channels() const { return channels_; } void DecodeSignal::auto_assign_signals() { + bool new_assignment = false; + // Try to auto-select channels that don't have signals assigned yet for (data::DecodeChannel &ch : channels_) { if (ch.assigned_signal) continue; - for (shared_ptr s : all_signals_) - if (s->logic_data() && (ch.name.toLower().contains(s->name().toLower()))) + for (shared_ptr s : session_.signalbases()) + if (s->logic_data() && (ch.name.toLower().contains(s->name().toLower()))) { ch.assigned_signal = s.get(); + new_assignment = true; + } + } + + if (new_assignment) { + logic_mux_data_invalid_ = true; + channels_updated(); } } void DecodeSignal::assign_signal(const uint16_t channel_id, const SignalBase *signal) { for (data::DecodeChannel &ch : channels_) - if (ch.id == channel_id) + if (ch.id == channel_id) { ch.assigned_signal = signal; + logic_mux_data_invalid_ = true; + } channels_updated(); - decoder_stack_->begin_decode(); + begin_decode(); } void DecodeSignal::set_initial_pin_state(const uint16_t channel_id, const int init_state) @@ -160,17 +277,17 @@ void DecodeSignal::set_initial_pin_state(const uint16_t channel_id, const int in channels_updated(); - decoder_stack_->begin_decode(); + begin_decode(); } double DecodeSignal::samplerate() const { - return decoder_stack_->samplerate(); + return samplerate_; } const pv::util::Timestamp& DecodeSignal::start_time() const { - return decoder_stack_->start_time(); + return start_time_; } int64_t DecodeSignal::get_working_sample_count() const @@ -202,12 +319,38 @@ int64_t DecodeSignal::get_working_sample_count() const int64_t DecodeSignal::get_decoded_sample_count() const { - return decoder_stack_->samples_decoded(); + lock_guard decode_lock(output_mutex_); + return samples_decoded_; } vector DecodeSignal::visible_rows() const { - return decoder_stack_->get_visible_rows(); + lock_guard lock(output_mutex_); + + vector rows; + + for (const shared_ptr &dec : stack_) { + assert(dec); + if (!dec->shown()) + continue; + + const srd_decoder *const decc = dec->decoder(); + assert(dec->decoder()); + + // Add a row for the decoder if it doesn't have a row list + if (!decc->annotation_rows) + rows.emplace_back(decc); + + // Add the decoder rows + for (const GSList *l = decc->annotation_rows; l; l = l->next) { + const srd_decoder_annotation_row *const ann_row = + (srd_decoder_annotation_row *)l->data; + assert(ann_row); + rows.emplace_back(decc, ann_row); + } + } + + return rows; } void DecodeSignal::get_annotation_subset( @@ -215,19 +358,42 @@ void DecodeSignal::get_annotation_subset( const decode::Row &row, uint64_t start_sample, uint64_t end_sample) const { - return decoder_stack_->get_annotation_subset(dest, row, - start_sample, end_sample); + lock_guard lock(output_mutex_); + + const auto iter = rows_.find(row); + if (iter != rows_.end()) + (*iter).second.get_annotation_subset(dest, + start_sample, end_sample); +} + +void DecodeSignal::save_settings(QSettings &settings) const +{ + SignalBase::save_settings(settings); + + // TODO Save decoder stack, channel mapping and decoder options +} + +void DecodeSignal::restore_settings(QSettings &settings) +{ + SignalBase::restore_settings(settings); + + // TODO Restore decoder stack, channel mapping and decoder options +} + +uint64_t DecodeSignal::inc_annotation_count() +{ + return (annotation_count_++); } void DecodeSignal::update_channel_list() { - list prev_channels = channels_; + vector prev_channels = channels_; channels_.clear(); uint16_t id = 0; // Copy existing entries, create new as needed - for (shared_ptr decoder : decoder_stack_->stack()) { + for (shared_ptr decoder : stack_) { const srd_decoder* srd_d = decoder->decoder(); const GSList *l; @@ -237,7 +403,7 @@ void DecodeSignal::update_channel_list() bool ch_added = false; // Copy but update ID if this channel was in the list before - for (data::DecodeChannel ch : prev_channels) + for (data::DecodeChannel &ch : prev_channels) if (ch.pdch_ == pdch) { ch.id = id++; channels_.push_back(ch); @@ -260,7 +426,7 @@ void DecodeSignal::update_channel_list() bool ch_added = false; // Copy but update ID if this channel was in the list before - for (data::DecodeChannel ch : prev_channels) + for (data::DecodeChannel &ch : prev_channels) if (ch.pdch_ == pdch) { ch.id = id++; channels_.push_back(ch); @@ -278,13 +444,203 @@ void DecodeSignal::update_channel_list() } } + // Invalidate the logic output data if the channel assignment changed + if (prev_channels.size() != channels_.size()) { + // The number of channels changed, there's definitely a difference + logic_mux_data_invalid_ = true; + } else { + // Same number but assignment may still differ, so compare all channels + for (size_t i = 0; i < channels_.size(); i++) { + const data::DecodeChannel &p_ch = prev_channels[i]; + const data::DecodeChannel &ch = channels_[i]; + + if ((p_ch.pdch_ != ch.pdch_) || + (p_ch.assigned_signal != ch.assigned_signal)) { + logic_mux_data_invalid_ = true; + break; + } + } + + } + channels_updated(); } -void DecodeSignal::on_new_annotations() +void DecodeSignal::logic_mux_proc() { - // Forward the signal to the frontend + +} + +optional DecodeSignal::wait_for_data() const +{ + unique_lock input_lock(input_mutex_); + + // Do wait if we decoded all samples but we're still capturing + // Do not wait if we're done capturing + while (!decode_interrupt_ && !frame_complete_ && + (samples_decoded_ >= sample_count_) && + (session_.get_capture_state() != Session::Stopped)) { + + decode_input_cond_.wait(input_lock); + } + + // Return value is valid if we're not aborting the decode, + return boost::make_optional(!decode_interrupt_ && + // and there's more work to do... + (samples_decoded_ < sample_count_ || !frame_complete_) && + // and if the end of the data hasn't been reached yet + (!((samples_decoded_ >= sample_count_) && (session_.get_capture_state() == Session::Stopped))), + sample_count_); +} + +void DecodeSignal::decode_data( + const int64_t abs_start_samplenum, const int64_t sample_count, + srd_session *const session) +{ + const unsigned int unit_size = segment_->unit_size(); + const unsigned int chunk_sample_count = DecodeChunkLength / unit_size; + + for (int64_t i = abs_start_samplenum; + !decode_interrupt_ && (i < (abs_start_samplenum + sample_count)); + i += chunk_sample_count) { + + const int64_t chunk_end = min(i + chunk_sample_count, + abs_start_samplenum + sample_count); + + const uint8_t* chunk = segment_->get_samples(i, chunk_end); + + if (srd_session_send(session, i, chunk_end, chunk, + (chunk_end - i) * unit_size, unit_size) != SRD_OK) { + error_message_ = tr("Decoder reported an error"); + delete[] chunk; + break; + } + delete[] chunk; + + { + lock_guard lock(output_mutex_); + samples_decoded_ = chunk_end; + } + } +} + +void DecodeSignal::decode_proc() +{ + optional sample_count; + srd_session *session; + srd_decoder_inst *prev_di = nullptr; + + // Prevent any other decode threads from accessing libsigrokdecode + lock_guard srd_lock(global_srd_mutex_); + + // Create the session + srd_session_new(&session); + assert(session); + + // Create the decoders + for (const shared_ptr &dec : stack_) { + srd_decoder_inst *const di = dec->create_decoder_inst(session); + + if (!di) { + error_message_ = tr("Failed to create decoder instance"); + srd_session_destroy(session); + return; + } + + if (prev_di) + srd_inst_stack(session, prev_di, di); + + prev_di = di; + } + + // Get the initial sample count + { + unique_lock input_lock(input_mutex_); + sample_count = sample_count_ = get_working_sample_count(); + } + + // Start the session + srd_session_metadata_set(session, SRD_CONF_SAMPLERATE, + g_variant_new_uint64(samplerate_)); + + srd_pd_output_callback_add(session, SRD_OUTPUT_ANN, + DecodeSignal::annotation_callback, this); + + srd_session_start(session); + + int64_t abs_start_samplenum = 0; + do { + decode_data(abs_start_samplenum, *sample_count, session); + abs_start_samplenum = *sample_count; + } while (error_message_.isEmpty() && (sample_count = wait_for_data())); + + // Make sure all annotations are known to the frontend new_annotations(); + + // Destroy the session + srd_session_destroy(session); +} + +void DecodeSignal::annotation_callback(srd_proto_data *pdata, void *decode_signal) +{ + assert(pdata); + assert(decoder); + + DecodeSignal *const ds = (DecodeSignal*)decode_signal; + assert(ds); + + lock_guard lock(ds->output_mutex_); + + const decode::Annotation a(pdata); + + // Find the row + assert(pdata->pdo); + assert(pdata->pdo->di); + const srd_decoder *const decc = pdata->pdo->di->decoder; + assert(decc); + + auto row_iter = ds->rows_.end(); + + // Try looking up the sub-row of this class + const auto r = ds->class_rows_.find(make_pair(decc, a.format())); + if (r != ds->class_rows_.end()) + row_iter = ds->rows_.find((*r).second); + else { + // Failing that, use the decoder as a key + row_iter = ds->rows_.find(Row(decc)); + } + + assert(row_iter != ds->rows_.end()); + if (row_iter == ds->rows_.end()) { + qDebug() << "Unexpected annotation: decoder = " << decc << + ", format = " << a.format(); + assert(false); + return; + } + + // Add the annotation + (*row_iter).second.push_annotation(a); + + // Notify the frontend every DecodeNotifyPeriod annotations + if (ds->inc_annotation_count() % DecodeNotifyPeriod == 0) + ds->new_annotations(); +} + +void DecodeSignal::on_capture_state_changed(int state) +{ + // If a new acquisition was started, we need to start decoding from scratch + if (state == Session::Running) + begin_decode(); +} + +void DecodeSignal::on_data_received() +{ + logic_mux_cond_.notify_one(); +} + +void DecodeSignal::on_frame_ended() +{ + logic_mux_cond_.notify_one(); } } // namespace data diff --git a/pv/data/decodesignal.hpp b/pv/data/decodesignal.hpp index 03b8d0af..9c8d382c 100644 --- a/pv/data/decodesignal.hpp +++ b/pv/data/decodesignal.hpp @@ -20,22 +20,35 @@ #ifndef PULSEVIEW_PV_DATA_DECODESIGNAL_HPP #define PULSEVIEW_PV_DATA_DECODESIGNAL_HPP +#include +#include #include #include +#include + +#include #include #include +#include +#include #include #include -using std::list; +using std::atomic; +using std::condition_variable; +using std::map; +using std::mutex; +using std::pair; using std::unordered_set; using std::vector; using std::shared_ptr; namespace pv { +class Session; + namespace data { namespace decode { @@ -44,8 +57,8 @@ class Decoder; class Row; } -class DecoderStack; class Logic; +class LogicSegment; class SignalBase; class SignalData; @@ -64,23 +77,28 @@ class DecodeSignal : public SignalBase { Q_OBJECT +private: + static const double DecodeMargin; + static const double DecodeThreshold; + static const int64_t DecodeChunkLength; + static const unsigned int DecodeNotifyPeriod; + public: - DecodeSignal(shared_ptr decoder_stack, - const unordered_set< shared_ptr > &all_signals); + DecodeSignal(pv::Session &session); virtual ~DecodeSignal(); bool is_decode_signal() const; - shared_ptr decoder_stack() const; - const list< shared_ptr >& decoder_stack_list() const; + const vector< shared_ptr >& decoder_stack() const; void stack_decoder(srd_decoder *decoder); void remove_decoder(int index); bool toggle_decoder_visibility(int index); + void reset_decode(); void begin_decode(); QString error_message() const; - const list get_channels() const; + const vector get_channels() const; void auto_assign_signals(); void assign_signal(const uint16_t channel_id, const SignalBase *signal); @@ -108,20 +126,74 @@ public: const decode::Row &row, uint64_t start_sample, uint64_t end_sample) const; + virtual void save_settings(QSettings &settings) const; + + virtual void restore_settings(QSettings &settings); + + /** + * Helper function for static annotation_callback(), + * must be public so the function can access it. + * Don't use from outside this class. + */ + uint64_t inc_annotation_count(); + private: void update_channel_list(); + void logic_mux_proc(); + + boost::optional wait_for_data() const; + + void decode_data(const int64_t abs_start_samplenum, const int64_t sample_count, + srd_session *const session); + + void decode_proc(); + + static void annotation_callback(srd_proto_data *pdata, void *decode_signal); + Q_SIGNALS: void new_annotations(); void channels_updated(); private Q_SLOTS: - void on_new_annotations(); + void on_capture_state_changed(int state); + void on_data_received(); + void on_frame_ended(); private: - shared_ptr decoder_stack_; - const unordered_set< shared_ptr > &all_signals_; - list channels_; + pv::Session &session_; + + vector channels_; + + shared_ptr logic_mux_data_; + shared_ptr segment_; + bool logic_mux_data_invalid_; + + pv::util::Timestamp start_time_; + double samplerate_; + + int64_t sample_count_, annotation_count_, samples_decoded_; + + vector< shared_ptr > stack_; + map rows_; + map, decode::Row> class_rows_; + + /** + * This mutex prevents more than one thread from accessing + * libsigrokdecode concurrently. + * @todo A proper solution should be implemented to allow multiple + * decode operations in parallel. + */ + static mutex global_srd_mutex_; + + mutable mutex input_mutex_, output_mutex_; + mutable condition_variable decode_input_cond_, logic_mux_cond_; + bool frame_complete_; + + std::thread decode_thread_, logic_mux_thread_; + atomic decode_interrupt_, logic_mux_interrupt_; + + QString error_message_; }; } // namespace data diff --git a/pv/data/signalbase.cpp b/pv/data/signalbase.cpp index 8693c58b..bbb7fdc3 100644 --- a/pv/data/signalbase.cpp +++ b/pv/data/signalbase.cpp @@ -200,14 +200,7 @@ void SignalBase::set_conversion_type(ConversionType t) #ifdef ENABLE_DECODE bool SignalBase::is_decode_signal() const { - // DecodeSignal class overrides this method, all others shall return false - return false; -} - -shared_ptr SignalBase::decoder_stack() const -{ - // DecodeSignal class overrides this method, all others shall return nothing - return nullptr; + return (channel_type_ == DecodeChannel); } #endif diff --git a/pv/data/signalbase.hpp b/pv/data/signalbase.hpp index 97dbd847..f70c934f 100644 --- a/pv/data/signalbase.hpp +++ b/pv/data/signalbase.hpp @@ -148,14 +148,12 @@ public: void set_conversion_type(ConversionType t); #ifdef ENABLE_DECODE - virtual bool is_decode_signal() const; - - virtual shared_ptr decoder_stack() const; + bool is_decode_signal() const; #endif - void save_settings(QSettings &settings) const; + virtual void save_settings(QSettings &settings) const; - void restore_settings(QSettings &settings); + virtual void restore_settings(QSettings &settings); private: uint8_t convert_a2l_threshold(float threshold, float value); diff --git a/pv/session.cpp b/pv/session.cpp index 07917e58..f96fc869 100644 --- a/pv/session.cpp +++ b/pv/session.cpp @@ -17,6 +17,7 @@ * along with this program; if not, see . */ +#include #include #include @@ -32,7 +33,6 @@ #include "data/analog.hpp" #include "data/analogsegment.hpp" #include "data/decode/decoder.hpp" -#include "data/decoderstack.hpp" #include "data/logic.hpp" #include "data/logicsegment.hpp" #include "data/signalbase.hpp" @@ -173,7 +173,7 @@ void Session::save_settings(QSettings &settings) const { map dev_info; list key_list; - int stacks = 0, views = 0; + int decode_signals = 0, views = 0; if (device_) { shared_ptr hw_device = @@ -217,14 +217,8 @@ void Session::save_settings(QSettings &settings) const for (shared_ptr base : signalbases_) { #ifdef ENABLE_DECODE if (base->is_decode_signal()) { - shared_ptr decoder_stack = - base->decoder_stack(); - shared_ptr top_decoder = - decoder_stack->stack().front(); - - settings.beginGroup("decoder_stack" + QString::number(stacks++)); - settings.setValue("id", top_decoder->decoder()->id); - settings.setValue("name", top_decoder->decoder()->name); + settings.beginGroup("decode_signal" + QString::number(decode_signals++)); + base->save_settings(settings); settings.endGroup(); } else #endif @@ -235,7 +229,7 @@ void Session::save_settings(QSettings &settings) const } } - settings.setValue("decoder_stacks", stacks); + settings.setValue("decode_signals", decode_signals); // Save view states and their signal settings // Note: main_view must be saved as view0 @@ -319,14 +313,12 @@ void Session::restore_settings(QSettings &settings) // Restore decoders #ifdef ENABLE_DECODE - int stacks = settings.value("decoder_stacks").toInt(); - - for (int i = 0; i < stacks; i++) { - settings.beginGroup("decoder_stack" + QString::number(i++)); - - QString id = settings.value("id").toString(); - add_decoder(srd_decoder_get_by_id(id.toStdString().c_str())); + int decode_signals = settings.value("decode_signals").toInt(); + for (int i = 0; i < decode_signals; i++) { + settings.beginGroup("decode_signal" + QString::number(i++)); + // TODO Split up add_decoder() into add_decode_signal() and add_decoder(), + // then call add_decode_signal() and signal->restore_settings() here settings.endGroup(); } #endif @@ -658,35 +650,25 @@ bool Session::add_decoder(srd_decoder *const dec) if (!dec) return false; - map > channels; - shared_ptr decoder_stack; - try { - // Create the decoder - decoder_stack = make_shared(*this, dec); - - assert(decoder_stack); - assert(!decoder_stack->stack().empty()); - assert(decoder_stack->stack().front()); - decoder_stack->stack().front()->set_channels(channels); - // Create the decode signal shared_ptr signal = - make_shared(decoder_stack, signalbases_); + make_shared(*this); signalbases_.insert(signal); + // Add the decode signal to all views for (shared_ptr view : views_) view->add_decode_signal(signal); + + // Add decoder + signal->stack_decoder(dec); } catch (runtime_error e) { return false; } signals_changed(); - // Do an initial decode - decoder_stack->begin_decode(); - return true; } diff --git a/pv/views/trace/decodetrace.cpp b/pv/views/trace/decodetrace.cpp index ece5f36c..d8f04c58 100644 --- a/pv/views/trace/decodetrace.cpp +++ b/pv/views/trace/decodetrace.cpp @@ -54,10 +54,8 @@ extern "C" { #include using std::all_of; -using std::list; using std::make_pair; using std::max; -using std::make_pair; using std::map; using std::min; using std::out_of_range; @@ -304,8 +302,7 @@ void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form) init_state_map_.clear(); decoder_forms_.clear(); - const list< shared_ptr > &stack = - decode_signal_->decoder_stack_list(); + const vector< shared_ptr > &stack = decode_signal_->decoder_stack(); if (stack.empty()) { QLabel *const l = new QLabel( @@ -776,7 +773,7 @@ void DecodeTrace::create_decoder_form(int index, QFormLayout *const decoder_form = new QFormLayout; group->add_layout(decoder_form); - const list channels = decode_signal_->get_channels(); + const vector channels = decode_signal_->get_channels(); // Add the channels for (DecodeChannel ch : channels) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 896ba028..e43d30dc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -158,7 +158,6 @@ if(ENABLE_DECODE) list(APPEND pulseview_TEST_SOURCES ${PROJECT_SOURCE_DIR}/pv/binding/decoder.cpp ${PROJECT_SOURCE_DIR}/pv/data/decodesignal.cpp - ${PROJECT_SOURCE_DIR}/pv/data/decoderstack.cpp ${PROJECT_SOURCE_DIR}/pv/data/decode/annotation.cpp ${PROJECT_SOURCE_DIR}/pv/data/decode/decoder.cpp ${PROJECT_SOURCE_DIR}/pv/data/decode/row.cpp @@ -166,12 +165,10 @@ if(ENABLE_DECODE) ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.cpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.cpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.cpp - data/decoderstack.cpp ) list(APPEND pulseview_TEST_HEADERS ${PROJECT_SOURCE_DIR}/pv/data/decodesignal.hpp - ${PROJECT_SOURCE_DIR}/pv/data/decoderstack.hpp ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.hpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.hpp ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.hpp