X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=c4m%2Fnmigen%2Fjtag%2Ftap.py;h=f5bfd8cc676e02f0e9e53b882a41c2cac25e6bf7;hb=HEAD;hp=a72c89a0956f25b41e9fb10ed088f3844afd0b84;hpb=63f28fc916c381075dd7f7c269eabea8312bdef8;p=c4m-jtag.git diff --git a/c4m/nmigen/jtag/tap.py b/c4m/nmigen/jtag/tap.py index a72c89a..f5bfd8c 100755 --- a/c4m/nmigen/jtag/tap.py +++ b/c4m/nmigen/jtag/tap.py @@ -1,4 +1,9 @@ #!/usr/bin/env python3 +#!/bin/env python3 +# Copyright (C) 2019,2020,2021 Staf Verhaegen +# Copyright (C) 2021,2022 Luke Kenneth Casson Leighton +# Funded by NLnet and NGI POINTER under EU Grants 871528 and 957073 + import os, textwrap from enum import Enum, auto @@ -169,7 +174,7 @@ class IOConn(Record): } """TAP subblock representing the interface for an JTAG IO cell. - It contains signal to connect to the core and to the pad + It contains signals to connect to the core and to the pad This object is normally only allocated and returned from ``TAP.add_io`` It is a Record subclass. @@ -192,9 +197,11 @@ class IOConn(Record): Input to pad with pad output value. oe: Signal(1), present only for IOType.TriOut and IOType.InTriOut. Input to pad with pad output enable value. + + bank select, pullup and pulldown may also be optionally added """ @staticmethod - def layout(iotype): + def layout(iotype, banksel=0, pullup=False, pulldown=False): sigs = [] if iotype in (IOType.In, IOType.InTriOut): sigs.append(("i", 1)) @@ -202,14 +209,25 @@ class IOConn(Record): sigs.append(("o", 1)) if iotype in (IOType.TriOut, IOType.InTriOut): sigs.append(("oe", 1)) + if banksel > 0: + sigs.append(("sel", banksel)) + if pullup: + sigs.append(("pu", 1)) + if pulldown: + sigs.append(("pd", 1)) return Layout((("core", sigs), ("pad", sigs))) - def __init__(self, *, iotype, name=None, src_loc_at=0): - super().__init__(self.__class__.layout(iotype), name=name, - src_loc_at=src_loc_at+1) + def __init__(self, *, iotype, name=None, banksel=0, + pullup=False, pulldown=False, + src_loc_at=0): + layout = self.__class__.layout(iotype, banksel, pullup, pulldown) + super().__init__(layout, name=name, src_loc_at=src_loc_at+1) self._iotype = iotype + self._banksel = banksel + self._pullup = pullup + self._pulldown = pulldown class _IDBypassBlock(Elaboratable): @@ -529,7 +547,9 @@ class TAP(Elaboratable): dmi.we_i.eq(ds.ongoing("WRRD")), ] - def add_io(self, *, iotype, name=None, src_loc_at=0): + def add_io(self, *, iotype, name=None, banksel=0, + pullup=False, pulldown=False, + src_loc_at=0): """Add a io cell to the boundary scan chain Parameters: @@ -541,12 +561,30 @@ class TAP(Elaboratable): if name is None: name = "ioconn" + str(len(self._ios)) - ioconn = IOConn(iotype=iotype, name=name, src_loc_at=src_loc_at+1) + ioconn = IOConn(iotype=iotype, banksel=banksel, + pullup=pullup, pulldown=pulldown, + name=name, src_loc_at=src_loc_at+1) self._ios.append(ioconn) return ioconn def _elaborate_ios(self, *, m, capture, shift, update, bd2io, bd2core): - length = sum(IOConn.lengths[conn._iotype] for conn in self._ios) + # note: the starting points where each IOConn is placed into + # the Shift Register depends *specifically* on the type (parameters) + # of each IOConn, and therefore on all IOConn(s) that came before it + # [prior calls to add_io]. this function consistently follows + # the exact same pattern in the exact same sequence every time, + # to compute consistent offsets. developers must do the same: + # note that each length depends on *all* parameters: + # IOtype, banksel, pullup *and* pulldown. + + # pre-compute the length of the IO shift registers needed. + length = 0 + for conn in self._ios: + length += IOConn.lengths[conn._iotype] + conn._banksel + if conn._pullup: + length += 1 + if conn._pulldown: + length += 1 if length == 0: return self.bus.tdi @@ -566,9 +604,19 @@ class TAP(Elaboratable): iol.append(conn.core.o) if conn._iotype in [IOType.TriOut, IOType.InTriOut]: iol.append(conn.core.oe) - # length double-check + # now also banksel, pullup and pulldown from core are added + if conn._banksel != 0: + iol.append(conn.core.sel) + idx += conn._banksel + if conn._pullup: + iol.append(conn.core.pu) + idx += 1 + if conn._pulldown: + iol.append(conn.core.pd) + idx += 1 + # help with length double-check idx += IOConn.lengths[conn._iotype] # fails if wrong type - assert idx == length, "Internal error" + assert idx == length, "Internal error, length mismatch" m.d.posjtag += io_sr.eq(Cat(*iol)) # assigns all io_sr in one hit # "Shift" mode (sends out captured data on tdo, sets incoming from tdi) @@ -581,8 +629,13 @@ class TAP(Elaboratable): # sets up IO (pad<->core) or in testing mode depending on requested # mode, via Muxes controlled by bd2core and bd2io + # for each IOConn, the number of bits needed from io_bd will vary + # and is computed on-the-fly, here. it is up to the developer to + # keep track of where each IO pad configuration starts and ends + # in the Shift Register (TODO: provide a dictionary of starting points) idx = 0 for conn in self._ios: + # mux the I/O/OE depending on IOType if conn._iotype == IOType.In: m.d.comb += conn.core.i.eq(Mux(bd2core, io_bd[idx], conn.pad.i)) idx += 1 @@ -604,8 +657,22 @@ class TAP(Elaboratable): idx += 3 else: raise("Internal error") + # optional mux of banksel, pullup and pulldown. note that idx + # advances each time, so that io_bd[idx] comes from the right point + comb = m.d.comb + if conn._banksel != 0: + s, e = (idx, idx+conn._banksel) # banksel can be multi-bit + comb += conn.pad.sel.eq(Mux(bd2io, io_bd[s:e], conn.core.sel)) + idx = e + if conn._pullup: + comb += conn.pad.pu.eq(Mux(bd2io, io_bd[idx], conn.core.pu)) + idx += 1 + if conn._pulldown: + comb += conn.pad.pd.eq(Mux(bd2io, io_bd[idx], conn.core.pd)) + idx += 1 assert idx == length, "Internal error" + # return the last bit of the shift register, for output on tdo return io_sr[-1] def add_shiftreg(self, *, ircode, length, domain="sync", name=None, @@ -662,6 +729,9 @@ class TAP(Elaboratable): # clockdomain latch update in `domain` clockdomain and see when # it has falling edge. # At that edge put isir in sr.oe for one `domain` clockdomain + # Using this custom sync <> JTAG domain synchronization avoids + # the use of more generic but also higher latency CDC solutions + # like FFSynchronizer. update_core = Signal(name=sr.name+"_update_core") update_core_prev = Signal(name=sr.name+"_update_core_prev") m.d[domain] += [ @@ -683,7 +753,6 @@ class TAP(Elaboratable): # tdo = reg[0], tdo_en = shift tdos.append((reg[0], sr_shift)) - # Assign the right tdo to the bus tdo for i, (tdo, tdo_en) in enumerate(tdos): if i == 0: @@ -700,7 +769,6 @@ class TAP(Elaboratable): # Always connect tdo_jtag to m.d.comb += self.bus.tdo.eq(tdo_jtag) - def add_wishbone(self, *, ircodes, address_width, data_width, granularity=None, domain="sync", features=None, name=None, src_loc_at=0): @@ -777,7 +845,10 @@ class TAP(Elaboratable): with m.State("READACK"): with m.If(wb.ack): # Store read data in sr_data.i - # and keep it there til next read + # and keep it there til next read. + # This is enough to synchronize between sync and JTAG + # clock domain and no higher latency solutions like + # FFSynchronizer is needed. m.d[domain] += sr_data.i.eq(wb.dat_r) m.next = "IDLE" with m.State("WRITEREAD"):