Пример #1
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()
Пример #2
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)
Пример #3
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
Пример #4
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()
Пример #5
0
class MiiSink(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 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 = Queue()
        self.active_event = Event()

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.width = 4
        self.byte_width = 1

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

        self._run_cr = None

        self._init_reset(reset, reset_active_level)

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

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

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

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

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

    def idle(self):
        return not self.active

    def clear(self):
        while not self.queue.empty():
            self.queue.get_nowait()
        self.active_event.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
        if timeout:
            await First(self.active_event.wait(), Timer(timeout, timeout_unit))
        else:
            await self.active_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
        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
        self.active = False

        clock_edge_event = RisingEdge(self.clock)

        while True:
            await clock_edge_event

            if self.enable is None or self.enable.value:
                d_val = self.data.value.integer
                dv_val = self.dv.value.integer
                er_val = 0 if self.er is None else self.er.value.integer

                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
                        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.put_nowait(frame)
                        self.active_event.set()

                        frame = None

                if frame is not None:
                    if frame.sim_time_sfd is None and d_val == 0xD:
                        frame.sim_time_sfd = get_sim_time()

                    frame.data.append(d_val)
                    frame.error.append(er_val)
Пример #6
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()
Пример #7
0
class UartSink:
    def __init__(self, data, baud=9600, bits=8, stop_bits=1, *args, **kwargs):
        self.log = logging.getLogger(f"cocotb.{data._path}")
        self._data = data
        self._baud = baud
        self._bits = bits
        self._stop_bits = stop_bits

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

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

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

        self.log.info("UART sink configuration:")
        self.log.info("  Baud rate: %d bps", self._baud)
        self.log.info("  Byte size: %d bits", self._bits)
        self.log.info("  Stop bits: %f bits", self._stop_bits)

        self._run_cr = None
        self._restart()

    def _restart(self):
        if self._run_cr is not None:
            self._run_cr.kill()
        self._run_cr = cocotb.fork(
            self._run(self._data, self._baud, self._bits, self._stop_bits))

    @property
    def baud(self):
        return self._baud

    @baud.setter
    def baud(self, value):
        self.baud = value
        self._restart()

    @property
    def bits(self):
        return self._bits

    @bits.setter
    def bits(self, value):
        self.bits = value
        self._restart()

    @property
    def stop_bits(self):
        return self._stop_bits

    @stop_bits.setter
    def stop_bits(self, value):
        self.stop_bits = value
        self._restart()

    async def read(self, count=-1):
        while self.empty():
            self.sync.clear()
            await self.sync.wait()
        return self.read_nowait(count)

    def read_nowait(self, count=-1):
        if count < 0:
            count = self.queue.qsize()
        if self.bits == 8:
            data = bytearray()
        else:
            data = []
        for k in range(count):
            data.append(self.queue.get_nowait())
        return data

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

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

    def idle(self):
        return not self.active

    def clear(self):
        while not self.queue.empty():
            frame = self.queue.get_nowait()

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

    async def _run(self, data, baud, bits, stop_bits):
        self.active = False

        half_bit_t = Timer(int(1e9 / self.baud / 2), 'ns')
        bit_t = Timer(int(1e9 / self.baud), 'ns')
        stop_bit_t = Timer(int(1e9 / self.baud * stop_bits), 'ns')

        while True:
            await FallingEdge(data)

            self.active = True

            # start bit
            await half_bit_t

            # data bits
            b = 0
            for k in range(bits):
                await bit_t
                b |= bool(data.value.integer) << k

            # stop bit
            await stop_bit_t

            self.log.info("Read byte 0x%02x", b)

            self.queue.put_nowait(b)
            self.sync.set()

            self.active = False
Пример #8
0
class UartSource:
    def __init__(self, data, baud=9600, bits=8, stop_bits=1, *args, **kwargs):
        self.log = logging.getLogger(f"cocotb.{data._path}")
        self._data = data
        self._baud = baud
        self._bits = bits
        self._stop_bits = stop_bits

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

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

        self.active = False
        self.queue = Queue()

        self._idle = Event()
        self._idle.set()

        self._data.setimmediatevalue(1)

        self.log.info("UART source configuration:")
        self.log.info("  Baud rate: %d bps", self._baud)
        self.log.info("  Byte size: %d bits", self._bits)
        self.log.info("  Stop bits: %f bits", self._stop_bits)

        self._run_cr = None
        self._restart()

    def _restart(self):
        if self._run_cr is not None:
            self._run_cr.kill()
        self._run_cr = cocotb.fork(
            self._run(self._data, self._baud, self._bits, self._stop_bits))

    @property
    def baud(self):
        return self._baud

    @baud.setter
    def baud(self, value):
        self.baud = value
        self._restart()

    @property
    def bits(self):
        return self._bits

    @bits.setter
    def bits(self, value):
        self.bits = value
        self._restart()

    @property
    def stop_bits(self):
        return self._stop_bits

    @stop_bits.setter
    def stop_bits(self, value):
        self.stop_bits = value
        self._restart()

    async def write(self, data):
        for b in data:
            await self.queue.put(int(b))
            self._idle.clear()

    def write_nowait(self, data):
        for b in data:
            self.queue.put_nowait(int(b))
        self._idle.clear()

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

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

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

    def clear(self):
        while not self.queue.empty():
            frame = self.queue.get_nowait()

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

    async def _run(self, data, baud, bits, stop_bits):
        self.active = False

        bit_t = Timer(int(1e9 / self.baud), 'ns')
        stop_bit_t = Timer(int(1e9 / self.baud * stop_bits), 'ns')

        while True:
            if self.empty():
                self.active = False
                self._idle.set()

            b = await self.queue.get()
            self.active = True

            self.log.info("Write byte 0x%02x", b)

            # start bit
            data.value = 0
            await bit_t

            # data bits
            for k in range(self.bits):
                data.value = b & 1
                b >>= 1
                await bit_t

            # stop bit
            data.value = 1
            await stop_bit_t
Пример #9
0
class XgmiiSink(Reset):
    def __init__(self,
                 data,
                 ctrl,
                 clock,
                 reset=None,
                 enable=None,
                 reset_active_level=True,
                 *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.log.info("XGMII 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 = Queue()
        self.active_event = Event()

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.width = len(self.data)
        self.byte_size = 8
        self.byte_lanes = len(self.ctrl)

        assert self.width == self.byte_lanes * self.byte_size

        self.log.info("XGMII sink model configuration")
        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._run_cr = None

        self._init_reset(reset, reset_active_level)

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

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

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

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

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

    def idle(self):
        return not self.active

    def clear(self):
        while not self.queue.empty():
            self.queue.get_nowait()
        self.active_event.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
        if timeout:
            await First(self.active_event.wait(), Timer(timeout, timeout_unit))
        else:
            await self.active_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
        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
        self.active = False

        clock_edge_event = RisingEdge(self.clock)

        while True:
            await clock_edge_event

            if self.enable is None or self.enable.value:
                for offset in range(self.byte_lanes):
                    d_val = (self.data.value.integer >> (offset * 8)) & 0xff
                    c_val = (self.ctrl.value.integer >> offset) & 1

                    if frame is None:
                        if c_val and d_val == XgmiiCtrl.START:
                            # start
                            frame = XgmiiFrame(bytearray([EthPre.PRE]), [0])
                            frame.sim_time_start = get_sim_time()
                            frame.start_lane = offset
                    else:
                        if c_val:
                            # got a control character; terminate frame reception
                            if d_val != XgmiiCtrl.TERM:
                                # store control character if it's not a termination
                                frame.data.append(d_val)
                                frame.ctrl.append(c_val)

                            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.put_nowait(frame)
                            self.active_event.set()

                            frame = None
                        else:
                            if frame.sim_time_sfd is None and d_val == EthPre.SFD:
                                frame.sim_time_sfd = get_sim_time()

                            frame.data.append(d_val)
                            frame.ctrl.append(c_val)
Пример #10
0
async def test_queue_contention(dut):
    NUM_PUTTERS = 20
    QUEUE_SIZE = 10

    q = Queue(maxsize=QUEUE_SIZE)

    async def putter(lst, item):
        await q.put(item)
        lst.append(item)

    async def getter(lst, item):
        assert item == await q.get()
        lst.append(item)

    coro_list = []
    putter_list = []
    getter_list = []

    # test put contention
    for k in range(NUM_PUTTERS):
        coro_list.append(await cocotb.start(putter(putter_list, k)))

    assert q.qsize() == QUEUE_SIZE

    # test killed putter
    coro = cocotb.start_soon(putter(putter_list, 100))
    coro.kill()
    coro_list.append(cocotb.start_soon(putter(putter_list, 101)))

    for k in range(NUM_PUTTERS):
        coro_list.append(cocotb.start_soon(getter(getter_list, k)))

    coro_list.append(cocotb.start_soon(getter(getter_list, 101)))

    await Combine(*coro_list)

    assert putter_list == list(range(NUM_PUTTERS)) + [101]
    assert getter_list == list(range(NUM_PUTTERS)) + [101]

    assert q.qsize() == 0

    coro_list = []
    putter_list = []
    getter_list = []

    # test get contention
    for k in range(NUM_PUTTERS):
        coro_list.append(cocotb.start_soon(getter(getter_list, k)))

    # test killed getter
    coro2 = cocotb.start_soon(getter(getter_list, 100))
    coro2.kill()
    coro_list.append(cocotb.start_soon(getter(getter_list, 101)))

    for k in range(NUM_PUTTERS):
        coro_list.append(cocotb.start_soon(putter(putter_list, k)))

    coro_list.append(cocotb.start_soon(putter(putter_list, 101)))

    await Combine(*coro_list)

    assert putter_list == list(range(NUM_PUTTERS)) + [101]
    assert getter_list == list(range(NUM_PUTTERS)) + [101]

    assert q.qsize() == 0
Пример #11
0
class XgmiiSource(Reset):
    def __init__(self,
                 data,
                 ctrl,
                 clock,
                 reset=None,
                 enable=None,
                 reset_active_level=True,
                 *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.log.info("XGMII 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.enable_dic = True
        self.ifg = 12
        self.force_offset_start = False

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

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

        self.width = len(self.data)
        self.byte_size = 8
        self.byte_lanes = len(self.ctrl)

        assert self.width == self.byte_lanes * self.byte_size

        self.log.info("XGMII source model configuration")
        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.idle_d = 0
        self.idle_c = 0

        for k in range(self.byte_lanes):
            self.idle_d |= XgmiiCtrl.IDLE << k * 8
            self.idle_c |= 1 << k

        self.data.setimmediatevalue(0)
        self.ctrl.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 = XgmiiFrame(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 = XgmiiFrame(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
            self.ctrl.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
        ifg_cnt = 0
        deficit_idle_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 + deficit_idle_cnt > self.byte_lanes - 1 or (
                        not self.enable_dic and ifg_cnt > 4):
                    # in IFG
                    ifg_cnt = ifg_cnt - self.byte_lanes
                    if ifg_cnt < 0:
                        if self.enable_dic:
                            deficit_idle_cnt = max(deficit_idle_cnt + ifg_cnt,
                                                   0)
                        ifg_cnt = 0

                elif frame is None:
                    # idle
                    if 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()
                        frame.start_lane = 0
                        assert frame.data[0] == EthPre.PRE
                        assert frame.ctrl[0] == 0
                        frame.data[0] = XgmiiCtrl.START
                        frame.ctrl[0] = 1
                        frame.data.append(XgmiiCtrl.TERM)
                        frame.ctrl.append(1)

                        # offset start
                        if self.enable_dic:
                            min_ifg = 3 - deficit_idle_cnt
                        else:
                            min_ifg = 0

                        if self.byte_lanes > 4 and (ifg_cnt > min_ifg or
                                                    self.force_offset_start):
                            ifg_cnt = ifg_cnt - 4
                            frame.start_lane = 4
                            frame.data = bytearray(
                                [XgmiiCtrl.IDLE] * 4) + frame.data
                            frame.ctrl = [1] * 4 + frame.ctrl

                        if self.enable_dic:
                            deficit_idle_cnt = max(deficit_idle_cnt + ifg_cnt,
                                                   0)
                        ifg_cnt = 0
                        self.active = True
                        frame_offset = 0
                    else:
                        # clear counters
                        deficit_idle_cnt = 0
                        ifg_cnt = 0

                if frame is not None:
                    d_val = 0
                    c_val = 0

                    for k in range(self.byte_lanes):
                        if frame is not None:
                            d = frame.data[frame_offset]
                            if frame.sim_time_sfd is None and d == EthPre.SFD:
                                frame.sim_time_sfd = get_sim_time()
                            d_val |= d << k * 8
                            c_val |= frame.ctrl[frame_offset] << k
                            frame_offset += 1

                            if frame_offset >= len(frame.data):
                                ifg_cnt = max(self.ifg - (self.byte_lanes - k),
                                              0)
                                frame.sim_time_end = get_sim_time()
                                frame.handle_tx_complete()
                                frame = None
                                self.current_frame = None
                        else:
                            d_val |= XgmiiCtrl.IDLE << k * 8
                            c_val |= 1 << k

                    self.data.value = d_val
                    self.ctrl.value = c_val
                else:
                    self.data.value = self.idle_d
                    self.ctrl.value = self.idle_c
                    self.active = False
                    self.idle_event.set()
Пример #12
0
class BaseRSerdesSource():
    def __init__(self,
                 data,
                 header,
                 clock,
                 enable=None,
                 slip=None,
                 scramble=True,
                 reverse=False,
                 *args,
                 **kwargs):
        self.log = logging.getLogger(f"cocotb.{data._path}")
        self.data = data
        self.header = header
        self.clock = clock
        self.enable = enable
        self.slip = slip
        self.scramble = scramble
        self.reverse = reverse

        self.log.info("BASE-R serdes source")
        self.log.info("Copyright (c) 2021 Alex Forencich")
        self.log.info("https://github.com/alexforencich/verilog-ethernet")

        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.enable_dic = True
        self.ifg = 12
        self.force_offset_start = False

        self.bit_offset = 0

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

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

        self.width = len(self.data)
        self.byte_size = 8
        self.byte_lanes = 8

        assert self.width == self.byte_lanes * self.byte_size

        self.log.info("BASE-R serdes source model configuration")
        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("  Enable scrambler: %s", self.scramble)
        self.log.info("  Bit reverse: %s", self.reverse)

        self.data.setimmediatevalue(0)
        self.header.setimmediatevalue(0)

        self._run_cr = cocotb.fork(self._run())

    async def send(self, frame):
        while self.full():
            self.dequeue_event.clear()
            await self.dequeue_event.wait()
        frame = XgmiiFrame(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 = XgmiiFrame(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()

    async def _run(self):
        frame = None
        frame_offset = 0
        ifg_cnt = 0
        deficit_idle_cnt = 0
        scrambler_state = 0
        last_d = 0
        self.active = False

        while True:
            await RisingEdge(self.clock)

            if self.enable is None or self.enable.value:
                if ifg_cnt + deficit_idle_cnt > self.byte_lanes - 1 or (
                        not self.enable_dic and ifg_cnt > 4):
                    # in IFG
                    ifg_cnt = ifg_cnt - self.byte_lanes
                    if ifg_cnt < 0:
                        if self.enable_dic:
                            deficit_idle_cnt = max(deficit_idle_cnt + ifg_cnt,
                                                   0)
                        ifg_cnt = 0

                elif frame is None:
                    # idle
                    if 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()
                        frame.start_lane = 0
                        assert frame.data[0] == EthPre.PRE
                        assert frame.ctrl[0] == 0
                        frame.data[0] = XgmiiCtrl.START
                        frame.ctrl[0] = 1
                        frame.data.append(XgmiiCtrl.TERM)
                        frame.ctrl.append(1)

                        # offset start
                        if self.enable_dic:
                            min_ifg = 3 - deficit_idle_cnt
                        else:
                            min_ifg = 0

                        if self.byte_lanes > 4 and (ifg_cnt > min_ifg or
                                                    self.force_offset_start):
                            ifg_cnt = ifg_cnt - 4
                            frame.start_lane = 4
                            frame.data = bytearray(
                                [XgmiiCtrl.IDLE] * 4) + frame.data
                            frame.ctrl = [1] * 4 + frame.ctrl

                        if self.enable_dic:
                            deficit_idle_cnt = max(deficit_idle_cnt + ifg_cnt,
                                                   0)
                        ifg_cnt = 0
                        self.active = True
                        frame_offset = 0
                    else:
                        # clear counters
                        deficit_idle_cnt = 0
                        ifg_cnt = 0

                if frame is not None:
                    dl = bytearray()
                    cl = []

                    for k in range(self.byte_lanes):
                        if frame is not None:
                            d = frame.data[frame_offset]
                            if frame.sim_time_sfd is None and d == EthPre.SFD:
                                frame.sim_time_sfd = get_sim_time()
                            dl.append(d)
                            cl.append(frame.ctrl[frame_offset])
                            frame_offset += 1

                            if frame_offset >= len(frame.data):
                                ifg_cnt = max(self.ifg - (self.byte_lanes - k),
                                              0)
                                frame.sim_time_end = get_sim_time()
                                frame.handle_tx_complete()
                                frame = None
                                self.current_frame = None
                        else:
                            dl.append(XgmiiCtrl.IDLE)
                            cl.append(1)

                    # remap control characters
                    ctrl = sum(
                        xgmii_ctrl_to_baser_mapping.get(d, BaseRCtrl.ERROR) <<
                        i * 7 for i, d in enumerate(dl))

                    if not any(cl):
                        # data
                        header = BaseRSync.DATA
                        data = int.from_bytes(dl, 'little')
                    else:
                        # control
                        header = BaseRSync.CTRL
                        if cl[0] and dl[0] == XgmiiCtrl.START and not any(
                                cl[1:]):
                            # start in lane 0
                            data = BaseRBlockType.START_0
                            for i in range(1, 8):
                                data |= dl[i] << i * 8
                        elif cl[4] and dl[4] == XgmiiCtrl.START and not any(
                                cl[5:]):
                            # start in lane 4
                            if cl[0] and (dl[0] == XgmiiCtrl.SEQ_OS or dl[0]
                                          == XgmiiCtrl.SIG_OS) and not any(
                                              cl[1:4]):
                                # ordered set in lane 0
                                data = BaseRBlockType.OS_START
                                for i in range(1, 4):
                                    data |= dl[i] << i * 8
                                if dl[0] == XgmiiCtrl.SIG_OS:
                                    # signal ordered set
                                    data |= BaseRO.SIG_OS << 32
                            else:
                                # other control
                                data = BaseRBlockType.START_4 | (
                                    ctrl & 0xfffffff) << 8

                            for i in range(5, 8):
                                data |= dl[i] << i * 8
                        elif cl[0] and (dl[0] == XgmiiCtrl.SEQ_OS or dl[0]
                                        == XgmiiCtrl.SIG_OS) and not any(
                                            cl[1:4]):
                            # ordered set in lane 0
                            if cl[4] and (dl[4] == XgmiiCtrl.SEQ_OS or dl[4]
                                          == XgmiiCtrl.SIG_OS) and not any(
                                              cl[5:8]):
                                # ordered set in lane 4
                                data = BaseRBlockType.OS_04
                                for i in range(5, 8):
                                    data |= dl[i] << i * 8
                                if dl[4] == XgmiiCtrl.SIG_OS:
                                    # signal ordered set
                                    data |= BaseRO.SIG_OS << 36
                            else:
                                data = BaseRBlockType.OS_0 | (
                                    ctrl & 0xfffffff) << 40
                            for i in range(1, 4):
                                data |= dl[i] << i * 8
                            if dl[0] == XgmiiCtrl.SIG_OS:
                                # signal ordered set
                                data |= BaseRO.SIG_OS << 32
                        elif cl[4] and (dl[4] == XgmiiCtrl.SEQ_OS or dl[4]
                                        == XgmiiCtrl.SIG_OS) and not any(
                                            cl[5:8]):
                            # ordered set in lane 4
                            data = BaseRBlockType.OS_4 | (ctrl
                                                          & 0xfffffff) << 8
                            for i in range(5, 8):
                                data |= dl[i] << i * 8
                            if dl[4] == XgmiiCtrl.SIG_OS:
                                # signal ordered set
                                data |= BaseRO.SIG_OS << 36
                        elif cl[0] and dl[0] == XgmiiCtrl.TERM:
                            # terminate in lane 0
                            data = BaseRBlockType.TERM_0 | (
                                ctrl & 0xffffffffffff80) << 8
                        elif cl[1] and dl[1] == XgmiiCtrl.TERM and not cl[0]:
                            # terminate in lane 1
                            data = BaseRBlockType.TERM_1 | (
                                ctrl & 0xffffffffffc000) << 8 | dl[0] << 8
                        elif cl[2] and dl[2] == XgmiiCtrl.TERM and not any(
                                cl[0:2]):
                            # terminate in lane 2
                            data = BaseRBlockType.TERM_2 | (
                                ctrl & 0xffffffffe00000) << 8
                            for i in range(2):
                                data |= dl[i] << ((i + 1) * 8)
                        elif cl[3] and dl[3] == XgmiiCtrl.TERM and not any(
                                cl[0:3]):
                            # terminate in lane 3
                            data = BaseRBlockType.TERM_3 | (
                                ctrl & 0xfffffff0000000) << 8
                            for i in range(3):
                                data |= dl[i] << ((i + 1) * 8)
                        elif cl[4] and dl[4] == XgmiiCtrl.TERM and not any(
                                cl[0:4]):
                            # terminate in lane 4
                            data = BaseRBlockType.TERM_4 | (
                                ctrl & 0xfffff800000000) << 8
                            for i in range(4):
                                data |= dl[i] << ((i + 1) * 8)
                        elif cl[5] and dl[5] == XgmiiCtrl.TERM and not any(
                                cl[0:5]):
                            # terminate in lane 5
                            data = BaseRBlockType.TERM_5 | (
                                ctrl & 0xfffc0000000000) << 8
                            for i in range(5):
                                data |= dl[i] << ((i + 1) * 8)
                        elif cl[6] and dl[6] == XgmiiCtrl.TERM and not any(
                                cl[0:6]):
                            # terminate in lane 6
                            data = BaseRBlockType.TERM_6 | (
                                ctrl & 0xfe000000000000) << 8
                            for i in range(6):
                                data |= dl[i] << ((i + 1) * 8)
                        elif cl[7] and dl[7] == XgmiiCtrl.TERM and not any(
                                cl[0:7]):
                            # terminate in lane 7
                            data = BaseRBlockType.TERM_7
                            for i in range(7):
                                data |= dl[i] << ((i + 1) * 8)
                        else:
                            # all control
                            data = BaseRBlockType.CTRL | ctrl << 8
                else:
                    data = BaseRBlockType.CTRL
                    header = BaseRSync.CTRL
                    self.active = False
                    self.idle_event.set()

                if self.scramble:
                    # 64b/66b scrambler
                    b = 0
                    for i in range(len(self.data)):
                        if bool(scrambler_state & (1 << 38)) ^ bool(
                                scrambler_state
                                & (1 << 57)) ^ bool(data & (1 << i)):
                            scrambler_state = (
                                (scrambler_state & 0x1ffffffffffffff) << 1) | 1
                            b = b | (1 << i)
                        else:
                            scrambler_state = (scrambler_state
                                               & 0x1ffffffffffffff) << 1
                    data = b

                if self.slip is not None and self.slip.value:
                    self.bit_offset += 1

                self.bit_offset = max(0, self.bit_offset) % 66

                if self.bit_offset != 0:
                    d = data << 2 | header

                    out_d = ((last_d | d << 66) >>
                             66 - self.bit_offset) & 0x3ffffffffffffffff

                    last_d = d

                    data = out_d >> 2
                    header = out_d & 3

                if self.reverse:
                    # bit reverse
                    data = sum(1 << (63 - i) for i in range(64)
                               if (data >> i) & 1)
                    header = sum(1 << (1 - i) for i in range(2)
                                 if (header >> i) & 1)

                self.data <= data
                self.header <= header
Пример #13
0
class BaseRSerdesSink:
    def __init__(self,
                 data,
                 header,
                 clock,
                 enable=None,
                 scramble=True,
                 reverse=False,
                 *args,
                 **kwargs):
        self.log = logging.getLogger(f"cocotb.{data._path}")
        self.data = data
        self.header = header
        self.clock = clock
        self.enable = enable
        self.scramble = scramble
        self.reverse = reverse

        self.log.info("BASE-R serdes sink")
        self.log.info("Copyright (c) 2021 Alex Forencich")
        self.log.info("https://github.com/alexforencich/verilog-ethernet")

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

        self.active = False
        self.queue = Queue()
        self.active_event = Event()

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.width = len(self.data)
        self.byte_size = 8
        self.byte_lanes = 8

        assert self.width == self.byte_lanes * self.byte_size

        self.log.info("BASE-R serdes sink model configuration")
        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("  Enable scrambler: %s", self.scramble)
        self.log.info("  Bit reverse: %s", self.reverse)

        self._run_cr = cocotb.fork(self._run())

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

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

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

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

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

    def idle(self):
        return not self.active

    def clear(self):
        while not self.queue.empty():
            self.queue.get_nowait()
        self.active_event.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
        if timeout:
            await First(self.active_event.wait(), Timer(timeout, timeout_unit))
        else:
            await self.active_event.wait()

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

        while True:
            await RisingEdge(self.clock)

            if self.enable is None or self.enable.value:
                data = self.data.value.integer
                header = self.header.value.integer

                if self.reverse:
                    # bit reverse
                    data = sum(1 << (63 - i) for i in range(64)
                               if (data >> i) & 1)
                    header = sum(1 << (1 - i) for i in range(2)
                                 if (header >> i) & 1)

                if self.scramble:
                    # 64b/66b descrambler
                    b = 0
                    for i in range(len(self.data)):
                        if bool(scrambler_state & (1 << 38)) ^ bool(
                                scrambler_state
                                & (1 << 57)) ^ bool(data & (1 << i)):
                            b = b | (1 << i)
                        scrambler_state = (scrambler_state & 0x1ffffffffffffff
                                           ) << 1 | bool(data & (1 << i))
                    data = b

                # 10GBASE-R decoding

                # remap control characters
                ctrl = bytearray(
                    baser_ctrl_to_xgmii_mapping.get((data >> i * 7 + 8)
                                                    & 0x7f, XgmiiCtrl.ERROR)
                    for i in range(8))

                data = data.to_bytes(8, 'little')

                dl = bytearray()
                cl = []
                if header == BaseRSync.DATA:
                    # data
                    dl = data
                    cl = [0] * 8
                elif header == BaseRSync.CTRL:
                    if data[0] == BaseRBlockType.CTRL:
                        # C7 C6 C5 C4 C3 C2 C1 C0 BT
                        dl = ctrl
                        cl = [1] * 8
                    elif data[0] == BaseRBlockType.OS_4:
                        # D7 D6 D5 O4 C3 C2 C1 C0 BT
                        dl = ctrl[0:4]
                        cl = [1] * 4
                        if (data[4] >> 4) & 0xf == BaseRO.SEQ_OS:
                            dl.append(XgmiiCtrl.SEQ_OS)
                        elif (data[4] >> 4) & 0xf == BaseRO.SIG_OS:
                            dl.append(XgmiiCtrl.SIG_OS)
                        else:
                            dl.append(XgmiiCtrl.ERROR)
                        cl.append(1)
                        dl += data[5:]
                        cl += [0] * 3
                    elif data[0] == BaseRBlockType.START_4:
                        # D7 D6 D5    C3 C2 C1 C0 BT
                        dl = ctrl[0:4]
                        cl = [1] * 4
                        dl.append(XgmiiCtrl.START)
                        cl.append(1)
                        dl += data[5:]
                        cl += [0] * 3
                    elif data[0] == BaseRBlockType.OS_START:
                        # D7 D6 D5    O0 D3 D2 D1 BT
                        if data[4] & 0xf == BaseRO.SEQ_OS:
                            dl.append(XgmiiCtrl.SEQ_OS)
                        elif data[4] & 0xf == BaseRO.SIG_OS:
                            dl.append(XgmiiCtrl.SIG_OS)
                        else:
                            dl.append(XgmiiCtrl.ERROR)
                        cl.append(1)
                        dl += data[1:4]
                        cl += [0] * 3
                        dl.append(XgmiiCtrl.START)
                        cl.append(1)
                        dl += data[5:]
                        cl += [0] * 3
                    elif data[0] == BaseRBlockType.OS_04:
                        # D7 D6 D5 O4 O0 D3 D2 D1 BT
                        if data[4] & 0xf == BaseRO.SEQ_OS:
                            dl.append(XgmiiCtrl.SEQ_OS)
                        elif data[4] & 0xf == BaseRO.SIG_OS:
                            dl.append(XgmiiCtrl.SIG_OS)
                        else:
                            dl.append(XgmiiCtrl.ERROR)
                        cl.append(1)
                        dl += data[1:4]
                        cl += [0] * 3
                        if (data[4] >> 4) & 0xf == BaseRO.SEQ_OS:
                            dl.append(XgmiiCtrl.SEQ_OS)
                        elif (data[4] >> 4) & 0xf == BaseRO.SIG_OS:
                            dl.append(XgmiiCtrl.SIG_OS)
                        else:
                            dl.append(XgmiiCtrl.ERROR)
                        cl.append(1)
                        dl += data[5:]
                        cl += [0] * 3
                    elif data[0] == BaseRBlockType.START_0:
                        # D7 D6 D5 D4 D3 D2 D1    BT
                        dl.append(XgmiiCtrl.START)
                        cl.append(1)
                        dl += data[1:]
                        cl += [0] * 7
                    elif data[0] == BaseRBlockType.OS_0:
                        # C7 C6 C5 C4 O0 D3 D2 D1 BT
                        if data[4] & 0xf == BaseRO.SEQ_OS:
                            dl.append(XgmiiCtrl.SEQ_OS)
                        elif data[4] & 0xf == BaseRO.SIG_OS:
                            dl.append(XgmiiCtrl.SEQ_OS)
                        else:
                            dl.append(XgmiiCtrl.ERROR)
                        cl.append(1)
                        dl += data[1:4]
                        cl += [0] * 3
                        dl += ctrl[4:]
                        cl += [1] * 4
                    elif data[0] in {
                            BaseRBlockType.TERM_0, BaseRBlockType.TERM_1,
                            BaseRBlockType.TERM_2, BaseRBlockType.TERM_3,
                            BaseRBlockType.TERM_4, BaseRBlockType.TERM_5,
                            BaseRBlockType.TERM_6, BaseRBlockType.TERM_7
                    }:
                        # C7 C6 C5 C4 C3 C2 C1    BT
                        # C7 C6 C5 C4 C3 C2    D0 BT
                        # C7 C6 C5 C4 C3    D1 D0 BT
                        # C7 C6 C5 C4    D2 D1 D0 BT
                        # C7 C6 C5    D3 D2 D1 D0 BT
                        # C7 C6    D4 D3 D2 D1 D0 BT
                        # C7    D5 D4 D3 D2 D1 D0 BT
                        #    D6 D5 D4 D3 D2 D1 D0 BT
                        term_lane = block_type_term_lane_mapping[data[0]]
                        dl += data[1:term_lane + 1]
                        cl += [0] * term_lane
                        dl.append(XgmiiCtrl.TERM)
                        cl.append(1)
                        dl += ctrl[term_lane + 1:]
                        cl += [1] * (7 - term_lane)
                    else:
                        # invalid block type
                        self.log.warning("Invalid block type")
                        dl = [XgmiiCtrl.ERROR] * 8
                        cl = [1] * 8
                else:
                    # invalid sync header
                    self.log.warning("Invalid sync header")
                    dl = [XgmiiCtrl.ERROR] * 8
                    cl = [1] * 8

                for offset in range(self.byte_lanes):
                    d_val = dl[offset]
                    c_val = cl[offset]

                    if frame is None:
                        if c_val and d_val == XgmiiCtrl.START:
                            # start
                            frame = XgmiiFrame(bytearray([EthPre.PRE]), [0])
                            frame.sim_time_start = get_sim_time()
                            frame.start_lane = offset
                    else:
                        if c_val:
                            # got a control character; terminate frame reception
                            if d_val != XgmiiCtrl.TERM:
                                # store control character if it's not a termination
                                frame.data.append(d_val)
                                frame.ctrl.append(c_val)

                            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.put_nowait(frame)
                            self.active_event.set()

                            frame = None
                        else:
                            if frame.sim_time_sfd is None and d_val == EthPre.SFD:
                                frame.sim_time_sfd = get_sim_time()

                            frame.data.append(d_val)
                            frame.ctrl.append(c_val)
Пример #14
0
class EthMacRx(Reset):
    def __init__(self,
                 bus,
                 clock,
                 reset=None,
                 ptp_time=None,
                 reset_active_level=True,
                 ifg=12,
                 speed=1000e6,
                 *args,
                 **kwargs):

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

        self.log.info("Ethernet MAC RX model")
        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.stream = AxiStreamSource(bus,
                                      clock,
                                      reset,
                                      reset_active_level=reset_active_level)
        self.stream.queue_occupancy_limit = 4

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

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

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

        self.time_scale = cocotb.utils.get_sim_steps(1, 'sec')

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

        if hasattr(self.bus, "tkeep"):
            self.byte_lanes = len(self.bus.tkeep)

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

        self.log.info("Ethernet MAC RX model configuration")
        self.log.info("  Byte size: %d bits", self.byte_size)
        self.log.info("  Data width: %d bits (%d bytes)", self.width,
                      self.byte_lanes)
        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, "tuser"):
            self.log.info("  tuser width: %d bits", len(self.bus.tuser))
        else:
            self.log.info("  tuser: not present")
        if self.ptp_time:
            self.log.info("  ptp_time width: %d bits", len(self.ptp_time))
        else:
            self.log.info("  ptp_time: not present")

        if self.byte_size != 8:
            raise ValueError("Byte size must be 8")

        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)

    async def send(self, frame):
        while self.full():
            self.dequeue_event.clear()
            await self.dequeue_event.wait()
        frame = EthMacFrame(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 = EthMacFrame(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

            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
        tuser = 0
        self.active = False

        while True:
            # wait for data
            frame = await self.queue.get()
            tuser = 0
            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_offset = 0

            # wait for preamble time
            await Timer(self.time_scale * 8 * 8 // self.speed, 'step')

            frame.sim_time_sfd = get_sim_time()

            if self.ptp_time:
                frame.ptp_timestamp = self.ptp_time.value.integer
                tuser |= frame.ptp_timestamp << 1

            # process frame data
            while frame is not None:
                byte_count = 0

                cycle = AxiStreamTransaction()

                cycle.tdata = 0
                cycle.tkeep = 0
                cycle.tlast = 0
                cycle.tuser = tuser

                for offset in range(self.byte_lanes):
                    cycle.tdata |= (frame.data[frame_offset] & self.byte_mask
                                    ) << (offset * self.byte_size)
                    cycle.tkeep |= 1 << offset
                    byte_count += 1
                    frame_offset += 1

                    if frame_offset >= len(frame.data):
                        cycle.tlast = 1
                        frame.sim_time_end = get_sim_time()
                        frame.handle_tx_complete()
                        frame = None
                        self.current_frame = None
                        break

                await self.stream.send(cycle)

                # wait for serialization time
                await Timer(self.time_scale * byte_count * 8 // self.speed,
                            'step')

            # wait for IFG
            await Timer(self.time_scale * self.ifg * 8 // self.speed, 'step')
Пример #15
0
class EthMacTx(Reset):
    def __init__(self,
                 bus,
                 clock,
                 reset=None,
                 ptp_time=None,
                 ptp_ts=None,
                 ptp_ts_tag=None,
                 ptp_ts_valid=None,
                 reset_active_level=True,
                 ifg=12,
                 speed=1000e6,
                 *args,
                 **kwargs):

        self.bus = bus
        self.clock = clock
        self.reset = reset
        self.ptp_time = ptp_time
        self.ptp_ts = ptp_ts
        self.ptp_ts_tag = ptp_ts_tag
        self.ptp_ts_valid = ptp_ts_valid
        self.ifg = ifg
        self.speed = speed
        self.log = logging.getLogger(f"cocotb.{bus._entity._name}.{bus._name}")

        self.log.info("Ethernet MAC TX model")
        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.stream = AxiStreamSink(bus,
                                    clock,
                                    reset,
                                    reset_active_level=reset_active_level)
        self.stream.queue_occupancy_limit = 4

        self.active = False
        self.queue = Queue()
        self.active_event = Event()

        self.ts_queue = Queue()

        self.queue_occupancy_bytes = 0
        self.queue_occupancy_frames = 0

        self.time_scale = cocotb.utils.get_sim_steps(1, 'sec')

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

        if hasattr(self.bus, "tkeep"):
            self.byte_lanes = len(self.bus.tkeep)

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

        self.log.info("Ethernet MAC TX model configuration")
        self.log.info("  Byte size: %d bits", self.byte_size)
        self.log.info("  Data width: %d bits (%d bytes)", self.width,
                      self.byte_lanes)
        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, "tuser"):
            self.log.info("  tuser width: %d bits", len(self.bus.tuser))
        else:
            self.log.info("  tuser: not present")
        if self.ptp_time:
            self.log.info("  ptp_time width: %d bits", len(self.ptp_time))
        else:
            self.log.info("  ptp_time: not present")

        if self.bus.tready is None:
            raise ValueError("tready is required")

        if self.byte_size != 8:
            raise ValueError("Byte size must be 8")

        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})")

        if self.ptp_ts:
            self.ptp_ts.setimmediatevalue(0)
        if self.ptp_ts_tag:
            self.ptp_ts_tag.setimmediatevalue(0)
        if self.ptp_ts_valid:
            self.ptp_ts_valid.setimmediatevalue(0)

        self._run_cr = None
        self._run_ts_cr = None

        self._init_reset(reset, reset_active_level)

    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 count(self):
        return self.queue.qsize()

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

    def idle(self):
        return not self.active

    def clear(self):
        while not self.queue.empty():
            self.queue.get_nowait()
        self.active_event.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
        if timeout:
            await First(self.active_event.wait(), Timer(timeout, timeout_unit))
        else:
            await self.active_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
            if self._run_ts_cr is not None:
                self._run_ts_cr.kill()
                self._run_ts_cr = None

            if self.ptp_ts_valid:
                self.ptp_ts_valid.value = 0

            self.active = False

            while not self.ts_queue.empty():
                self.ts_queue.get_nowait()
        else:
            self.log.info("Reset de-asserted")
            if self._run_cr is None:
                self._run_cr = cocotb.start_soon(self._run())
            if self._run_ts_cr is None and self.ptp_ts:
                self._run_ts_cr = cocotb.start_soon(self._run_ts())

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

        while True:
            # wait for data
            cycle = await self.stream.recv()

            frame = EthMacFrame(bytearray())
            frame.sim_time_start = get_sim_time()

            # wait for preamble time
            await Timer(self.time_scale * 8 * 8 // self.speed, 'step')

            frame.sim_time_sfd = get_sim_time()

            if self.ptp_time:
                frame.ptp_timestamp = self.ptp_time.value.integer
                frame.ptp_tag = cycle.tuser.integer >> 1
                self.ts_queue.put_nowait((frame.ptp_timestamp, frame.ptp_tag))

            # process frame data
            while True:
                byte_count = 0

                for offset in range(self.byte_lanes):
                    if not hasattr(self.bus, "tkeep") or (
                            cycle.tkeep.integer >> offset) & 1:
                        frame.data.append((cycle.tdata.integer >>
                                           (offset * self.byte_size))
                                          & self.byte_mask)
                        byte_count += 1

                # wait for serialization time
                await Timer(self.time_scale * byte_count * 8 // self.speed,
                            'step')

                if cycle.tlast.integer:
                    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

                    await self.queue.put(frame)
                    self.active_event.set()

                    frame = None

                    break

                # get next cycle
                # TODO improve underflow handling
                assert not self.stream.empty(), "underflow"
                cycle = await self.stream.recv()

            # wait for IFG
            await Timer(self.time_scale * self.ifg * 8 // self.speed, 'step')

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

        while True:
            await clock_edge_event
            self.ptp_ts_valid.value = 0

            if not self.ts_queue.empty():
                ts, tag = self.ts_queue.get_nowait()
                self.ptp_ts.value = ts
                if self.ptp_ts_tag is not None:
                    self.ptp_ts_tag.value = tag
                self.ptp_ts_valid.value = 1