Example #1
0
class ResponseQueue(UVMQueue):
    """
    Returns either the next response or the item with the id.
    """
    def __init__(self, maxsize: int = 0):
        super().__init__(maxsize=maxsize)
        self.put_event = CocotbEvent("put event")

    def put_nowait(self, item):
        super().put_nowait(item)
        self.put_event.set()
        self.put_event.clear()

    async def get_response(self, txn_id=None):
        if txn_id is None:
            return await self.get()
        else:
            while True:
                item_list = list(self._queue)
                txn_list = [
                    xx for xx in item_list if xx.transaction_id == txn_id
                ]
                if len(txn_list) == 0:
                    await self.put_event.wait()
                else:
                    assert len(txn_list) == 1, \
                        f"Multiple transactionsn have the same ID: {txn_id}"
                    _ = self._queue.index(txn_list[0])
                    self._queue.remove(txn_list[0])
                    return txn_list[0]

    def __str__(self):
        return str([str(xx) for xx in self.queue])
Example #2
0
class SfifoMonitor(WrRdDriverMonitor):
    '''
    Monitors the reading interface of a powlib_sfifo.
    '''
    def __init__(self, *args, **kwargs):
        '''
        This constructor simply creates the state and event objects needed
        to implement the read method defined for the monitor.
        '''
        self.__state = "stopped"
        self.__evt = Event()
        WrRdDriverMonitor.__init__(self, *args, **kwargs)

    def start(self):
        '''
        Indicate to the read method it should start its operation.
        '''
        self.__evt.set()

    def stop(self):
        '''
        After one more read, stop further read transactions.
        '''
        self.__state = "stopped"

    @coroutine
    def read(self):
        '''
        Implements the behavior of the monitor's read method. Initially,
        the monitor is in a stopped state, until the user decides to start it.
        Once started, the read method will call the drivers read method that 
        sets the rdrdy signal and set the state of the monitor to started. While
        in its started state, the monitor will call the driver's read method, 
        without any arguments, indicating it should simply read whenever the rdvld 
        is high.
        '''

        if self.__state == "stopped":
            '''
            Stopped state indicates the monitor is waiting.
            '''
            yield self.driver.read(rdrdy=0)
            yield self.__evt.wait()
            self.__evt.clear()
            self.__state = "started"
            yield self.cycle()
            value = yield self.driver.read(rdrdy=1)
            raise ReturnValue(value)

        elif self.__state == "started":
            '''
            Once started, the monitor will continue to read valid data from
            the read interface.
            '''
            value = yield self.driver.read()
            raise ReturnValue(value)

        else:
            raise ValueError("Unknown state occurred.")
Example #3
0
class TestBench:

    ## dut = design under test
    def __init__(self, dut, num_executions):
        self.dut = dut
        self.num_executions = num_executions
        self.in_vals = Event(
        )  # Event to communicate Driver and ReferenceModel
        self.out_vals = Event(
        )  # Event to communicate ReferenceModel and Checker
        self.ref_equal = 0

    @cocotb.coroutine
    def Driver(self):
        self.dut.rst <= 1
        yield RisingEdge(self.dut.clk)
        yield RisingEdge(self.dut.clk)
        self.dut.rst <= 0
        vals = rand_vals(2**SIGNAL_LENGTH)
        for _ in range(self.num_executions):
            vals.randomize()  # Randomize values of the vectors
            self.dut.signal_to_delay <= vals.signal_to_delay
            self.dut.signal_delayed <= vals.signal_delayed
            self.in_vals.set((vals.signal_to_delay, vals.signal_delayed))
            yield RisingEdge(self.dut.clk)
            sample_coverage(vals)

    @cocotb.coroutine
    def ReferenceModel(self):
        ref_signal_to_delay_delayed_1 = 0
        ref_signal_to_delay_delayed_2 = 0
        ref_signal_to_delay_delayed_3 = 0
        for _ in range(self.num_executions):
            yield self.in_vals.wait()
            self.ref_equal = (
                ref_signal_to_delay_delayed_3 == self.in_vals.data[1])
            ref_signal_to_delay_delayed_3 = ref_signal_to_delay_delayed_2
            ref_signal_to_delay_delayed_2 = ref_signal_to_delay_delayed_1
            ref_signal_to_delay_delayed_1 = self.in_vals.data[0]
            self.out_vals.set(self.ref_equal)
            self.in_vals.clear()

    @cocotb.coroutine
    def Checker(self, ref_th):
        last_ref_equal = False
        for _ in range(self.num_executions):
            yield self.out_vals.wait()
            cocotb.log.info(" ref_equal={0}, dut_equal={1}".format(
                last_ref_equal, int(self.dut.equal.value)))
            assert self.dut.equal == last_ref_equal
            last_ref_equal = self.out_vals.data
            self.out_vals.clear()

    @cocotb.coroutine
    def run(self):
        cocotb.fork(self.Driver())
        ref_th = cocotb.fork(self.ReferenceModel())
        yield cocotb.fork(self.Checker(ref_th)).join()
Example #4
0
class ObjectionHandler(metaclass=Singleton):
    """
    This singleton accepts objections and then allows
    them to be removed. It returns True to run_phase_complete()
    when there are no objections left.
    """
    def __init__(self):
        self.__objections = {}
        self._objection_event = Event("objection changed")
        self.objection_raised = False
        self.run_phase_done_flag = None  # used in test suites
        self.printed_warning = False

    def __str__(self):
        ss = f"run_phase complete: {self.run_phase_complete()}\n"
        ss += "Current Objections:\n"
        for cc in self.__objections:
            ss += f"{self.__objections[cc]}\n"
        return ss

    def clear(self):
        if len(self.__objections) != 0:
            logging.warning("Clearing objections raised by %s",
                            ', '.join(self.__objections.values()))
            self.__objections = {}
        self.objection_raised = False

    def raise_objection(self, raiser):
        name = raiser.get_full_name()
        self.__objections[name] = self.__objections.setdefault(name, 0) + 1
        self.objection_raised = True
        self._objection_event.clear()

    def drop_objection(self, dropper):
        name = dropper.get_full_name()
        try:
            self.__objections[name] -= 1
        except KeyError:
            self.objection_raised = True
            pass
        if self.__objections[name] == 0:
            del self.__objections[name]
            self._objection_event.set()

    async def run_phase_complete(self):
        # Allow the run_phase coros to get scheduled and raise objections:
        await NullTrigger()
        if self.objection_raised:
            await self._objection_event.wait()
        else:
            logging.warning(
                "You did not call self.raise_objection() in any run_phase")
Example #5
0
class test_drv(BusDriver):
    _signals = ["A", "B", "X"]
    _optional_signals= []

    def __init__(self, entity, name, clock):
        BusDriver.__init__(self, entity, name, clock)
        self.bus.A.setimmediatevalue(5)
        self.bus.B.setimmediatevalue(5)
        self.log.debug("Test DrvM created")
        self.busy_event = Event("%s_busy" % name)
        self.busy = False

    @cocotb.coroutine
    def _acquire_lock(self):
        if self.busy:
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy = True

    def _release_lock(self):
        self.busy = False
        self.busy_event.set()


    @cocotb.coroutine
    def write(self, value_a, value_b, sync=True):
        """
        """
        yield self._acquire_lock()

        if sync:
            yield RisingEdge(self.clock)
        self.bus.A <= value_a
        self.bus.B <= value_b

        self._release_lock()
Example #6
0
class Driver(object):
    """Class defining the standard interface for a driver within a testbench.

    The driver is responsible for serialising transactions onto the physical
    pins of the interface.  This may consume simulation time.
    """
    def __init__(self):
        """Constructor for a driver instance."""
        self._pending = Event(name="Driver._pending")
        self._sendQ = deque()

        # Subclasses may already set up logging
        if not hasattr(self, "log"):
            self.log = SimLog("cocotb.driver.%s" % (self.__class__.__name__))

        # Create an independent coroutine which can send stuff
        self._thread = cocotb.scheduler.add(self._send_thread())

    def kill(self):
        """Kill the coroutine sending stuff."""
        if self._thread:
            self._thread.kill()
            self._thread = None

    def append(self, transaction, callback=None, event=None, **kwargs):
        """Queue up a transaction to be sent over the bus.

        Mechanisms are provided to permit the caller to know when the
        transaction is processed.

        Args:
            transaction (any): The transaction to be sent.
            callback (callable, optional): Optional function to be called 
                when the transaction has been sent.
            event (optional): :class:`~cocotb.triggers.Event` to be set
                when the transaction has been sent.
            **kwargs: Any additional arguments used in child class' 
                :any:`_driver_send` method.
        """
        self._sendQ.append((transaction, callback, event, kwargs))
        self._pending.set()

    def clear(self):
        """Clear any queued transactions without sending them onto the bus."""
        self._sendQ = deque()

    @coroutine
    def send(self, transaction, sync=True, **kwargs):
        """Blocking send call (hence must be "yielded" rather than called).

        Sends the transaction over the bus.

        Args:
            transaction (any): The transaction to be sent.
            sync (bool, optional): Synchronise the transfer by waiting for a rising edge.
            **kwargs (dict): Additional arguments used in child class'
                :any:`_driver_send` method.
        """
        yield self._send(transaction, None, None, sync=sync, **kwargs)

    def _driver_send(self, transaction, sync=True, **kwargs):
        """Actual implementation of the send.

        Subclasses should override this method to implement the actual 
        :meth:`~cocotb.drivers.Driver.send` routine.

        Args:
            transaction (any): The transaction to be sent.
            sync (boolean, optional): Synchronise the transfer by waiting for a rising edge.
            **kwargs: Additional arguments if required for protocol implemented in subclass.
        """
        raise NotImplementedError("Subclasses of Driver should define a "
                                  "_driver_send coroutine")

    @coroutine
    def _send(self, transaction, callback, event, sync=True, **kwargs):
        """Send coroutine.

        Args:
            transaction (any): The transaction to be sent.
            callback (callable, optional): Optional function to be called 
                when the transaction has been sent.
            event (optional): event to be set when the transaction has been sent.
            sync (boolean, optional): Synchronise the transfer by waiting for a rising edge.
            **kwargs: Any additional arguments used in child class' 
                :any:`_driver_send` method.
        """
        yield self._driver_send(transaction, sync=sync, **kwargs)

        # Notify the world that this transaction is complete
        if event:
            event.set()
        if callback:
            callback(transaction)

    @coroutine
    def _send_thread(self):
        while True:

            # Sleep until we have something to send
            while not self._sendQ:
                self._pending.clear()
                yield self._pending.wait()

            synchronised = False

            # Send in all the queued packets,
            # only synchronise on the first send
            while self._sendQ:
                transaction, callback, event, kwargs = self._sendQ.popleft()
                self.log.debug("Sending queued packet...")
                yield self._send(transaction, callback, event,
                                 sync=not synchronised, **kwargs)
                synchronised = True
Example #7
0
class Queue(Generic[T]):
    """A queue, useful for coordinating producer and consumer coroutines.

    If *maxsize* is less than or equal to 0, the queue size is infinite. If it
    is an integer greater than 0, then :meth:`put` will block when the queue
    reaches *maxsize*, until an item is removed by :meth:`get`.
    """
    def __init__(self, maxsize: int = 0):

        self._maxsize = maxsize

        self._finished = Event()
        self._finished.set()

        self._getters = collections.deque()
        self._putters = collections.deque()

        self._init(maxsize)

    def _init(self, maxsize):
        self._queue = collections.deque()

    def _put(self, item):
        self._queue.append(item)

    def _get(self):
        return self._queue.popleft()

    def _wakeup_next(self, waiters):
        while waiters:
            event, task = waiters.popleft()
            if not task._finished:
                event.set()
                break

    def __repr__(self):
        return '<{} {} at {}>'.format(
            type(self).__name__, self._format(), _pointer_str(self))

    def __str__(self):
        return '<{} {}>'.format(type(self).__name__, self._format())

    def __class_getitem__(cls, type):
        return cls

    def _format(self):
        result = 'maxsize={}'.format(repr(self._maxsize))
        if getattr(self, '_queue', None):
            result += ' _queue={}'.format(repr(list(self._queue)))
        if self._getters:
            result += ' _getters[{}]'.format(len(self._getters))
        if self._putters:
            result += ' _putters[{}]'.format(len(self._putters))
        return result

    def qsize(self) -> int:
        """Number of items in the queue."""
        return len(self._queue)

    @property
    def maxsize(self) -> int:
        """Number of items allowed in the queue."""
        return self._maxsize

    def empty(self) -> bool:
        """Return ``True`` if the queue is empty, ``False`` otherwise."""
        return not self._queue

    def full(self) -> bool:
        """Return ``True`` if there are :meth:`maxsize` items in the queue.

        .. note::
            If the Queue was initialized with ``maxsize=0`` (the default), then
            :meth:`full` is never ``True``.
        """
        if self._maxsize <= 0:
            return False
        else:
            return self.qsize() >= self._maxsize

    async def put(self, item: T) -> None:
        """Put an *item* into the queue.

        If the queue is full, wait until a free
        slot is available before adding the item.
        """
        while self.full():
            event = Event('{} put'.format(type(self).__name__))
            self._putters.append((event, cocotb.scheduler._current_task))
            await event.wait()
        self.put_nowait(item)

    def put_nowait(self, item: T) -> None:
        """Put an *item* into the queue without blocking.

        If no free slot is immediately available, raise :exc:`asyncio.QueueFull`.
        """
        if self.full():
            raise QueueFull()
        self._put(item)
        self._finished.clear()
        self._wakeup_next(self._getters)

    async def get(self) -> T:
        """Remove and return an item from the queue.

        If the queue is empty, wait until an item is available.
        """
        while self.empty():
            event = Event('{} get'.format(type(self).__name__))
            self._getters.append((event, cocotb.scheduler._current_task))
            await event.wait()
        return self.get_nowait()

    def get_nowait(self) -> T:
        """Remove and return an item from the queue.

        Return an item if one is immediately available, else raise
        :exc:`asyncio.QueueEmpty`.
        """
        if self.empty():
            raise QueueEmpty()
        item = self._get()
        self._wakeup_next(self._putters)
        return item
Example #8
0
class UsPcieBase:

    _signal_widths = {"tvalid": 1, "tready": 1}

    _valid_signal = "tvalid"
    _ready_signal = "tready"

    _transaction_obj = UsPcieTransaction
    _frame_obj = UsPcieFrame

    def __init__(self, bus, clock, reset=None, *args, **kwargs):
        self.bus = bus
        self.clock = clock
        self.reset = reset
        self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")

        super().__init__(*args, **kwargs)

        self.active = False
        self.queue = Queue()
        self.dequeue_event = Event()
        self.idle_event = Event()
        self.idle_event.set()
        self.active_event = Event()

        self.pause = False
        self._pause_generator = None
        self._pause_cr = None

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.width = len(self.bus.tdata)
        self.byte_lanes = len(self.bus.tkeep)

        self.byte_size = self.width // self.byte_lanes
        self.byte_mask = 2**self.byte_size - 1

        assert self.width in [64, 128, 256, 512]
        assert self.byte_size == 32

    def _init(self):
        pass

    def count(self):
        return self.queue.qsize()

    def empty(self):
        return self.queue.empty()

    def clear(self):
        while not self.queue.empty():
            self.queue.get_nowait()
        self.idle_event.set()
        self.active_event.clear()

    def idle(self):
        raise NotImplementedError()

    async def wait(self):
        raise NotImplementedError()

    def set_pause_generator(self, generator=None):
        if self._pause_cr is not None:
            self._pause_cr.kill()
            self._pause_cr = None

        self._pause_generator = generator

        if self._pause_generator is not None:
            self._pause_cr = cocotb.fork(self._run_pause())

    def clear_pause_generator(self):
        self.set_pause_generator(None)

    async def _run_pause(self):
        for val in self._pause_generator:
            self.pause = val
            await RisingEdge(self.clock)
Example #9
0
class AvalonMaster(AvalonMM):
    """Avalon Memory Mapped Interface (Avalon-MM) Master."""
    def __init__(self, entity, name, clock, **kwargs):
        AvalonMM.__init__(self, entity, name, clock, **kwargs)
        self.log.debug("AvalonMaster created")
        self.busy_event = Event("%s_busy" % name)
        self.busy = False

    def __len__(self):
        return 2**len(self.bus.address)

    @coroutine
    def _acquire_lock(self):
        if self.busy:
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy = True

    def _release_lock(self):
        self.busy = False
        self.busy_event.set()

    @coroutine
    def read(self, address, sync=True):
        """Issue a request to the bus and block until this comes back.

        Simulation time still progresses
        but syntactically it blocks.

        Args:
            address (int): The address to read from.
            sync (bool, optional): Wait for rising edge on clock initially.
                Defaults to True.

        Returns:
            BinaryValue: The read data value.

        Raises:
            :any:`TestError`: If master is write-only.
        """
        if not self._can_read:
            self.log.error("Cannot read - have no read signal")
            raise TestError("Attempt to read on a write-only AvalonMaster")

        yield self._acquire_lock()

        # Apply values for next clock edge
        if sync:
            yield RisingEdge(self.clock)
        self.bus.address <= address
        self.bus.read <= 1
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= int("1" * len(self.bus.byteenable), 2)
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 1

        # Wait for waitrequest to be low
        if hasattr(self.bus, "waitrequest"):
            yield self._wait_for_nsignal(self.bus.waitrequest)
        yield RisingEdge(self.clock)

        # Deassert read
        self.bus.read <= 0
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= 0
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 0
        v = self.bus.address.value
        v.binstr = "x" * len(self.bus.address)
        self.bus.address <= v

        if hasattr(self.bus, "readdatavalid"):
            while True:
                yield ReadOnly()
                if int(self.bus.readdatavalid):
                    break
                yield RisingEdge(self.clock)
        else:
            # Assume readLatency = 1 if no readdatavalid
            # FIXME need to configure this,
            # should take a dictionary of Avalon properties.
            yield ReadOnly()

        # Get the data
        data = self.bus.readdata.value

        self._release_lock()
        raise ReturnValue(data)

    @coroutine
    def write(self, address, value):
        """Issue a write to the given address with the specified
        value.

        Args:
            address (int): The address to write to.
            value (int): The data value to write.

        Raises:
            :any:`TestError`: If master is read-only.
        """
        if not self._can_write:
            self.log.error("Cannot write - have no write signal")
            raise TestError("Attempt to write on a read-only AvalonMaster")

        yield self._acquire_lock()

        # Apply values to bus
        yield RisingEdge(self.clock)
        self.bus.address <= address
        self.bus.writedata <= value
        self.bus.write <= 1
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= int("1" * len(self.bus.byteenable), 2)
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 1

        # Wait for waitrequest to be low
        if hasattr(self.bus, "waitrequest"):
            count = yield self._wait_for_nsignal(self.bus.waitrequest)

        # Deassert write
        yield RisingEdge(self.clock)
        self.bus.write <= 0
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= 0
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 0
        v = self.bus.address.value
        v.binstr = "x" * len(self.bus.address)
        self.bus.address <= v

        v = self.bus.writedata.value
        v.binstr = "x" * len(self.bus.writedata)
        self.bus.writedata <= v
        self._release_lock()
Example #10
0
class UVMEventBase(UVMObject):
    #const static string type_name = "uvm_event_base"
    type_name = "uvm_event_base"

    # Function: new
    # Creates a new event object.
    def __init__(self, name=""):
        UVMObject.__init__(self, name)
        self.m_event = Event()
        self.num_waiters = 0
        self.on = False
        self.on_event = Event("on_event_" + name)
        self.trigger_time = 0
        self.callbacks = UVMQueue()  # uvm_event_callback
        self.m_waiters = 0
        self.m_value_changed_event = Event("value_changed_event_" + name)

    # endfunction

    def set_value(self, key, value):
        setattr(self, key, value)
        self.m_value_changed_event.set()

    def set(self):
        if self.m_waiters > 0:
            self.on_event.set()

    @cocotb.coroutine
    def wait(self):
        if self.m_waiters == 0:
            self.on_event.clear()

        self.m_waiters += 1
        yield self.on_event.wait()
        self.m_waiters -= 1
        if self.m_waiters == 0:
            self.on_event.clear()
        # else-branch needed with Timer(0)

    #    //---------//
    #    // waiting //
    #    //---------//
    #
    #    // Task: wait_on
    #    //
    #    // Waits for the event to be activated for the first time.
    #    //
    #    // If the event has already been triggered, this task returns immediately.
    #    // If ~delta~ is set, the caller will be forced to wait a single delta #0
    #    // before returning. This prevents the caller from returning before
    #    // previously waiting processes have had a chance to resume.
    #    //
    #    // Once an event has been triggered, it will be remain "on" until the event
    #    // is <reset>.
    #    virtual task wait_on (bit delta = 0)
    @cocotb.coroutine
    def wait_on(self, delta=False):
        if self.on is True:
            if delta is True:
                #0
                yield Timer(0)
            yield Timer(0)
            return
        self.num_waiters += 1
        yield wait(lambda: self.on is True, self.m_value_changed_event)
        #while True:
        #    yield self.m_value_changed_event.wait()
        #    self.m_value_changed_event.clear()
        #    if self.on is True:
        #        break
        # endtask

    #    // Task: wait_off
    #    //
    #    // If the event has already triggered and is "on", this task waits for the
    #    // event to be turned "off" via a call to <reset>.
    #    //
    #    // If the event has not already been triggered, this task returns immediately.
    #    // If ~delta~ is set, the caller will be forced to wait a single delta #0
    #    // before returning. This prevents the caller from returning before
    #    // previously waiting processes have had a chance to resume.
    #
    #    virtual task wait_off (bit delta = 0)
    @cocotb.coroutine
    def wait_off(self, delta=False):
        if self.on is False:
            if delta is True:
                yield Timer(0)  #0
            return
        self.num_waiters += 1
        yield wait(lambda: self.on is False, self.m_value_changed_event)
        #    endtask

    #    // Task: wait_trigger
    #    //
    #    // Waits for the event to be triggered.
    #    //
    #    // If one process calls wait_trigger in the same delta as another process
    #    // calls <uvm_event#(T)::trigger>, a race condition occurs. If the call to wait occurs
    #    // before the trigger, this method will return in this delta. If the wait
    #    // occurs after the trigger, this method will not return until the next
    #    // trigger, which may never occur and thus cause deadlock.
    #
    @cocotb.coroutine
    def wait_trigger(self):
        self.num_waiters += 1
        yield self.m_event.wait()
        self.m_event.clear()

    # endtask

    #    // Task: wait_ptrigger
    #    //
    #    // Waits for a persistent trigger of the event. Unlike <wait_trigger>, this
    #    // views the trigger as persistent within a given time-slice and thus avoids
    #    // certain race conditions. If this method is called after the trigger but
    #    // within the same time-slice, the caller returns immediately.

    @cocotb.coroutine
    def wait_ptrigger(self):
        if self.m_event.fired:
            return
        self.num_waiters += 1
        yield self.m_event.wait()
        self.m_event.clear()

    #    // Function: get_trigger_time
    #    //
    #    // Gets the time that this event was last triggered. If the event has not bee
    #    // triggered, or the event has been reset, then the trigger time will be 0.
    #
    def get_trigger_time(self):
        return self.trigger_time

    #    //-------//
    #    // state //
    #    //-------//

    #    // Function: is_on
    #    //
    #    // Indicates whether the event has been triggered since it was last reset.
    #    //
    #    // A return of 1 indicates that the event has triggered.
    #
    def is_on(self):
        return self.on

    #    // Function: is_off
    #    //
    #    // Indicates whether the event has been triggered or been reset.
    #    //
    #    // A return of 1 indicates that the event has not been triggered.
    #
    def is_off(self):
        return self.on is False

    #    // Function: reset
    #    //
    #    // Resets the event to its off state. If ~wakeup~ is set, then all processes
    #    // currently waiting for the event are activated before the reset.
    #    //
    #    // No self.callbacks are called during a reset.
    #
    def reset(self, wakeup=False):
        if wakeup is True:
            self.m_event.set()
        self.m_event = Event()
        self.num_waiters = 0
        self.set_value("on", False)
        self.trigger_time = 0
Example #11
0
class AvalonMaster(AvalonMM):
    """Avalon Memory Mapped Interface (Avalon-MM) Master"""
    def __init__(self, entity, name, clock, **kwargs):
        AvalonMM.__init__(self, entity, name, clock, **kwargs)
        self.log.debug("AvalonMaster created")
        self.busy_event = Event("%s_busy" % name)
        self.busy = False

    def __len__(self):
        return 2**len(self.bus.address)

    @coroutine
    def _acquire_lock(self):
        if self.busy:
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy = True

    def _release_lock(self):
        self.busy = False
        self.busy_event.set()

    @coroutine
    def read(self, address, sync=True):
        """Issue a request to the bus and block until this
        comes back. Simulation time still progresses
        but syntactically it blocks.
        
        Args:
            address (int): The address to read from.
            sync (bool, optional): Wait for rising edge on clock initially.
                Defaults to True.
            
        Returns:
            BinaryValue: The read data value.
            
        Raises:
            :any:`TestError`: If master is write-only.
        """
        if not self._can_read:
            self.log.error("Cannot read - have no read signal")
            raise TestError("Attempt to read on a write-only AvalonMaster")

        yield self._acquire_lock()

        # Apply values for next clock edge
        if sync:
            yield RisingEdge(self.clock)
        self.bus.address <= address
        self.bus.read <= 1
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= int("1"*len(self.bus.byteenable), 2)
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 1

        # Wait for waitrequest to be low
        if hasattr(self.bus, "waitrequest"):
            yield self._wait_for_nsignal(self.bus.waitrequest)
        yield RisingEdge(self.clock)

        # Deassert read
        self.bus.read <= 0
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= 0
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 0
        v = self.bus.address.value
        v.binstr = "x" * len(self.bus.address)
        self.bus.address <= v

        if hasattr(self.bus, "readdatavalid"):
            while True:
                yield ReadOnly()
                if int(self.bus.readdatavalid):
                    break
                yield RisingEdge(self.clock)
        else:
            # Assume readLatency = 1 if no readdatavalid
            # FIXME need to configure this,
            # should take a dictionary of Avalon properties.
            yield ReadOnly()

        # Get the data
        data = self.bus.readdata.value

        self._release_lock()
        raise ReturnValue(data)

    @coroutine
    def write(self, address, value):
        """Issue a write to the given address with the specified
        value.

        Args:
            address (int): The address to write to.
            value (int): The data value to write.

        Raises:
            :any:`TestError`: If master is read-only.
        """
        if not self._can_write:
            self.log.error("Cannot write - have no write signal")
            raise TestError("Attempt to write on a read-only AvalonMaster")

        yield self._acquire_lock()

        # Apply values to bus
        yield RisingEdge(self.clock)
        self.bus.address <= address
        self.bus.writedata <= value
        self.bus.write <= 1
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= int("1"*len(self.bus.byteenable), 2)
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 1

        # Wait for waitrequest to be low
        if hasattr(self.bus, "waitrequest"):
            count = yield self._wait_for_nsignal(self.bus.waitrequest)

        # Deassert write
        yield RisingEdge(self.clock)
        self.bus.write <= 0
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= 0
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 0
        v = self.bus.address.value
        v.binstr = "x" * len(self.bus.address)
        self.bus.address <= v

        v = self.bus.writedata.value
        v.binstr = "x" * len(self.bus.writedata)
        self.bus.writedata <= v
        self._release_lock()
Example #12
0
class WishboneMaster(Wishbone):
    """
    Wishbone master
    """
    def __init__(self, entity, name, clock, timeout=None, width=32):
        sTo = ", no cycle timeout"
        if timeout is not None:
            sTo = ", cycle timeout is %u clock cycles" % timeout
        self.busy_event = Event("%s_busy" % name)
        self._timeout = timeout
        self.busy = False
        self._acked_ops = 0
        self._res_buf = []
        self._aux_buf = []
        self._op_cnt = 0
        self._clk_cycle_count = 0
        Wishbone.__init__(self, entity, name, clock, width)
        self.log.info("Wishbone Master created%s" % sTo)

    @coroutine
    def _clk_cycle_counter(self):
        """
        Cycle counter to time bus operations
        """
        clkedge = RisingEdge(self.clock)
        self._clk_cycle_count = 0
        while self.busy:
            yield clkedge
            self._clk_cycle_count += 1

    @coroutine
    def _open_cycle(self):
        # Open new wishbone cycle
        if self.busy:
            self.log.error(
                "Opening Cycle, but WB Driver is already busy."
            )
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy = True
        cocotb.fork(self._read())
        cocotb.fork(self._clk_cycle_counter())
        self.bus.cyc <= 1
        self._acked_ops = 0
        self._res_buf = []
        self._aux_buf = []
        self.log.debug("Opening cycle, %u Ops" % self._op_cnt)

    @coroutine
    def _close_cycle(self):
        # Close current wishbone cycle
        clkedge = RisingEdge(self.clock)
        count = 0
        last_acked_ops = 0
        # Wait for all Operations being acknowledged by the slave
        # before lowering the cycle line. This is not mandatory by the bus
        # standard, but a crossbar might send acks to the wrong master if we
        # don't wait. We don't want to risk that, it could hang the bus
        while self._acked_ops < self._op_cnt:
            if last_acked_ops != self._acked_ops:
                self.log.debug("Waiting for missing acks: %u/%u" %
                               (self._acked_ops, self._op_cnt))
            last_acked_ops = self._acked_ops
            # check for timeout when finishing the cycle
            count += 1
            if (not (self._timeout is None)):
                if (count > self._timeout):
                    raise TestFailure(
                        "Timeout of %u clock cycles reached when waiting for"
                        "reply from slave"
                        % self._timeout)
            yield clkedge

        self.busy = False
        self.busy_event.set()
        self.bus.cyc <= 0
        yield clkedge

    @coroutine
    def _wait_stall(self):
        """Wait for stall to be low before continuing (Pipelined Wishbone)
        """
        clkedge = RisingEdge(self.clock)
        count = 0
        if hasattr(self.bus, "stall"):
            count = 0
            while self.bus.stall:
                yield clkedge
                count += 1
                if (not (self._timeout is None)):
                    if (count > self._timeout):
                        raise TestFailure(
                            "Timeout of %u clock cycles reached when on stall"
                            "from slave"
                            % self._timeout)
            self.log.debug("Stalled for %u cycles" % count)
        raise ReturnValue(count)

    @coroutine
    def _wait_ack(self):
        """Wait for ACK on the bus before continuing (Non pipelined Wishbone)
        """
        # wait for acknownledgement before continuing
        # Classic Wishbone without pipelining
        clkedge = RisingEdge(self.clock)
        count = 0
        if not hasattr(self.bus, "stall"):
            while not self._get_reply():
                yield clkedge
                count += 1
            self.log.debug("Waited %u cycles for acknowledge" % count)
        raise ReturnValue(count)

    def _get_reply(self):
        # helper function for slave acks
        tmpAck = int(self.bus.ack)
        tmpErr = 0
        tmpRty = 0
        if hasattr(self.bus, "err"):
            tmpErr = int(self.bus.err)
        if hasattr(self.bus, "rty"):
            tmpRty = int(self.bus.rty)
        # check if more than one line was raised
        if ((tmpAck + tmpErr + tmpRty) > 1):
            raise TestFailure(
                "Slave raised more than one reply line at once! ACK: %u ERR:"
                "%u RTY: %u"
                % (tmpAck, tmpErr, tmpRty))
        # return 0 if no reply, 1 for ACK, 2 for ERR, 3 for RTY.
        # use 'replyTypes' Dict for lookup
        return (tmpAck + 2 * tmpErr + 3 * tmpRty)

    @coroutine
    def _read(self):
        """
        Reader for slave replies
        """
        count = 0
        clkedge = RisingEdge(self.clock)
        while self.busy:
            reply = self._get_reply()
            # valid reply?
            if (bool(reply)):
                datrd = int(self.bus.datrd)
                # append reply and meta info to result buffer
                tmpRes = WBRes(ack=reply,
                               sel=None,
                               adr=None,
                               datrd=datrd,
                               datwr=None,
                               waitIdle=None,
                               waitStall=None,
                               waitAck=self._clk_cycle_count)
                self._res_buf.append(tmpRes)
                self._acked_ops += 1
            yield clkedge
            count += 1

    @coroutine
    def _drive(self, we, adr, datwr, sel, idle):
        """
        Drive the Wishbone Master Out Lines
        """

        clkedge = RisingEdge(self.clock)
        if self.busy:
            # insert requested idle cycles
            if idle is not None:
                idlecnt = idle
                while idlecnt > 0:
                    idlecnt -= 1
                    yield clkedge
            # drive outputs
            self.bus.stb <= 1
            self.bus.adr <= adr
            self.bus.sel <= sel
            self.bus.datwr <= datwr
            self.bus.we <= we
            yield clkedge
            # deal with flow control (pipelined wishbone)
            stalled = yield self._wait_stall()
            # append operation and meta info to auxiliary buffer
            self._aux_buf.append(
                WBAux(sel, adr, datwr, stalled, idle, self._clk_cycle_count))
            # non pipelined wishbone
            yield self._wait_ack()
            # reset strobe and write enable after the acknowledgement was
            # received. Note that this was different from the original code,
            # which cleared these values immediately.
            # It is not clear from the spec whether these values must only be
            # set on the first cycle, or they must be asserted until the other
            # side acknowledges.
            self.bus.stb <= 0
            self.bus.we <= 0
        else:
            self.log.error("Cannot drive the Wishbone bus outside a cycle!")

    @coroutine
    def send_cycle(self, arg):
        """
        The main sending routine

        Args:
            list(WishboneOperations)
        """
        cnt = 0
        clkedge = RisingEdge(self.clock)
        yield clkedge
        if is_sequence(arg):
            if len(arg) < 1:
                self.log.error("List contains no operations to carry out")
            else:
                self._op_cnt = len(arg)
                firstword = True
                for op in arg:
                    if not isinstance(op, WBOp):
                        raise TestFailure(
                            "Sorry, argument must be a list of WBOp (Wishbone"
                            "Operation) objects!"
                        )
                    if firstword:
                        firstword = False
                        result = []
                        yield self._open_cycle()

                    if op.dat is not None:
                        we = 1
                        dat = op.dat
                    else:
                        we = 0
                        dat = 0
                    yield self._drive(we, op.adr, dat, op.sel, op.idle)
                    self.log.debug(
                        "#%3u WE: %s ADR: 0x%08x DAT: 0x%08x SEL: 0x%1x IDLE:"
                        "%3u"
                        % (cnt, we, op.adr << 2, dat, op.sel, op.idle))
                    cnt += 1
                yield self._close_cycle()

                # do pick and mix from result- and auxiliary buffer so we get
                # all operation and meta info
                for res, aux in zip(self._res_buf, self._aux_buf):
                    res.datwr = aux.datwr
                    res.sel = aux.sel
                    res.adr = aux.adr
                    res.waitIdle = aux.waitIdle
                    res.waitStall = aux.waitStall
                    res.waitAck -= aux.ts
                    result.append(res)

            raise ReturnValue(result)
        else:
            raise TestFailure(
                "Sorry, argument must be a list of WBOp (Wishbone Operation)"
                " objects!"
            )
            raise ReturnValue(None)

    @coroutine
    def read(self, adr):
        result = yield self.send_cycle([WBOp(adr >> 2)])
        for rec in result:
            self.log.debug("Result: {}".format(rec))
        raise ReturnValue(result[-1].datrd)

    @coroutine
    def write(self, adr, data):
        result = yield self.send_cycle([WBOp(adr >> 2, data)])
        for rec in result:
            self.log.debug("Result: {}".format(rec))
        raise ReturnValue(0)
Example #13
0
class S10PcieSink(S10PcieBase):

    _signal_widths = {"valid": 1, "ready": 1}

    _valid_signal = "valid"
    _ready_signal = "ready"

    _transaction_obj = S10PcieTransaction
    _frame_obj = S10PcieFrame

    def __init__(self,
                 bus,
                 clock,
                 reset=None,
                 ready_latency=0,
                 *args,
                 **kwargs):
        super().__init__(bus, clock, reset, ready_latency, *args, **kwargs)

        self.sample_obj = None
        self.sample_sync = Event()

        self.queue_occupancy_limit_bytes = -1
        self.queue_occupancy_limit_frames = -1

        self.bus.ready.setimmediatevalue(0)

        cocotb.start_soon(self._run_sink())
        cocotb.start_soon(self._run())

    def _recv(self, frame):
        if self.queue.empty():
            self.active_event.clear()
        self.queue_occupancy_bytes -= len(frame)
        self.queue_occupancy_frames -= 1
        return frame

    async def recv(self):
        frame = await self.queue.get()
        return self._recv(frame)

    def recv_nowait(self):
        frame = self.queue.get_nowait()
        return self._recv(frame)

    def full(self):
        if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
            return True
        elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
            return True
        else:
            return False

    def idle(self):
        return not self.active

    async def wait(self, timeout=0, timeout_unit='ns'):
        if not self.empty():
            return
        if timeout:
            await First(self.active_event.wait(), Timer(timeout, timeout_unit))
        else:
            await self.active_event.wait()

    async def _run_sink(self):
        ready_delay = []

        clock_edge_event = RisingEdge(self.clock)

        while True:
            await clock_edge_event

            # read handshake signals
            ready_sample = self.bus.ready.value
            valid_sample = self.bus.valid.value

            if self.reset is not None and self.reset.value:
                self.bus.ready.value = 0
                continue

            # ready delay
            if self.ready_latency > 0:
                if len(ready_delay) != self.ready_latency:
                    ready_delay = [0] * self.ready_latency
                ready_delay.append(ready_sample)
                ready_sample = ready_delay.pop(0)

            if valid_sample and ready_sample:
                self.sample_obj = self._transaction_obj()
                self.bus.sample(self.sample_obj)
                self.sample_sync.set()
            elif self.ready_latency > 0:
                assert not valid_sample, "handshake error: valid asserted outside of ready cycle"

            self.bus.ready.value = (not self.full() and not self.pause)

    async def _run(self):
        self.active = False
        frame = None
        dword_count = 0

        while True:
            while not self.sample_obj:
                self.sample_sync.clear()
                await self.sample_sync.wait()

            self.active = True
            sample = self.sample_obj
            self.sample_obj = None

            for seg in range(self.seg_count):
                if not sample.valid & (1 << seg):
                    continue

                if sample.sop & (1 << seg):
                    assert frame is None, "framing error: sop asserted in frame"
                    frame = S10PcieFrame()

                    hdr = (sample.data >>
                           (seg * self.seg_width)) & self.seg_mask
                    fmt = (hdr >> 29) & 0b111
                    if fmt & 0b001:
                        dword_count = 4
                    else:
                        dword_count = 3

                    if fmt & 0b010:
                        count = hdr & 0x3ff
                        if count == 0:
                            count = 1024
                        dword_count += count

                    frame.bar_range = (sample.bar_range >> seg * 3) & 0x7
                    frame.func_num = (sample.func_num >> seg * 3) & 0x7
                    if sample.vf_active & (1 << seg):
                        frame.vf_num = (sample.vf_num >> seg * 11) & 0x7ff
                    frame.err = (sample.err >> seg) & 0x1

                assert frame is not None, "framing error: data transferred outside of frame"

                if dword_count > 0:
                    data = (sample.data >>
                            (seg * self.seg_width)) & self.seg_mask
                    parity = (sample.parity >>
                              (seg * self.seg_par_width)) & self.seg_par_mask
                    for k in range(min(self.seg_byte_lanes, dword_count)):
                        frame.data.append((data >> 32 * k) & 0xffffffff)
                        frame.parity.append((parity >> 4 * k) & 0xf)
                        dword_count -= 1

                if sample.eop & (1 << seg):
                    assert dword_count == 0, "framing error: incorrect length or early eop"
                    self.log.info(f"RX frame: {frame}")
                    self._sink_frame(frame)
                    self.active = False
                    frame = None

    def _sink_frame(self, frame):
        self.queue_occupancy_bytes += len(frame)
        self.queue_occupancy_frames += 1

        self.queue.put_nowait(frame)
        self.active_event.set()
Example #14
0
class S10PcieBase:

    _signal_widths = {"ready": 1}

    _valid_signal = "valid"
    _ready_signal = "ready"

    _transaction_obj = S10PcieTransaction
    _frame_obj = S10PcieFrame

    def __init__(self,
                 bus,
                 clock,
                 reset=None,
                 ready_latency=0,
                 *args,
                 **kwargs):
        self.bus = bus
        self.clock = clock
        self.reset = reset
        self.ready_latency = ready_latency
        self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")

        super().__init__(*args, **kwargs)

        self.active = False
        self.queue = Queue()
        self.dequeue_event = Event()
        self.idle_event = Event()
        self.idle_event.set()
        self.active_event = Event()

        self.pause = False
        self._pause_generator = None
        self._pause_cr = None

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.width = len(self.bus.data)
        self.byte_size = 32
        self.byte_lanes = self.width // self.byte_size
        self.byte_mask = 2**self.byte_size - 1

        self.seg_count = len(self.bus.valid)
        self.seg_width = self.width // self.seg_count
        self.seg_mask = 2**self.seg_width - 1
        self.seg_par_width = self.seg_width // 8
        self.seg_par_mask = 2**self.seg_par_width - 1
        self.seg_byte_lanes = self.byte_lanes // self.seg_count
        self.seg_empty_width = (self.seg_byte_lanes - 1).bit_length()
        self.seg_empty_mask = 2**self.seg_empty_width - 1

        assert self.width in {256, 512}

        assert len(self.bus.data) == self.seg_count * self.seg_width
        assert len(self.bus.sop) == self.seg_count
        assert len(self.bus.eop) == self.seg_count
        assert len(self.bus.valid) == self.seg_count

        if hasattr(self.bus, "empty"):
            assert len(self.bus.empty) == self.seg_count * self.seg_empty_width

        if hasattr(self.bus, "err"):
            assert len(self.bus.err) == self.seg_count
        if hasattr(self.bus, "bar_range"):
            assert len(self.bus.bar_range) == self.seg_count * 3

        if hasattr(self.bus, "vf_active"):
            assert len(self.bus.vf_active) == self.seg_count
        if hasattr(self.bus, "func_num"):
            assert len(self.bus.func_num) == self.seg_count * 2
        if hasattr(self.bus, "vf_num"):
            assert len(self.bus.vf_num) == self.seg_count * 11

        if hasattr(self.bus, "parity"):
            assert len(self.bus.parity) == self.seg_count * self.seg_width // 8

    def count(self):
        return self.queue.qsize()

    def empty(self):
        return self.queue.empty()

    def clear(self):
        while not self.queue.empty():
            self.queue.get_nowait()
        self.idle_event.set()
        self.active_event.clear()

    def idle(self):
        raise NotImplementedError()

    async def wait(self):
        raise NotImplementedError()

    def set_pause_generator(self, generator=None):
        if self._pause_cr is not None:
            self._pause_cr.kill()
            self._pause_cr = None

        self._pause_generator = generator

        if self._pause_generator is not None:
            self._pause_cr = cocotb.start_soon(self._run_pause())

    def clear_pause_generator(self):
        self.set_pause_generator(None)

    async def _run_pause(self):
        clock_edge_event = RisingEdge(self.clock)

        for val in self._pause_generator:
            self.pause = val
            await clock_edge_event
Example #15
0
class Scheduler:
    """The main scheduler.

    Here we accept callbacks from the simulator and schedule the appropriate
    coroutines.

    A callback fires, causing the :any:`react` method to be called, with the
    trigger that caused the callback as the first argument.

    We look up a list of coroutines to schedule (indexed by the trigger) and
    schedule them in turn.

    .. attention::

       Implementors should not depend on the scheduling order!

    Some additional management is required since coroutines can return a list
    of triggers, to be scheduled when any one of the triggers fires.  To
    ensure we don't receive spurious callbacks, we have to un-prime all the
    other triggers when any one fires.

    Due to the simulator nuances and fun with delta delays we have the
    following modes:

    Normal mode
        - Callbacks cause coroutines to be scheduled
        - Any pending writes are cached and do not happen immediately

    ReadOnly mode
        - Corresponds to :any:`cbReadOnlySynch` (VPI) or :any:`vhpiCbLastKnownDeltaCycle`
          (VHPI).  In this state we are not allowed to perform writes.

    Write mode
        - Corresponds to :any:`cbReadWriteSynch` (VPI) or :c:macro:`vhpiCbEndOfProcesses` (VHPI)
          In this mode we play back all the cached write updates.

    We can legally transition from Normal to Write by registering a :class:`~cocotb.triggers.ReadWrite`
    callback, however usually once a simulator has entered the ReadOnly phase
    of a given timestep then we must move to a new timestep before performing
    any writes.  The mechanism for moving to a new timestep may not be
    consistent across simulators and therefore we provide an abstraction to
    assist with compatibility.


    Unless a coroutine has explicitly requested to be scheduled in ReadOnly
    mode (for example wanting to sample the finally settled value after all
    delta delays) then it can reasonably be expected to be scheduled during
    "normal mode" i.e. where writes are permitted.
    """

    _MODE_NORMAL = 1  # noqa
    _MODE_READONLY = 2  # noqa
    _MODE_WRITE = 3  # noqa
    _MODE_TERM = 4  # noqa

    # Singleton events, recycled to avoid spurious object creation
    _next_time_step = NextTimeStep()
    _read_write = ReadWrite()
    _read_only = ReadOnly()
    _timer1 = Timer(1)

    def __init__(self):

        self.log = SimLog("cocotb.scheduler")
        if _debug:
            self.log.setLevel(logging.DEBUG)

        # Use OrderedDict here for deterministic behavior (gh-934)

        # A dictionary of pending coroutines for each trigger,
        # indexed by trigger
        self._trigger2coros = _py_compat.insertion_ordered_dict()

        # Our main state
        self._mode = Scheduler._MODE_NORMAL

        # A dictionary of pending (write_func, args), keyed by handle. Only the last scheduled write
        # in a timestep is performed, all the rest are discarded in python.
        self._write_calls = _py_compat.insertion_ordered_dict()

        self._pending_coros = []
        self._pending_triggers = []
        self._pending_threads = []
        self._pending_events = [
        ]  # Events we need to call set on once we've unwound

        self._terminate = False
        self._test = None
        self._main_thread = threading.current_thread()

        self._current_task = None

        self._is_reacting = False

        self._write_coro_inst = None
        self._writes_pending = Event()

    async def _do_writes(self):
        """ An internal coroutine that performs pending writes """
        while True:
            await self._writes_pending.wait()
            if self._mode != Scheduler._MODE_NORMAL:
                await self._next_time_step

            await self._read_write

            while self._write_calls:
                handle, (func, args) = self._write_calls.popitem()
                func(*args)
            self._writes_pending.clear()

    def _check_termination(self):
        """
        Handle a termination that causes us to move onto the next test.
        """
        if self._terminate:
            if _debug:
                self.log.debug("Test terminating, scheduling Timer")

            if self._write_coro_inst is not None:
                self._write_coro_inst.kill()
                self._write_coro_inst = None

            for t in self._trigger2coros:
                t.unprime()

            if self._timer1.primed:
                self._timer1.unprime()

            self._timer1.prime(self._test_completed)
            self._trigger2coros = _py_compat.insertion_ordered_dict()
            self._terminate = False
            self._write_calls = _py_compat.insertion_ordered_dict()
            self._writes_pending.clear()
            self._mode = Scheduler._MODE_TERM

    def _test_completed(self, trigger=None):
        """Called after a test and its cleanup have completed
        """
        if _debug:
            self.log.debug("begin_test called with trigger: %s" %
                           (str(trigger)))
        if _profiling:
            ps = pstats.Stats(_profile).sort_stats('cumulative')
            ps.dump_stats("test_profile.pstat")
            ctx = profiling_context()
        else:
            ctx = _py_compat.nullcontext()

        with ctx:
            self._mode = Scheduler._MODE_NORMAL
            if trigger is not None:
                trigger.unprime()

            # extract the current test, and clear it
            test = self._test
            self._test = None
            if test is None:
                raise InternalError(
                    "_test_completed called with no active test")
            if test._outcome is None:
                raise InternalError(
                    "_test_completed called with an incomplete test")

            # Issue previous test result
            if _debug:
                self.log.debug("Issue test result to regression object")

            # this may scheduler another test
            cocotb.regression_manager.handle_result(test)

            # if it did, make sure we handle the test completing
            self._check_termination()

    def react(self, trigger):
        """
        Called when a trigger fires.

        We ensure that we only start the event loop once, rather than
        letting it recurse.
        """
        if self._is_reacting:
            # queue up the trigger, the event loop will get to it
            self._pending_triggers.append(trigger)
            return

        if self._pending_triggers:
            raise InternalError(
                "Expected all triggers to be handled but found {}".format(
                    self._pending_triggers))

        # start the event loop
        self._is_reacting = True
        try:
            self._event_loop(trigger)
        finally:
            self._is_reacting = False

    def _event_loop(self, trigger):
        """
        Run an event loop triggered by the given trigger.

        The loop will keep running until no further triggers fire.

        This should be triggered by only:
        * The beginning of a test, when there is no trigger to react to
        * A GPI trigger
        """
        if _profiling:
            ctx = profiling_context()
        else:
            ctx = _py_compat.nullcontext()

        with ctx:
            # When a trigger fires it is unprimed internally
            if _debug:
                self.log.debug("Trigger fired: %s" % str(trigger))
            # trigger.unprime()

            if self._mode == Scheduler._MODE_TERM:
                if _debug:
                    self.log.debug(
                        "Ignoring trigger %s since we're terminating" %
                        str(trigger))
                return

            if trigger is self._read_only:
                self._mode = Scheduler._MODE_READONLY
            # Only GPI triggers affect the simulator scheduling mode
            elif isinstance(trigger, GPITrigger):
                self._mode = Scheduler._MODE_NORMAL

            # work through triggers one by one
            is_first = True
            self._pending_triggers.append(trigger)
            while self._pending_triggers:
                trigger = self._pending_triggers.pop(0)

                if not is_first and isinstance(trigger, GPITrigger):
                    self.log.warning(
                        "A GPI trigger occurred after entering react - this "
                        "should not happen.")
                    assert False

                # this only exists to enable the warning above
                is_first = False

                # Scheduled coroutines may append to our waiting list so the first
                # thing to do is pop all entries waiting on this trigger.
                try:
                    scheduling = self._trigger2coros.pop(trigger)
                except KeyError:
                    # GPI triggers should only be ever pending if there is an
                    # associated coroutine waiting on that trigger, otherwise it would
                    # have been unprimed already
                    if isinstance(trigger, GPITrigger):
                        self.log.critical(
                            "No coroutines waiting on trigger that fired: %s" %
                            str(trigger))

                        trigger.log.info("I'm the culprit")
                    # For Python triggers this isn't actually an error - we might do
                    # event.set() without knowing whether any coroutines are actually
                    # waiting on this event, for example
                    elif _debug:
                        self.log.debug(
                            "No coroutines waiting on trigger that fired: %s" %
                            str(trigger))

                    del trigger
                    continue

                if _debug:
                    debugstr = "\n\t".join(
                        [coro._coro.__qualname__ for coro in scheduling])
                    if len(scheduling):
                        debugstr = "\n\t" + debugstr
                    self.log.debug("%d pending coroutines for event %s%s" %
                                   (len(scheduling), str(trigger), debugstr))

                # This trigger isn't needed any more
                trigger.unprime()

                for coro in scheduling:
                    if coro._outcome is not None:
                        # coroutine was killed by another coroutine waiting on the same trigger
                        continue
                    if _debug:
                        self.log.debug("Scheduling coroutine %s" %
                                       (coro._coro.__qualname__))
                    self.schedule(coro, trigger=trigger)
                    if _debug:
                        self.log.debug("Scheduled coroutine %s" %
                                       (coro._coro.__qualname__))

                # Schedule may have queued up some events so we'll burn through those
                while self._pending_events:
                    if _debug:
                        self.log.debug("Scheduling pending event %s" %
                                       (str(self._pending_events[0])))
                    self._pending_events.pop(0).set()

                # remove our reference to the objects at the end of each loop,
                # to try and avoid them being destroyed at a weird time (as
                # happened in gh-957)
                del trigger
                del coro
                del scheduling

            # no more pending triggers
            self._check_termination()
            if _debug:
                self.log.debug("All coroutines scheduled, handing control back"
                               " to simulator")

    def unschedule(self, coro):
        """Unschedule a coroutine.  Unprime any pending triggers"""

        # Unprime the trigger this coroutine is waiting on
        trigger = coro._trigger
        if trigger is not None:
            coro._trigger = None
            if coro in self._trigger2coros.setdefault(trigger, []):
                self._trigger2coros[trigger].remove(coro)
            if not self._trigger2coros[trigger]:
                trigger.unprime()
                del self._trigger2coros[trigger]

        assert self._test is not None

        if coro is self._test:
            if _debug:
                self.log.debug("Unscheduling test {}".format(coro))

            if not self._terminate:
                self._terminate = True
                self.cleanup()

        elif Join(coro) in self._trigger2coros:
            self.react(Join(coro))
        else:
            try:
                # throws an error if the background coroutine errored
                # and no one was monitoring it
                coro._outcome.get()
            except (TestComplete, AssertionError) as e:
                coro.log.info("Test stopped by this forked coroutine")
                e = remove_traceback_frames(e, ['unschedule', 'get'])
                self._test.abort(e)
            except Exception as e:
                coro.log.error("Exception raised by this forked coroutine")
                e = remove_traceback_frames(e, ['unschedule', 'get'])
                self._test.abort(e)

    def _schedule_write(self, handle, write_func, *args):
        """ Queue `write_func` to be called on the next ReadWrite trigger. """
        if self._mode == Scheduler._MODE_READONLY:
            raise Exception(
                "Write to object {0} was scheduled during a read-only sync phase."
                .format(handle._name))

        # TODO: we should be able to better keep track of when this needs to
        # be scheduled
        if self._write_coro_inst is None:
            self._write_coro_inst = self.add(self._do_writes())

        self._write_calls[handle] = (write_func, args)
        self._writes_pending.set()

    def _resume_coro_upon(self, coro, trigger):
        """Schedule `coro` to be resumed when `trigger` fires."""
        coro._trigger = trigger

        trigger_coros = self._trigger2coros.setdefault(trigger, [])
        if coro is self._write_coro_inst:
            # Our internal write coroutine always runs before any user coroutines.
            # This preserves the behavior prior to the refactoring of writes to
            # this coroutine.
            trigger_coros.insert(0, coro)
        else:
            # Everything else joins the back of the queue
            trigger_coros.append(coro)

        if not trigger.primed:

            if trigger_coros != [coro]:
                # should never happen
                raise InternalError(
                    "More than one coroutine waiting on an unprimed trigger")

            try:
                trigger.prime(self.react)
            except Exception as e:
                # discard the trigger we associated, it will never fire
                self._trigger2coros.pop(trigger)

                # replace it with a new trigger that throws back the exception
                self._resume_coro_upon(
                    coro,
                    NullTrigger(name="Trigger.prime() Error",
                                outcome=outcomes.Error(e)))

    def queue(self, coroutine):
        """Queue a coroutine for execution"""
        self._pending_coros.append(coroutine)

    def queue_function(self, coro):
        """Queue a coroutine for execution and move the containing thread
        so that it does not block execution of the main thread any longer.
        """
        # We should be able to find ourselves inside the _pending_threads list
        matching_threads = [
            t for t in self._pending_threads
            if t.thread == threading.current_thread()
        ]
        if len(matching_threads) == 0:
            raise RuntimeError(
                "queue_function called from unrecognized thread")

        # Raises if there is more than one match. This can never happen, since
        # each entry always has a unique thread.
        t, = matching_threads

        async def wrapper():
            # This function runs in the scheduler thread
            try:
                _outcome = outcomes.Value(await coro)
            except BaseException as e:
                _outcome = outcomes.Error(e)
            event.outcome = _outcome
            # Notify the current (scheduler) thread that we are about to wake
            # up the background (`@external`) thread, making sure to do so
            # before the background thread gets a chance to go back to sleep by
            # calling thread_suspend.
            # We need to do this here in the scheduler thread so that no more
            # coroutines run until the background thread goes back to sleep.
            t.thread_resume()
            event.set()

        event = threading.Event()
        self._pending_coros.append(cocotb.decorators.RunningTask(wrapper()))
        # The scheduler thread blocks in `thread_wait`, and is woken when we
        # call `thread_suspend` - so we need to make sure the coroutine is
        # queued before that.
        t.thread_suspend()
        # This blocks the calling `@external` thread until the coroutine finishes
        event.wait()
        return event.outcome.get()

    def run_in_executor(self, func, *args, **kwargs):
        """Run the coroutine in a separate execution thread
        and return an awaitable object for the caller.
        """

        # Create a thread
        # Create a trigger that is called as a result of the thread finishing
        # Create an Event object that the caller can await on
        # Event object set when the thread finishes execution, this blocks the
        #   calling coroutine (but not the thread) until the external completes

        def execute_external(func, _waiter):
            _waiter._outcome = outcomes.capture(func, *args, **kwargs)
            if _debug:
                self.log.debug("Execution of external routine done %s" %
                               threading.current_thread())
            _waiter.thread_done()

        async def wrapper():
            waiter = external_waiter()
            thread = threading.Thread(group=None,
                                      target=execute_external,
                                      name=func.__qualname__ + "_thread",
                                      args=([func, waiter]),
                                      kwargs={})

            waiter.thread = thread
            self._pending_threads.append(waiter)

            await waiter.event.wait()

            return waiter.result  # raises if there was an exception

        return wrapper()

    @staticmethod
    def create_task(coroutine: Any) -> RunningTask:
        """ Checks to see if the given object is a schedulable coroutine object and if so, returns it """

        if isinstance(coroutine, RunningTask):
            return coroutine
        if inspect.iscoroutine(coroutine):
            return RunningTask(coroutine)
        if inspect.iscoroutinefunction(coroutine):
            raise TypeError(
                "Coroutine function {} should be called prior to being "
                "scheduled.".format(coroutine))
        if isinstance(coroutine, cocotb.decorators.coroutine):
            raise TypeError(
                "Attempt to schedule a coroutine that hasn't started: {}.\n"
                "Did you forget to add parentheses to the @cocotb.test() "
                "decorator?".format(coroutine))
        if sys.version_info >= (3, 6) and inspect.isasyncgen(coroutine):
            raise TypeError(
                "{} is an async generator, not a coroutine. "
                "You likely used the yield keyword instead of await.".format(
                    coroutine.__qualname__))
        raise TypeError(
            "Attempt to add an object of type {} to the scheduler, which "
            "isn't a coroutine: {!r}\n"
            "Did you forget to use the @cocotb.coroutine decorator?".format(
                type(coroutine), coroutine))

    def add(self, coroutine: Union[RunningTask, Coroutine]) -> RunningTask:
        """Add a new coroutine.

        Just a wrapper around self.schedule which provides some debug and
        useful error messages in the event of common gotchas.
        """

        task = self.create_task(coroutine)

        if _debug:
            self.log.debug("Adding new coroutine %s" % task._coro.__qualname__)

        self.schedule(task)
        self._check_termination()
        return task

    def start_soon(self, coro: Union[Coroutine, RunningTask]) -> RunningTask:
        """
        Schedule a coroutine to be run concurrently, starting after the current coroutine yields control.

        In contrast to :func:`~cocotb.fork` which starts the given coroutine immediately, this function
        starts the given coroutine only after the current coroutine yields control.
        This is useful when the coroutine to be forked has logic before the first
        :keyword:`await` that may not be safe to execute immediately.
        """

        task = self.create_task(coro)

        if _debug:
            self.log.debug("Queueing a new coroutine %s" %
                           task._coro.__qualname__)

        self.queue(task)
        return task

    def add_test(self, test_coro):
        """Called by the regression manager to queue the next test"""
        if self._test is not None:
            raise InternalError("Test was added while another was in progress")

        self._test = test_coro
        self._resume_coro_upon(
            test_coro,
            NullTrigger(name="Start {!s}".format(test_coro),
                        outcome=outcomes.Value(None)))

    # This collection of functions parses a trigger out of the object
    # that was yielded by a coroutine, converting `list` -> `Waitable`,
    # `Waitable` -> `RunningTask`, `RunningTask` -> `Trigger`.
    # Doing them as separate functions allows us to avoid repeating unencessary
    # `isinstance` checks.

    def _trigger_from_started_coro(
            self, result: cocotb.decorators.RunningTask) -> Trigger:
        if _debug:
            self.log.debug("Joining to already running coroutine: %s" %
                           result._coro.__qualname__)
        return result.join()

    def _trigger_from_unstarted_coro(
            self, result: cocotb.decorators.RunningTask) -> Trigger:
        self.queue(result)
        if _debug:
            self.log.debug("Scheduling nested coroutine: %s" %
                           result._coro.__qualname__)
        return result.join()

    def _trigger_from_waitable(self,
                               result: cocotb.triggers.Waitable) -> Trigger:
        return self._trigger_from_unstarted_coro(
            cocotb.decorators.RunningTask(result._wait()))

    def _trigger_from_list(self, result: list) -> Trigger:
        return self._trigger_from_waitable(cocotb.triggers.First(*result))

    def _trigger_from_any(self, result) -> Trigger:
        """Convert a yielded object into a Trigger instance"""
        # note: the order of these can significantly impact performance

        if isinstance(result, Trigger):
            return result

        if isinstance(result, cocotb.decorators.RunningTask):
            if not result.has_started():
                return self._trigger_from_unstarted_coro(result)
            else:
                return self._trigger_from_started_coro(result)

        if inspect.iscoroutine(result):
            return self._trigger_from_unstarted_coro(
                cocotb.decorators.RunningTask(result))

        if isinstance(result, list):
            return self._trigger_from_list(result)

        if isinstance(result, cocotb.triggers.Waitable):
            return self._trigger_from_waitable(result)

        if sys.version_info >= (3, 6) and inspect.isasyncgen(result):
            raise TypeError(
                "{} is an async generator, not a coroutine. "
                "You likely used the yield keyword instead of await.".format(
                    result.__qualname__))

        raise TypeError(
            "Coroutine yielded an object of type {}, which the scheduler can't "
            "handle: {!r}\n"
            "Did you forget to decorate with @cocotb.coroutine?".format(
                type(result), result))

    @contextmanager
    def _task_context(self, task):
        """Context manager for the currently running task."""
        old_task = self._current_task
        self._current_task = task
        try:
            yield
        finally:
            self._current_task = old_task

    def schedule(self, coroutine, trigger=None):
        """Schedule a coroutine by calling the send method.

        Args:
            coroutine (cocotb.decorators.coroutine): The coroutine to schedule.
            trigger (cocotb.triggers.Trigger): The trigger that caused this
                coroutine to be scheduled.
        """
        with self._task_context(coroutine):
            if trigger is None:
                send_outcome = outcomes.Value(None)
            else:
                send_outcome = trigger._outcome
            if _debug:
                self.log.debug("Scheduling with {}".format(send_outcome))

            coro_completed = False
            try:
                coroutine._trigger = None
                result = coroutine._advance(send_outcome)
                if _debug:
                    self.log.debug("Coroutine %s yielded %s (mode %d)" %
                                   (coroutine._coro.__qualname__, str(result),
                                    self._mode))

            except cocotb.decorators.CoroutineComplete:
                if _debug:
                    self.log.debug("Coroutine {} completed with {}".format(
                        coroutine, coroutine._outcome))
                coro_completed = True

            # this can't go in the else above, as that causes unwanted exception
            # chaining
            if coro_completed:
                self.unschedule(coroutine)

            # Don't handle the result if we're shutting down
            if self._terminate:
                return

            if not coro_completed:
                try:
                    result = self._trigger_from_any(result)
                except TypeError as exc:
                    # restart this coroutine with an exception object telling it that
                    # it wasn't allowed to yield that
                    result = NullTrigger(outcome=outcomes.Error(exc))

                self._resume_coro_upon(coroutine, result)

            # We do not return from here until pending threads have completed, but only
            # from the main thread, this seems like it could be problematic in cases
            # where a sim might change what this thread is.

            if self._main_thread is threading.current_thread():

                for ext in self._pending_threads:
                    ext.thread_start()
                    if _debug:
                        self.log.debug(
                            "Blocking from %s on %s" %
                            (threading.current_thread(), ext.thread))
                    state = ext.thread_wait()
                    if _debug:
                        self.log.debug(
                            "Back from wait on self %s with newstate %d" %
                            (threading.current_thread(), state))
                    if state == external_state.EXITED:
                        self._pending_threads.remove(ext)
                        self._pending_events.append(ext.event)

            # Handle any newly queued coroutines that need to be scheduled
            while self._pending_coros:
                self.add(self._pending_coros.pop(0))

    def finish_test(self, exc):
        self._test.abort(exc)
        self._check_termination()

    def finish_scheduler(self, exc):
        """Directly call into the regression manager and end test
           once we return the sim will close us so no cleanup is needed.
        """
        # If there is an error during cocotb initialization, self._test may not
        # have been set yet. Don't cause another Python exception here.

        if self._test:
            self.log.debug("Issue sim closedown result to regression object")
            self._test.abort(exc)
            cocotb.regression_manager.handle_result(self._test)

    def cleanup(self):
        """Clear up all our state.

        Unprime all pending triggers and kill off any coroutines stop all externals.
        """
        # copy since we modify this in kill
        items = list(self._trigger2coros.items())

        # reversing seems to fix gh-928, although the order is still somewhat
        # arbitrary.
        for trigger, waiting in items[::-1]:
            for coro in waiting:
                if _debug:
                    self.log.debug("Killing %s" % str(coro))
                coro.kill()

        if self._main_thread is not threading.current_thread():
            raise Exception("Cleanup() called outside of the main thread")

        for ext in self._pending_threads:
            self.log.warning("Waiting for %s to exit", ext.thread)
Example #16
0
class Monitor(object):

    def __init__(self, callback=None, event=None):
        """
        Constructor for a monitor instance

        callback will be called with each recovered transaction as the argument

        If the callback isn't used, received transactions will be placed on a
        queue and the event used to notify any consumers.
        """
        self._event = event
        self._wait_event = None
        self._recvQ = []
        self._callbacks = []
        self.stats = MonitorStatistics()
        self._wait_event = Event()

        # Subclasses may already set up logging
        if not hasattr(self, "log"):
            self.log = SimLog("cocotb.monitor.%s" % (self.__class__.__name__))

        if callback is not None:
            self.add_callback(callback)

        # Create an independent coroutine which can receive stuff
        self._thread = cocotb.scheduler.add(self._monitor_recv())

    def kill(self):
        if self._thread:
            self._thread.kill()
            self._thread = None

    def __len__(self):
        return len(self._recvQ)

    def __getitem__(self, idx):
        return self._recvQ[idx]

    def add_callback(self, callback):
        self.log.debug("Adding callback of function %s to monitor" %
                       (callback.__name__))
        self._callbacks.append(callback)

    @coroutine
    def wait_for_recv(self, timeout=None):
        if timeout:
            t = Timer(timeout)
            fired = yield [self._wait_event.wait(), t]
            if fired is t:
                raise ReturnValue(None)
        else:
            yield self._wait_event.wait()

        pkt = self._wait_event.data
        raise ReturnValue(pkt)

    @coroutine
    def _monitor_recv(self):
        """
        actual impementation of the receiver

        subclasses should override this method to implement the actual receive
        routine and call self._recv() with the recovered transaction
        """
        raise NotImplementedError("Attempt to use base monitor class without "
                                  "providing a _monitor_recv method")

    def _recv(self, transaction):
        """Common handling of a received transaction."""

        self.stats.received_transactions += 1

        # either callback based consumer
        for callback in self._callbacks:
            callback(transaction)

        # Or queued with a notification
        if not self._callbacks:
            self._recvQ.append(transaction)

        if self._event is not None:
            self._event.set()

        # If anyone was waiting then let them know
        if self._wait_event is not None:
            self._wait_event.set(data=transaction)
            self._wait_event.clear()
Example #17
0
class Scheduler(object):
    """The main scheduler.

    Here we accept callbacks from the simulator and schedule the appropriate
    coroutines.

    A callback fires, causing the :any:`react` method to be called, with the
    trigger that caused the callback as the first argument.

    We look up a list of coroutines to schedule (indexed by the trigger) and
    schedule them in turn. NB implementors should not depend on the scheduling
    order!

    Some additional management is required since coroutines can return a list
    of triggers, to be scheduled when any one of the triggers fires.  To
    ensure we don't receive spurious callbacks, we have to un-prime all the
    other triggers when any one fires.

    Due to the simulator nuances and fun with delta delays we have the
    following modes:

    Normal mode
        - Callbacks cause coroutines to be scheduled
        - Any pending writes are cached and do not happen immediately

    ReadOnly mode
        - Corresponds to cbReadOnlySynch (VPI) or vhpiCbLastKnownDeltaCycle
          (VHPI).  In this state we are not allowed to perform writes.

    Write mode
        - Corresponds to cbReadWriteSynch (VPI) or vhpiCbEndOfProcesses (VHPI)
          In this mode we play back all the cached write updates.

    We can legally transition from normal->write by registering a ReadWrite
    callback, however usually once a simulator has entered the ReadOnly phase
    of a given timestep then we must move to a new timestep before performing
    any writes.  The mechanism for moving to a new timestep may not be
    consistent across simulators and therefore we provide an abstraction to
    assist with compatibility.


    Unless a coroutine has explicitly requested to be scheduled in ReadOnly
    mode (for example wanting to sample the finally settled value after all
    delta delays) then it can reasonably be expected to be scheduled during
    "normal mode" i.e. where writes are permitted.
    """

    _MODE_NORMAL   = 1  # noqa
    _MODE_READONLY = 2  # noqa
    _MODE_WRITE    = 3  # noqa
    _MODE_TERM     = 4  # noqa

    # Singleton events, recycled to avoid spurious object creation
    _readonly = ReadOnly()
    _timer1 = Timer(1)

    def __init__(self):

        self.log = SimLog("cocotb.scheduler")
        if _debug:
            self.log.setLevel(logging.DEBUG)

        # Use OrderedDict here for deterministic behavior (gh-934)

        # A dictionary of pending coroutines for each trigger,
        # indexed by trigger
        self._trigger2coros = collections.OrderedDict()

        # A dictionary mapping coroutines to the trigger they are waiting for
        self._coro2trigger = collections.OrderedDict()

        # Our main state
        self._mode = Scheduler._MODE_NORMAL

        # A dictionary of pending writes
        self._writes = collections.OrderedDict()

        self._pending_coros = []
        self._pending_triggers = []
        self._pending_threads = []
        self._pending_events = []   # Events we need to call set on once we've unwound

        self._terminate = False
        self._test_result = None
        self._entrypoint = None
        self._main_thread = threading.current_thread()

        self._is_reacting = False

        self._write_coro_inst = None
        self._writes_pending = Event()

    @cocotb.decorators.coroutine
    def _do_writes(self):
        """ An internal coroutine that performs pending writes """
        while True:
            yield self._writes_pending.wait()
            if self._mode != Scheduler._MODE_NORMAL:
                yield NextTimeStep()

            yield ReadWrite()

            while self._writes:
                handle, value = self._writes.popitem()
                handle.setimmediatevalue(value)
            self._writes_pending.clear()

    def _check_termination(self):
        """
        Handle a termination that causes us to move onto the next test.
        """
        if self._terminate:
            if _debug:
                self.log.debug("Test terminating, scheduling Timer")

            if self._write_coro_inst is not None:
                self._write_coro_inst.kill()
                self._write_coro_inst = None

            for t in self._trigger2coros:
                t.unprime()

            if self._timer1.primed:
                self._timer1.unprime()

            self._timer1.prime(self.begin_test)
            self._trigger2coros = collections.OrderedDict()
            self._coro2trigger = collections.OrderedDict()
            self._terminate = False
            self._writes = collections.OrderedDict()
            self._writes_pending.clear()
            self._mode = Scheduler._MODE_TERM

    def begin_test(self, trigger=None):
        """Called to initiate a test.

        Could be called on start-up or from a callback.
        """
        if _debug:
            self.log.debug("begin_test called with trigger: %s" %
                           (str(trigger)))
        if _profiling:
            ps = pstats.Stats(_profile).sort_stats('cumulative')
            ps.dump_stats("test_profile.pstat")
            ctx = profiling_context()
        else:
            ctx = nullcontext()

        with ctx:
            self._mode = Scheduler._MODE_NORMAL
            if trigger is not None:
                trigger.unprime()

            # Issue previous test result, if there is one
            if self._test_result is not None:
                if _debug:
                    self.log.debug("Issue test result to regression object")
                cocotb.regression_manager.handle_result(self._test_result)
                self._test_result = None
            if self._entrypoint is not None:
                test = self._entrypoint
                self._entrypoint = None
                self.schedule(test)
                self._check_termination()

    def react(self, trigger):
        """
        Called when a trigger fires.

        We ensure that we only start the event loop once, rather than
        letting it recurse.
        """
        if self._is_reacting:
            # queue up the trigger, the event loop will get to it
            self._pending_triggers.append(trigger)
            return

        assert not self._pending_triggers

        # start the event loop
        self._is_reacting = True
        try:
            self._event_loop(trigger)
        finally:
            self._is_reacting = False


    def _event_loop(self, trigger):
        """
        Run an event loop triggered by the given trigger.

        The loop will keep running until no further triggers fire.

        This should be triggered by only:
        * The beginning of a test, when there is no trigger to react to
        * A GPI trigger
        """
        if _profiling:
            ctx = profiling_context()
        else:
            ctx = nullcontext()

        with ctx:
            # When a trigger fires it is unprimed internally
            if _debug:
                self.log.debug("Trigger fired: %s" % str(trigger))
            # trigger.unprime()

            if self._mode == Scheduler._MODE_TERM:
                if _debug:
                    self.log.debug("Ignoring trigger %s since we're terminating" %
                                   str(trigger))
                return

            if trigger is self._readonly:
                self._mode = Scheduler._MODE_READONLY
            # Only GPI triggers affect the simulator scheduling mode
            elif isinstance(trigger, GPITrigger):
                self._mode = Scheduler._MODE_NORMAL

            # work through triggers one by one
            is_first = True
            self._pending_triggers.append(trigger)
            while self._pending_triggers:
                trigger = self._pending_triggers.pop(0)

                if not is_first and isinstance(trigger, GPITrigger):
                    self.log.warning(
                        "A GPI trigger occurred after entering react - this "
                        "should not happen."
                    )
                    assert False

                # this only exists to enable the warning above
                is_first = False

                if trigger not in self._trigger2coros:

                    # GPI triggers should only be ever pending if there is an
                    # associated coroutine waiting on that trigger, otherwise it would
                    # have been unprimed already
                    if isinstance(trigger, GPITrigger):
                        self.log.critical(
                            "No coroutines waiting on trigger that fired: %s" %
                            str(trigger))

                        trigger.log.info("I'm the culprit")
                    # For Python triggers this isn't actually an error - we might do
                    # event.set() without knowing whether any coroutines are actually
                    # waiting on this event, for example
                    elif _debug:
                        self.log.debug(
                            "No coroutines waiting on trigger that fired: %s" %
                            str(trigger))

                    continue

                # Scheduled coroutines may append to our waiting list so the first
                # thing to do is pop all entries waiting on this trigger.
                scheduling = self._trigger2coros.pop(trigger)

                if _debug:
                    debugstr = "\n\t".join([coro.__name__ for coro in scheduling])
                    if len(scheduling):
                        debugstr = "\n\t" + debugstr
                    self.log.debug("%d pending coroutines for event %s%s" %
                                   (len(scheduling), str(trigger), debugstr))

                # This trigger isn't needed any more
                trigger.unprime()

                for coro in scheduling:
                    if _debug:
                        self.log.debug("Scheduling coroutine %s" % (coro.__name__))
                    self.schedule(coro, trigger=trigger)
                    if _debug:
                        self.log.debug("Scheduled coroutine %s" % (coro.__name__))

                # Schedule may have queued up some events so we'll burn through those
                while self._pending_events:
                    if _debug:
                        self.log.debug("Scheduling pending event %s" %
                                       (str(self._pending_events[0])))
                    self._pending_events.pop(0).set()

            # no more pending triggers
            self._check_termination()
            if _debug:
                self.log.debug("All coroutines scheduled, handing control back"
                               " to simulator")


    def unschedule(self, coro):
        """Unschedule a coroutine.  Unprime any pending triggers"""

        # Unprime the trigger this coroutine is waiting on
        try:
            trigger = self._coro2trigger.pop(coro)
        except KeyError:
            # coroutine probably finished
            pass
        else:
            if coro in self._trigger2coros.setdefault(trigger, []):
                self._trigger2coros[trigger].remove(coro)
            if not self._trigger2coros[trigger]:
                trigger.unprime()
                del self._trigger2coros[trigger]

        if Join(coro) in self._trigger2coros:
            self.react(Join(coro))
        else:
            try:
                # throws an error if the background coroutine errored
                # and no one was monitoring it
                coro.retval
            except TestComplete as test_result:
                self.log.debug("TestComplete received: {}".format(test_result.__class__.__name__))
                self.finish_test(test_result)
            except Exception as e:
                self.finish_test(create_error(self, "Forked coroutine {} raised exception: {}".format(coro, e)))

    def save_write(self, handle, value):
        if self._mode == Scheduler._MODE_READONLY:
            raise Exception("Write to object {0} was scheduled during a read-only sync phase.".format(handle._name))

        # TODO: we should be able to better keep track of when this needs to
        # be scheduled
        if self._write_coro_inst is None:
            self._write_coro_inst = self._do_writes()
            self.schedule(self._write_coro_inst)

        self._writes[handle] = value
        self._writes_pending.set()

    def _coroutine_yielded(self, coro, trigger):
        """Prime the trigger and update our internal mappings."""
        self._coro2trigger[coro] = trigger

        trigger_coros = self._trigger2coros.setdefault(trigger, [])
        if coro is self._write_coro_inst:
            # Our internal write coroutine always runs before any user coroutines.
            # This preserves the behavior prior to the refactoring of writes to
            # this coroutine.
            trigger_coros.insert(0, coro)
        else:
            # Everything else joins the back of the queue
            trigger_coros.append(coro)

        if not trigger.primed:
            try:
                trigger.prime(self.react)
            except Exception as e:
                # Convert any exceptions into a test result
                self.finish_test(
                    create_error(self, "Unable to prime trigger %s: %s" %
                                 (str(trigger), str(e))))

    def queue(self, coroutine):
        """Queue a coroutine for execution"""
        self._pending_coros.append(coroutine)

    def queue_function(self, coroutine):
        """Queue a coroutine for execution and move the containing thread
        so that it does not block execution of the main thread any longer.
        """
        # We should be able to find ourselves inside the _pending_threads list
        matching_threads = [
            t
            for t in self._pending_threads
            if t.thread == threading.current_thread()
        ]
        if len(matching_threads) == 0:
            raise RuntimeError("queue_function called from unrecognized thread")

        # Raises if there is more than one match. This can never happen, since
        # each entry always has a unique thread.
        t, = matching_threads

        t.thread_suspend()
        self._pending_coros.append(coroutine)
        return t

    def run_in_executor(self, func, *args, **kwargs):
        """Run the coroutine in a separate execution thread
        and return a yieldable object for the caller.
        """
        # Create a thread
        # Create a trigger that is called as a result of the thread finishing
        # Create an Event object that the caller can yield on
        # Event object set when the thread finishes execution, this blocks the
        #   calling coroutine (but not the thread) until the external completes

        def execute_external(func, _waiter):
            _waiter._outcome = outcomes.capture(func, *args, **kwargs)
            if _debug:
                self.log.debug("Execution of external routine done %s" % threading.current_thread())
            _waiter.thread_done()

        waiter = external_waiter()
        thread = threading.Thread(group=None, target=execute_external,
                                  name=func.__name__ + "_thread",
                                  args=([func, waiter]), kwargs={})

        waiter.thread = thread
        self._pending_threads.append(waiter)

        return waiter

    def add(self, coroutine):
        """Add a new coroutine.

        Just a wrapper around self.schedule which provides some debug and
        useful error messages in the event of common gotchas.
        """
        if isinstance(coroutine, cocotb.decorators.coroutine):
            self.log.critical(
                "Attempt to schedule a coroutine that hasn't started")
            coroutine.log.error("This is the failing coroutine")
            self.log.warning(
                "Did you forget to add parentheses to the @test decorator?")
            self._test_result = TestError(
                "Attempt to schedule a coroutine that hasn't started")
            self._terminate = True
            return

        elif not isinstance(coroutine, cocotb.decorators.RunningCoroutine):
            self.log.critical(
                "Attempt to add something to the scheduler which isn't a "
                "coroutine")
            self.log.warning(
                "Got: %s (%s)" % (str(type(coroutine)), repr(coroutine)))
            self.log.warning("Did you use the @coroutine decorator?")
            self._test_result = TestError(
                "Attempt to schedule a coroutine that hasn't started")
            self._terminate = True
            return

        if _debug:
            self.log.debug("Adding new coroutine %s" % coroutine.__name__)

        self.schedule(coroutine)
        self._check_termination()
        return coroutine

    def new_test(self, coroutine):
        self._entrypoint = coroutine

    def schedule(self, coroutine, trigger=None):
        """Schedule a coroutine by calling the send method.

        Args:
            coroutine (cocotb.decorators.coroutine): The coroutine to schedule.
            trigger (cocotb.triggers.Trigger): The trigger that caused this
                coroutine to be scheduled.
        """
        if trigger is None:
            send_outcome = outcomes.Value(None)
        else:
            send_outcome = trigger._outcome
        if _debug:
            self.log.debug("Scheduling with {}".format(send_outcome))

        try:
            result = coroutine._advance(send_outcome)
            if _debug:
                self.log.debug("Coroutine %s yielded %s (mode %d)" %
                               (coroutine.__name__, str(result), self._mode))

        # TestComplete indication is game over, tidy up
        except TestComplete as test_result:
            # Tag that close down is needed, save the test_result
            # for later use in cleanup handler
            self.log.debug("TestComplete received: %s" % test_result.__class__.__name__)
            self.finish_test(test_result)
            return

        # Normal coroutine completion
        except cocotb.decorators.CoroutineComplete as exc:
            if _debug:
                self.log.debug("Coroutine completed: %s" % str(coroutine))
            self.unschedule(coroutine)
            return

        # Don't handle the result if we're shutting down
        if self._terminate:
            return

        # convert lists into `First` Waitables.
        if isinstance(result, list):
            result = cocotb.triggers.First(*result)

        # convert waitables into coroutines
        if isinstance(result, cocotb.triggers.Waitable):
            result = result._wait()

        # convert coroutinues into triggers
        if isinstance(result, cocotb.decorators.RunningCoroutine):
            if not result.has_started():
                self.queue(result)
                if _debug:
                    self.log.debug("Scheduling nested coroutine: %s" %
                                   result.__name__)
            else:
                if _debug:
                    self.log.debug("Joining to already running coroutine: %s" %
                                   result.__name__)

            result = result.join()

        if isinstance(result, Trigger):
            if _debug:
                self.log.debug("%s: is instance of Trigger" % result)
            self._coroutine_yielded(coroutine, result)

        else:
            msg = ("Coroutine %s yielded something the scheduler can't handle"
                   % str(coroutine))
            msg += ("\nGot type: %s repr: %s str: %s" %
                    (type(result), repr(result), str(result)))
            msg += "\nDid you forget to decorate with @cocotb.coroutine?"
            try:
                raise_error(self, msg)
            except Exception as e:
                self.finish_test(e)

        # We do not return from here until pending threads have completed, but only
        # from the main thread, this seems like it could be problematic in cases
        # where a sim might change what this thread is.
        def unblock_event(ext):
            @cocotb.coroutine
            def wrapper():
                ext.event.set()
                yield PythonTrigger()

        if self._main_thread is threading.current_thread():

            for ext in self._pending_threads:
                ext.thread_start()
                if _debug:
                    self.log.debug("Blocking from %s on %s" % (threading.current_thread(), ext.thread))
                state = ext.thread_wait()
                if _debug:
                    self.log.debug("Back from wait on self %s with newstate %d" % (threading.current_thread(), state))
                if state == external_state.EXITED:
                    self._pending_threads.remove(ext)
                    self._pending_events.append(ext.event)

        # Handle any newly queued coroutines that need to be scheduled
        while self._pending_coros:
            self.add(self._pending_coros.pop(0))

    def finish_test(self, test_result):
        """Cache the test result and set the terminate flag."""
        self.log.debug("finish_test called with %s" % (repr(test_result)))
        if not self._terminate:
            self._terminate = True
            self._test_result = test_result
            self.cleanup()

    def finish_scheduler(self, test_result):
        """Directly call into the regression manager and end test
           once we return the sim will close us so no cleanup is needed.
        """
        self.log.debug("Issue sim closedown result to regression object")
        cocotb.regression_manager.handle_result(test_result)

    def cleanup(self):
        """Clear up all our state.

        Unprime all pending triggers and kill off any coroutines stop all externals.
        """
        # copy since we modify this in kill
        items = list(self._trigger2coros.items())

        # reversing seems to fix gh-928, although the order is still somewhat
        # arbitrary.
        for trigger, waiting in items[::-1]:
            for coro in waiting:
                if _debug:
                    self.log.debug("Killing %s" % str(coro))
                coro.kill()

        if self._main_thread is not threading.current_thread():
            raise Exception("Cleanup() called outside of the main thread")

        for ext in self._pending_threads:
            self.log.warn("Waiting for %s to exit", ext.thread)
Example #18
0
class WishboneMaster(Wishbone):
    """Wishbone master
    """
    _acked_ops          = 0  # ack cntr. comp with opbuf len. wait for equality before releasing lock
    _res_buf            = [] # save readdata/ack/err
    _aux_buf            = [] # save read/write order
    _op_cnt             = 0 # number of ops we've been issued
    _clk_cycle_count    = 0
    _timeout            = None

    
    def __init__(self, entity, name, clock, timeout=5000):
        Wishbone.__init__(self, entity, name, clock)
        sTo = ", no cycle timeout"        
        if not (timeout is None):
            sTo = ", cycle timeout is %u clockcycles" % timeout
        self.log.info("Wishbone Master created%s" % sTo)
        self.busy_event = Event("%s_busy" % name)
        self.busy = False
        self._timeout = timeout
        
    @coroutine 
    def _clk_cycle_counter(self):
        """
            Cycle counter to time bus operations
        """
        clkedge = RisingEdge(self.clock)
        self._clk_cycle_count = 0
        while self.busy:
            yield clkedge
            self._clk_cycle_count += 1    
  
    @coroutine
    def _open_cycle(self):
        #Open new wishbone cycle        
        if self.busy:
            self.log.error("Opening Cycle, but WB Driver is already busy. Someting's wrong")
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy       = True
        cocotb.fork(self._read())
        cocotb.fork(self._clk_cycle_counter()) 
        self.bus.cyc    <= 1
        self._acked_ops = 0  
        self._res_buf   = [] 
        self._aux_buf   = []
        self.log.debug("Opening cycle, %u Ops" % self._op_cnt)
        
    @coroutine    
    def _close_cycle(self):
        #Close current wishbone cycle  
        clkedge = RisingEdge(self.clock)
        count           = 0
        last_acked_ops  = 0
        #Wait for all Operations being acknowledged by the slave before lowering the cycle line
        #This is not mandatory by the bus standard, but a crossbar might send acks to the wrong master
        #if we don't wait. We don't want to risk that, it could hang the bus
        while self._acked_ops < self._op_cnt:
            if last_acked_ops != self._acked_ops:
                self.log.debug("Waiting for missing acks: %u/%u" % (self._acked_ops, self._op_cnt) )
            last_acked_ops = self._acked_ops    
            #check for timeout when finishing the cycle            
            count += 1
            if (not (self._timeout is None)):
                if (count > self._timeout): 
                    raise TestFailure("Timeout of %u clock cycles reached when waiting for reply from slave" % self._timeout)                
            yield clkedge
            
        self.busy = False
        self.busy_event.set()
        self.bus.cyc <= 0 
        self.log.debug("Closing cycle")
        yield clkedge        

    
    @coroutine
    def _wait_stall(self):
        """Wait for stall to be low before continuing
        """
        clkedge = RisingEdge(self.clock)
        count = 0
        if hasattr(self.bus, "stall"):
            count = 0            
            while self.bus.stall.getvalue():
                yield clkedge
                count += 1
                if (not (self._timeout is None)):
                    if (count > self._timeout): 
                        raise TestFailure("Timeout of %u clock cycles reached when on stall from slave" % self._timeout)                
            self.log.debug("Stalled for %u cycles" % count)
        raise ReturnValue(count)
    
    
    @coroutine
    def _wait_ack(self):
        """Wait for ACK on the bus before continuing
        """
        #wait for acknownledgement before continuing - Classic Wishbone without pipelining
        clkedge = RisingEdge(self.clock)
        count = 0
        if not hasattr(self.bus, "stall"):
            while not self._get_reply():
                yield clkedge
                count += 1
            self.log.debug("Waited %u cycles for ackknowledge" % count)
        raise ReturnValue(count)    
    
    
    def _get_reply(self):
        #helper function for slave acks
        tmpAck = int(self.bus.ack.getvalue())
        tmpErr = 0
        tmpRty = 0
        if hasattr(self.bus, "err"):        
            tmpErr = int(self.bus.err.getvalue())
        if hasattr(self.bus, "rty"):        
            tmpRty = int(self.bus.rty.getvalue())
        #check if more than one line was raised    
        if ((tmpAck + tmpErr + tmpRty)  > 1):
            raise TestFailure("Slave raised more than one reply line at once! ACK: %u ERR: %u RTY: %u" % (tmpAck, tmpErr, tmpRty))
        #return 0 if no reply, 1 for ACK, 2 for ERR, 3 for RTY. use 'replyTypes' Dict for lookup
        return (tmpAck + 2 * tmpErr + 3 * tmpRty)
        
    
    @coroutine 
    def _read(self):
        """
            Reader for slave replies
        """
        count = 0
        clkedge = RisingEdge(self.clock)
        while self.busy:
            reply = self._get_reply()    
            # valid reply?            
            if(bool(reply)):
                datrd = int(self.bus.datrd.getvalue())
                #append reply and meta info to result buffer
                tmpRes =  wbr(ack=reply, sel=None, adr=None, datrd=datrd, datwr=None, waitIdle=None, waitStall=None, waitAck=self._clk_cycle_count)               
                self._res_buf.append(tmpRes)
                self._acked_ops += 1
            yield clkedge
            count += 1    

    
    @coroutine
    def _drive(self, we, adr, datwr, sel, idle):
        """
            Drive the Wishbone Master Out Lines
        """
    
        clkedge = RisingEdge(self.clock)
        if self.busy:
            # insert requested idle cycles            
            if idle != None:
                idlecnt = idle
                while idlecnt > 0:
                    idlecnt -= 1
                    yield clkedge
            # drive outputs    
            self.bus.stb    <= 1
            self.bus.adr    <= adr
            self.bus.sel    <= sel
            self.bus.datwr  <= datwr
            self.bus.we     <= we
            yield clkedge
            #deal with flow control (pipelined wishbone)
            stalled = yield self._wait_stall()
            #append operation and meta info to auxiliary buffer
            self._aux_buf.append(wba(sel, adr, datwr, stalled, idle, self._clk_cycle_count))
            #reset strobe and write enable without advancing time
            self.bus.stb    <= 0
            self.bus.we     <= 0
            # non pipelined wishbone
            yield self._wait_ack()
        else:
           self.log.error("Cannot drive the Wishbone bus outside a cycle!")


  
    @coroutine
    def send_cycle(self, arg):
        """
            The main sending routine        
        
        Args:
            list(WishboneOperations)
            
        """
        cnt = 0
        clkedge = RisingEdge(self.clock)
        yield clkedge
        if is_sequence(arg):
            if len(arg) < 1:
                self.log.error("List contains no operations to carry out")
            else:
         
                self._op_cnt = len(arg)
                firstword = True
                for op in arg:
                    if firstword:
                        firstword = False
                        result = []
                        yield self._open_cycle()
                        
                    if op.dat != None:
                        we  = 1
                        dat = op.dat
                    else:
                        we  = 0
                        dat = 0
                    yield self._drive(we, op.adr, dat, op.sel, op.idle)
                    self.log.debug("#%3u WE: %s ADR: 0x%08x DAT: 0x%08x SEL: 0x%1x IDLE: %3u" % (cnt, we, op.adr, dat, op.sel, op.idle))
                    cnt += 1
                yield self._close_cycle()
                
                #do pick and mix from result- and auxiliary buffer so we get all operation and meta info
                for res, aux in zip(self._res_buf, self._aux_buf):
                    res.datwr       = aux.datwr
                    res.sel         = aux.sel
                    res.adr         = aux.adr
                    res.waitIdle    = aux.waitIdle
                    res.waitStall   = aux.waitStall
                    res.waitack    -= aux.ts
                    result.append(res)
                
            raise ReturnValue(result)
        else:
            self.log.error("Expecting a list")
            raise ReturnValue(None)    
Example #19
0
class AvalonMaster(AvalonMM):
    """Avalon-MM master
    """
    def __init__(self, entity, name, clock):
        AvalonMM.__init__(self, entity, name, clock)
        self.log.debug("AvalonMaster created")
        self.busy_event = Event("%s_busy" % name)
        self.busy = False

    @coroutine
    def _acquire_lock(self):
        if self.busy:
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy = True

    def _release_lock(self):
        self.busy = False
        self.busy_event.set()

    @coroutine
    def read(self, address):
        """
        Issue a request to the bus and block until this
        comes back. Simulation time still progresses
        but syntactically it blocks.
        See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf
        """
        yield self._acquire_lock()

        # Apply values for next clock edge
        yield RisingEdge(self.clock)
        self.bus.address <= address
        self.bus.read <= 1

        # Wait for waitrequest to be low
        yield self._wait_for_nsignal(self.bus.waitrequest)

        # Assume readLatency = 1
        # FIXME need to configure this, should take a dictionary of Avalon properties.
        yield RisingEdge(self.clock)

        # Deassert read
        self.bus.read <= 0

        # Get the data
        yield ReadOnly()
        data = self.bus.readdata.value
        yield NextTimeStep()
        self._release_lock()
        raise ReturnValue(data)

    @coroutine
    def write(self, address, value):
        """
        Issue a write to the given address with the specified
        value.
        See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf
        """
        yield self._acquire_lock()

        # Apply valuse to bus
        yield RisingEdge(self.clock)
        self.bus.address <= address
        self.bus.writedata <= value
        self.bus.write <= 1

        # Wait for waitrequest to be low
        count = yield self._wait_for_nsignal(self.bus.waitrequest)

        # Deassert write
        yield RisingEdge(self.clock)
        self.bus.write <= 0
        self._release_lock()
Example #20
0
class Port:
    """Basic port"""
    def __init__(self, parent=None, rx_handler=None):
        self.parent = parent
        self.other = None
        self.rx_handler = rx_handler

        self.tx_queue = deque()
        self.tx_sync = Event()
        self.tx_scheduled = False

        self.max_speed = 3
        self.max_width = 16
        self.port_delay = 5

        self.cur_speed = 1
        self.cur_width = 1
        self.link_delay = 0
        self.link_delay_unit = None

        self.time_scale = 10**cocotb.utils._get_simulator_precision()

        cocotb.fork(self._run_transmit())

    def connect(self, other):
        if isinstance(other, Port):
            self._connect(other)
        else:
            other.connect(self)

    def _connect(self, port):
        if self.other is not None:
            raise Exception("Already connected")
        port._connect_int(self)
        self._connect_int(port)

    def _connect_int(self, port):
        if self.other is not None:
            raise Exception("Already connected")
        self.other = port
        self.cur_speed = min(self.max_speed, port.max_speed)
        self.cur_width = min(self.max_width, port.max_width)
        self.link_delay = self.port_delay + port.port_delay

    async def send(self, tlp):
        self.tx_queue.append(tlp)
        self.tx_sync.set()

    async def _run_transmit(self):
        while True:
            while not self.tx_queue:
                self.tx_sync.clear()
                await self.tx_sync.wait()

            tlp = self.tx_queue.popleft()
            d = int(tlp.get_wire_size() * 8 /
                    (PCIE_GEN_RATE[self.cur_speed] * self.cur_width *
                     self.time_scale))
            await Timer(d)
            cocotb.fork(self._transmit(tlp))

    async def _transmit(self, tlp):
        if self.other is None:
            raise Exception("Port not connected")
        await Timer(self.link_delay, self.link_delay_unit)
        await self.other.ext_recv(tlp)

    async def ext_recv(self, tlp):
        if self.rx_handler is None:
            raise Exception("Receive handler not set")
        await self.rx_handler(tlp)
Example #21
0
class MiiSource(Reset):
    def __init__(self,
                 data,
                 er,
                 dv,
                 clock,
                 reset=None,
                 enable=None,
                 reset_active_level=True,
                 *args,
                 **kwargs):
        self.log = logging.getLogger(f"cocotb.{data._path}")
        self.data = data
        self.er = er
        self.dv = dv
        self.clock = clock
        self.reset = reset
        self.enable = enable

        self.log.info("MII source")
        self.log.info("cocotbext-eth version %s", __version__)
        self.log.info("Copyright (c) 2020 Alex Forencich")
        self.log.info("https://github.com/alexforencich/cocotbext-eth")

        super().__init__(*args, **kwargs)

        self.active = False
        self.queue = Queue()
        self.dequeue_event = Event()
        self.current_frame = None
        self.idle_event = Event()
        self.idle_event.set()

        self.ifg = 12

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.queue_occupancy_limit_bytes = -1
        self.queue_occupancy_limit_frames = -1

        self.width = 4
        self.byte_width = 1

        assert len(self.data) == 4
        self.data.setimmediatevalue(0)
        if self.er is not None:
            assert len(self.er) == 1
            self.er.setimmediatevalue(0)
        assert len(self.dv) == 1
        self.dv.setimmediatevalue(0)

        self._run_cr = None

        self._init_reset(reset, reset_active_level)

    async def send(self, frame):
        while self.full():
            self.dequeue_event.clear()
            await self.dequeue_event.wait()
        frame = GmiiFrame(frame)
        await self.queue.put(frame)
        self.idle_event.clear()
        self.queue_occupancy_bytes += len(frame)
        self.queue_occupancy_frames += 1

    def send_nowait(self, frame):
        if self.full():
            raise QueueFull()
        frame = GmiiFrame(frame)
        self.queue.put_nowait(frame)
        self.idle_event.clear()
        self.queue_occupancy_bytes += len(frame)
        self.queue_occupancy_frames += 1

    def count(self):
        return self.queue.qsize()

    def empty(self):
        return self.queue.empty()

    def full(self):
        if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
            return True
        elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
            return True
        else:
            return False

    def idle(self):
        return self.empty() and not self.active

    def clear(self):
        while not self.queue.empty():
            frame = self.queue.get_nowait()
            frame.sim_time_end = None
            frame.handle_tx_complete()
        self.dequeue_event.set()
        self.idle_event.set()
        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

    async def wait(self):
        await self.idle_event.wait()

    def _handle_reset(self, state):
        if state:
            self.log.info("Reset asserted")
            if self._run_cr is not None:
                self._run_cr.kill()
                self._run_cr = None

            self.active = False
            self.data.value = 0
            if self.er is not None:
                self.er.value = 0
            self.dv.value = 0

            if self.current_frame:
                self.log.warning("Flushed transmit frame during reset: %s",
                                 self.current_frame)
                self.current_frame.handle_tx_complete()
                self.current_frame = None

            if self.queue.empty():
                self.idle_event.set()
        else:
            self.log.info("Reset de-asserted")
            if self._run_cr is None:
                self._run_cr = cocotb.start_soon(self._run())

    async def _run(self):
        frame = None
        frame_offset = 0
        frame_data = None
        frame_error = None
        ifg_cnt = 0
        self.active = False

        clock_edge_event = RisingEdge(self.clock)

        while True:
            await clock_edge_event

            if self.enable is None or self.enable.value:
                if ifg_cnt > 0:
                    # in IFG
                    ifg_cnt -= 1

                elif frame is None and not self.queue.empty():
                    # send frame
                    frame = self.queue.get_nowait()
                    self.dequeue_event.set()
                    self.queue_occupancy_bytes -= len(frame)
                    self.queue_occupancy_frames -= 1
                    self.current_frame = frame
                    frame.sim_time_start = get_sim_time()
                    frame.sim_time_sfd = None
                    frame.sim_time_end = None
                    self.log.info("TX frame: %s", frame)
                    frame.normalize()

                    # convert to MII
                    frame_data = []
                    frame_error = []
                    for b, e in zip(frame.data, frame.error):
                        frame_data.append(b & 0x0F)
                        frame_data.append(b >> 4)
                        frame_error.append(e)
                        frame_error.append(e)

                    self.active = True
                    frame_offset = 0

                if frame is not None:
                    d = frame_data[frame_offset]
                    if frame.sim_time_sfd is None and d == 0xD:
                        frame.sim_time_sfd = get_sim_time()
                    self.data.value = d
                    if self.er is not None:
                        self.er.value = frame_error[frame_offset]
                    self.dv.value = 1
                    frame_offset += 1

                    if frame_offset >= len(frame_data):
                        ifg_cnt = max(self.ifg, 1)
                        frame.sim_time_end = get_sim_time()
                        frame.handle_tx_complete()
                        frame = None
                        self.current_frame = None
                else:
                    self.data.value = 0
                    if self.er is not None:
                        self.er.value = 0
                    self.dv.value = 0
                    self.active = False
                    self.idle_event.set()
Example #22
0
class S10PcieSource(S10PcieBase):

    _signal_widths = {"valid": 1, "ready": 1}

    _valid_signal = "valid"
    _ready_signal = "ready"

    _transaction_obj = S10PcieTransaction
    _frame_obj = S10PcieFrame

    def __init__(self,
                 bus,
                 clock,
                 reset=None,
                 ready_latency=0,
                 *args,
                 **kwargs):
        super().__init__(bus, clock, reset, ready_latency, *args, **kwargs)

        self.drive_obj = None
        self.drive_sync = Event()

        self.queue_occupancy_limit_bytes = -1
        self.queue_occupancy_limit_frames = -1

        self.bus.data.setimmediatevalue(0)
        self.bus.sop.setimmediatevalue(0)
        self.bus.eop.setimmediatevalue(0)
        self.bus.valid.setimmediatevalue(0)

        if hasattr(self.bus, "empty"):
            self.bus.empty.setimmediatevalue(0)

        if hasattr(self.bus, "err"):
            self.bus.err.setimmediatevalue(0)
        if hasattr(self.bus, "bar_range"):
            self.bus.bar_range.setimmediatevalue(0)

        if hasattr(self.bus, "vf_active"):
            self.bus.vf_active.setimmediatevalue(0)
        if hasattr(self.bus, "func_num"):
            self.bus.func_num.setimmediatevalue(0)
        if hasattr(self.bus, "vf_num"):
            self.bus.vf_num.setimmediatevalue(0)

        if hasattr(self.bus, "parity"):
            self.bus.parity.setimmediatevalue(0)

        cocotb.start_soon(self._run_source())
        cocotb.start_soon(self._run())

    async def _drive(self, obj):
        if self.drive_obj is not None:
            self.drive_sync.clear()
            await self.drive_sync.wait()

        self.drive_obj = obj

    async def send(self, frame):
        while self.full():
            self.dequeue_event.clear()
            await self.dequeue_event.wait()
        frame = S10PcieFrame(frame)
        await self.queue.put(frame)
        self.idle_event.clear()
        self.queue_occupancy_bytes += len(frame)
        self.queue_occupancy_frames += 1

    def send_nowait(self, frame):
        if self.full():
            raise QueueFull()
        frame = S10PcieFrame(frame)
        self.queue.put_nowait(frame)
        self.idle_event.clear()
        self.queue_occupancy_bytes += len(frame)
        self.queue_occupancy_frames += 1

    def full(self):
        if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
            return True
        elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
            return True
        else:
            return False

    def idle(self):
        return self.empty() and not self.active

    async def wait(self):
        await self.idle_event.wait()

    async def _run_source(self):
        self.active = False
        ready_delay = []

        clock_edge_event = RisingEdge(self.clock)

        while True:
            await clock_edge_event

            # read handshake signals
            ready_sample = self.bus.ready.value
            valid_sample = self.bus.valid.value

            if self.reset is not None and self.reset.value:
                self.active = False
                self.bus.valid.value = 0
                continue

            # ready delay
            if self.ready_latency > 1:
                if len(ready_delay) != (self.ready_latency - 1):
                    ready_delay = [0] * (self.ready_latency - 1)
                ready_delay.append(ready_sample)
                ready_sample = ready_delay.pop(0)

            if (ready_sample and valid_sample
                ) or not valid_sample or self.ready_latency > 0:
                if self.drive_obj and not self.pause and (
                        ready_sample or self.ready_latency == 0):
                    self.bus.drive(self.drive_obj)
                    self.drive_obj = None
                    self.drive_sync.set()
                    self.active = True
                else:
                    self.bus.valid.value = 0
                    self.active = bool(self.drive_obj)
                    if not self.drive_obj:
                        self.idle_event.set()

    async def _run(self):
        while True:
            frame = await self._get_frame()
            frame_offset = 0
            self.log.info(f"TX frame: {frame}")
            first = True

            while frame is not None:
                transaction = self._transaction_obj()

                for seg in range(self.seg_count):
                    if frame is None:
                        if not self.empty():
                            frame = self._get_frame_nowait()
                            frame_offset = 0
                            self.log.info(f"TX frame: {frame}")
                            first = True
                        else:
                            break

                    if first:
                        first = False

                        transaction.valid |= 1 << seg
                        transaction.sop |= 1 << seg

                    transaction.bar_range |= frame.bar_range << seg * 3
                    transaction.func_num |= frame.func_num << seg * 3
                    if frame.vf_num is not None:
                        transaction.vf_active |= 1 << seg
                        transaction.vf_num |= frame.vf_num << seg * 11
                    transaction.err |= frame.err << seg

                    empty = 0
                    if frame.data:
                        transaction.valid |= 1 << seg

                        for k in range(
                                min(self.seg_byte_lanes,
                                    len(frame.data) - frame_offset)):
                            transaction.data |= frame.data[
                                frame_offset] << 32 * (
                                    k + seg * self.seg_byte_lanes)
                            transaction.parity |= frame.parity[
                                frame_offset] << 4 * (
                                    k + seg * self.seg_byte_lanes)
                            empty = self.seg_byte_lanes - 1 - k
                            frame_offset += 1

                    if frame_offset >= len(frame.data):
                        transaction.eop |= 1 << seg
                        transaction.empty |= empty << seg * self.seg_empty_width

                        frame = None

                await self._drive(transaction)

    async def _get_frame(self):
        frame = await self.queue.get()
        self.dequeue_event.set()
        self.queue_occupancy_bytes -= len(frame)
        self.queue_occupancy_frames -= 1
        return frame

    def _get_frame_nowait(self):
        frame = self.queue.get_nowait()
        self.dequeue_event.set()
        self.queue_occupancy_bytes -= len(frame)
        self.queue_occupancy_frames -= 1
        return frame
Example #23
0
class UVMMailbox():

    def __init__(self, size=0, name='mailbox'):
        self.max_size = size
        self.name = name
        self.m_queue = UVMQueue()
        self.m_read_event = Event('mailbox_read_event')
        self.m_write_event = Event('mailbox_write_event')

    @cocotb.coroutine
    def put(self, item):
        _uvm_debug(self, 'put', 'Starting to check can_put()')
        if not self.can_put():
            self.m_read_event.clear()
            yield self.m_read_event.wait()
            self.m_read_event.clear()
        _uvm_debug(self, 'put', 'Putting an event into item queue')
        self.m_queue.push_back(item)
        _uvm_debug(self, 'put', 'ZZZ pushed to queue, can_get is ' + str(self.can_get()))
        self.m_write_event.set()
        yield Timer(0)
        _uvm_debug(self, 'put', 'Finished')

    @cocotb.coroutine
    def get(self, itemq):
        if not self.can_get():
            _uvm_debug(self, 'get', 'waiting write event to get item')
            self.m_write_event.clear()
            yield self.m_write_event.wait()
            self.m_write_event.clear()
            _uvm_debug(self, 'get', 'event cleared, can_get ' + str(self.can_get()))

        _uvm_debug(self, 'get', 'wait write event DONE')
        if not self.can_get():
            raise Exception('can_get() should return True, but got false. q: ' +
                    str(self.m_queue))
        item = self.m_queue.pop_front()
        self.m_read_event.set()
        _uvm_debug(self, 'get', 'getting an item from mailbox now')
        itemq.append(item)
        yield Timer(0)

    @cocotb.coroutine
    def peek(self, itemq):
        """ Peeks (with blocking) next item from mailbox without removing it """
        if not self.can_get():
            _uvm_debug(self, 'get', 'waiting write event to get item')
            self.m_write_event.clear()
            yield self.m_write_event.wait()
            self.m_write_event.clear()
            _uvm_debug(self, 'get', 'event cleared, can_get ' + str(self.can_get()))
        item = self.m_queue.front()
        itemq.append(item)

    def try_put(self, item):
        _uvm_debug(self, 'try_put', 'Starting function')
        if self.can_put() is True:
            _uvm_debug(self, 'try_put', 'can_put is True')
            self.m_queue.push_back(item)
            _uvm_debug(self, 'try_put', 'pushed to queue, can_get is ' + str(self.can_get()))
            self.m_write_event.set()
            _uvm_debug(self, 'try_put', 'try_put finishing OK')
            return True
        return False

    # Tries to retrieve an item and append it to given list.
    # Returns True if success, False otherwise.
    def try_get(self, itemq):
        if self.can_get() is True:
            item = self.m_queue.pop_front()
            itemq.append(item)
            self.m_read_event.set()
            _uvm_debug(self, 'try_get', 'try_get finishing OK')
            return True
        return False

    def try_peek(self, itemq):
        if self.can_get() is True:
            item = self.m_queue.front()
            itemq.append(item)
            _uvm_debug(self, 'try_peek', 'try_peek finishing OK')
            return True
        return False

    def can_get(self):
        return self.m_queue.size() > 0

    def can_put(self):
        if self.max_size <= 0:
            return True
        return self.m_queue.size() < self.max_size

    def num(self):
        return self.m_queue.size() > 0
Example #24
0
class EIMMaster(EIM):
    """
    EIM master
    """
    def __init__(self, entity, name, clock, timeout=None, width=16, **kwargs):
        sTo = ", no cycle timeout"
        if timeout is not None:
            sTo = ", cycle timeout is %u clockcycles" % timeout
        self.busy_event         = Event("%s_busy" % name)
        self._timeout           = timeout
        self.busy               = False
        self._acked_ops         = 0
        self._res_buf           = []
        self._aux_buf           = []
        self._op_cnt            = 0
        self._clk_cycle_count   = 0
        EIM.__init__(self, entity, name, clock, width, **kwargs)
        self.log.info("EIM Master created%s" % sTo)


    @coroutine
    def _clk_cycle_counter(self):
        """
        Cycle counter to time bus operations
        """
        clkedge = RisingEdge(self.clock)
        self._clk_cycle_count = 0
        while self.busy:
            yield clkedge
            self._clk_cycle_count += 1


    @coroutine
    def _open_cycle(self):
        #Open new eim cycle
        if self.busy:
            self.log.error("Opening Cycle, but EIM Driver is already busy." +
                           "Someting's wrong")
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy       = True
        cocotb.fork(self._read())
        cocotb.fork(self._clk_cycle_counter())
        #self.bus.cyc    <= 1
        self._acked_ops = 0
        self._res_buf   = []
        self._aux_buf   = []
        self.log.debug("Opening cycle, %u Ops" % self._op_cnt)


    @coroutine
    def _close_cycle(self):
        #Close current eim cycle
        clkedge = RisingEdge(self.clock)
        count           = 0
        last_acked_ops  = 0
        #Wait for all Operations being acknowledged by the slave before
        #lowering the cycle line.
        #This is not mandatory by the bus standard, but a crossbar might
        #send acks to the wrong master if we don't wait.
        #We don't want to risk that, it could hang the bus
        while self._acked_ops < self._op_cnt:
            if last_acked_ops != self._acked_ops:
                self.log.debug("Waiting for missing acks: %u/%u"
                        % (self._acked_ops, self._op_cnt) )
            last_acked_ops = self._acked_ops
            #check for timeout when finishing the cycle
            count += 1
            if (not (self._timeout is None)):
                if (count > self._timeout):
                    raise TestFailure(
                            "Timeout of %u clock cycles "%self._timeout +
                            "reached when waiting for reply from " +
                            "slave")
            yield clkedge

        self.busy = False
        self.busy_event.set()
        self.bus.lba <= 1
        self.bus.cs <= 1
        self.log.debug("Closing cycle")
        yield clkedge


    @coroutine
    def _read(self):
        """
        Reader for slave replies
        """
        clkedge = RisingEdge(self.clock)
        while self.busy:
            datrd = self.bus.daout.value
            #append reply and meta info to result buffer
            tmpRes =  EIMRes(adr=None,
                             datrd=datrd,
                             datwr=None,
                             waitIdle=None,
                             waitStall=None,
                             waitAck=self._clk_cycle_count)
            self._res_buf.append(tmpRes)
            self._acked_ops += 1
            yield clkedge

    @coroutine
    def _drive(self, we, adr, datwr, wsc, idle):
        """
        Drive the EIM Master Out Lines
        """

        clkedge = RisingEdge(self.clock)
        if self.busy:
            # insert requested idle cycles
            if idle is not None:
                idlecnt = idle
                while idlecnt > 0:
                    idlecnt -= 1
                    yield clkedge
            # set address
            self.bus.lba <= 0
            self.bus.dain <= adr
            yield clkedge
            self.bus.lba <= 1
            # drive outputs
            self.bus.cs  <= 0
            self.bus.dain <= datwr
            self.bus.rw   <= we
            yield clkedge
            for i in range(wsc):
                yield clkedge
            #append operation and meta info to auxiliary buffer
            self._aux_buf.append(
                    EIMAux(adr, datwr, idle, self._clk_cycle_count))
#XXX            yield self._wait_ack()
            self.bus.rw <= 0
            self.bus.cs  <= 1
        else:
            self.log.error("Cannot drive the EIM bus outside a cycle!")



    @coroutine
    def send_cycle(self, arg):
        """
        The main sending routine

        Args:
            list(EIMOperations)
        """
        cnt = 0
        clkedge = RisingEdge(self.clock)
        yield clkedge
        if is_sequence(arg):
            self._op_cnt = len(arg)
            if self._op_cnt < 1:
                self.log.error("List contains no operations to carry out")
            else:
                result = []
                yield self._open_cycle()

                for op in arg:
                    if not isinstance(op, EIMOp):
                        raise TestFailure("Sorry, argument must be a list " +
                                          "of EIMOp (EIM Operation) objects.")

                    if op.dat is not None:
                        we  = 1
                        dat = op.dat
                    else:
                        we  = 0
                        dat = 0
                    yield self._drive(we, op.adr, dat, op.wsc, op.idle)
                    self.log.debug(
                            "#%3u WE: %s ADR: 0x%08x " % (cnt, we, op.adr) +
                            "DAT: 0x%08x " % dat +
                            "IDLE: %3u" % op.idle)
                    cnt += 1

                yield self._close_cycle()

                #do pick and mix from result-
                #and auxiliary buffer so we get all operation and meta info
                for res, aux in zip(self._res_buf, self._aux_buf):
                    res.datwr       = aux.datwr
                    res.adr         = aux.adr
                    res.waitIdle    = aux.waitIdle
                    res.waitStall   = aux.waitStall
                    res.waitAck    -= aux.ts
                    result.append(res)

            raise ReturnValue(result)
        else:
            raise TestFailure("Sorry, argument must be a list of EIMOp " +
                              "(EIM Operation) objects!")
            raise ReturnValue(None)
Example #25
0
class AvalonMaster(AvalonMM):
    """Avalon-MM master
    """
    def __init__(self, entity, name, clock):
        AvalonMM.__init__(self, entity, name, clock)
        self.log.debug("AvalonMaster created")
        self.busy_event = Event("%s_busy" % name)
        self.busy = False

    def __len__(self):
        return 2**len(self.bus.address)

    @coroutine
    def _acquire_lock(self):
        if self.busy:
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy = True

    def _release_lock(self):
        self.busy = False
        self.busy_event.set()

    @coroutine
    def read(self, address, sync=True):
        """
        Issue a request to the bus and block until this
        comes back. Simulation time still progresses
        but syntactically it blocks.
        See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf
        """
        if not self._can_read:
            self.log.error("Cannot read - have no read signal")
            raise TestError("Attempt to read on a write-only AvalonMaster")

        yield self._acquire_lock()

        # Apply values for next clock edge
        if sync:
            yield RisingEdge(self.clock)
        self.bus.address <= address
        self.bus.read <= 1
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= int("1"*len(self.bus.byteenable), 2)
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 1

        # Wait for waitrequest to be low
        if hasattr(self.bus, "waitrequest"):
            yield self._wait_for_nsignal(self.bus.waitrequest)
        yield RisingEdge(self.clock)
        # Get the data
        data = self.bus.readdata.value
        # Deassert read
        self.bus.read <= 0
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= 0
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 0
        v = self.bus.address.value
        v.binstr = "x" * len(self.bus.address)
        self.bus.address <= v

        if hasattr(self.bus, "readdatavalid"):
            while True:
                yield ReadOnly()
                # Get the data
                if int(self.bus.readdatavalid):
                    data = self.bus.readdata.value
                    break
                yield RisingEdge(self.clock)
        else:
            # Assume readLatency = 1 if no readdatavalid
            # FIXME need to configure this,
            # should take a dictionary of Avalon properties.
            yield ReadOnly()

        self._release_lock()
        raise ReturnValue(data)

    @coroutine
    def write(self, address, value):
        """
        Issue a write to the given address with the specified
        value.
        See http://www.altera.com/literature/manual/mnl_avalon_spec_1_3.pdf
        """
        if not self._can_write:
            self.log.error("Cannot write - have no write signal")
            raise TestError("Attempt to write on a read-only AvalonMaster")

        yield self._acquire_lock()

        # Apply valuse to bus
        yield RisingEdge(self.clock)
        self.bus.address <= address
        self.bus.writedata <= value
        self.bus.write <= 1
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= int("1"*len(self.bus.byteenable), 2)
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 1

        # Wait for waitrequest to be low
        if hasattr(self.bus, "waitrequest"):
            count = yield self._wait_for_nsignal(self.bus.waitrequest)

        # Deassert write
        yield RisingEdge(self.clock)
        self.bus.write <= 0
        if hasattr(self.bus, "byteenable"):
            self.bus.byteenable <= 0
        if hasattr(self.bus, "cs"):
            self.bus.cs <= 0
        v = self.bus.address.value
        v.binstr = "x" * len(self.bus.address)
        self.bus.address <= v

        v = self.bus.writedata.value
        v.binstr = "x" * len(self.bus.writedata)
        self.bus.writedata <= v
        self._release_lock()
Example #26
0
class AxiStreamBase(Reset):

    _signals = ["tdata"]
    _optional_signals = [
        "tvalid", "tready", "tlast", "tkeep", "tid", "tdest", "tuser"
    ]

    _type = "base"

    _init_x = False

    _valid_init = None
    _ready_init = None

    def __init__(self,
                 bus,
                 clock,
                 reset=None,
                 reset_active_level=True,
                 byte_size=None,
                 byte_lanes=None,
                 *args,
                 **kwargs):

        self.bus = bus
        self.clock = clock
        self.reset = reset
        self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")

        self.log.info("AXI stream %s", self._type)
        self.log.info("cocotbext-axi version %s", __version__)
        self.log.info("Copyright (c) 2020 Alex Forencich")
        self.log.info("https://github.com/alexforencich/cocotbext-axi")

        super().__init__(*args, **kwargs)

        self.active = False
        self.queue = Queue()
        self.dequeue_event = Event()
        self.current_frame = None
        self.idle_event = Event()
        self.idle_event.set()
        self.active_event = Event()

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.width = len(self.bus.tdata)
        self.byte_lanes = 1

        if self._valid_init is not None and hasattr(self.bus, "tvalid"):
            self.bus.tvalid.setimmediatevalue(self._valid_init)
        if self._ready_init is not None and hasattr(self.bus, "tready"):
            self.bus.tready.setimmediatevalue(self._ready_init)

        for sig in self._signals + self._optional_signals:
            if hasattr(self.bus, sig):
                if self._init_x and sig not in ("tvalid", "tready"):
                    v = getattr(self.bus, sig).value
                    v.binstr = 'x' * len(v)
                    getattr(self.bus, sig).setimmediatevalue(v)

        if hasattr(self.bus, "tkeep"):
            self.byte_lanes = len(self.bus.tkeep)
            if byte_size is not None or byte_lanes is not None:
                raise ValueError(
                    "Cannot specify byte_size or byte_lanes if tkeep is connected"
                )
        else:
            if byte_lanes is not None:
                self.byte_lanes = byte_lanes
                if byte_size is not None:
                    raise ValueError(
                        "Cannot specify both byte_size and byte_lanes")
            elif byte_size is not None:
                self.byte_lanes = self.width // byte_size

        self.byte_size = self.width // self.byte_lanes
        self.byte_mask = 2**self.byte_size - 1

        self.log.info("AXI stream %s configuration:", self._type)
        self.log.info("  Byte size: %d bits", self.byte_size)
        self.log.info("  Data width: %d bits (%d bytes)", self.width,
                      self.byte_lanes)
        self.log.info(
            "  tvalid: %s",
            "present" if hasattr(self.bus, "tvalid") else "not present")
        self.log.info(
            "  tready: %s",
            "present" if hasattr(self.bus, "tready") else "not present")
        self.log.info(
            "  tlast: %s",
            "present" if hasattr(self.bus, "tlast") else "not present")
        if hasattr(self.bus, "tkeep"):
            self.log.info("  tkeep width: %d bits", len(self.bus.tkeep))
        else:
            self.log.info("  tkeep: not present")
        if hasattr(self.bus, "tid"):
            self.log.info("  tid width: %d bits", len(self.bus.tid))
        else:
            self.log.info("  tid: not present")
        if hasattr(self.bus, "tdest"):
            self.log.info("  tdest width: %d bits", len(self.bus.tdest))
        else:
            self.log.info("  tdest: not present")
        if hasattr(self.bus, "tuser"):
            self.log.info("  tuser width: %d bits", len(self.bus.tuser))
        else:
            self.log.info("  tuser: not present")

        if self.byte_lanes * self.byte_size != self.width:
            raise ValueError(
                f"Bus does not evenly divide into byte lanes "
                f"({self.byte_lanes} * {self.byte_size} != {self.width})")

        self._run_cr = None

        self._init_reset(reset, reset_active_level)

    def count(self):
        return self.queue.qsize()

    def empty(self):
        return self.queue.empty()

    def clear(self):
        while not self.queue.empty():
            frame = self.queue.get_nowait()
            frame.sim_time_end = None
            frame.handle_tx_complete()
        self.dequeue_event.set()
        self.idle_event.set()
        self.active_event.clear()
        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

    def _handle_reset(self, state):
        if state:
            self.log.info("Reset asserted")
            if self._run_cr is not None:
                self._run_cr.kill()
                self._run_cr = None

            self.active = False

            if self.queue.empty():
                self.idle_event.set()
        else:
            self.log.info("Reset de-asserted")
            if self._run_cr is None:
                self._run_cr = cocotb.fork(self._run())

    async def _run(self):
        raise NotImplementedError()
Example #27
0
class Driver(object):
    """

    Class defining the standard interface for a driver within a testbench

    The driver is responsible for serialising transactions onto the physical
    pins of the interface.  This may consume simulation time.
    """
    def __init__(self):
        """
        Constructor for a driver instance
        """
        # self._busy = Lock()
        self._pending = Event(name="Driver._pending")
        self._sendQ = deque()

        # Subclasses may already set up logging
        if not hasattr(self, "log"):
            self.log = SimLog("cocotb.driver.%s" % (self.__class__.__name__))

        # Create an independent coroutine which can send stuff
        self._thread = cocotb.scheduler.add(self._send_thread())

    def kill(self):
        if self._thread:
            self._thread.kill()
            self._thread = None

    def append(self, transaction, callback=None, event=None):
        """
        Queue up a transaction to be sent over the bus.

        Mechanisms are provided to permit the caller to know when the
        transaction is processed

        callback: optional function to be called when the transaction has been
        sent

        event: event to be set when the tansaction has been sent
        """
        self._sendQ.append((transaction, callback, event))
        self._pending.set()

    def clear(self):
        """
        Clear any queued transactions without sending them onto the bus
        """
        self._sendQ = deque()

    @coroutine
    def send(self, transaction, sync=True):
        """
        Blocking send call (hence must be "yielded" rather than called)

        Sends the transaction over the bus

        Args:
            transaction (any): the transaction to send

        Kwargs:
            sync (boolean): synchronise the transfer by waiting for risingedge
        """
        yield self._send(transaction, None, None, sync=sync)

    def _driver_send(self, transaction, sync=True):
        """
        actual impementation of the send.

        subclasses should override this method to implement the actual send
        routine
        """
        raise NotImplementedError("Subclasses of Driver should define a "
                                  "_driver_send coroutine")

    @coroutine
    def _send(self, transaction, callback, event, sync=True):
        """
        assumes the caller has already acquired the busy lock

        releases busy lock once sending is complete
        """
        yield self._driver_send(transaction, sync=sync)

        # Notify the world that this transaction is complete
        if event:
            event.set()
        if callback:
            callback(transaction)

        # No longer hogging the bus
        # self.busy.release()

    @coroutine
    def _send_thread(self):
        while True:

            # Sleep until we have something to send
            while not self._sendQ:
                self._pending.clear()
                yield self._pending.wait()

            synchronised = False

            # Send in all the queued packets,
            # only synchronise on the first send
            while self._sendQ:
                transaction, callback, event = self._sendQ.popleft()
                self.log.debug("Sending queued packet...")
                yield self._send(transaction,
                                 callback,
                                 event,
                                 sync=not synchronised)
                synchronised = True
Example #28
0
class WishboneMaster(Wishbone):
    """
    Wishbone master
    """
    def __init__(self, entity, name, clock, timeout=None, width=32, **kwargs):
        sTo = ", no cycle timeout"
        if timeout is not None:
            sTo = ", cycle timeout is %u clockcycles" % timeout
        self.busy_event = Event("%s_busy" % name)
        self._timeout = timeout
        self.busy = False
        self._acked_ops = 0
        self._res_buf = []
        self._aux_buf = []
        self._op_cnt = 0
        self._clk_cycle_count = 0
        Wishbone.__init__(self, entity, name, clock, width, **kwargs)
        self.log.info("Wishbone Master created%s" % sTo)

    async def _clk_cycle_counter(self):
        """
        Cycle counter to time bus operations
        """
        clkedge = RisingEdge(self.clock)
        self._clk_cycle_count = 0
        while self.busy:
            await clkedge
            self._clk_cycle_count += 1

    async def _open_cycle(self):
        #Open new wishbone cycle
        if self.busy:
            self.log.error(
                "Opening Cycle, but WB Driver is already busy. Someting's wrong"
            )
            await self.busy_event.wait()
            print('\n--------\nsarasa')
        self.busy_event.clear()
        self.busy = True
        cocotb.fork(self._read())
        cocotb.fork(self._clk_cycle_counter())
        self.bus.cyc.value = 1
        self._acked_ops = 0
        self._res_buf = []
        self._aux_buf = []
        self.log.debug("Opening cycle, %u Ops" % self._op_cnt)

    async def _close_cycle(self):
        #Close current wishbone cycle
        clkedge = RisingEdge(self.clock)
        count = 0
        last_acked_ops = 0
        #Wait for all Operations being acknowledged by the slave before lowering the cycle line
        #This is not mandatory by the bus standard, but a crossbar might send acks to the wrong master
        #if we don't wait. We don't want to risk that, it could hang the bus
        while self._acked_ops < self._op_cnt:
            if last_acked_ops != self._acked_ops:
                self.log.debug("Waiting for missing acks: %u/%u" %
                               (self._acked_ops, self._op_cnt))
            last_acked_ops = self._acked_ops
            #check for timeout when finishing the cycle
            count += 1
            if (not (self._timeout is None)):
                if (count > self._timeout):
                    raise TestFailure(
                        "Timeout of %u clock cycles reached when waiting for reply from slave"
                        % self._timeout)
            await clkedge

        self.busy = False
        self.busy_event.set()
        self.bus.cyc.value = 0
        self.log.debug("Closing cycle")
        await clkedge

    async def _wait_stall(self):
        """Wait for stall to be low before continuing (Pipelined Wishbone)
        """
        clkedge = RisingEdge(self.clock)
        count = 0
        if hasattr(self.bus, "stall"):
            count = 0
            while self.bus.stall.value == 1:
                await clkedge
                count += 1
                if (not (self._timeout is None)):
                    if (count > self._timeout):
                        raise TestFailure(
                            "Timeout of %u clock cycles reached when on stall from slave"
                            % self._timeout)
            self.log.debug("Stalled for %u cycles" % count)
        return count

    async def _wait_ack(self):
        """Wait for ACK on the bus before continuing (Non pipelined Wishbone)
        """
        #wait for acknownledgement before continuing - Classic Wishbone without pipelining
        clkedge = RisingEdge(self.clock)
        count = 0
        if hasattr(self.bus, "stall"):
            self.bus.stb.value = 0
        if self._acktimeout == 0:
            while not self._get_reply()[0]:
                await clkedge
                count += 1
        else:
            while (not self._get_reply()[0]) and (count < self._acktimeout):
                await clkedge
                count += 1
        if (self._acktimeout != 0) and (count >= self._acktimeout):
            raise TestFailure(
                "Timeout of %u clock cycles reached when waiting for acknowledge"
                % count)

        if not hasattr(self.bus, "stall"):
            self.bus.stb.value = 0
        self._acked_ops += 1
        self.log.debug("Waited %u cycles for ackknowledge" % count)

        return count

    def _get_reply(self):
        code = 0  # 0 if no reply, 1 for ACK, 2 for ERR, 3 for RTY
        ack = self.bus.ack.value == 1
        if ack:
            code = 1
        if hasattr(self.bus, "err") and self.bus.err.value == 1:
            if ack:
                raise TestFailure("Slave raised ACK and ERR line")
            ack = True
            code = 2
        if hasattr(self.bus, "rty") and self.bus.rty.value == 1:
            if ack:
                raise TestFailure("Slave raised {} and RTY line".format(
                    "ACK" if code == 1 else "ERR"))
            ack = True
            code = 3
        return ack, code

    async def _read(self):
        """
        Reader for slave replies
        """
        count = 0
        clkedge = RisingEdge(self.clock)
        while self.busy:
            ack, reply = self._get_reply()
            # valid reply?
            if ack:
                datrd = self.bus.datrd.value
                #append reply and meta info to result buffer
                tmpRes = WBRes(ack=reply,
                               sel=None,
                               adr=None,
                               datrd=datrd,
                               datwr=None,
                               waitIdle=None,
                               waitStall=None,
                               waitAck=self._clk_cycle_count)
                self._res_buf.append(tmpRes)
                self._acked_ops += 1
            await clkedge
            count += 1

    async def _drive(self, we, adr, datwr, sel, idle):
        """
        Drive the Wishbone Master Out Lines
        """

        clkedge = RisingEdge(self.clock)
        if self.busy:
            # insert requested idle cycles
            if idle is not None:
                idlecnt = idle
                while idlecnt > 0:
                    idlecnt -= 1
                    await clkedge
            # drive outputs
            self.bus.stb.value = 1
            self.bus.adr.value = adr
            if hasattr(self.bus, "sel"):
                self.bus.sel.value = sel if sel is not None else BinaryValue(
                    "1" * len(self.bus.sel))
            self.bus.datwr.value = datwr
            self.bus.we.value = we
            await clkedge
            #deal with flow control (pipelined wishbone)
            stalled = await self._wait_stall()
            #append operation and meta info to auxiliary buffer
            self._aux_buf.append(
                WBAux(sel, adr, datwr, stalled, idle, self._clk_cycle_count))
            await self._wait_ack()
            self.bus.we.value = 0
        else:
            self.log.error("Cannot drive the Wishbone bus outside a cycle!")

    async def send_cycle(self, arg):
        """
        The main sending routine

        Args:
            list(WishboneOperations)
        """
        cnt = 0
        clkedge = RisingEdge(self.clock)
        await clkedge
        if is_sequence(arg):
            self._op_cnt = len(arg)
            if self._op_cnt < 1:
                self.log.error("List contains no operations to carry out")
            else:
                result = []
                await self._open_cycle()

                for op in arg:
                    if not isinstance(op, WBOp):
                        raise TestFailure(
                            "Sorry, argument must be a list of WBOp (Wishbone Operation) objects!"
                        )

                    self._acktimeout = op.acktimeout

                    if op.dat is not None:
                        we = 1
                        dat = op.dat
                    else:
                        we = 0
                        dat = 0
                    await self._drive(we, op.adr, dat, op.sel, op.idle)
                    if op.sel is not None:
                        self.log.debug(
                            "#%3u WE: %s ADR: 0x%08x DAT: 0x%08x SEL: 0x%1x IDLE: %3u"
                            % (cnt, we, op.adr, dat, op.sel, op.idle))
                    else:
                        self.log.debug(
                            "#%3u WE: %s ADR: 0x%08x DAT: 0x%08x SEL: None  IDLE: %3u"
                            % (cnt, we, op.adr, dat, op.idle))
                    cnt += 1

                await self._close_cycle()

                #do pick and mix from result- and auxiliary buffer so we get all operation and meta info
                for res, aux in zip(self._res_buf, self._aux_buf):
                    res.datwr = aux.datwr
                    res.sel = aux.sel
                    res.adr = aux.adr
                    res.waitIdle = aux.waitIdle
                    res.waitStall = aux.waitStall
                    res.waitAck -= aux.ts
                    result.append(res)

            return result
        else:
            raise TestFailure(
                "Sorry, argument must be a list of WBOp (Wishbone Operation) objects!"
            )
            return None
Example #29
0
class Driver(object):
    """

    Class defining the standard interface for a driver within a testbench

    The driver is responsible for serialising transactions onto the physical
    pins of the interface.  This may consume simulation time.
    """
    def __init__(self):
        """
        Constructor for a driver instance
        """
        # self._busy = Lock()
        self._pending = Event(name="Driver._pending")
        self._sendQ = deque()

        # Subclasses may already set up logging
        if not hasattr(self, "log"):
            self.log = SimLog("cocotb.driver.%s" % (self.__class__.__name__))

        # Create an independent coroutine which can send stuff
        self._thread = cocotb.scheduler.add(self._send_thread())

    def kill(self):
        if self._thread:
            self._thread.kill()
            self._thread = None

    def append(self, transaction, callback=None, event=None):
        """
        Queue up a transaction to be sent over the bus.

        Mechanisms are provided to permit the caller to know when the
        transaction is processed

        callback: optional function to be called when the transaction has been
        sent

        event: event to be set when the tansaction has been sent
        """
        self._sendQ.append((transaction, callback, event))
        self._pending.set()

    def clear(self):
        """
        Clear any queued transactions without sending them onto the bus
        """
        self._sendQ = deque()

    @coroutine
    def send(self, transaction, sync=True):
        """
        Blocking send call (hence must be "yielded" rather than called)

        Sends the transaction over the bus

        Args:
            transaction (any): the transaction to send

        Kwargs:
            sync (boolean): synchronise the transfer by waiting for risingedge
        """
        yield self._send(transaction, None, None, sync=sync)

    def _driver_send(self, transaction, sync=True):
        """
        actual impementation of the send.

        subclasses should override this method to implement the actual send
        routine
        """
        raise NotImplementedError("Subclasses of Driver should define a "
                                  "_driver_send coroutine")

    @coroutine
    def _send(self, transaction, callback, event, sync=True):
        """
        assumes the caller has already acquired the busy lock

        releases busy lock once sending is complete
        """
        yield self._driver_send(transaction, sync=sync)

        # Notify the world that this transaction is complete
        if event:
            event.set()
        if callback:
            callback(transaction)

        # No longer hogging the bus
        # self.busy.release()

    @coroutine
    def _send_thread(self):
        while True:

            # Sleep until we have something to send
            while not self._sendQ:
                self._pending.clear()
                yield self._pending.wait()

            synchronised = False

            # Send in all the queued packets,
            # only synchronise on the first send
            while self._sendQ:
                transaction, callback, event = self._sendQ.popleft()
                self.log.debug("Sending queued packet...")
                yield self._send(transaction, callback, event,
                                 sync=not synchronised)
                synchronised = True
Example #30
0
class StreamBase(Reset):

    _signals = ["data", "valid", "ready"]
    _optional_signals = []

    _signal_widths = {"valid": 1, "ready": 1}

    _init_x = False

    _valid_signal = "valid"
    _valid_init = None
    _ready_signal = "ready"
    _ready_init = None

    _transaction_obj = StreamTransaction
    _bus_obj = StreamBus

    def __init__(self,
                 bus,
                 clock,
                 reset=None,
                 reset_active_level=True,
                 *args,
                 **kwargs):
        self.bus = bus
        self.clock = clock
        self.reset = reset
        self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")

        super().__init__(*args, **kwargs)

        self.active = False

        self.queue = Queue()
        self.dequeue_event = Event()
        self.idle_event = Event()
        self.idle_event.set()
        self.active_event = Event()

        self.ready = None
        self.valid = None

        if self._ready_signal is not None and hasattr(self.bus,
                                                      self._ready_signal):
            self.ready = getattr(self.bus, self._ready_signal)
            if self._ready_init is not None:
                self.ready.setimmediatevalue(self._ready_init)

        if self._valid_signal is not None and hasattr(self.bus,
                                                      self._valid_signal):
            self.valid = getattr(self.bus, self._valid_signal)
            if self._valid_init is not None:
                self.valid.setimmediatevalue(self._valid_init)

        for sig in self._signals + self._optional_signals:
            if hasattr(self.bus, sig):
                if sig in self._signal_widths:
                    assert len(getattr(self.bus,
                                       sig)) == self._signal_widths[sig]
                if self._init_x and sig not in (self._valid_signal,
                                                self._ready_signal):
                    v = getattr(self.bus, sig).value
                    v.binstr = 'x' * len(v)
                    getattr(self.bus, sig).setimmediatevalue(v)

        self._run_cr = None

        self._init_reset(reset, reset_active_level)

    def count(self):
        return self.queue.qsize()

    def empty(self):
        return self.queue.empty()

    def clear(self):
        while not self.queue.empty():
            self.queue.get_nowait()
        self.dequeue_event.set()
        self.idle_event.set()
        self.active_event.clear()

    def _handle_reset(self, state):
        if state:
            self.log.info("Reset asserted")
            if self._run_cr is not None:
                self._run_cr.kill()
                self._run_cr = None

            self.active = False

            if self.queue.empty():
                self.idle_event.set()
        else:
            self.log.info("Reset de-asserted")
            if self._run_cr is None:
                self._run_cr = cocotb.start_soon(self._run())

    async def _run(self):
        raise NotImplementedError()
Example #31
0
class Monitor:
    """Base class for Monitor objects.

    Monitors are passive 'listening' objects that monitor pins going in or out of a DUT.
    This class should not be used
    directly, but should be sub-classed and the internal :any:`_monitor_recv` method
    should be overridden and decorated as a :any:`coroutine`.  This :any:`_monitor_recv`
    method should capture some behavior of the pins, form a transaction, and
    pass this transaction to the internal :any:`_recv` method.  The :any:`_monitor_recv`
    method is added to the cocotb scheduler during the ``__init__`` phase, so it
    should not be awaited anywhere.

    The primary use of a Monitor is as an interface for a
    :class:`~cocotb.scoreboard.Scoreboard`.

    Args:
        callback (callable): Callback to be called with each recovered transaction
            as the argument. If the callback isn't used, received transactions will
            be placed on a queue and the event used to notify any consumers.
        event (cocotb.triggers.Event): Event that will be called when a transaction
            is received through the internal :any:`_recv` method.
            `Event.data` is set to the received transaction.
    """

    def __init__(self, callback=None, event=None):
        self._event = event
        self._wait_event = Event()
        self._recvQ = deque()
        self._callbacks = []
        self.stats = MonitorStatistics()

        # Sub-classes may already set up logging
        if not hasattr(self, "log"):
            self.log = SimLog("cocotb.monitor.%s" % (type(self).__qualname__))

        if callback is not None:
            self.add_callback(callback)

        # Create an independent coroutine which can receive stuff
        self._thread = cocotb.scheduler.add(self._monitor_recv())

    def kill(self):
        """Kill the monitor coroutine."""
        if self._thread:
            self._thread.kill()
            self._thread = None

    def __len__(self):
        return len(self._recvQ)

    def __getitem__(self, idx):
        return self._recvQ[idx]

    def add_callback(self, callback):
        """Add function as a callback.

        Args:
            callback (callable): The function to call back.
        """
        self.log.debug("Adding callback of function %s to monitor",
                       callback.__qualname__)
        self._callbacks.append(callback)

    @coroutine
    async def wait_for_recv(self, timeout=None):
        """With *timeout*, :meth:`.wait` for transaction to arrive on monitor
        and return its data.

        Args:
            timeout: The timeout value for :class:`~.triggers.Timer`.
                Defaults to ``None``.

        Returns:
            Data of received transaction.
        """
        if timeout:
            t = Timer(timeout)
            fired = await First(self._wait_event.wait(), t)
            if fired is t:
                return None
        else:
            await self._wait_event.wait()

        return self._wait_event.data

    # this is not `async` so that we fail in `__init__` on subclasses without it
    def _monitor_recv(self):
        """Actual implementation of the receiver.

        Sub-classes should override this method to implement the actual receive
        routine and call :any:`_recv` with the recovered transaction.
        """
        raise NotImplementedError("Attempt to use base monitor class without "
                                  "providing a ``_monitor_recv`` method")

    def _recv(self, transaction):
        """Common handling of a received transaction."""

        self.stats.received_transactions += 1

        # either callback based consumer
        for callback in self._callbacks:
            callback(transaction)

        # Or queued with a notification
        if not self._callbacks:
            self._recvQ.append(transaction)

        if self._event is not None:
            self._event.set(data=transaction)

        # If anyone was waiting then let them know
        if self._wait_event is not None:
            self._wait_event.set(data=transaction)
            self._wait_event.clear()
Example #32
0
class OPBMaster(BusDriver):
    """
    On-chip peripheral bus master
    """
    _signals = ["xferAck", "errAck", "toutSup", "retry", "DBus_out", "select", "RNW", "BE", "ABus", "DBus_in"]
    _optional_signals = ["seqAddr"]
    _max_cycles = 16

    def __init__(self, entity, name, clock):
        BusDriver.__init__(self, entity, name, clock)
        self.bus.select.setimmediatevalue(0)
        self.log.debug("OPBMaster created")
        self.busy_event = Event("%s_busy" % name)
        self.busy = False

    @cocotb.coroutine
    def _acquire_lock(self):
        if self.busy:
            yield self.busy_event.wait()
        self.busy_event.clear()
        self.busy = True

    def _release_lock(self):
        self.busy = False
        self.busy_event.set()

    @cocotb.coroutine
    def read(self, address, sync=True):
        """
        Issue a request to the bus and block until this
        comes back. Simulation time still progresses
        but syntactically it blocks.
        """
        yield self._acquire_lock()

        # Apply values for next clock edge
        if sync:
            yield RisingEdge(self.clock)
        self.bus.ABus <= address
        self.bus.select <= 1
        self.bus.RNW <= 1
        self.bus.BE <= 0xF

        count = 0
        while not int(self.bus.xferAck.value):
            yield RisingEdge(self.clock)
            yield ReadOnly()
            if int(self.bus.toutSup.value):
                count = 0
            else:
                count += 1
            if count >= self._max_cycles:
                raise OPBException("Read took longer than 16 cycles")
        data = int(self.bus.DBus_out.value)

        # Deassert read
        self.bus.select <= 0
        self._release_lock()
        self.log.info("Read of address 0x%x returned 0x%08x" % (address, data))
        raise ReturnValue(data)

    @cocotb.coroutine
    def write(self, address, value, sync=True):
        """
        """
        yield self._acquire_lock()

        if sync:
            yield RisingEdge(self.clock)
        self.bus.ABus <= address
        self.bus.select <= 1
        self.bus.RNW <= 0
        self.bus.BE <= 0xF
        self.bus.DBus_out <= value

        count = 0
        while not int(self.bus.xferAck.value):
            yield RisingEdge(self.clock)
            yield ReadOnly()
            if int(self.bus.toutSup.value):
                count = 0
            else:
                count += 1
            if count >= self._max_cycles:
                raise OPBException("Write took longer than 16 cycles")

        self.bus.select <= 0
        self._release_lock()
Example #33
0
class SWSwitcherBlock(software_block.SoftwareBlock):
    def __init__(self, clock, name):
        super().__init__(clock, name)

        self._l0id_recvd = []
        self._event_hook = Event()  # used to sync up writing to outputs

    def input_handler_gen(self, input_num):
        if input_num == 0:
            return self._port_0_handle_input_data
        elif input_num == 1:
            return self._port_1_handle_input_data

    def output_handler_gen(self, output_num):
        if output_num == 0:
            return self._output_port_0_handler
        elif output_num == 1:
            return self._output_port_1_handler

    def _port_0_handle_input_data(self, transaction):
        data, timestamp = transaction

        ##
        ## input 0 --> output 1
        ##
        self.output_drivers[1].append(transaction)

    def _port_1_handle_input_data(self, transaction):
        data, timestamp = transaction

        ##
        ## input 1 --> output 0
        ##
        self.output_drivers[0].append(transaction)

    def event_is_ready(self, l0id):
        self._l0id_recvd.append(l0id)
        c = Counter(self._l0id_recvd)
        if c[l0id] >= len(self.output_drivers):
            self._event_hook.set()
            return True
        elif c[l0id] >= 1:
            return False
        else:
            self._event_hook.clear()
            return False

    @cocotb.coroutine
    def _sync_event_header(self, l0id, output_num=-1, data_word=-1):
        if l0id and not self.event_is_ready(l0id):
            yield self._event_hook.wait()
            self._event_hook.clear()

    @cocotb.coroutine
    def _output_port_0_handler(self, transaction):
        data, timestamp = transaction
        driver = self.output_drivers[0]
        data_word = utils.transaction_to_data_word(data)
        l0id = utils.l0id_from_data_word(data_word)

        ##
        ## mimic some logic taking some time
        ##
        time_delay = Timer(50, "ns")
        yield time_delay

        ##
        ## sync up the writing of output data between the
        ## two outputs, based on event-boundaries
        ##
        yield self._sync_event_header(l0id)

        ##
        ## write the output
        ##
        yield driver.write_to_fifo(data)

    @cocotb.coroutine
    def _output_port_1_handler(self, transaction):
        data, timestamp = transaction
        driver = self.output_drivers[1]
        data_word = utils.transaction_to_data_word(data)
        l0id = utils.l0id_from_data_word(data_word)

        ##
        ## mimic some logic taking some time
        ##
        time_delay = Timer(200, "ns")
        yield time_delay

        ##
        ## sync up the writing of output data between the
        ## two outputs, based on event-boundaries
        ##
        yield self._sync_event_header(l0id)

        ##
        ## write the output
        ##
        yield driver.write_to_fifo(data)
Example #34
0
class UsPcieSource(UsPcieBase):

    _signal_widths = {"tvalid": 1, "tready": 1}

    _valid_signal = "tvalid"
    _ready_signal = "tready"

    _transaction_obj = UsPcieTransaction
    _frame_obj = UsPcieFrame

    def __init__(self, bus, clock, reset=None, *args, **kwargs):
        super().__init__(bus, clock, reset, *args, **kwargs)

        self.drive_obj = None
        self.drive_sync = Event()

        self.queue_occupancy_limit_bytes = -1
        self.queue_occupancy_limit_frames = -1

        self.bus.tdata.setimmediatevalue(0)
        self.bus.tvalid.setimmediatevalue(0)
        self.bus.tlast.setimmediatevalue(0)
        self.bus.tkeep.setimmediatevalue(0)
        self.bus.tuser.setimmediatevalue(0)

        self._init()

        cocotb.fork(self._run_source())
        cocotb.fork(self._run())

    async def _drive(self, obj):
        if self.drive_obj is not None:
            self.drive_sync.clear()
            await self.drive_sync.wait()

        self.drive_obj = obj

    async def send(self, frame):
        while self.full():
            self.dequeue_event.clear()
            await self.dequeue_event.wait()
        frame = UsPcieFrame(frame)
        await self.queue.put(frame)
        self.idle_event.clear()
        self.queue_occupancy_bytes += len(frame)
        self.queue_occupancy_frames += 1

    def send_nowait(self, frame):
        if self.full():
            raise QueueFull()
        frame = UsPcieFrame(frame)
        self.queue.put_nowait(frame)
        self.idle_event.clear()
        self.queue_occupancy_bytes += len(frame)
        self.queue_occupancy_frames += 1

    def full(self):
        if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes:
            return True
        elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames:
            return True
        else:
            return False

    def idle(self):
        return self.empty() and not self.active

    async def wait(self):
        await self.idle_event.wait()

    async def _run_source(self):
        self.active = False

        while True:
            await RisingEdge(self.clock)

            # read handshake signals
            tready_sample = self.bus.tready.value
            tvalid_sample = self.bus.tvalid.value

            if self.reset is not None and self.reset.value:
                self.active = False
                self.bus.tdata <= 0
                self.bus.tvalid <= 0
                self.bus.tlast <= 0
                self.bus.tkeep <= 0
                self.bus.tuser <= 0
                continue

            if (tready_sample and tvalid_sample) or not tvalid_sample:
                if self.drive_obj and not self.pause:
                    self.bus.drive(self.drive_obj)
                    self.drive_obj = None
                    self.drive_sync.set()
                    self.bus.tvalid <= 1
                    self.active = True
                else:
                    self.bus.tvalid <= 0
                    self.active = bool(self.drive_obj)
                    if not self.drive_obj:
                        self.idle_event.set()

    async def _run(self):
        raise NotImplementedError

    async def _get_frame(self):
        frame = await self.queue.get()
        self.dequeue_event.set()
        self.queue_occupancy_bytes -= len(frame)
        self.queue_occupancy_frames -= 1
        return frame

    def _get_frame_nowait(self):
        frame = self.queue.get_nowait()
        self.dequeue_event.set()
        self.queue_occupancy_bytes -= len(frame)
        self.queue_occupancy_frames -= 1
        return frame
Example #35
0
class RgmiiSink(Reset):

    def __init__(self, data, ctrl, clock, reset=None, enable=None, mii_select=None, *args, **kwargs):
        self.log = logging.getLogger(f"cocotb.{data._path}")
        self.data = data
        self.ctrl = ctrl
        self.clock = clock
        self.reset = reset
        self.enable = enable
        self.mii_select = mii_select

        self.log.info("RGMII sink")
        self.log.info("cocotbext-eth version %s", __version__)
        self.log.info("Copyright (c) 2020 Alex Forencich")
        self.log.info("https://github.com/alexforencich/cocotbext-eth")

        super().__init__(*args, **kwargs)

        self.active = False
        self.queue = deque()
        self.sync = Event()

        self.mii_mode = False

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.width = 8
        self.byte_width = 1

        assert len(self.data) == 4
        assert len(self.ctrl) == 1

        self._run_cr = None

        self._init_reset(reset)

    async def recv(self, compact=True):
        while self.empty():
            self.sync.clear()
            await self.sync.wait()
        return self.recv_nowait(compact)

    def recv_nowait(self, compact=True):
        if self.queue:
            frame = self.queue.popleft()
            self.queue_occupancy_bytes -= len(frame)
            self.queue_occupancy_frames -= 1
            return frame
        return None

    def count(self):
        return len(self.queue)

    def empty(self):
        return not self.queue

    def idle(self):
        return not self.active

    def clear(self):
        self.queue.clear()
        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

    async def wait(self, timeout=0, timeout_unit=None):
        if not self.empty():
            return
        self.sync.clear()
        if timeout:
            await First(self.sync.wait(), Timer(timeout, timeout_unit))
        else:
            await self.sync.wait()

    def _handle_reset(self, state):
        if state:
            self.log.info("Reset asserted")
            if self._run_cr is not None:
                self._run_cr.kill()
                self._run_cr = None
        else:
            self.log.info("Reset de-asserted")
            if self._run_cr is None:
                self._run_cr = cocotb.fork(self._run())

        self.active = False

    async def _run(self):
        frame = None
        self.active = False
        d_val = 0
        dv_val = 0
        er_val = 0

        while True:
            await RisingEdge(self.clock)

            # capture low nibble on rising edge
            d_val = self.data.value.integer
            dv_val = self.ctrl.value.integer

            await FallingEdge(self.clock)

            # capture high nibble on falling edge
            d_val |= self.data.value.integer << 4
            er_val = dv_val ^ self.ctrl.value.integer

            if self.enable is None or self.enable.value:

                if frame is None:
                    if dv_val:
                        # start of frame
                        frame = GmiiFrame(bytearray(), [])
                        frame.sim_time_start = get_sim_time()
                else:
                    if not dv_val:
                        # end of frame

                        if self.mii_select is not None:
                            self.mii_mode = bool(self.mii_select.value.integer)

                        if self.mii_mode:
                            odd = True
                            sync = False
                            b = 0
                            be = 0
                            data = bytearray()
                            error = []
                            for n, e in zip(frame.data, frame.error):
                                odd = not odd
                                b = (n & 0x0F) << 4 | b >> 4
                                be |= e
                                if not sync and b == EthPre.SFD:
                                    odd = True
                                    sync = True
                                if odd:
                                    data.append(b)
                                    error.append(be)
                                    be = 0
                            frame.data = data
                            frame.error = error

                        frame.compact()
                        frame.sim_time_end = get_sim_time()
                        self.log.info("RX frame: %s", frame)

                        self.queue_occupancy_bytes += len(frame)
                        self.queue_occupancy_frames += 1

                        self.queue.append(frame)
                        self.sync.set()

                        frame = None

                if frame is not None:
                    if frame.sim_time_sfd is None and d_val in (EthPre.SFD, 0xD, 0xDD):
                        frame.sim_time_sfd = get_sim_time()

                    frame.data.append(d_val)
                    frame.error.append(er_val)
Example #36
0
class Monitor(object):
    """Base class for Monitor objects. 

    Monitors are passive 'listening' objects that monitor pins going in or out of a DUT. 
    This class should not be used
    directly, but should be subclassed and the internal :any:`_monitor_recv` method
    should be overridden and decorated as a :any:`coroutine`.  This :any:`_monitor_recv`
    method should capture some behavior of the pins, form a transaction, and
    pass this transaction to the internal :any:`_recv` method.  The :any:`_monitor_recv`
    method is added to the cocotb scheduler during the ``__init__`` phase, so it
    should not be yielded anywhere.

    The primary use of a Monitor is as an interface for a
    :class:`~cocotb.scoreboard.Scoreboard`.

    Args:
        callback (callable): Callback to be called with each recovered transaction 
            as the argument. If the callback isn't used, received transactions will 
            be placed on a queue and the event used to notify any consumers.
        event (event): Object that supports a ``set`` method that will be called when
            a transaction is received through the internal :any:`_recv` method.
    """

    def __init__(self, callback=None, event=None):
        self._event = event
        self._wait_event = None
        self._recvQ = deque()
        self._callbacks = []
        self.stats = MonitorStatistics()
        self._wait_event = Event()

        # Subclasses may already set up logging
        if not hasattr(self, "log"):
            self.log = SimLog("cocotb.monitor.%s" % (self.__class__.__name__))

        if callback is not None:
            self.add_callback(callback)

        # Create an independent coroutine which can receive stuff
        self._thread = cocotb.scheduler.add(self._monitor_recv())

    def kill(self):
        """Kill the monitor coroutine."""
        if self._thread:
            self._thread.kill()
            self._thread = None

    def __len__(self):
        return len(self._recvQ)

    def __getitem__(self, idx):
        return self._recvQ[idx]

    def add_callback(self, callback):
        """Add function as a callback.

        Args:
            callback (callable): The function to call back.
        """
        self.log.debug("Adding callback of function %s to monitor" %
                       (callback.__name__))
        self._callbacks.append(callback)

    @coroutine
    def wait_for_recv(self, timeout=None):
        """With *timeout*, :meth:`.wait` for transaction to arrive on monitor 
        and return its data.

        Args:
            timeout (optional): The timeout value for :class:`~.triggers.Timer`.
                Defaults to ``None``.

        Returns: Data of received transaction.
        """
        if timeout:
            t = Timer(timeout)
            fired = yield [self._wait_event.wait(), t]
            if fired is t:
                raise ReturnValue(None)
        else:
            yield self._wait_event.wait()

        pkt = self._wait_event.data
        raise ReturnValue(pkt)

    @coroutine
    def _monitor_recv(self):
        """Actual implementation of the receiver.

        Subclasses should override this method to implement the actual receive
        routine and call :any:`_recv` with the recovered transaction.
        """
        raise NotImplementedError("Attempt to use base monitor class without "
                                  "providing a ``_monitor_recv`` method")

    def _recv(self, transaction):
        """Common handling of a received transaction."""

        self.stats.received_transactions += 1

        # either callback based consumer
        for callback in self._callbacks:
            callback(transaction)

        # Or queued with a notification
        if not self._callbacks:
            self._recvQ.append(transaction)

        if self._event is not None:
            self._event.set()

        # If anyone was waiting then let them know
        if self._wait_event is not None:
            self._wait_event.set(data=transaction)
            self._wait_event.clear()
Example #37
0
class TB:
    def __init__(self, dut):
        self.dut = dut

        self.log = logging.getLogger("cocotb.tb")
        self.log.setLevel(logging.DEBUG)

        # PCIe
        self.rc = RootComplex()

        self.dev = UltraScalePcieDevice(
            # configuration options
            # pcie_generation=3,
            # pcie_link_width=2,
            # user_clk_frequency=250e6,
            alignment="dword",
            straddle=False,
            enable_pf1=False,
            enable_client_tag=True,
            enable_extended_tag=False,
            enable_parity=False,
            enable_rx_msg_interface=False,
            enable_sriov=False,
            enable_extended_configuration=False,
            enable_pf0_msi=True,
            enable_pf1_msi=False,

            # signals
            user_clk=dut.user_clk,
            user_reset=dut.user_reset,
            user_lnk_up=dut.user_lnk_up,
            sys_clk=dut.sys_clk,
            sys_clk_gt=dut.sys_clk_gt,
            sys_reset=dut.sys_reset,
            pcie_perstn1_in=dut.pcie_perstn1_in,
            pcie_perstn0_out=dut.pcie_perstn0_out,
            pcie_perstn1_out=dut.pcie_perstn1_out,
            phy_rdy_out=dut.phy_rdy_out,
            rq_bus=AxiStreamBus.from_prefix(dut, "s_axis_rq"),
            pcie_rq_seq_num=dut.pcie_rq_seq_num,
            pcie_rq_seq_num_vld=dut.pcie_rq_seq_num_vld,
            pcie_rq_tag=dut.pcie_rq_tag,
            pcie_rq_tag_av=dut.pcie_rq_tag_av,
            pcie_rq_tag_vld=dut.pcie_rq_tag_vld,
            rc_bus=AxiStreamBus.from_prefix(dut, "m_axis_rc"),
            cq_bus=AxiStreamBus.from_prefix(dut, "m_axis_cq"),
            pcie_cq_np_req=dut.pcie_cq_np_req,
            pcie_cq_np_req_count=dut.pcie_cq_np_req_count,
            cc_bus=AxiStreamBus.from_prefix(dut, "s_axis_cc"),
            pcie_tfc_nph_av=dut.pcie_tfc_nph_av,
            pcie_tfc_npd_av=dut.pcie_tfc_npd_av,
            cfg_phy_link_down=dut.cfg_phy_link_down,
            cfg_phy_link_status=dut.cfg_phy_link_status,
            cfg_negotiated_width=dut.cfg_negotiated_width,
            cfg_current_speed=dut.cfg_current_speed,
            cfg_max_payload=dut.cfg_max_payload,
            cfg_max_read_req=dut.cfg_max_read_req,
            cfg_function_status=dut.cfg_function_status,
            cfg_function_power_state=dut.cfg_function_power_state,
            cfg_vf_status=dut.cfg_vf_status,
            cfg_vf_power_state=dut.cfg_vf_power_state,
            cfg_link_power_state=dut.cfg_link_power_state,
            cfg_mgmt_addr=dut.cfg_mgmt_addr,
            cfg_mgmt_write=dut.cfg_mgmt_write,
            cfg_mgmt_write_data=dut.cfg_mgmt_write_data,
            cfg_mgmt_byte_enable=dut.cfg_mgmt_byte_enable,
            cfg_mgmt_read=dut.cfg_mgmt_read,
            cfg_mgmt_read_data=dut.cfg_mgmt_read_data,
            cfg_mgmt_read_write_done=dut.cfg_mgmt_read_write_done,
            cfg_mgmt_type1_cfg_reg_access=dut.cfg_mgmt_type1_cfg_reg_access,
            cfg_err_cor_out=dut.cfg_err_cor_out,
            cfg_err_nonfatal_out=dut.cfg_err_nonfatal_out,
            cfg_err_fatal_out=dut.cfg_err_fatal_out,
            cfg_local_error=dut.cfg_local_error,
            cfg_ltr_enable=dut.cfg_ltr_enable,
            cfg_ltssm_state=dut.cfg_ltssm_state,
            cfg_rcb_status=dut.cfg_rcb_status,
            cfg_dpa_substate_change=dut.cfg_dpa_substate_change,
            cfg_obff_enable=dut.cfg_obff_enable,
            cfg_pl_status_change=dut.cfg_pl_status_change,
            cfg_tph_requester_enable=dut.cfg_tph_requester_enable,
            cfg_tph_st_mode=dut.cfg_tph_st_mode,
            cfg_vf_tph_requester_enable=dut.cfg_vf_tph_requester_enable,
            cfg_vf_tph_st_mode=dut.cfg_vf_tph_st_mode,
            cfg_msg_received=dut.cfg_msg_received,
            cfg_msg_received_data=dut.cfg_msg_received_data,
            cfg_msg_received_type=dut.cfg_msg_received_type,
            cfg_msg_transmit=dut.cfg_msg_transmit,
            cfg_msg_transmit_type=dut.cfg_msg_transmit_type,
            cfg_msg_transmit_data=dut.cfg_msg_transmit_data,
            cfg_msg_transmit_done=dut.cfg_msg_transmit_done,
            cfg_fc_ph=dut.cfg_fc_ph,
            cfg_fc_pd=dut.cfg_fc_pd,
            cfg_fc_nph=dut.cfg_fc_nph,
            cfg_fc_npd=dut.cfg_fc_npd,
            cfg_fc_cplh=dut.cfg_fc_cplh,
            cfg_fc_cpld=dut.cfg_fc_cpld,
            cfg_fc_sel=dut.cfg_fc_sel,
            cfg_per_func_status_control=dut.cfg_per_func_status_control,
            cfg_per_func_status_data=dut.cfg_per_func_status_data,
            cfg_per_function_number=dut.cfg_per_function_number,
            cfg_per_function_output_request=dut.
            cfg_per_function_output_request,
            cfg_per_function_update_done=dut.cfg_per_function_update_done,
            cfg_dsn=dut.cfg_dsn,
            cfg_power_state_change_ack=dut.cfg_power_state_change_ack,
            cfg_power_state_change_interrupt=dut.
            cfg_power_state_change_interrupt,
            cfg_err_cor_in=dut.cfg_err_cor_in,
            cfg_err_uncor_in=dut.cfg_err_uncor_in,
            cfg_flr_in_process=dut.cfg_flr_in_process,
            cfg_flr_done=dut.cfg_flr_done,
            cfg_vf_flr_in_process=dut.cfg_vf_flr_in_process,
            cfg_vf_flr_done=dut.cfg_vf_flr_done,
            cfg_link_training_enable=dut.cfg_link_training_enable,
            cfg_interrupt_int=dut.cfg_interrupt_int,
            cfg_interrupt_pending=dut.cfg_interrupt_pending,
            cfg_interrupt_sent=dut.cfg_interrupt_sent,
            cfg_interrupt_msi_enable=dut.cfg_interrupt_msi_enable,
            cfg_interrupt_msi_vf_enable=dut.cfg_interrupt_msi_vf_enable,
            cfg_interrupt_msi_mmenable=dut.cfg_interrupt_msi_mmenable,
            cfg_interrupt_msi_mask_update=dut.cfg_interrupt_msi_mask_update,
            cfg_interrupt_msi_data=dut.cfg_interrupt_msi_data,
            cfg_interrupt_msi_select=dut.cfg_interrupt_msi_select,
            cfg_interrupt_msi_int=dut.cfg_interrupt_msi_int,
            cfg_interrupt_msi_pending_status=dut.
            cfg_interrupt_msi_pending_status,
            cfg_interrupt_msi_pending_status_data_enable=dut.
            cfg_interrupt_msi_pending_status_data_enable,
            cfg_interrupt_msi_pending_status_function_num=dut.
            cfg_interrupt_msi_pending_status_function_num,
            cfg_interrupt_msi_sent=dut.cfg_interrupt_msi_sent,
            cfg_interrupt_msi_fail=dut.cfg_interrupt_msi_fail,
            cfg_interrupt_msi_attr=dut.cfg_interrupt_msi_attr,
            cfg_interrupt_msi_tph_present=dut.cfg_interrupt_msi_tph_present,
            cfg_interrupt_msi_tph_type=dut.cfg_interrupt_msi_tph_type,
            cfg_interrupt_msi_tph_st_tag=dut.cfg_interrupt_msi_tph_st_tag,
            cfg_interrupt_msi_function_number=dut.
            cfg_interrupt_msi_function_number,
            cfg_hot_reset_out=dut.cfg_hot_reset_out,
            cfg_config_space_enable=dut.cfg_config_space_enable,
            cfg_req_pm_transition_l23_ready=dut.
            cfg_req_pm_transition_l23_ready,
            cfg_hot_reset_in=dut.cfg_hot_reset_in,
            cfg_ds_port_number=dut.cfg_ds_port_number,
            cfg_ds_bus_number=dut.cfg_ds_bus_number,
            cfg_ds_device_number=dut.cfg_ds_device_number,
            cfg_ds_function_number=dut.cfg_ds_function_number,
            cfg_subsys_vend_id=dut.cfg_subsys_vend_id)

        self.dev.log.setLevel(logging.DEBUG)

        dut.pcie_cq_np_req.setimmediatevalue(1)
        dut.cfg_mgmt_addr.setimmediatevalue(0)
        dut.cfg_mgmt_write.setimmediatevalue(0)
        dut.cfg_mgmt_write_data.setimmediatevalue(0)
        dut.cfg_mgmt_byte_enable.setimmediatevalue(0)
        dut.cfg_mgmt_read.setimmediatevalue(0)
        dut.cfg_mgmt_type1_cfg_reg_access.setimmediatevalue(0)
        dut.cfg_msg_transmit.setimmediatevalue(0)
        dut.cfg_msg_transmit_type.setimmediatevalue(0)
        dut.cfg_msg_transmit_data.setimmediatevalue(0)
        dut.cfg_fc_sel.setimmediatevalue(0)
        dut.cfg_per_func_status_control.setimmediatevalue(0)
        dut.cfg_per_function_number.setimmediatevalue(0)
        dut.cfg_per_function_output_request.setimmediatevalue(0)
        dut.cfg_dsn.setimmediatevalue(0)
        dut.cfg_power_state_change_ack.setimmediatevalue(0)
        dut.cfg_err_cor_in.setimmediatevalue(0)
        dut.cfg_err_uncor_in.setimmediatevalue(0)
        dut.cfg_flr_done.setimmediatevalue(0)
        dut.cfg_vf_flr_done.setimmediatevalue(0)
        dut.cfg_link_training_enable.setimmediatevalue(1)
        dut.cfg_interrupt_int.setimmediatevalue(0)
        dut.cfg_interrupt_pending.setimmediatevalue(0)
        dut.cfg_interrupt_msi_select.setimmediatevalue(0)
        dut.cfg_interrupt_msi_int.setimmediatevalue(0)
        dut.cfg_interrupt_msi_pending_status.setimmediatevalue(0)
        dut.cfg_interrupt_msi_pending_status_data_enable.setimmediatevalue(0)
        dut.cfg_interrupt_msi_pending_status_function_num.setimmediatevalue(0)
        dut.cfg_interrupt_msi_attr.setimmediatevalue(0)
        dut.cfg_interrupt_msi_tph_present.setimmediatevalue(0)
        dut.cfg_interrupt_msi_tph_type.setimmediatevalue(0)
        dut.cfg_interrupt_msi_tph_st_tag.setimmediatevalue(0)
        dut.cfg_interrupt_msi_function_number.setimmediatevalue(0)
        dut.cfg_config_space_enable.setimmediatevalue(1)
        dut.cfg_req_pm_transition_l23_ready.setimmediatevalue(0)
        dut.cfg_hot_reset_in.setimmediatevalue(0)
        dut.cfg_ds_port_number.setimmediatevalue(0)
        dut.cfg_ds_bus_number.setimmediatevalue(0)
        dut.cfg_ds_device_number.setimmediatevalue(0)
        dut.cfg_ds_function_number.setimmediatevalue(0)
        dut.cfg_subsys_vend_id.setimmediatevalue(0)
        dut.sys_clk.setimmediatevalue(0)
        dut.sys_clk_gt.setimmediatevalue(0)
        dut.sys_reset.setimmediatevalue(1)
        dut.pcie_perstn1_in.setimmediatevalue(1)

        self.rc.make_port().connect(self.dev)

        # user logic
        self.rq_source = RqSource(AxiStreamBus.from_prefix(dut, "s_axis_rq"),
                                  dut.user_clk, dut.user_reset)
        self.rc_sink = RcSink(AxiStreamBus.from_prefix(dut, "m_axis_rc"),
                              dut.user_clk, dut.user_reset)
        self.cq_sink = CqSink(AxiStreamBus.from_prefix(dut, "m_axis_cq"),
                              dut.user_clk, dut.user_reset)
        self.cc_source = CcSource(AxiStreamBus.from_prefix(dut, "s_axis_cc"),
                                  dut.user_clk, dut.user_reset)

        self.regions = [None] * 6
        self.regions[0] = mmap.mmap(-1, 1024 * 1024)
        self.regions[1] = mmap.mmap(-1, 1024 * 1024)
        self.regions[3] = mmap.mmap(-1, 1024)

        self.current_tag = 0
        self.tag_count = 32
        self.tag_active = [False] * 256
        self.tag_release = Event()

        self.dev.functions[0].msi_cap.msi_multiple_message_capable = 5

        self.dev.functions[0].configure_bar(0, len(self.regions[0]))
        self.dev.functions[0].configure_bar(1, len(self.regions[1]), True,
                                            True)
        self.dev.functions[0].configure_bar(3, len(self.regions[3]), False,
                                            False, True)

        cocotb.fork(self._run_cq())

    def set_idle_generator(self, generator=None):
        if generator:
            self.dev.rc_source.set_pause_generator(generator())
            self.dev.cq_source.set_pause_generator(generator())

    def set_backpressure_generator(self, generator=None):
        if generator:
            self.dev.rq_sink.set_pause_generator(generator())
            self.dev.cc_sink.set_pause_generator(generator())

    async def alloc_tag(self):
        tag_count = min(256, self.tag_count)

        while True:
            tag = self.current_tag
            for k in range(tag_count):
                tag = (tag + 1) % tag_count
                if not self.tag_active[tag]:
                    self.tag_active[tag] = True
                    self.current_tag = tag
                    return tag

            self.tag_release.clear()
            await self.tag_release.wait()

    def release_tag(self, tag):
        assert self.tag_active[tag]
        self.tag_active[tag] = False
        self.tag_release.set()

    async def dma_io_write(self, addr, data, timeout=0, timeout_unit='ns'):
        n = 0

        while True:
            tlp = Tlp_us()
            tlp.fmt_type = TlpType.IO_WRITE
            tlp.requester_id = PcieId(0, 0, 0)

            first_pad = addr % 4
            byte_length = min(len(data) - n, 4 - first_pad)
            tlp.set_addr_be_data(addr, data[n:n + byte_length])

            tlp.tag = await self.alloc_tag()

            await self.rq_source.send(tlp.pack_us_rq())
            pkt = await self.rc_sink.recv()

            self.release_tag(tlp.tag)

            if not pkt:
                raise Exception("Timeout")

            cpl = Tlp_us.unpack_us_rc(pkt)

            if cpl.status != CplStatus.SC:
                raise Exception("Unsuccessful completion")

            n += byte_length
            addr += byte_length

            if n >= len(data):
                break

    async def dma_io_read(self, addr, length, timeout=0, timeout_unit='ns'):
        data = b''
        n = 0

        while True:
            tlp = Tlp_us()
            tlp.fmt_type = TlpType.IO_READ
            tlp.requester_id = PcieId(0, 0, 0)

            first_pad = addr % 4
            byte_length = min(length - n, 4 - first_pad)
            tlp.set_addr_be(addr, byte_length)

            tlp.tag = await self.alloc_tag()

            await self.rq_source.send(tlp.pack_us_rq())
            pkt = await self.rc_sink.recv()

            self.release_tag(tlp.tag)

            if not pkt:
                raise Exception("Timeout")

            cpl = Tlp_us.unpack_us_rc(pkt)

            if cpl.status != CplStatus.SC:
                raise Exception("Unsuccessful completion")
            else:
                d = cpl.get_data()

            data += d[first_pad:]

            n += byte_length
            addr += byte_length

            if n >= length:
                break

        return data[:length]

    async def dma_mem_write(self, addr, data, timeout=0, timeout_unit='ns'):
        n = 0

        while True:
            tlp = Tlp_us()
            if addr > 0xffffffff:
                tlp.fmt_type = TlpType.MEM_WRITE_64
            else:
                tlp.fmt_type = TlpType.MEM_WRITE
            tlp.requester_id = PcieId(0, 0, 0)

            first_pad = addr % 4
            byte_length = len(data) - n
            # max payload size
            byte_length = min(byte_length,
                              (128 << self.dut.cfg_max_payload.value.integer) -
                              first_pad)
            # 4k address align
            byte_length = min(byte_length, 0x1000 - (addr & 0xfff))
            tlp.set_addr_be_data(addr, data[n:n + byte_length])

            await self.rq_source.send(tlp.pack_us_rq())

            n += byte_length
            addr += byte_length

            if n >= len(data):
                break

    async def dma_mem_read(self, addr, length, timeout=0, timeout_unit='ns'):
        data = b''
        n = 0

        while True:
            tlp = Tlp_us()
            if addr > 0xffffffff:
                tlp.fmt_type = TlpType.MEM_READ_64
            else:
                tlp.fmt_type = TlpType.MEM_READ
            tlp.requester_id = PcieId(0, 0, 0)

            first_pad = addr % 4
            byte_length = length - n
            # max read request size
            byte_length = min(
                byte_length,
                (128 << self.dut.cfg_max_read_req.value.integer) - first_pad)
            # 4k address align
            byte_length = min(byte_length, 0x1000 - (addr & 0xfff))
            tlp.set_addr_be(addr, byte_length)

            tlp.tag = await self.alloc_tag()

            await self.rq_source.send(tlp.pack_us_rq())

            m = 0

            while True:
                pkt = await self.rc_sink.recv()

                if not pkt:
                    raise Exception("Timeout")

                cpl = Tlp_us.unpack_us_rc(pkt)

                if cpl.status != CplStatus.SC:
                    raise Exception("Unsuccessful completion")
                else:
                    assert cpl.byte_count + 3 + (cpl.lower_address
                                                 & 3) >= cpl.length * 4
                    assert cpl.byte_count == max(byte_length - m, 1)

                    d = cpl.get_data()

                    offset = cpl.lower_address & 3
                    data += d[offset:offset + cpl.byte_count]

                m += len(d) - offset

                if m >= byte_length:
                    break

            self.release_tag(tlp.tag)

            n += byte_length
            addr += byte_length

            if n >= length:
                break

        return data[:length]

    async def _run_cq(self):
        while True:
            pkt = await self.cq_sink.recv()

            tlp = Tlp_us.unpack_us_cq(pkt)

            self.log.debug("CQ TLP: %s", repr(tlp))

            if tlp.fmt_type == TlpType.IO_READ:
                self.log.info("IO read")

                cpl = Tlp_us.create_completion_data_for_tlp(
                    tlp, PcieId(0, 0, 0))

                region = tlp.bar_id
                addr = tlp.address % len(self.regions[region])
                offset = 0
                start_offset = None
                mask = tlp.first_be

                # perform operation
                data = bytearray(4)

                for k in range(4):
                    if mask & (1 << k):
                        if start_offset is None:
                            start_offset = offset
                    else:
                        if start_offset is not None and offset != start_offset:
                            data[start_offset:offset] = self.regions[region][
                                addr + start_offset:addr + offset]
                        start_offset = None

                    offset += 1

                if start_offset is not None and offset != start_offset:
                    data[start_offset:offset] = self.regions[region][
                        addr + start_offset:addr + offset]

                cpl.set_data(data)
                cpl.byte_count = 4
                cpl.length = 1

                self.log.debug("Completion: %s", repr(cpl))
                await self.cc_source.send(cpl.pack_us_cc())

            elif tlp.fmt_type == TlpType.IO_WRITE:
                self.log.info("IO write")

                cpl = Tlp_us.create_completion_for_tlp(tlp, PcieId(0, 0, 0))

                region = tlp.bar_id
                addr = tlp.address % len(self.regions[region])
                offset = 0
                start_offset = None
                mask = tlp.first_be

                # perform operation
                data = tlp.get_data()

                for k in range(4):
                    if mask & (1 << k):
                        if start_offset is None:
                            start_offset = offset
                    else:
                        if start_offset is not None and offset != start_offset:
                            self.regions[region][addr + start_offset:addr +
                                                 offset] = data[
                                                     start_offset:offset]
                        start_offset = None

                    offset += 1

                if start_offset is not None and offset != start_offset:
                    self.regions[region][addr + start_offset:addr +
                                         offset] = data[start_offset:offset]

                self.log.debug("Completion: %s", repr(cpl))
                await self.cc_source.send(cpl.pack_us_cc())

            elif tlp.fmt_type in {TlpType.MEM_READ, TlpType.MEM_READ_64}:
                self.log.info("Memory read")

                # perform operation
                region = tlp.bar_id
                addr = tlp.address % len(self.regions[region])
                offset = 0
                length = tlp.length

                # perform read
                data = self.regions[region][addr:addr + length * 4]

                # prepare completion TLP(s)
                m = 0
                n = 0
                addr = tlp.address + tlp.get_first_be_offset()
                dw_length = tlp.length
                byte_length = tlp.get_be_byte_count()

                while m < dw_length:
                    cpl = Tlp_us.create_completion_data_for_tlp(
                        tlp, PcieId(0, 0, 0))

                    cpl_dw_length = dw_length - m
                    cpl_byte_length = byte_length - n
                    cpl.byte_count = cpl_byte_length
                    if cpl_dw_length > 32 << self.dut.cfg_max_payload.value.integer:
                        # max payload size
                        cpl_dw_length = 32 << self.dut.cfg_max_payload.value.integer
                        # RCB align
                        cpl_dw_length -= (addr & 0x7c) >> 2

                    cpl.lower_address = addr & 0x7f

                    cpl.set_data(data[m * 4:(m + cpl_dw_length) * 4])

                    self.log.debug("Completion: %s", repr(cpl))
                    await self.cc_source.send(cpl.pack_us_cc())

                    m += cpl_dw_length
                    n += cpl_dw_length * 4 - (addr & 3)
                    addr += cpl_dw_length * 4 - (addr & 3)

            elif tlp.fmt_type in {TlpType.MEM_WRITE, TlpType.MEM_WRITE_64}:
                self.log.info("Memory write")

                # perform operation
                region = tlp.bar_id
                addr = tlp.address % len(self.regions[region])
                offset = 0
                start_offset = None
                mask = tlp.first_be
                length = tlp.length

                # perform write
                data = tlp.get_data()

                # first dword
                for k in range(4):
                    if mask & (1 << k):
                        if start_offset is None:
                            start_offset = offset
                    else:
                        if start_offset is not None and offset != start_offset:
                            self.regions[region][addr + start_offset:addr +
                                                 offset] = data[
                                                     start_offset:offset]
                        start_offset = None

                    offset += 1

                if length > 2:
                    # middle dwords
                    if start_offset is None:
                        start_offset = offset
                    offset += (length - 2) * 4

                if length > 1:
                    # last dword
                    mask = tlp.last_be

                    for k in range(4):
                        if mask & (1 << k):
                            if start_offset is None:
                                start_offset = offset
                        else:
                            if start_offset is not None and offset != start_offset:
                                self.regions[region][addr + start_offset:addr +
                                                     offset] = data[
                                                         start_offset:offset]
                            start_offset = None

                        offset += 1

                if start_offset is not None and offset != start_offset:
                    self.regions[region][addr + start_offset:addr +
                                         offset] = data[start_offset:offset]