fix dependency problems caused by pypi
[c4m-jtag.git] / c4m / nmigen / jtag / tap.py
index a72c89a0956f25b41e9fb10ed088f3844afd0b84..f5bfd8cc676e02f0e9e53b882a41c2cac25e6bf7 100755 (executable)
@@ -1,4 +1,9 @@
 #!/usr/bin/env python3
+#!/bin/env python3
+# Copyright (C) 2019,2020,2021 Staf Verhaegen <staf@fibraservi.eu>
+# Copyright (C) 2021,2022 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
+# 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"):