From f1a9f4d23705ec955d434a6519a54c7b017efc6f Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Sat, 13 Nov 2021 22:04:48 +0000 Subject: [PATCH] add first cut of jtag.py (from soc) to be cut down later --- src/spec/jtag.py | 143 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/spec/jtag.py diff --git a/src/spec/jtag.py b/src/spec/jtag.py new file mode 100644 index 0000000..4b4f17a --- /dev/null +++ b/src/spec/jtag.py @@ -0,0 +1,143 @@ +"""JTAG interface + +using Staf Verhaegen (Chips4Makers) wishbone TAP +""" + +from collections import OrderedDict +from nmigen import (Module, Signal, Elaboratable, Cat) +from nmigen.cli import rtlil +from c4m.nmigen.jtag.tap import IOType +from soc.debug.dmi import DMIInterface, DBGCore +from soc.debug.dmi2jtag import DMITAP + +# map from pinmux to c4m jtag iotypes +iotypes = {'-': IOType.In, + '+': IOType.Out, + '>': IOType.TriOut, + '*': IOType.InTriOut, + } + +scanlens = {IOType.In: 1, + IOType.Out: 1, + IOType.TriOut: 2, + IOType.InTriOut: 3, + } + +def dummy_pinset(): + # sigh this needs to come from pinmux. + gpios = [] + for i in range(16): + gpios.append("%d*" % i) + return {'uart': ['tx+', 'rx-'], + 'gpio': gpios, + 'i2c': ['sda*', 'scl+']} + +# TODO: move to suitable location +class Pins: + """declare a list of pins, including name and direction. grouped by fn + the pin dictionary needs to be in a reliable order so that the JTAG + Boundary Scan is also in a reliable order + """ + def __init__(self, pindict): + self.io_names = OrderedDict() + if isinstance(pindict, OrderedDict): + self.io_names.update(pindict) + else: + keys = list(pindict.keys()) + keys.sort() + for k in keys: + self.io_names[k] = pindict[k] + + def __iter__(self): + # start parsing io_names and enumerate them to return pin specs + scan_idx = 0 + for fn, pins in self.io_names.items(): + for pin in pins: + # decode the pin name and determine the c4m jtag io type + name, pin_type = pin[:-1], pin[-1] + iotype = iotypes[pin_type] + pin_name = "%s_%s" % (fn, name) + yield (fn, name, iotype, pin_name, scan_idx) + scan_idx += scanlens[iotype] # inc boundary reg scan offset + + +class JTAG(DMITAP, Pins): + # 32-bit data width here so that it matches with litex + def __init__(self, pinset, domain, wb_data_wid=32): + self.domain = domain + DMITAP.__init__(self, ir_width=4) + Pins.__init__(self, pinset) + + # enumerate pin specs and create IOConn Records. + # we store the boundary scan register offset in the IOConn record + self.ios = [] # these are enumerated in external_ports + self.scan_len = 0 + for fn, pin, iotype, pin_name, scan_idx in list(self): + io = self.add_io(iotype=iotype, name=pin_name) + io._scan_idx = scan_idx # hmm shouldn't really do this + self.scan_len += scan_idx # record full length of boundary scan + self.ios.append(io) + + # this is redundant. or maybe part of testing, i don't know. + self.sr = self.add_shiftreg(ircode=4, length=3, + domain=domain) + + # create and connect wishbone + self.wb = self.add_wishbone(ircodes=[5, 6, 7], features={'err'}, + address_width=30, data_width=wb_data_wid, + granularity=8, # 8-bit wide + name="jtag_wb", + domain=domain) + + # create DMI2JTAG (goes through to dmi_sim()) + self.dmi = self.add_dmi(ircodes=[8, 9, 10], + domain=domain) + + # use this for enable/disable of parts of the ASIC. + # XXX make sure to add the _en sig to en_sigs list + self.wb_icache_en = Signal(reset=1) + self.wb_dcache_en = Signal(reset=1) + self.wb_sram_en = Signal(reset=1) + self.en_sigs = en_sigs = Cat(self.wb_icache_en, self.wb_dcache_en, + self.wb_sram_en) + self.sr_en = self.add_shiftreg(ircode=11, length=len(en_sigs), + domain=domain) + + def elaborate(self, platform): + m = super().elaborate(platform) + m.d.comb += self.sr.i.eq(self.sr.o) # loopback as part of test? + + # provide way to enable/disable wishbone caches and SRAM + # just in case of issues + # see https://bugs.libre-soc.org/show_bug.cgi?id=520 + with m.If(self.sr_en.oe): + m.d.sync += self.en_sigs.eq(self.sr_en.o) + # also make it possible to read the enable/disable current state + with m.If(self.sr_en.ie): + m.d.comb += self.sr_en.i.eq(self.en_sigs) + + # create a fake "stall" + #wb = self.wb + #m.d.comb += wb.stall.eq(wb.cyc & ~wb.ack) # No burst support + + return m + + def external_ports(self): + """create a list of ports that goes into the top level il (or verilog) + """ + ports = super().external_ports() # gets JTAG signal names + ports += list(self.wb.fields.values()) # wishbone signals + for io in self.ios: + ports += list(io.core.fields.values()) # io "core" signals + ports += list(io.pad.fields.values()) # io "pad" signals" + return ports + + +if __name__ == '__main__': + pinset = dummy_pinset() + dut = JTAG(pinset) + + vl = rtlil.convert(dut) + with open("test_jtag.il", "w") as f: + f.write(vl) + -- 2.30.2