diff --git a/examples/arty.py b/examples/arty.py index cd9cec8..1f7cda7 100755 --- a/examples/arty.py +++ b/examples/arty.py @@ -48,6 +48,7 @@ class LiteScopeSoC(BaseSoC): self.submodules.analyzer = LiteScopeAnalyzer(analyzer_signals, depth = 1024, clock_domain = "sys", + samplerate = self.sys_clk_freq, csr_csv = "analyzer.csv") self.add_csr("analyzer") diff --git a/litescope/core.py b/litescope/core.py index d35e44a..3257491 100644 --- a/litescope/core.py +++ b/litescope/core.py @@ -226,9 +226,10 @@ class _Storage(Module, AutoCSR): class LiteScopeAnalyzer(Module, AutoCSR): - def __init__(self, groups, depth, clock_domain="sys", trigger_depth=16, register=False, csr_csv="analyzer.csv"): - self.groups = groups = self.format_groups(groups) - self.depth = depth + def __init__(self, groups, depth, samplerate=1e-12, clock_domain="sys", trigger_depth=16, register=False, csr_csv="analyzer.csv"): + self.groups = groups = self.format_groups(groups) + self.depth = depth + self.samplerate = samplerate self.data_width = data_width = max([sum([len(s) for s in g]) for g in groups.values()]) @@ -294,6 +295,7 @@ class LiteScopeAnalyzer(Module, AutoCSR): return ",".join(args) + "\n" r = format_line("config", "None", "data_width", str(self.data_width)) r += format_line("config", "None", "depth", str(self.depth)) + r += format_line("config", "None", "samplerate", str(self.samplerate)) for i, signals in self.groups.items(): for s in signals: r += format_line("signal", str(i), vns.get_name(s), str(len(s))) diff --git a/litescope/software/driver/analyzer.py b/litescope/software/driver/analyzer.py index 3e1ea46..ee9d9ec 100644 --- a/litescope/software/driver/analyzer.py +++ b/litescope/software/driver/analyzer.py @@ -118,6 +118,7 @@ class LiteScopeAnalyzerDriver: self.add_trigger(value, mask, cond) def configure_subsampler(self, value): + self.subsampling = value self.subsampler_value.write(value-1) def run(self, offset=0, length=None): @@ -166,11 +167,13 @@ class LiteScopeAnalyzerDriver: return self.data def save(self, filename, samplerate=None, flatten=False): + if samplerate is None: + samplerate = self.samplerate / self.subsampling if self.debug: print("[writing to " + filename + "]...") name, ext = os.path.splitext(filename) if ext == ".vcd": - dump = VCDDump() + dump = VCDDump(samplerate=samplerate) elif ext == ".csv": dump = CSVDump() elif ext == ".py": diff --git a/litescope/software/dump/vcd.py b/litescope/software/dump/vcd.py index e3045b5..f5a8834 100644 --- a/litescope/software/dump/vcd.py +++ b/litescope/software/dump/vcd.py @@ -6,6 +6,7 @@ from itertools import count import datetime +import re from litescope.software.dump.common import Dump, dec2bin @@ -19,14 +20,37 @@ def vcd_codes(): code = codechars[r] + code yield code +_si_prefix2exp = { + "": 0, + "m": -3, + "u": -6, + "n": -9, + "p": -12, + "f": -15, +} + +def _timescale_str2num(timescale): + match = re.fullmatch("(\d+)(\w{0,1})s", timescale) + num = int(match.group(1)) + si_prefix = match.group(2) + exp = _si_prefix2exp[si_prefix] + return num * 10**exp, si_prefix + class VCDDump(Dump): - def __init__(self, dump=None, timescale="1ps", comment=""): + def __init__(self, dump=None, samplerate=1e-12, timescale="1ps", comment=""): Dump.__init__(self) self.variables = [] if dump is None else dump.variables self.timescale = timescale self.comment = comment self.cnt = -1 + # rescale the timescale from the provided one to one where it is equal to the samplerate + # this lets us output sequential change timestamps which helps with software like PulseView + # that slow down if a much smaller timescale than necessary is used + timescale_seconds, si_prefix = _timescale_str2num(timescale) + # factor of 2 scale is because of 2x samples from fake clock + self.count_timescale = int(1 / (timescale_seconds * samplerate * 2)) + self.timescale_unit_str = si_prefix + "s" def change(self): r = "" @@ -65,14 +89,12 @@ class VCDDump(Dump): def generate_timescale(self): r = "$timescale " - r += self.timescale + r += str(self.count_timescale) + self.timescale_unit_str r += " $end\n" return r def generate_vars(self): - r = "$scope " - r += self.timescale - r += " $end\n" + r = "$scope dumped_signals $end\n" for v in self.variables: r += "$var wire " r += str(v.width)