Skip to content

Commit

Permalink
Add runtime switching of pin logging, with export to VCD file (#8)
Browse files Browse the repository at this point in the history
* Add option to enable/disable/reset pin logging at runtime
* Add ability to export one of a regex of signals
  • Loading branch information
sjasonsmith committed Dec 10, 2021
1 parent 23a41b7 commit 9f21b26
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 64 deletions.
3 changes: 2 additions & 1 deletion library.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"dependencies":
{
"imgui": "https://github.com/p3p/imgui.git#pio_docking",
"implot": "https://github.com/p3p/implot.git#pio_master"
"implot": "https://github.com/p3p/implot.git#pio_master",
"vcd-writer": "https://github.com/favorart/vcd-writer"
},
"build": {
"libLDFMode": "deep"
Expand Down
197 changes: 145 additions & 52 deletions src/MarlinSimulator/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#include "../HAL.h"
#include <src/MarlinCore.h>
#include <src/pins/pinsDebug.h>
#include <fstream>
#include <vcd_writer.h>
#include <regex>

Application::Application() {
sim.vis.create();
Expand Down Expand Up @@ -72,67 +75,157 @@ Application::Application() {
});

user_interface.addElement<UiWindow>("Signal Analyser", [this](UiWindow* window){
struct ScrollingData {
int MaxSize;
int Offset;
ImVector<ImPlotPoint> Data;
ScrollingData() {
MaxSize = 100000;
Offset = 0;
Data.reserve(MaxSize);
if (!Gpio::isLoggingEnabled()) {
if (ImGui::Button("Enable Pin Logging")) {
Gpio::setLoggingEnabled(true);
}
void AddPoint(double x, double y) {
if (Data.size() < MaxSize)
Data.push_back(ImPlotPoint(x,y));
else {
Data[Offset] = ImPlotPoint(x,y);
Offset = (Offset + 1) % MaxSize;
}
}
else {
if (ImGui::Button("Disable Pin Logging")) {
Gpio::setLoggingEnabled(false);
}
ImGui::SameLine();
if (ImGui::Button("Reset logs")) {
Gpio::resetLogs();
}
void Erase() {
if (Data.size() > 0) {
Data.shrink(0);
Offset = 0;

struct ScrollingData {
int MaxSize;
int Offset;
ImVector<ImPlotPoint> Data;
ScrollingData() {
MaxSize = 100000;
Offset = 0;
Data.reserve(MaxSize);
}
void AddPoint(double x, double y) {
if (Data.size() < MaxSize)
Data.push_back(ImPlotPoint(x,y));
else {
Data[Offset] = ImPlotPoint(x,y);
Offset = (Offset + 1) % MaxSize;
}
}
void Erase() {
if (Data.size() > 0) {
Data.shrink(0);
Offset = 0;
}
}
};

static pin_type monitor_pin = X_STEP_PIN;
static const char* label = "Select Pin";
static char* active_label = (char *)label;
if(ImGui::BeginCombo("##Select Pin", active_label)) {
for (auto p : pin_array) {
if (ImGui::Selectable(p.name, p.pin == monitor_pin)) {
monitor_pin = p.pin;
active_label = (char *)p.name;
}
if (p.pin == monitor_pin) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
};

static pin_type monitor_pin = X_STEP_PIN;
static const char* label = "Select Pin";
static char* active_label = (char *)label;
if(ImGui::BeginCombo("##Select Pin", active_label)) {
for (auto p : pin_array) {
if (ImGui::Selectable(p.name, p.pin == monitor_pin)) {
monitor_pin = p.pin;
active_label = (char *)p.name;

if (Gpio::pin_map[monitor_pin].event_log.size()) {
ScrollingData sdata;

pin_log_data last{};
for (auto v : Gpio::pin_map[monitor_pin].event_log) {
if (last.timestamp) sdata.AddPoint(v.timestamp, last.value);
sdata.AddPoint(v.timestamp, v.value);
last = v;
}
sdata.AddPoint(Kernel::SimulationRuntime::nanos(), last.value);

static float window = 10000000000.0f;
ImGui::SliderFloat("Window", &window, 10.f, 100000000000.f,"%.0f ns", ImGuiSliderFlags_Logarithmic);
static float offset = 0.0f;
ImGui::SliderFloat("X offset", &offset, 0.f, 10000000000.f,"%.0f ns");
ImGui::SliderFloat("X offset", &offset, 0.f, 100000000000.f,"%.0f ns");
if (!ImPlot::GetCurrentContext()) ImPlot::CreateContext();
ImPlot::SetNextPlotLimitsX(Kernel::SimulationRuntime::nanos() - window - offset, Kernel::SimulationRuntime::nanos() - offset, ImGuiCond_Always);
ImPlot::SetNextPlotLimitsY(0.0f, 1.2f, ImGuiCond_Always);
static int rt_axis = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_LockMin;
if (ImPlot::BeginPlot("##Scrolling", "Time (ns)", NULL, ImVec2(-1,150), ImPlotAxisFlags_NoTickLabels | ImPlotFlags_Query, rt_axis, rt_axis)) {
ImPlot::PlotLine("pin", &sdata.Data[0].x, &sdata.Data[0].y, sdata.Data.size(), sdata.Offset, sizeof(ImPlotPoint));
ImPlot::EndPlot();
}
if (p.pin == monitor_pin) ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}

if (Gpio::pin_map[monitor_pin].event_log.size()) {
ScrollingData sdata;
static bool export_single_pin = false;
if (ImGui::Button("Export selected pin to file")) {
export_single_pin = true;
ImGuiFileDialog::Instance()->OpenDialog("PulseExportDlgKey", "Choose File", "Value Change Dump (*.vcd){.vcd},.*", ".");
}


pin_log_data last{};
for (auto v : Gpio::pin_map[monitor_pin].event_log) {
if (last.timestamp) sdata.AddPoint(v.timestamp, last.value);
sdata.AddPoint(v.timestamp, v.value);
last = v;
if (ImGui::Button("Export pins matching regex to file")) {
export_single_pin = false;
ImGuiFileDialog::Instance()->OpenDialog("PulseExportDlgKey", "Choose File", "Value Change Dump (*.vcd){.vcd},.*", ".");
}

static char export_regex[128] = "";
ImGui::SameLine();
ImGui::InputText("Pin regex", export_regex, sizeof(export_regex));

if (ImGuiFileDialog::Instance()->Display("PulseExportDlgKey", ImGuiWindowFlags_NoDocking)) {
try{
if (ImGuiFileDialog::Instance()->IsOk()) {
std::string image_filename = ImGuiFileDialog::Instance()->GetFilePathName();

using namespace vcd;

HeadPtr head = makeVCDHeader(static_cast<TimeScale>(50), TimeScaleUnit::ns, utils::now());
VCDWriter writer{image_filename, std::move(head)};

if (export_single_pin) {
std::string pin_name(active_label);
auto scope = pin_name.substr(0, pin_name.find_first_of('_'));
auto var = writer.register_var(scope, pin_name, VariableType::wire, 1);

if (Gpio::pin_map[monitor_pin].event_log.size() && pin_array[monitor_pin].is_digital) {
for (const auto &value : Gpio::pin_map[monitor_pin].event_log) {
writer.change(var, value.timestamp / 50, utils::format("%u", value.value));
}
}
}
else {
std::map<size_t, VarPtr> pin_to_var_map;
std::regex expression(export_regex);

for (auto pin : pin_array) {
std::string pin_name(pin.name);
bool regex_match = strlen(export_regex) == 0 || std::regex_search(pin_name, expression);
auto scope = pin_name.substr(0, pin_name.find_first_of('_'));
if (pin.is_digital && regex_match)
pin_to_var_map[pin.pin] = writer.register_var(scope, pin_name, VariableType::wire, 1);
}

std::multimap<uint64_t, std::pair<pin_t, uint16_t> > timestamp_pin_change_map;

for (auto pin : pin_array) {
if (pin.is_digital && pin_to_var_map.find(pin.pin) != pin_to_var_map.end())
for (const auto &data : Gpio::pin_map[pin.pin].event_log)
{
timestamp_pin_change_map.emplace(std::make_pair(data.timestamp, std::make_pair(pin.pin, data.value)));
}
}

auto timestamp_offset = timestamp_pin_change_map.begin()->first;
for (const auto &timestamp : timestamp_pin_change_map) {
writer.change(pin_to_var_map[timestamp.second.first], (timestamp.first - timestamp_offset) / 50, utils::format("%u", timestamp.second.second));
}
}
}
}
catch (const std::exception& e)
{
auto test = e.what();
}
ImGuiFileDialog::Instance()->Close();
}
sdata.AddPoint(Kernel::SimulationRuntime::nanos(), last.value);

static float window = 10000000000.0f;
ImGui::SliderFloat("Window", &window, 10.f, 100000000000.f,"%.0f ns");
static float offset = 0.0f;
ImGui::SliderFloat("X offset", &offset, 0.f, 10000000000.f,"%.0f ns");
// ImPlot::SetNextPlotLimitsX(Kernel::SimulationRuntime::nanos() - window - offset, Kernel::SimulationRuntime::nanos() - offset, ImGuiCond_Always);
// ImPlot::SetNextPlotLimitsY(0.0f, 1.2f, ImGuiCond_Always);
// static int rt_axis = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_LockMin;
// if (ImPlot::BeginPlot("##Scrolling", "Time (ns)", NULL, ImVec2(-1,150), ImPlotAxisFlags_NoTickLabels | ImPlotFlags_Query, rt_axis, rt_axis)) {
// ImPlot::PlotLine("pin", &sdata.Data[0].x, &sdata.Data[0].y, sdata.Data.size(), sdata.Offset, sizeof(ImPlotPoint));
// ImPlot::EndPlot();
// }
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/MarlinSimulator/hardware/Gpio.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "Gpio.h"

pin_data Gpio::pin_map[Gpio::pin_count] = {};

bool Gpio::logging_enabled = false;
49 changes: 39 additions & 10 deletions src/MarlinSimulator/hardware/Gpio.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ struct pin_data {
std::atomic_uint8_t mode;
std::atomic_uint16_t value;
std::vector<std::function<void(GpioEvent&)>> callbacks;
bool event_log_enabled = false;
std::deque<pin_log_data> event_log;
};

Expand All @@ -83,9 +82,13 @@ class Gpio {

static void set_pin_value(const pin_type pin, const uint16_t value) {
if (!valid_pin(pin)) return;
pin_map[pin].value = value;
//pin_map[pin].event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin_map[pin].value});
//if (pin_map[pin].event_log.size() > 100000) pin_map[pin].event_log.pop_front();
if (value != pin_map[pin].value) { // Optimizes for size, but misses "meaningless" sets
pin_map[pin].value = value;
if (logging_enabled) {
pin_map[pin].event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin_map[pin].value});
if (pin_map[pin].event_log.size() > 100000) pin_map[pin].event_log.pop_front();
}
}
}

static uint16_t get_pin_value(const pin_type pin) {
Expand All @@ -103,12 +106,16 @@ class Gpio {

static void set(const pin_type pin, const uint16_t value) {
if (!valid_pin(pin)) return;
GpioEvent::Type evt_type = value > 1 ? GpioEvent::SET_VALUE : value > pin_map[pin].value ? GpioEvent::RISE : value < pin_map[pin].value ? GpioEvent::FALL : GpioEvent::NOP;
pin_map[pin].value = value;
GpioEvent evt(Kernel::TimeControl::getTicks(), pin, evt_type);
//pin_map[pin].event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin_map[pin].value});
//if (pin_map[pin].event_log.size() > 100000) pin_map[pin].event_log.pop_front();
for (auto callback : pin_map[pin].callbacks) callback(evt);
if (value != pin_map[pin].value) { // Optimizes for size, but misses "meaningless" sets
GpioEvent::Type evt_type = value > 1 ? GpioEvent::SET_VALUE : value > pin_map[pin].value ? GpioEvent::RISE : value < pin_map[pin].value ? GpioEvent::FALL : GpioEvent::NOP;
pin_map[pin].value = value;
GpioEvent evt(Kernel::TimeControl::getTicks(), pin, evt_type);
if (logging_enabled) {
pin_map[pin].event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin_map[pin].value});
if (pin_map[pin].event_log.size() > 100000) pin_map[pin].event_log.pop_front();
}
for (auto callback : pin_map[pin].callbacks) callback(evt);
}
}

static uint16_t get(const pin_type pin) {
Expand Down Expand Up @@ -159,5 +166,27 @@ class Gpio {
return pin_map[pin].attach(args...);
}

static void resetLogs() {
for (auto &pin : pin_map) {
// Seed each pin with an initial value to ensure important edges are not the first sample.
pin.event_log.clear();
pin.event_log.push_back(pin_log_data{Kernel::TimeControl::nanos(), pin.value});
}
}

static void setLoggingEnabled(bool enable) {
if (!logging_enabled && enable) {
resetLogs();
}
logging_enabled = enable;
}

static bool isLoggingEnabled() {
return logging_enabled;
}

static pin_data pin_map[pin_count];

private:
static bool logging_enabled;
};

0 comments on commit 9f21b26

Please sign in to comment.