Merge upstream master in libre-soc master.
authorStaf Verhaegen <staf@stafverhaegen.be>
Wed, 21 Apr 2021 15:54:40 +0000 (17:54 +0200)
committerStaf Verhaegen <staf@stafverhaegen.be>
Wed, 21 Apr 2021 15:54:40 +0000 (17:54 +0200)
c4m/cocotb/jtag/c4m_jtag.py
c4m/cocotb/jtag/c4m_jtag_svfcocotb.py
c4m/cocotb/jtag/c4m_jtag_svfgrammar.py
c4m/nmigen/jtag/bus.py
c4m/nmigen/jtag/tap.py
setup.py

index c4bb5af7ea7ccce9c3a437e66b6222453aa5e24f..2cf99819f77dfa51828a17436a710a901f6fe051 100644 (file)
@@ -108,7 +108,7 @@ class JTAG_Master(object):
         isreset = False
         if self.state is None:
             yield self.reset()
-        if self.state is "Reset":
+        if self.state == "Reset":
             isreset = True
             self.tms <= 0
             yield self.cycle_clock()
index 1560359383385847083396621feca0d477e9d8b3..ff5107740a1364f0395a2e97654ec56e9aba2bbf 100755 (executable)
@@ -108,7 +108,7 @@ class SVF_Executor(object):
         if tdo is not None:
             if self._d_mask is not None:
                 raise(JTAGException("MASK not supported for SDR"))
-            assert(self.result == tdo)
+            assert(self.master.result == tdo)
 
     @cocotb.coroutine
     def _execute_SIR(self, node):
@@ -139,7 +139,7 @@ class SVF_Executor(object):
         if tdo is not None:
             if self._i_mask is not None:
                 raise(JTAGException("MASK not supported for SIR"))
-            assert(self.result == tdo)
+            assert(self.master.result == tdo)
         
 
     @cocotb.coroutine
index faf3aebd87c4bcb9b98b629285cbaa912ecda7c2..8bc949df9da2c44c2bd747741395c85e45499f4a 100644 (file)
@@ -19,7 +19,7 @@ class Float(Grammar):
     grammar = (Integer, (L("."), OPTIONAL(Integer)), OPTIONAL(L("E"), Integer))
 
 class Hexadecimal(Grammar):
-    grammar = (L("("), WORD("0-9A-F"), L(")"))
+    grammar = (L("("), WORD("0-9A-Fa-f"), L(")"))
     grammar_collapse = True
 
 class StableState(Grammar):
@@ -93,4 +93,4 @@ class Trst(Grammar):
 
 
 class SVFFile(Grammar):
-    grammar = ONE_OR_MORE(EmptyLine | Comment | Trst | EndDR | EndIR | SIR | SDR | Runtest | State)
+    grammar = ONE_OR_MORE(EmptyLine | Comment | Trst | EndDR | EndIR | SIR | SDR | Runtest | State | HDR | HIR | TDR | TIR )
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 44cae0453869f971b9f587e5fd5abfa06cc8701d..84adf9d57fb2463c7f172533065f5ee5d5494341 100755 (executable)
@@ -2,15 +2,14 @@
 import os, textwrap
 from enum import Enum, auto
 
-from nmigen import *
-from nmigen.build import *
-from nmigen.lib.io import *
+from nmigen import (Elaboratable, Signal, Module, ClockDomain, Cat, Record,
+                    Const, Mux)
 from nmigen.hdl.rec import Direction, Layout
 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",
@@ -26,6 +25,7 @@ class _FSM(Elaboratable):
         self.shift = Signal()
         self.update = Signal()
 
+        # JTAG uses both edges of the incoming clock (TCK). set them up here
         self.posjtag = ClockDomain("posjtag", local=True)
         self.negjtag = ClockDomain("negjtag", local=True, clk_edge="neg")
 
@@ -119,6 +119,7 @@ class _FSM(Elaboratable):
 
         return m
 
+
 class _IRBlock(Elaboratable):
     """TAP subblock for handling the IR shift register"""
     def __init__(self, *, ir_width, cmd_idcode,
@@ -151,13 +152,22 @@ class _IRBlock(Elaboratable):
 
         return m
 
+
 class IOType(Enum):
     In = auto()
     Out = auto()
     TriOut = auto()
     InTriOut = auto()
 
+
 class IOConn(Record):
+    lengths = {
+        IOType.In: 1,
+        IOType.Out: 1,
+        IOType.TriOut: 2,
+        IOType.InTriOut: 3,
+    }
+
     """TAP subblock representing the interface for an JTAG IO cell.
     It contains signal to connect to the core and to the pad
 
@@ -169,14 +179,16 @@ class IOConn(Record):
     core: subrecord with signals for the core
         i: Signal(1), present only for IOType.In and IOType.InTriOut.
             Signal input to core with pad input value.
-        o: Signal(1), present only for IOType.Out, IOType.TriOut and IOType.InTriOut.
+        o: Signal(1), present only for IOType.Out, IOType.TriOut and
+            IOType.InTriOut.
             Signal output from core with the pad output value.
         oe: Signal(1), present only for IOType.TriOut and IOType.InTriOut.
             Signal output from core with the pad output enable value.
     pad: subrecord with for the pad
         i: Signal(1), present only for IOType.In and IOType.InTriOut
             Output from pad with pad input value for core.
-        o: Signal(1), present only for IOType.Out, IOType.TriOut and IOType.InTriOut.
+        o: Signal(1), present only for IOType.Out, IOType.TriOut and
+            IOType.InTriOut.
             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.
@@ -194,17 +206,20 @@ class IOConn(Record):
         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)
+        super().__init__(self.__class__.layout(iotype), name=name,
+                         src_loc_at=src_loc_at+1)
 
         self._iotype = iotype
 
+
 class _IDBypassBlock(Elaboratable):
     """TAP subblock for the ID shift register"""
     def __init__(self, *, manufacturer_id, part_number, version,
                  tdi, capture, shift, update, bypass,
                  name):
         self.name = name
-        if not isinstance(manufacturer_id, Const) and len(manufacturer_id) != 11:
+        if (not isinstance(manufacturer_id, Const) and
+            len(manufacturer_id) != 11):
             raise ValueError("manufacturer_id has to be Const of length 11")
         if not isinstance(part_number, Const) and len(manufacturer_id) != 16:
             raise ValueError("part_number has to be Const of length 16")
@@ -298,13 +313,13 @@ class ShiftReg(Record):
 
 class TAP(Elaboratable):
     #TODO: Document TAP
-    def __init__(
-        self, *, with_reset=False, ir_width=None,
-        manufacturer_id=Const(0b10001111111, 11), part_number=Const(1, 16),
-        version=Const(0, 4),
-        name=None, src_loc_at=0
-    ):
-        assert((ir_width is None) or (isinstance(ir_width, int) and ir_width >= 2))
+    def __init__(self, *, with_reset=False, ir_width=None,
+                 manufacturer_id=Const(0b10001111111, 11),
+                 part_number=Const(1, 16),
+                 version=Const(0, 4),
+                 name=None, src_loc_at=0):
+        assert((ir_width is None) or (isinstance(ir_width, int) and
+               ir_width >= 2))
         assert(len(version) == 4)
 
         if name is None:
@@ -320,12 +335,12 @@ class TAP(Elaboratable):
         self._part_number = part_number
         self._version = version
 
-        self._ircodes = [0, 1, 2] # Already taken codes, all ones added at the end
+        self._ircodes = [0, 1, 2] # Already taken codes, all ones added at end
 
         self._ios = []
         self._srs = []
         self._wbs = []
-
+        self._dmis = []
 
     def elaborate(self, platform):
         m = Module()
@@ -334,7 +349,8 @@ class TAP(Elaboratable):
         ir_max = max(self._ircodes) + 1 # One extra code needed with all ones
         ir_width = len("{:b}".format(ir_max))
         if self._ir_width is not None:
-            assert self._ir_width >= ir_width, "Specified JTAG IR width not big enough for allocated shiift registers"
+            assert self._ir_width >= ir_width, "Specified JTAG IR width " \
+                   "not big enough for allocated shiift registers"
             ir_width = self._ir_width
 
         # TODO: Make commands numbers configurable
@@ -345,12 +361,13 @@ class TAP(Elaboratable):
         cmd_preload = 2
         cmd_bypass = 2**ir_width - 1 # All ones
 
-        m.submodules._fsm = fsm = _FSM(bus=self.bus)
+        m.submodules.fsm = fsm = _FSM(bus=self.bus)
         m.domains.posjtag = fsm.posjtag
         m.domains.negjtag = fsm.negjtag
 
+        # IR block
         select_ir = fsm.isir
-        m.submodules._irblock = irblock = _IRBlock(
+        m.submodules.irblock = irblock = _IRBlock(
             ir_width=ir_width, cmd_idcode=cmd_idcode, tdi=self.bus.tdi,
             capture=(fsm.isir & fsm.capture),
             shift=(fsm.isir & fsm.shift),
@@ -359,17 +376,24 @@ class TAP(Elaboratable):
         )
         ir = irblock.ir
 
-        select_id = fsm.isdr & ((ir == cmd_idcode) | (ir == cmd_bypass))
-        m.submodules._idblock = idblock = _IDBypassBlock(
-            manufacturer_id=self._manufacturer_id, part_number=self._part_number,
+        # ID block
+        select_id = Signal()
+        id_bypass = Signal()
+        m.d.comb += select_id.eq(fsm.isdr &
+                                 ((ir == cmd_idcode) | (ir == cmd_bypass)))
+        m.d.comb += id_bypass.eq(ir == cmd_bypass)
+        m.submodules.idblock = idblock = _IDBypassBlock(
+            manufacturer_id=self._manufacturer_id,
+            part_number=self._part_number,
             version=self._version, tdi=self.bus.tdi,
             capture=(select_id & fsm.capture),
             shift=(select_id & fsm.shift),
             update=(select_id & fsm.update),
-            bypass=(ir == cmd_bypass),
+            bypass=id_bypass,
             name=self.name+"_id",
         )
 
+        # IO (Boundary scan) block
         io_capture = Signal()
         io_shift = Signal()
         io_update = Signal()
@@ -379,7 +403,8 @@ class TAP(Elaboratable):
         preload = (ir == cmd_preload)
         select_io = fsm.isdr & (sample | preload)
         m.d.comb += [
-            io_capture.eq(sample & fsm.capture), # Don't capture if not sample (like for PRELOAD)
+            io_capture.eq(sample & fsm.capture), # Don't capture if not sample
+                                                 # (like for PRELOAD)
             io_shift.eq(select_io & fsm.shift),
             io_update.eq(select_io & fsm.update),
             io_bd2io.eq(ir == cmd_extest),
@@ -391,22 +416,120 @@ class TAP(Elaboratable):
             bd2io=io_bd2io, bd2core=io_bd2core,
         )
 
+        # chain tdo: select as appropriate, to go into into shiftregs
         tdo = Signal(name=self.name+"_tdo")
         with m.If(select_ir):
             m.d.comb += tdo.eq(irblock.tdo)
         with m.Elif(select_id):
             m.d.comb += tdo.eq(idblock.tdo)
-        with m.Elif(select_io):
-            m.d.comb += tdo.eq(io_tdo)
+        if io_tdo is not None:
+            with m.Elif(select_io):
+                m.d.comb += tdo.eq(io_tdo)
 
+        # shiftregs block
         self._elaborate_shiftregs(
             m, capture=fsm.capture, shift=fsm.shift, update=fsm.update,
             ir=irblock.ir, tdo_jtag=tdo
         )
+
+        # 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
+                        # Note: could use FFSynchroniser
+                        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
@@ -425,47 +548,41 @@ class TAP(Elaboratable):
         return ioconn
 
     def _elaborate_ios(self, *, m, capture, shift, update, bd2io, bd2core):
-        connlength = {
-            IOType.In: 1,
-            IOType.Out: 1,
-            IOType.TriOut: 2,
-            IOType.InTriOut: 3,
-        }
-        length = sum(connlength[conn._iotype] for conn in self._ios)
+        length = sum(IOConn.lengths[conn._iotype] for conn in self._ios)
+        if length == 0:
+            return None
 
         io_sr = Signal(length)
         io_bd = Signal(length)
 
+        # Boundary scan "capture" mode.  makes I/O status available via SR
         with m.If(capture):
+            iol = []
             idx = 0
             for conn in self._ios:
-                if conn._iotype == IOType.In:
-                    m.d.posjtag += io_sr[idx].eq(conn.pad.i)
-                    idx += 1
-                elif conn._iotype == IOType.Out:
-                    m.d.posjtag += io_sr[idx].eq(conn.core.o)
-                    idx += 1
-                elif conn._iotype == IOType.TriOut:
-                    m.d.posjtag += [
-                        io_sr[idx].eq(conn.core.o),
-                        io_sr[idx+1].eq(conn.core.oe),
-                    ]
-                    idx += 2
-                elif conn._iotype == IOType.InTriOut:
-                    m.d.posjtag += [
-                        io_sr[idx].eq(conn.pad.i),
-                        io_sr[idx+1].eq(conn.core.o),
-                        io_sr[idx+2].eq(conn.core.oe),
-                    ]
-                    idx += 3
-                else:
-                    raise("Internal error")
+                # in appropriate sequence: In/TriOut has pad.i,
+                # Out.InTriOut has everything, Out and TriOut have core.o
+                if conn._iotype in [IOType.In, IOType.InTriOut]:
+                    iol.append(conn.pad.i)
+                if conn._iotype in [IOType.Out, IOType.InTriOut]:
+                    iol.append(conn.core.o)
+                if conn._iotype in [IOType.TriOut, IOType.InTriOut]:
+                    iol.append(conn.core.oe)
+                # length double-check
+                idx += IOConn.lengths[conn._iotype] # fails if wrong type
             assert idx == length, "Internal error"
+            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)
         with m.Elif(shift):
             m.d.posjtag += io_sr.eq(Cat(self.bus.tdi, io_sr[:-1]))
+
+        # "Update" mode
         with m.Elif(update):
             m.d.negjtag += io_bd.eq(io_sr)
 
+        # sets up IO (pad<->core) or in testing mode depending on requested
+        # mode, via Muxes controlled by bd2core and bd2io
         idx = 0
         for conn in self._ios:
             if conn._iotype == IOType.In:
@@ -493,13 +610,13 @@ class TAP(Elaboratable):
 
         return io_sr[-1]
 
-
-    def add_shiftreg(self, *, ircode, length, domain="sync", name=None, src_loc_at=0):
+    def add_shiftreg(self, *, ircode, length, domain="sync", name=None,
+                     src_loc_at=0):
         """Add a shift register to the JTAG interface
 
         Parameters:
-        - ircode: code(s) for the IR; int or sequence of ints. In the latter case this
-          shiftreg is shared between different IR codes.
+        - ircode: code(s) for the IR; int or sequence of ints. In the latter
+          case this shiftreg is shared between different IR codes.
         - length: the length of the shift register
         - domain: the domain on which the signal will be used"""
 
@@ -510,7 +627,8 @@ class TAP(Elaboratable):
             ir_it = ircodes = (ircode,)
         for _ircode in ir_it:
             if not isinstance(_ircode, int) or _ircode <= 0:
-                raise ValueError("IR code '{}' is not an int greater than 0".format(_ircode))
+                raise ValueError("IR code '{}' is not an int "
+                                 "greater than 0".format(_ircode))
             if _ircode in self._ircodes:
                 raise ValueError("IR code '{}' already taken".format(_ircode))
 
@@ -518,7 +636,8 @@ class TAP(Elaboratable):
 
         if name is None:
             name = "sr{}".format(len(self._srs))
-        sr = ShiftReg(sr_length=length, cmds=len(ircodes), name=name, src_loc_at=src_loc_at+1)
+        sr = ShiftReg(sr_length=length, cmds=len(ircodes), name=name,
+                      src_loc_at=src_loc_at+1)
         self._srs.append((ircodes, domain, sr))
 
         return sr
@@ -541,13 +660,16 @@ class TAP(Elaboratable):
                 sr_update.eq((isir != 0) & update),
             ]
 
-            # update signal is on the JTAG clockdomain, sr.oe is on `domain` clockdomain
-            # latch update in `domain` clockdomain and see when it has falling edge.
+            # update signal is on the JTAG clockdomain, sr.oe is on `domain`
+            # 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
+            # Note: could use FFSynchroniser instead
             update_core = Signal(name=sr.name+"_update_core")
             update_core_prev = Signal(name=sr.name+"_update_core_prev")
             m.d[domain] += [
-                update_core.eq(sr_update), # This is CDC from JTAG domain to given domain
+                update_core.eq(sr_update), # This is CDC from JTAG domain
+                                           # to given domain
                 update_core_prev.eq(update_core)
             ]
             with m.If(update_core_prev & ~update_core):
@@ -559,6 +681,7 @@ class TAP(Elaboratable):
             with m.If(sr_shift):
                 m.d.posjtag += reg.eq(Cat(reg[1:], self.bus.tdi))
             with m.If(sr_capture):
+                # could also use FFSynchroniser here too
                 m.d.posjtag += reg.eq(sr.i)
 
             # tdo = reg[0], tdo_en = shift
@@ -578,16 +701,18 @@ class TAP(Elaboratable):
             with m.Else():
                 m.d.comb += self.bus.tdo.eq(tdo_jtag)
         else:
-            # Always connect tdo_jtag to 
+            # 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",
+    def add_wishbone(self, *, ircodes, address_width, data_width,
+                     granularity=None, domain="sync", features=None,
                      name=None, src_loc_at=0):
         """Add a wishbone interface
 
-        In order to allow high JTAG clock speed, data will be cached. This means that if data is
-        output the value of the next address will be read automatically.
+        In order to allow high JTAG clock speed, data will be cached.
+        This means that if data is output the value of the next address
+        will be read automatically.
 
         Parameters:
         -----------
@@ -597,6 +722,7 @@ class TAP(Elaboratable):
           share a shift register of length data_width.
         address_width: width of the address
         data_width: width of the data
+        features: features required.  defaults to stall, lock, err, rty
 
         Returns:
         wb: nmigen_soc.wishbone.bus.Interface
@@ -605,6 +731,8 @@ class TAP(Elaboratable):
         if len(ircodes) != 3:
             raise ValueError("3 IR Codes have to be provided")
 
+        if features is None:
+            features={"stall", "lock", "err", "rty"}
         if name is None:
             name = "wb" + str(len(self._wbs))
         sr_addr = self.add_shiftreg(
@@ -617,7 +745,7 @@ class TAP(Elaboratable):
         )
 
         wb = WishboneInterface(data_width=data_width, addr_width=address_width,
-                               granularity=granularity, features={"stall", "lock", "err", "rty"},
+                               granularity=granularity, features=features,
                                name=name, src_loc_at=src_loc_at+1)
 
         self._wbs.append((sr_addr, sr_data, wb, domain))
@@ -645,23 +773,39 @@ class TAP(Elaboratable):
                         m.d[domain] += wb.dat_w.eq(sr_data.o)
                         m.next = "WRITEREAD"
                 with m.State("READ"):
-                    with m.If(~wb.stall):
+                    if not hasattr(wb, "stall"):
                         m.next = "READACK"
+                    else:
+                        with m.If(~wb.stall):
+                            m.next = "READACK"
                 with m.State("READACK"):
                     with m.If(wb.ack):
-                        # Store read data in sr_data.i and keep it there til next read
+                        # Store read data in sr_data.i and keep it there
+                        # til next read. could use FFSynchroniser (see above)
                         m.d[domain] += sr_data.i.eq(wb.dat_r)
                         m.next = "IDLE"
                 with m.State("WRITEREAD"):
-                    with m.If(~wb.stall):
+                    if not hasattr(wb, "stall"):
                         m.next = "WRITEREADACK"
+                    else:
+                        with m.If(~wb.stall):
+                            m.next = "WRITEREADACK"
                 with m.State("WRITEREADACK"):
                     with m.If(wb.ack):
                         m.d[domain] += wb.adr.eq(wb.adr + 1)
                         m.next = "READ"
 
-                m.d.comb += [
-                    wb.cyc.eq(~fsm.ongoing("IDLE")),
-                    wb.stb.eq(fsm.ongoing("READ") | fsm.ongoing("WRITEREAD")),
-                    wb.we.eq(fsm.ongoing("WRITEREAD")),
-                ]
+                if hasattr(wb, "stall"):
+                    m.d.comb += wb.stb.eq(fsm.ongoing("READ") |
+                                          fsm.ongoing("WRITEREAD"))
+                    m.d.comb += wb.we.eq(fsm.ongoing("WRITEREAD"))
+                else:
+                    # non-stall is single-cycle (litex), must assert stb
+                    # until ack is sent
+                    m.d.comb += wb.stb.eq(fsm.ongoing("READ") |
+                                          fsm.ongoing("WRITEREAD") |
+                                          fsm.ongoing("READACK") |
+                                          fsm.ongoing("WRITEREADACK"))
+                    m.d.comb += wb.we.eq(fsm.ongoing("WRITEREAD") |
+                                         fsm.ongoing("WRITEREADACK"))
+                m.d.comb += wb.cyc.eq(~fsm.ongoing("IDLE"))
index 0f22531daf6f41a9d7cb2f84768a91a06ff7b034..b57c2e4587251ef5acd8da52d0ca8f77b4631e4d 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,13 @@ setup(
     license="multi",
     python_requires="~=3.6",
     setup_requires=["setuptools_scm"],
-    install_requires=["setuptools", "cocotb", "nmigen", "nmigen-soc"],
+
+    # removing cocotb, causing unnecessary dependency and install problems
+    install_requires=["setuptools", "nmigen", "nmigen-soc", "modgrammar"],
+
+    # unit tests require cocotb: main operation does not
+    tests_require=['cocotb'],
+
     include_package_data=True,
     packages=find_packages(),
     project_urls={