add DMI interface to JTAG TAP
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 9 Oct 2020 12:54:19 +0000 (13:54 +0100)
committerStaf Verhaegen <staf@stafverhaegen.be>
Wed, 21 Apr 2021 17:43:58 +0000 (19:43 +0200)
c4m/nmigen/jtag/bus.py
c4m/nmigen/jtag/tap.py

index c6015a72657eb77b15d36afb0a440a37bd23e7e5..d1532bc08d0d8170c5b7c5e62dba67f8c6fe709a 100644 (file)
@@ -1,6 +1,20 @@
 from nmigen import *
 from nmigen.hdl.rec import Direction
 
+
+class DMIInterface(Record):
+    def __init__(self, name=None, addr_wid=4, data_wid=64):
+        layout = [
+           ('addr_i', addr_wid, Direction.FANIN),  # DMI register address
+           ('din',    data_wid, Direction.FANIN),  # DMI data write in (we=1)
+           ('dout',   data_wid, Direction.FANOUT), # DMI data read out (we=0)
+           ('req_i',  1,        Direction.FANIN),  # DMI request valid (stb)
+           ('we_i',   1,        Direction.FANIN),  # DMI write-enable
+           ('ack_o',  1,        Direction.FANOUT), # DMI ack request
+        ]
+        super().__init__(name=name, layout=layout)
+
+
 class Interface(Record):
     """JTAG Interface.
 
index 19b8788aee7b8f518dddbd66edafd714e30a5f09..4ead48c2621226e8c29cf02e394867670848f21e 100755 (executable)
@@ -9,7 +9,7 @@ from nmigen.tracer import get_var_name
 
 from nmigen_soc.wishbone import Interface as WishboneInterface
 
-from .bus import Interface
+from .bus import Interface, DMIInterface
 
 __all__ = [
     "TAP", "ShiftReg", "IOType", "IOConn",
@@ -329,6 +329,7 @@ class TAP(Elaboratable):
         self._ios = []
         self._srs = []
         self._wbs = []
+        self._dmis = []
 
     def elaborate(self, platform):
         m = Module()
@@ -418,8 +419,100 @@ class TAP(Elaboratable):
         # wishbone
         self._elaborate_wishbones(m)
 
+        # DMI (Debug Memory Interface)
+        self._elaborate_dmis(m)
+
         return m
 
+    def add_dmi(self, *, ircodes, address_width=8, data_width=64,
+                     domain="sync", name=None):
+        """Add a DMI interface
+
+        * writing to DMIADDR will automatically trigger a DMI READ.
+          the DMI address does not alter (so writes can be done at that addr)
+        * reading from DMIREAD triggers a DMI READ at the current DMI addr
+          the address is automatically incremented by 1 after.
+        * writing to DMIWRITE triggers a DMI WRITE at the current DMI addr
+          the address is automatically incremented by 1 after.
+
+        Parameters:
+        -----------
+        ircodes: sequence of three integer for the JTAG IR codes;
+                 they represent resp. DMIADDR, DMIREAD and DMIWRITE.
+                 First code has a shift register of length 'address_width',
+                 the two other codes share a shift register of length
+                data_width.
+
+        address_width: width of the address
+        data_width: width of the data
+
+        Returns:
+        dmi: soc.debug.dmi.DMIInterface
+            The DMI interface
+        """
+        if len(ircodes) != 3:
+            raise ValueError("3 IR Codes have to be provided")
+
+        if name is None:
+            name = "dmi" + str(len(self._dmis))
+
+        # add 2 shift registers: one for addr, one for data.
+        sr_addr = self.add_shiftreg(ircode=ircodes[0], length=address_width,
+                                     domain=domain, name=name+"_addrsr")
+        sr_data = self.add_shiftreg(ircode=ircodes[1:], length=data_width,
+                                    domain=domain, name=name+"_datasr")
+
+        dmi = DMIInterface(name=name)
+        self._dmis.append((sr_addr, sr_data, dmi, domain))
+
+        return dmi
+
+    def _elaborate_dmis(self, m):
+        for sr_addr, sr_data, dmi, domain in self._dmis:
+            cd = m.d[domain]
+            m.d.comb += sr_addr.i.eq(dmi.addr_i)
+
+            with m.FSM(domain=domain) as ds:
+
+                # detect mode based on whether jtag addr or data read/written
+                with m.State("IDLE"):
+                    with m.If(sr_addr.oe): # DMIADDR code
+                        cd += dmi.addr_i.eq(sr_addr.o)
+                        m.next = "READ"
+                    with m.Elif(sr_data.oe[0]): # DMIREAD code
+                        # If data is
+                        cd += dmi.addr_i.eq(dmi.addr_i + 1)
+                        m.next = "READ"
+                    with m.Elif(sr_data.oe[1]): # DMIWRITE code
+                        cd += dmi.din.eq(sr_data.o)
+                        m.next = "WRRD"
+
+                # req_i raises for 1 clock
+                with m.State("READ"):
+                    m.next = "READACK"
+
+                # wait for read ack
+                with m.State("READACK"):
+                    with m.If(dmi.ack_o):
+                        # Store read data in sr_data.i hold till next read
+                        cd += sr_data.i.eq(dmi.dout)
+                        m.next = "IDLE"
+
+                # req_i raises for 1 clock
+                with m.State("WRRD"):
+                    m.next = "WRRDACK"
+
+                # wait for write ack
+                with m.State("WRRDACK"):
+                    with m.If(dmi.ack_o):
+                        cd += dmi.addr_i.eq(dmi.addr_i + 1)
+                        m.next = "READ" # for readwrite
+
+                # set DMI req and write-enable based on ongoing FSM states
+                m.d.comb += [
+                    dmi.req_i.eq(ds.ongoing("READ") | ds.ongoing("WRRD")),
+                    dmi.we_i.eq(ds.ongoing("WRRD")),
+                ]
 
     def add_io(self, *, iotype, name=None, src_loc_at=0):
         """Add a io cell to the boundary scan chain