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()
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)
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
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()
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)
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()
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
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
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)
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
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()
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
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)
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')
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