Skip to content

Commit ba96fba

Browse files
committed
Analog waves working well
1 parent 7874b34 commit ba96fba

File tree

5 files changed

+135
-47
lines changed

5 files changed

+135
-47
lines changed

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,9 @@ A list of features that have not yet been implemented.
5757
* A colorscheme that works with white backgrounds.
5858
* Polish: Syntax highlighting of macros
5959
* Source window: Need a clean way to add multiple signals from the source to the waves at once.
60-
* Expose local nets in functions and tasks
61-
* Expose local variables in always blocks?
6260
* Time multiplier for waves (e.g. wave 1 unit is really X us/ns/ps/)
6361
* Wave + source should be able to figure out enums in waves.
6462
* Search for value in wave.
65-
* Analog signals.
6663
* Detect unrolled arrays in waves, make them expandable in the waveforms
6764
* Lazy / on-demand loading of VCD wave data. Not clear if this is useful since
6865
loading anything requires parsing the whole file anyway.

src/wave_image.cc

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
#include "wave_image.h"
2+
23
#include <cstdlib>
4+
#include <string_view>
35

46
namespace sv {
57

8+
char BraillePatternToAscii(uint8_t b) {
9+
constexpr std::string_view lo_lut = " .,:;a&#@";
10+
constexpr std::string_view hi_lut = " '\":PM&#@";
11+
int num_bits = 0;
12+
int num_lo_bits = 0;
13+
int num_hi_bits = 0;
14+
for (int i = 0; i < 8; ++i) {
15+
if ((b >> i) & 1) {
16+
num_bits++;
17+
// Count the number of bits in the upper and lower half of the character.
18+
if (i <= 4 && i != 2) {
19+
num_hi_bits++;
20+
} else {
21+
num_lo_bits++;
22+
}
23+
}
24+
}
25+
return (num_lo_bits < num_hi_bits ? hi_lut : lo_lut)[num_bits];
26+
}
27+
628
void WaveImage::SetPixel(int x, int y) {
729
if (x < 0 || x >= width_) return;
830
if (y < 0 || y >= height_) return;
9-
buffer_[y * width_ * x] = true;
31+
buffer_[y * width_ + x] = true;
1032
}
1133

1234
void WaveImage::DrawLine(int x0, int y0, int x1, int y1) {
13-
if (x0 < 0 || x1 < 0 || x0 >= width_ || x1 >= width_) return;
14-
if (y0 < 0 || y1 < 0 || y0 >= height_ || y1 >= height_) return;
1535
// Bresenham's Line Algorithm
1636
const bool steep = std::abs(y1 - y0) > std::abs(x1 - x0);
1737
if (steep) {
@@ -49,9 +69,9 @@ uint16_t WaveImage::GetBrailleChar(int x, int y) {
4969
// 1 4
5070
// 2 5
5171
// 6 7
52-
// nicode value is 0x28xx, with xx being the 8-bits above.
72+
// Unicode value is 0x28xx, with xx being the 8-bits above.
5373
uint16_t braille = 0x2800;
54-
if (x >= width_ / 2 || y >= height_ / 4) return braille;
74+
if (x < 0 || y < 0 || x >= width_ / 2 || y >= height_ / 4) return braille;
5575
braille |= buffer_[(4 * y + 0) * width_ + (2 * x + 0)] << 0;
5676
braille |= buffer_[(4 * y + 1) * width_ + (2 * x + 0)] << 1;
5777
braille |= buffer_[(4 * y + 2) * width_ + (2 * x + 0)] << 2;
@@ -64,20 +84,52 @@ uint16_t WaveImage::GetBrailleChar(int x, int y) {
6484
}
6585

6686
WaveImage RenderWaves(const WaveImageConfig &cfg, const std::vector<WaveData::Sample> &wave) {
67-
68-
69-
7087
// Braille patterns are 2x4 dots, "pixels".
71-
WaveImage img(cfg.char_w * 2, cfg.char_h * 4);
88+
const int img_w = cfg.char_w * 2;
89+
const int img_h = cfg.char_h * 4;
90+
WaveImage img(img_w, img_h);
7291

7392
int p0_idx = cfg.left_idx;
7493
while (p0_idx > 0 && wave[p0_idx].time > cfg.left_time) {
7594
p0_idx--;
7695
}
77-
// Draw stable signal up to the first point.
78-
if (wave[p0_idx].time > cfg.left_time) {
96+
// Make a list of all points and track the max value.
97+
std::vector<std::pair<double, double>> samples;
98+
double max = 0;
99+
double min = 0;
100+
for (int i = p0_idx; i < wave.size(); ++i) {
101+
double v = 0;
102+
switch (cfg.radix) {
103+
case Radix::kFloat:
104+
if (wave[i].value.size() <= 32) {
105+
v = BinStringToFp32(wave[i].value).value_or(0);
106+
} else {
107+
v = BinStringToFp64(wave[i].value).value_or(0);
108+
}
109+
break;
110+
case Radix::kSignedDecimal: v = BinStringToSigned(wave[i].value).value_or(0); break;
111+
default: v = BinStringToUnsigned(wave[i].value).value_or(0);
112+
}
113+
if (v > max) max = v;
114+
if (v < min) min = v;
115+
samples.push_back({static_cast<double>(wave[i].time) - cfg.left_time, v});
116+
if (wave[i].time >= cfg.right_time) break;
79117
}
80118

119+
const double x_scale = (img_w - 1) / static_cast<double>(cfg.right_time - cfg.left_time);
120+
const double y_scale = max == min ? 1 : ((img_h - 1) / (max - min));
121+
for (int i = 1; i < samples.size(); ++i) {
122+
const int x0 = std::lround(samples[i - 1].first * x_scale);
123+
const int y0 = img_h - 1 - std::lround(samples[i - 1].second * y_scale);
124+
const int x1 = std::lround(samples[i].first * x_scale);
125+
const int y1 = img_h - 1 - std::lround(samples[i].second * y_scale);
126+
if (cfg.analog_type == AnalogWaveType::kLinear) {
127+
img.DrawLine(x0, y0, x1, y1);
128+
} else if (cfg.analog_type == AnalogWaveType::kSampleAndHold) {
129+
img.DrawLine(x0, y0, x1, y0);
130+
img.DrawLine(x1, y0, x1, y1);
131+
}
132+
}
81133

82134
return img;
83135
}

src/wave_image.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class WaveImage {
2222
std::vector<bool> buffer_;
2323
};
2424

25+
enum class AnalogWaveType { kSampleAndHold = 0, kLinear };
26+
2527
struct WaveImageConfig {
2628
int char_w;
2729
int char_h;
@@ -30,8 +32,12 @@ struct WaveImageConfig {
3032
uint64_t left_time;
3133
uint64_t right_time;
3234
Radix radix = Radix::kBinary;
35+
AnalogWaveType analog_type;
3336
};
3437

3538
WaveImage RenderWaves(const WaveImageConfig &cfg, const std::vector<WaveData::Sample> &wave);
3639

40+
// Returns a close-enough ASCII character for the given braille pattern.
41+
char BraillePatternToAscii(uint8_t b);
42+
3743
} // namespace sv

src/waves_panel.cc

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ void WavesPanel::FindEdge(bool forward, bool *time_changed, bool *range_changed)
162162
void WavesPanel::SnapToValue() {
163163
const auto *item = visible_items_[line_idx_];
164164
if (item->signal == nullptr) return;
165-
auto time_per_char = TimePerChar();
166-
auto &wave = wave_data_->Wave(item->signal);
165+
const double time_per_char = TimePerChar();
166+
const std::vector<WaveData::Sample> &wave = wave_data_->Wave(item->signal);
167167
// See if there is an edge within the current cursor's character span
168168
const uint64_t left_time = left_time_ + cursor_pos_ * time_per_char;
169169
const uint64_t right_time = left_time_ + (cursor_pos_ + 1) * time_per_char;
@@ -416,7 +416,7 @@ void WavesPanel::Draw() {
416416
int left_sample_idx = wave_data_->FindSampleIndex(left_time_, item->signal);
417417
// Save the locations of transitions and times to fill in values.
418418
struct WaveValueInfo {
419-
int xpos;
419+
int xpos = 0;
420420
int size;
421421
uint64_t time;
422422
std::string value;
@@ -441,14 +441,27 @@ void WavesPanel::Draw() {
441441
const int num_rows = item->analog_rows > 1 ? item->analog_rows : 1;
442442
std::optional<WaveImage> analog_image;
443443
if (analog) {
444-
// TODO
444+
const WaveImageConfig cfg = {
445+
.char_w = max_w - wave_x,
446+
.char_h = item->analog_rows,
447+
.left_idx = left_sample_idx,
448+
.left_time = left_time_,
449+
.right_time = right_time_,
450+
.radix = item->radix,
451+
.analog_type = item->analog_type,
452+
};
453+
analog_image.emplace(RenderWaves(cfg, wave));
445454
}
446455
for (int render_row = 0; render_row < num_rows && row < max_h; render_row++, row++) {
447456
// Draw charachter by charachter
448457
wmove(w_, row, wave_x);
449458
for (int x = 0; x < max_w - wave_x; ++x) {
450459
if (analog) {
451-
// TODO
460+
if (unicode_) {
461+
AddUnicodeChar(w_, analog_image->GetBrailleChar(x, render_row));
462+
} else {
463+
waddch(w_, BraillePatternToAscii(analog_image->GetBrailleChar(x, render_row)));
464+
}
452465
} else {
453466
// Find what sample index corresponds to the right edge of this character.
454467
const int right_sample_idx = wave_data_->FindSampleIndex(
@@ -486,7 +499,8 @@ void WavesPanel::Draw() {
486499
} else {
487500
waddch(w_, '|');
488501
}
489-
// Only save value locations if they are at least 3 characters.
502+
// Only save value locations if they are at least 3 characters, comparing against the
503+
// previous one.
490504
if (x - wvi.xpos >= 3) {
491505
wvi.size = x - wvi.xpos;
492506
wvi.value = FormatValue(wave[wave_value_idx].value, item->radix, leading_zeroes_,
@@ -546,17 +560,17 @@ void WavesPanel::Draw() {
546560
if (!analog) {
547561
// Draw waveform values inline where possible.
548562
SetColor(w_, kWavesInlineValuePair + highlight);
549-
for (const auto &wv : wave_value_list) {
563+
for (const WaveValueInfo &wv : wave_value_list) {
550564
int start_pos, char_offset;
551565
if (wv.size - 1 < wv.value.size()) {
552566
start_pos = wv.xpos + 1;
553567
char_offset = wv.value.size() - wv.size + 1;
554568
} else {
555-
start_pos = wv.xpos + wv.size / 2 - wv.value.size() / 2;
569+
start_pos = wv.xpos + 1 + (wv.size - 1) / 2 - wv.value.size() / 2;
556570
char_offset = 0;
557571
}
558572
// Draw on row-1, since row was already incremented after rendering the wave.
559-
wmove(w_, row-1, start_pos + wave_x);
573+
wmove(w_, row - 1, start_pos + wave_x);
560574
for (int i = char_offset; i < wv.value.size(); ++i) {
561575
waddch(w_, (i == char_offset && char_offset != 0) ? '.' : wv.value[i]);
562576
}
@@ -568,19 +582,23 @@ void WavesPanel::Draw() {
568582
// Draw the cursor and markers.
569583
const auto draw_vline = [&](int row, int col) {
570584
const int current_line = 1 + line_idx_ - scroll_row_;
571-
if (visible_items_[line_idx_]->signal != nullptr) {
585+
if (visible_items_[line_idx_]->signal != nullptr &&
586+
visible_items_[line_idx_]->analog_rows == 0) {
572587
// Draw the line in two sections, above and below the current line to
573-
// avoid overwriting the drawn wave.
588+
// avoid overwriting the drawn wave. Draw over analog waves though.
574589
if (current_line > row) {
575590
mvwvline(w_, row, col, ACS_VLINE, current_line - row);
576591
}
577-
if (current_line < max_h - 1) {
578-
mvwvline(w_, current_line + 1, col, ACS_VLINE, max_h - current_line - 1);
592+
const int next_line_start =
593+
current_line + std::max(1, visible_items_[line_idx_]->analog_rows);
594+
if (next_line_start < max_h) {
595+
mvwvline(w_, next_line_start, col, ACS_VLINE, max_h - next_line_start);
579596
}
580597
} else {
581598
mvwvline(w_, row, col, ACS_VLINE, max_h - row);
582599
}
583600
};
601+
// Helper to see if the marker should start under the current time, to avoid covering it.
584602
auto vline_row = [&](int col) { return col >= time_width ? 0 : 1; };
585603
for (int i = -1; i < 10; ++i) {
586604
const uint64_t time = i < 0 ? marker_time_ : numbered_marker_times_[i];
@@ -590,17 +608,17 @@ void WavesPanel::Draw() {
590608
std::string marker_label = "m";
591609
if (i >= 0) marker_label += '0' + i;
592610
SetColor(w_, kWavesMarkerPair);
593-
const int col = wave_x + marker_pos;
594-
const int row = vline_row(wave_x + marker_pos);
595-
draw_vline(row, col);
611+
const int marker_col = wave_x + marker_pos;
612+
const int marker_row = vline_row(wave_x + marker_pos);
613+
draw_vline(marker_row, marker_col);
596614
if (marker_pos + marker_label.size() < max_w) {
597-
mvwaddstr(w_, row, col, marker_label.c_str());
615+
mvwaddstr(w_, marker_row, marker_col, marker_label.c_str());
598616
}
599617
}
600618
}
601619
// Also the interactive cursor.
602-
int cursor_col = wave_x + cursor_pos_;
603-
int cursor_row = vline_row(cursor_col);
620+
const int cursor_col = wave_x + cursor_pos_;
621+
const int cursor_row = vline_row(cursor_col);
604622
SetColor(w_, kWavesCursorPair);
605623
draw_vline(cursor_row, cursor_col);
606624

@@ -636,6 +654,7 @@ void WavesPanel::UIChar(int ch) {
636654

637655
bool time_changed = false;
638656
bool range_changed = false;
657+
bool edge_search = false;
639658
// Most actions cancel multi-line.
640659
bool cancel_multi_line = true;
641660
if (showing_path_) {
@@ -846,7 +865,10 @@ void WavesPanel::UIChar(int ch) {
846865
}
847866
break;
848867
case 'e':
849-
case 'E': FindEdge(ch == 'e', &time_changed, &range_changed); break;
868+
case 'E':
869+
FindEdge(ch == 'e', &time_changed, &range_changed);
870+
edge_search = true;
871+
break;
850872
case 'r':
851873
if (item->signal != nullptr) {
852874
item->CycleRadix();
@@ -890,11 +912,14 @@ void WavesPanel::UIChar(int ch) {
890912
case 'A':
891913
if (item->analog_rows > 0) item->analog_rows--;
892914
break;
915+
case 0x1: // Ctrl-a
916+
item->CycleAnalogType();
917+
break;
893918
default: Panel::UIChar(ch);
894919
}
895920
}
896921
if (time_changed) {
897-
SnapToValue();
922+
if (!edge_search) SnapToValue();
898923
UpdateValues();
899924
}
900925
if (range_changed) UpdateWaves();
@@ -1168,9 +1193,6 @@ std::vector<Tooltip> WavesPanel::Tooltips() const {
11681193
{"-", "default color"},
11691194
};
11701195
}
1171-
// TODO: Missing features
1172-
// "C-r": "Reload",
1173-
// "aA" : "Adjust analog height",
11741196
std::vector<Tooltip> tt{
11751197
{"zZ", "Zoom"},
11761198
{"F", "Zoom full"},
@@ -1179,25 +1201,26 @@ std::vector<Tooltip> WavesPanel::Tooltips() const {
11791201
{"eE", "Prev/next edge"},
11801202
{"sS", "Adjust size"},
11811203
{"aA", "Analog size"},
1182-
{"0", "Show leading zeroes"},
1183-
{"c", "Change signal color"},
1184-
{"p", "Show full signal path"},
1204+
{"C-a", "Analog type"},
1205+
{"0", "Leading zeroes"},
1206+
{"c", "Signal color"},
1207+
{"p", "Show path"},
11851208
{"T", "Go to time"},
11861209
{"t", "Cycle time units"},
11871210
{"m", "Place marker"},
1188-
{"M", "Place numbered marker"},
1211+
{"M", "Place # marker"},
11891212
{"C-g", "Create group"},
11901213
{"R", "Rename group"},
1191-
{"UD", "Move signal up / down"},
1192-
{"b", "Insert blank signal"},
1193-
{"x", "Delete highlighted signal"},
1194-
{"r", "Cycle signal radix"},
1214+
{"UD", "Move signal up/dn"},
1215+
{"b", "Insert blank"},
1216+
{"x", "Delete"},
1217+
{"r", "Cycle radix"},
11951218
{"C-u", "Toggle Unicode"},
11961219
{"C-o", "Open list file"},
11971220
{"C-s", "Save list file"},
11981221
};
11991222
if (Workspace::Get().Design() != nullptr) {
1200-
tt.push_back({"d", "Show signal declaration in source"});
1223+
tt.push_back({"d", "Declaration"});
12011224
}
12021225
return tt;
12031226
}
@@ -1218,6 +1241,13 @@ void WavesPanel::ListItem::CycleRadix() {
12181241
}
12191242
}
12201243

1244+
void WavesPanel::ListItem::CycleAnalogType() {
1245+
switch (analog_type) {
1246+
case AnalogWaveType::kLinear: analog_type = AnalogWaveType::kSampleAndHold; break;
1247+
case AnalogWaveType::kSampleAndHold: analog_type = AnalogWaveType::kLinear; break;
1248+
}
1249+
}
1250+
12211251
std::optional<const WaveData::Signal *> WavesPanel::SignalForSource() {
12221252
if (signal_for_source_ == nullptr) return std::nullopt;
12231253
auto s = signal_for_source_;

src/waves_panel.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "radix.h"
66
#include "text_input.h"
77
#include "wave_data.h"
8+
#include "wave_image.h"
89

910
namespace sv {
1011

@@ -38,10 +39,12 @@ class WavesPanel : public Panel {
3839
// Helpers
3940
std::string Name() const;
4041
void CycleRadix();
42+
void CycleAnalogType();
4143
// Member data.
4244
Radix radix = Radix::kHex;
4345
const WaveData::Signal *signal = nullptr;
4446
int analog_rows = 0;
47+
AnalogWaveType analog_type = AnalogWaveType::kSampleAndHold;
4548
int depth = 0;
4649
int custom_color = -1;
4750
// Members when this item represents a group container.

0 commit comments

Comments
 (0)