diff --git a/examples/fast_scope_arty.py b/examples/fast_scope_arty.py new file mode 100755 index 0000000..cee6f63 --- /dev/null +++ b/examples/fast_scope_arty.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 + +from migen import * + +from litex.boards.platforms import arty +from migen.genlib.io import CRG,DifferentialInput +from litex.soc.integration.soc_core import SoCCore +from litex.soc.cores.uart import UARTWishboneBridge +from litex.build.generic_platform import Subsignal +from litex.build.generic_platform import Pins +from litex.build.generic_platform import IOStandard +from litex.soc.cores.clock import * + +from litescope import LiteScopeIO, LiteScopeAnalyzer + +# +# Use the 8 input on the dual PMOD connector B as input +# Those are the fast and not so well protected pins. +_serdes_io = [ + ("serdes_io", 0, + Subsignal("d0", Pins("E15"),IOStandard("LVCMOS33")), + Subsignal("d1", Pins("E16"),IOStandard("LVCMOS33")), + Subsignal("d2", Pins("D15"),IOStandard("LVCMOS33")), + Subsignal("d3", Pins("C15"),IOStandard("LVCMOS33")), + Subsignal("d4", Pins("J17"),IOStandard("LVCMOS33")), + Subsignal("d5", Pins("J18"),IOStandard("LVCMOS33")), + Subsignal("d6", Pins("K15"),IOStandard("LVCMOS33")), + Subsignal("d7", Pins("J15"),IOStandard("LVCMOS33")), + ) +] + +class SerdesInputSignal(Module): + def __init__(self, pad): + + self.signals = Signal(8) + # + # Based on a 100MHz input clock and a 400MHz sample clock and + # Measuring at ddr speed we are sampling at 800Mhz + # + self.specials += Instance("ISERDESE2", + p_DATA_WIDTH=8, p_DATA_RATE="DDR", + p_SERDES_MODE="MASTER", p_INTERFACE_TYPE="NETWORKING", + p_NUM_CE=1, p_IOBDELAY="NONE", + + i_D=pad, + i_CE1=1, + i_RST=ResetSignal("sys"), + i_CLK=ClockSignal("sys4x"), i_CLKB=~ClockSignal("sys4x"), + i_CLKDIV=ClockSignal("sys"), + i_BITSLIP=0, + o_Q8=self.signals[0], o_Q7=self.signals[1], + o_Q6=self.signals[2], o_Q5=self.signals[3], + o_Q4=self.signals[4], o_Q3=self.signals[5], + o_Q2=self.signals[6], o_Q1=self.signals[7] + ) + +class SerdesIO(Module): + + def __init__(self,platform): + platform.add_extension(_serdes_io) + + pads = platform.request("serdes_io") + self.submodules.d0 = SerdesInputSignal(pads.d0) + self.submodules.d1 = SerdesInputSignal(pads.d1) + self.submodules.d2 = SerdesInputSignal(pads.d2) + self.submodules.d3 = SerdesInputSignal(pads.d3) + self.submodules.d4 = SerdesInputSignal(pads.d4) + self.submodules.d5 = SerdesInputSignal(pads.d5) + self.submodules.d6 = SerdesInputSignal(pads.d6) + self.submodules.d7 = SerdesInputSignal(pads.d7) + + platform.add_platform_command(""" +set_property CFGBVS VCCO [current_design] +set_property CONFIG_VOLTAGE 3.3 [current_design] +""") + +# CRG ---------------------------------------------------------------------------------------------- + +class _CRG(Module): + def __init__(self, platform, sys_clk_freq): + self.clock_domains.cd_sys = ClockDomain() + self.clock_domains.cd_sys4x = ClockDomain(reset_less=True) + + self.cd_sys.clk.attr.add("keep") + self.cd_sys4x.clk.attr.add("keep") + + self.submodules.pll = pll = S7PLL(speedgrade=-1) + self.comb += pll.reset.eq(~platform.request("cpu_reset")) + pll.register_clkin(platform.request("clk100"), 100e6) + pll.create_clkout(self.cd_sys, sys_clk_freq) + pll.create_clkout(self.cd_sys4x, 4*sys_clk_freq) + +class LiteScopeSoC(SoCCore): + csr_map = { + "analyzer": 17 + } + csr_map.update(SoCCore.csr_map) + + def __init__(self, platform): + sys_clk_freq = int(100e6) + + SoCCore.__init__(self, platform, sys_clk_freq, + cpu_type=None, + csr_data_width=32, + with_uart=False, + ident="Fast scope", ident_version=True, + with_timer=False + ) + self.submodules.serdes = SerdesIO(platform) + # crg + self.submodules.crg = _CRG(platform,sys_clk_freq) + + # bridge + self.add_cpu(UARTWishboneBridge(platform.request("serial"), + sys_clk_freq, baudrate=115200)) + self.add_wb_master(self.cpu.wishbone) + + # Litescope Analyzer + analyzer_groups = {} + + # Analyzer group + analyzer_groups[0] = [ + self.serdes.d0.signals, + self.serdes.d1.signals, + self.serdes.d2.signals, + self.serdes.d3.signals, + ] + + # analyzer + self.submodules.analyzer = LiteScopeAnalyzer(analyzer_groups, 512) + + def do_exit(self, vns): + self.analyzer.export_csv(vns, "test/analyzer.csv") + + +platform = arty.Platform() + +soc = LiteScopeSoC(platform) +vns = platform.build(soc) + +# +# Create csr and analyzer files +# +soc.finalize() +csr_regions = soc.get_csr_regions() +csr_constants = soc.get_constants() +from litex.build.tools import write_to_file +from litex.soc.integration import cpu_interface + +csr_csv = cpu_interface.get_csr_csv(csr_regions, csr_constants) +write_to_file("test/csr.csv", csr_csv) +soc.do_exit(vns) + + +# +# Program +# +platform.create_programmer().load_bitstream("build/top.bit") diff --git a/examples/test/test_fast_scope_arty.py b/examples/test/test_fast_scope_arty.py new file mode 100755 index 0000000..70aa323 --- /dev/null +++ b/examples/test/test_fast_scope_arty.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from litex import RemoteClient + +from litescope import LiteScopeAnalyzerDriver + +wb = RemoteClient() +wb.open() + +# # # + +subsample = 1 +analyzer = LiteScopeAnalyzerDriver(wb.regs, "analyzer", debug=True) +analyzer.configure_subsampler(subsample) +analyzer.configure_group(0) +analyzer.run(offset=32, length=512) +analyzer.wait_done() +analyzer.upload() + +# +# Convert parallel input back to a flaten view (e.g. the 8 bits values are flattened) +# +analyzer.save("dump.vcd",flatten=True) + +# # # + +wb.close() diff --git a/litescope/software/driver/analyzer.py b/litescope/software/driver/analyzer.py index 563e327..ef9f375 100644 --- a/litescope/software/driver/analyzer.py +++ b/litescope/software/driver/analyzer.py @@ -126,7 +126,7 @@ class LiteScopeAnalyzerDriver: print("") return self.data - def save(self, filename, samplerate=None): + def save(self, filename, samplerate=None, flatten=False): if self.debug: print("[writing to " + filename + "]...") name, ext = os.path.splitext(filename) @@ -140,7 +140,10 @@ class LiteScopeAnalyzerDriver: dump = SigrokDump(samplerate=samplerate) else: raise NotImplementedError - dump.add_from_layout(self.layouts[self.group], self.data) + if not flatten: + dump.add_from_layout(self.layouts[self.group], self.data) + else: + dump.add_from_layout_flatten(self.layouts[self.group], self.data) dump.write(filename) def get_instant_value(self, group, name): diff --git a/litescope/software/dump/common.py b/litescope/software/dump/common.py index d93f027..183e1b6 100644 --- a/litescope/software/dump/common.py +++ b/litescope/software/dump/common.py @@ -48,8 +48,8 @@ class DumpData(list): class DumpVariable: def __init__(self, name, width, values=[]): - self.width = width self.name = name + self.width = width self.values = [int(v)%2**width for v in values] def __len__(self): @@ -64,12 +64,26 @@ class Dump: self.variables.append(variable) def add_from_layout(self, layout, variable): - i = 0 - for s, n in layout: - values = variable[i:i+n] - values2x = [values[j//2] for j in range(len(values)*2)] - self.add(DumpVariable(s, n, values2x)) - i += n + offset = 0 + for name, sample_width in layout: + values = variable[offset:offset+sample_width] + values2x = [values[i//2] for i in range(len(values)*2)] + self.add(DumpVariable(name, sample_width, values2x)) + offset += sample_width + self.add(DumpVariable("scope_clk", 1, [1, 0]*(len(self)//2))) + + def add_from_layout_flatten(self, layout, variable): + offset = 0 + for name, sample_width in layout: + # The samples from the logic analyzer end up in an array of size sample size + # and have n (number of channel) bits. The following does a bit slice on the array + # elements (implemented above) + values = variable[offset:offset+sample_width] + values_flatten = [values[i//sample_width] >> (i % sample_width ) & 1 for i in range(len(values)*sample_width)] + self.add(DumpVariable(name, 1, values_flatten)) + offset += sample_width + # the clock.. might need some more love here. the clock pattern probably should be sample_width wide + # e.g. 11110000 and not 10101010 self.add(DumpVariable("scope_clk", 1, [1, 0]*(len(self)//2))) def __len__(self):