async def test_fair_scheduling(dut): NUM_PUTTERS = 10 NUM_PUTS = 10 q = Queue(maxsize=1) async def putter(i): for _ in range(NUM_PUTS): await q.put(i) # fill queue to force contention q.put_nowait(None) # create NUM_PUTTER contending putters putters = [await cocotb.start(putter(i)) for i in range(NUM_PUTTERS)] # remove value that forced contention assert q.get_nowait() is None, "Popped unexpected value" # test fair scheduling by ensuring that each putter is serviced for its first # write before the second write on any putter is serviced. for _ in range(NUM_PUTS): remaining = set(range(NUM_PUTTERS)) for _ in range(NUM_PUTTERS): v = await q.get() assert v in remaining, "Unfair scheduling occurred" remaining.remove(v) assert all(not p for p in putters), "Not all putters finished?"
class Port: """Base port""" def __init__(self, fc_init=[[0]*6]*8, *args, **kwargs): self.log = logging.getLogger(f"cocotb.pcie.{type(self).__name__}.{id(self)}") self.log.name = f"cocotb.pcie.{type(self).__name__}" self.parent = None self.rx_handler = None self.max_link_speed = None self.max_link_width = None self.tx_queue = Queue(1) self.tx_queue_sync = Event() self.rx_queue = Queue() self.cur_link_speed = None self.cur_link_width = None self.time_scale = get_sim_steps(1, 'sec') # ACK/NAK protocol # TX self.next_transmit_seq = 0x000 self.ackd_seq = 0xfff self.retry_buffer = Queue() # RX self.next_recv_seq = 0x000 self.nak_scheduled = False self.ack_nak_latency_timer = 0 self.max_payload_size = 128 self.max_latency_timer = 0 self.send_ack = Event() self._ack_latency_timer_cr = None # Flow control self.send_fc = Event() self.fc_state = [FcChannelState(fc_init[k], self.start_fc_update_timer) for k in range(8)] self.fc_initialized = False self.fc_init_vc = 0 self.fc_init_type = FcType.P self.fc_idle_timer_steps = get_sim_steps(10, 'us') self.fc_update_steps = get_sim_steps(30, 'us') self._fc_update_timer_cr = None super().__init__(*args, **kwargs) # VC0 is always active self.fc_state[0].active = True cocotb.fork(self._run_transmit()) cocotb.fork(self._run_receive()) cocotb.fork(self._run_fc_update_idle_timer()) def classify_tlp_vc(self, tlp): return 0 async def send(self, pkt): pkt.release_fc() await self.fc_state[self.classify_tlp_vc(pkt)].tx_tlp_fc_gate(pkt) await self.tx_queue.put(pkt) self.tx_queue_sync.set() async def _run_transmit(self): await NullTrigger() while True: while self.tx_queue.empty() and not self.send_ack.is_set() and not self.send_fc.is_set() and self.fc_initialized: self.tx_queue_sync.clear() await First(self.tx_queue_sync.wait(), self.send_ack.wait(), self.send_fc.wait()) pkt = None if self.send_ack.is_set(): # Send ACK or NAK DLLP # Runs when # - ACK timer expires # - ACK/NAK transmit requested self.send_ack.clear() if self.nak_scheduled: pkt = Dllp.create_nak((self.next_recv_seq-1) & 0xfff) else: pkt = Dllp.create_ack((self.next_recv_seq-1) & 0xfff) elif self.send_fc.is_set() or (not self.fc_initialized and self.tx_queue.empty()): # Send FC DLLP # Runs when # - FC timer expires # - FC update DLLP transmit requested # - FC init is not done AND no TLPs are queued for transmit if self.send_fc.is_set(): # Send FC update DLLP for fc_ch in self.fc_state: if not fc_ch.active or not fc_ch.fi2: continue sim_time = get_sim_time() if fc_ch.next_fc_p_tx <= sim_time: pkt = Dllp() pkt.vc = self.fc_init_vc pkt.type = DllpType.UPDATE_FC_P pkt.hdr_fc = fc_ch.ph.rx_credits_allocated pkt.data_fc = fc_ch.pd.rx_credits_allocated fc_ch.next_fc_p_tx = sim_time + self.fc_update_steps break if fc_ch.next_fc_np_tx <= sim_time: pkt = Dllp() pkt.vc = self.fc_init_vc pkt.type = DllpType.UPDATE_FC_NP pkt.hdr_fc = fc_ch.nph.rx_credits_allocated pkt.data_fc = fc_ch.npd.rx_credits_allocated fc_ch.next_fc_np_tx = sim_time + self.fc_update_steps break if fc_ch.next_fc_cpl_tx <= sim_time: pkt = Dllp() pkt.vc = self.fc_init_vc pkt.type = DllpType.UPDATE_FC_CPL pkt.hdr_fc = fc_ch.cplh.rx_credits_allocated pkt.data_fc = fc_ch.cpld.rx_credits_allocated fc_ch.next_fc_cpl_tx = sim_time + self.fc_update_steps break if not self.fc_initialized and not pkt: # Send FC init DLLP fc_ch = self.fc_state[self.fc_init_vc] pkt = Dllp() pkt.vc = self.fc_init_vc if self.fc_init_type == FcType.P: pkt.type = DllpType.INIT_FC1_P if not fc_ch.fi1 else DllpType.INIT_FC2_P pkt.hdr_fc = fc_ch.ph.rx_credits_allocated pkt.data_fc = fc_ch.pd.rx_credits_allocated self.fc_init_type = FcType.NP elif self.fc_init_type == FcType.NP: pkt.type = DllpType.INIT_FC1_NP if not fc_ch.fi1 else DllpType.INIT_FC2_NP pkt.hdr_fc = fc_ch.nph.rx_credits_allocated pkt.data_fc = fc_ch.npd.rx_credits_allocated self.fc_init_type = FcType.CPL elif self.fc_init_type == FcType.CPL: pkt.type = DllpType.INIT_FC1_CPL if not fc_ch.fi1 else DllpType.INIT_FC2_CPL pkt.hdr_fc = fc_ch.cplh.rx_credits_allocated pkt.data_fc = fc_ch.cpld.rx_credits_allocated self.fc_init_type = FcType.P # find next active VC that hasn't finished FC init for k in range(8): vc = (self.fc_init_vc+1+k) % 8 if self.fc_state[vc].active and not self.fc_state[vc].fi2: self.fc_init_vc = vc break # check all active VC and report FC not initialized if any are not complete self.fc_initialized = True for vc in range(8): if self.fc_state[vc].active and not self.fc_state[vc].fi2: self.fc_initialized = False if not pkt: # no more DLLPs to send, clear event self.send_fc.clear() if pkt is not None: self.log.debug("Send DLLP %s", pkt) elif not self.tx_queue.empty(): pkt = self.tx_queue.get_nowait() pkt.seq = self.next_transmit_seq self.log.debug("Send TLP %s", pkt) self.next_transmit_seq = (self.next_transmit_seq + 1) & 0xfff self.retry_buffer.put_nowait(pkt) if pkt: await self.handle_tx(pkt) async def handle_tx(self, pkt): raise NotImplementedError() async def ext_recv(self, pkt): if isinstance(pkt, Dllp): # DLLP self.log.debug("Receive DLLP %s", pkt) self.handle_dllp(pkt) else: # TLP self.log.debug("Receive TLP %s", pkt) if pkt.seq == self.next_recv_seq: # expected seq self.next_recv_seq = (self.next_recv_seq + 1) & 0xfff self.nak_scheduled = False self.start_ack_latency_timer() pkt = Tlp(pkt) self.fc_state[self.classify_tlp_vc(pkt)].rx_process_tlp_fc(pkt) self.rx_queue.put_nowait(pkt) elif (self.next_recv_seq - pkt.seq) & 0xfff < 2048: self.log.warning("Received duplicate TLP, discarding (seq %d, expecting %d)", pkt.seq, self.next_recv_seq) self.stop_ack_latency_timer() self.send_ack.set() else: self.log.warning("Received out-of-sequence TLP, sending NAK (seq %d, expecting %d)", pkt.seq, self.next_recv_seq) if not self.nak_scheduled: self.nak_scheduled = True self.stop_ack_latency_timer() self.send_ack.set() async def _run_receive(self): while True: tlp = await self.rx_queue.get() if self.rx_handler is None: raise Exception("Receive handler not set") await self.rx_handler(tlp) def handle_dllp(self, dllp): if dllp.type == DllpType.NOP: # discard NOP pass elif dllp.type in {DllpType.ACK, DllpType.NAK}: # ACK or NAK if (((self.next_transmit_seq-1) & 0xfff) - dllp.seq) & 0xfff > 2048: self.log.warning("Received ACK/NAK DLLP for future TLP, discarding (seq %d, next TX %d, ACK %d)", dllp.seq, self.next_transmit_seq, self.ackd_seq) elif (dllp.seq - self.ackd_seq) & 0xfff > 2048: self.log.warning("Received ACK/NAK DLLP for previously-ACKed TLP, discarding (seq %d, next TX %d, ACK %d)", dllp.seq, self.next_transmit_seq, self.ackd_seq) else: while dllp.seq != self.ackd_seq: # purge TLPs from retry buffer self.retry_buffer.get_nowait() self.ackd_seq = (self.ackd_seq + 1) & 0xfff self.log.debug("ACK TLP seq %d", self.ackd_seq) if dllp.type == DllpType.NAK: # retransmit self.log.warning("Got NAK DLLP, start TLP replay") raise Exception("TODO") elif dllp.type in {DllpType.INIT_FC1_P, DllpType.INIT_FC1_NP, DllpType.INIT_FC1_CPL, DllpType.INIT_FC2_P, DllpType.INIT_FC2_NP, DllpType.INIT_FC2_CPL, DllpType.UPDATE_FC_P, DllpType.UPDATE_FC_NP, DllpType.UPDATE_FC_CPL}: # Flow control self.fc_state[dllp.vc].handle_fc_dllp(dllp) else: raise Exception("TODO") def start_ack_latency_timer(self): if self._ack_latency_timer_cr is not None: if not self._ack_latency_timer_cr._finished: # already running return self._ack_latency_timer_cr = cocotb.fork(self._run_ack_latency_timer()) def stop_ack_latency_timer(self): if self._ack_latency_timer_cr is not None: self._ack_latency_timer_cr.kill() self._ack_latency_timer_cr = None async def _run_ack_latency_timer(self): d = int(self.time_scale * self.max_latency_timer) await Timer(max(d, 1), 'step') if not self.nak_scheduled: self.send_ack.set() def start_fc_update_timer(self): if self._fc_update_timer_cr is not None: if not self._fc_update_timer_cr._finished: # already running return self._fc_update_timer_cr = cocotb.fork(self._run_fc_update_timer()) def stop_fc_update_timer(self): if self._fc_update_timer_cr is not None: self._fc_update_timer_cr.kill() self._fc_update_timer_cr = None async def _run_fc_update_timer(self): d = int(self.time_scale * self.max_latency_timer) await Timer(max(d, 1), 'step') self.send_fc.set() async def _run_fc_update_idle_timer(self): while True: await Timer(self.fc_idle_timer_steps, 'step') self.send_fc.set()
class TinyAluBfm(metaclass=utility_classes.Singleton): def __init__(self): self.dut = cocotb.top self.driver_queue = Queue(maxsize=1) self.cmd_mon_queue = Queue(maxsize=0) self.result_mon_queue = Queue(maxsize=0) async def send_op(self, aa, bb, op): command_tuple = (aa, bb, op) await self.driver_queue.put(command_tuple) async def get_cmd(self): cmd = await self.cmd_mon_queue.get() return cmd async def get_result(self): result = await self.result_mon_queue.get() return result async def reset(self): await FallingEdge(self.dut.clk) self.dut.reset_n.value = 0 self.dut.A.value = 0 self.dut.B.value = 0 self.dut.op.value = 0 await FallingEdge(self.dut.clk) self.dut.reset_n.value = 1 await FallingEdge(self.dut.clk) async def driver_bfm(self): self.dut.start.value = 0 self.dut.A.value = 0 self.dut.B.value = 0 self.dut.op.value = 0 while True: await FallingEdge(self.dut.clk) start = get_int(self.dut.start) done = get_int(self.dut.done) if start == 0 and done == 0: try: (aa, bb, op) = self.driver_queue.get_nowait() self.dut.A.value = aa self.dut.B.value = bb self.dut.op.value = op self.dut.start.value = 1 except QueueEmpty: pass elif start == 1: if done == 1: self.dut.start.value = 0 async def cmd_mon_bfm(self): prev_start = 0 while True: await FallingEdge(self.dut.clk) start = get_int(self.dut.start) if start == 1 and prev_start == 0: cmd_tuple = (get_int(self.dut.A), get_int(self.dut.B), get_int(self.dut.op)) self.cmd_mon_queue.put_nowait(cmd_tuple) prev_start = start async def result_mon_bfm(self): prev_done = 0 while True: await FallingEdge(self.dut.clk) done = get_int(self.dut.done) if prev_done == 0 and done == 1: result = get_int(self.dut.result) self.result_mon_queue.put_nowait(result) prev_done = done def start_bfm(self): cocotb.start_soon(self.driver_bfm()) cocotb.start_soon(self.cmd_mon_bfm()) cocotb.start_soon(self.result_mon_bfm())
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 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 AxiMasterWrite(Reset): def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256): self.log = logging.getLogger(f"cocotb.{bus.aw._entity._name}.{bus.aw._name}") self.log.info("AXI master (write)") 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") self.aw_channel = AxiAWSource(bus.aw, clock, reset, reset_active_level) self.aw_channel.queue_occupancy_limit = 2 self.w_channel = AxiWSource(bus.w, clock, reset, reset_active_level) self.w_channel.queue_occupancy_limit = 2 self.b_channel = AxiBSink(bus.b, clock, reset, reset_active_level) self.b_channel.queue_occupancy_limit = 2 self.write_command_queue = Queue() self.current_write_command = None self.id_count = 2**len(self.aw_channel.bus.awid) self.cur_id = 0 self.active_id = Counter() self.int_write_resp_command_queue = [Queue() for k in range(self.id_count)] self.current_write_resp_command = [None for k in range(self.id_count)] self.int_write_resp_queue_list = [Queue() for k in range(self.id_count)] self.in_flight_operations = 0 self._idle = Event() self._idle.set() self.width = len(self.w_channel.bus.wdata) self.byte_size = 8 self.byte_width = self.width // self.byte_size self.strb_mask = 2**self.byte_width-1 self.max_burst_len = max(min(max_burst_len, 256), 1) self.max_burst_size = (self.byte_width-1).bit_length() self.log.info("AXI master configuration:") self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr)) self.log.info(" ID width: %d bits", len(self.aw_channel.bus.awid)) self.log.info(" Byte size: %d bits", self.byte_size) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width) self.log.info(" Max burst size: %d (%d bytes)", self.max_burst_size, 2**self.max_burst_size) self.log.info(" Max burst length: %d cycles (%d bytes)", self.max_burst_len, self.max_burst_len*self.byte_width) assert self.byte_width == len(self.w_channel.bus.wstrb) assert self.byte_width * self.byte_size == self.width assert len(self.b_channel.bus.bid) == len(self.aw_channel.bus.awid) self._process_write_cr = None self._process_write_resp_cr = None self._process_write_resp_id_cr = None self._init_reset(reset, reset_active_level) def init_write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0, event=None): if event is None: event = Event() if not isinstance(event, Event): raise ValueError("Expected event object") if awid is None or awid < 0: awid = None elif awid > self.id_count: raise ValueError("Requested ID exceeds maximum ID allowed for ID signal width") burst = AxiBurstType(burst) if size is None or size < 0: size = self.max_burst_size elif size > self.max_burst_size: raise ValueError("Requested burst size exceeds maximum burst size allowed for bus width") lock = AxiLockType(lock) prot = AxiProt(prot) if wuser is None: wuser = 0 elif isinstance(wuser, int): pass else: wuser = list(wuser) self.in_flight_operations += 1 self._idle.clear() cmd = AxiWriteCmd(address, bytearray(data), awid, burst, size, lock, cache, prot, qos, region, user, wuser, event) self.write_command_queue.put_nowait(cmd) return event def idle(self): return not self.in_flight_operations async def wait(self): while not self.idle(): await self._idle.wait() async def write(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): event = self.init_write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser) await event.wait() return event.data async def write_words(self, address, data, byteorder='little', ws=2, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): words = data data = bytearray() for w in words: data.extend(w.to_bytes(ws, byteorder)) await self.write(address, data, awid, burst, size, lock, cache, prot, qos, region, user, wuser) async def write_dwords(self, address, data, byteorder='little', awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): await self.write_words(address, data, byteorder, 4, awid, burst, size, lock, cache, prot, qos, region, user, wuser) async def write_qwords(self, address, data, byteorder='little', awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): await self.write_words(address, data, byteorder, 8, awid, burst, size, lock, cache, prot, qos, region, user, wuser) async def write_byte(self, address, data, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): await self.write(address, [data], awid, burst, size, lock, cache, prot, qos, region, user, wuser) async def write_word(self, address, data, byteorder='little', ws=2, awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): await self.write_words(address, [data], byteorder, ws, awid, burst, size, lock, cache, prot, qos, region, user, wuser) async def write_dword(self, address, data, byteorder='little', awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): await self.write_dwords(address, [data], byteorder, awid, burst, size, lock, cache, prot, qos, region, user, wuser) async def write_qword(self, address, data, byteorder='little', awid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, wuser=0): await self.write_qwords(address, [data], byteorder, awid, burst, size, lock, cache, prot, qos, region, user, wuser) def _handle_reset(self, state): if state: self.log.info("Reset asserted") if self._process_write_cr is not None: self._process_write_cr.kill() self._process_write_cr = None if self._process_write_resp_cr is not None: self._process_write_resp_cr.kill() self._process_write_resp_cr = None if self._process_write_resp_id_cr is not None: for cr in self._process_write_resp_id_cr: cr.kill() self._process_write_resp_id_cr = None self.aw_channel.clear() self.w_channel.clear() self.b_channel.clear() def flush_cmd(cmd): self.log.warning("Flushed write operation during reset: %s", cmd) if cmd.event: cmd.event.set(None) while not self.write_command_queue.empty(): cmd = self.write_command_queue.get_nowait() flush_cmd(cmd) if self.current_write_command: cmd = self.current_write_command self.current_write_command = None flush_cmd(cmd) for q in self.int_write_resp_command_queue: while not q.empty(): cmd = q.get_nowait() flush_cmd(cmd) for k in range(len(self.current_write_resp_command)): if self.current_write_resp_command[k]: cmd = self.current_write_resp_command[k] self.current_write_resp_command[k] = None flush_cmd(cmd) for q in self.int_write_resp_queue_list: while not q.empty(): q.get_nowait() self.cur_id = 0 self.active_id = Counter() self.in_flight_operations = 0 self._idle.set() else: self.log.info("Reset de-asserted") if self._process_write_cr is None: self._process_write_cr = cocotb.fork(self._process_write()) if self._process_write_resp_cr is None: self._process_write_resp_cr = cocotb.fork(self._process_write_resp()) if self._process_write_resp_id_cr is None: self._process_write_resp_id_cr = [cocotb.fork(self._process_write_resp_id(i)) for i in range(self.id_count)] async def _process_write(self): while True: cmd = await self.write_command_queue.get() self.current_write_command = cmd num_bytes = 2**cmd.size aligned_addr = (cmd.address // num_bytes) * num_bytes word_addr = (cmd.address // self.byte_width) * self.byte_width start_offset = cmd.address % self.byte_width end_offset = ((cmd.address + len(cmd.data) - 1) % self.byte_width) + 1 cycles = (len(cmd.data) + (cmd.address % num_bytes) + num_bytes-1) // num_bytes cur_addr = aligned_addr offset = 0 cycle_offset = aligned_addr-word_addr n = 0 transfer_count = 0 burst_list = [] burst_length = 0 if cmd.awid is not None: awid = cmd.awid else: awid = self.cur_id self.cur_id = (self.cur_id+1) % self.id_count wuser = cmd.wuser self.log.info("Write start addr: 0x%08x awid: 0x%x prot: %s data: %s", cmd.address, awid, cmd.prot, ' '.join((f'{c:02x}' for c in cmd.data))) for k in range(cycles): start = cycle_offset stop = cycle_offset+num_bytes if k == 0: start = start_offset if k == cycles-1: stop = end_offset strb = (self.strb_mask << start) & self.strb_mask & (self.strb_mask >> (self.byte_width - stop)) val = 0 for j in range(start, stop): val |= cmd.data[offset] << j*8 offset += 1 if n >= burst_length: transfer_count += 1 n = 0 # split on burst length burst_length = min(cycles-k, min(max(self.max_burst_len, 1), 256)) # split on 4k address boundary burst_length = (min(burst_length*num_bytes, 0x1000-(cur_addr & 0xfff))+num_bytes-1)//num_bytes burst_list.append(burst_length) aw = self.aw_channel._transaction_obj() aw.awid = awid aw.awaddr = cur_addr aw.awlen = burst_length-1 aw.awsize = cmd.size aw.awburst = cmd.burst aw.awlock = cmd.lock aw.awcache = cmd.cache aw.awprot = cmd.prot aw.awqos = cmd.qos aw.awregion = cmd.region aw.awuser = cmd.user self.active_id[awid] += 1 await self.aw_channel.send(aw) self.log.info("Write burst start awid: 0x%x awaddr: 0x%08x awlen: %d awsize: %d awprot: %s", awid, cur_addr, burst_length-1, cmd.size, cmd.prot) n += 1 w = self.w_channel._transaction_obj() w.wdata = val w.wstrb = strb w.wlast = n >= burst_length if isinstance(wuser, int): w.wuser = wuser else: if wuser: w.wuser = wuser.pop(0) else: w.wuser = 0 await self.w_channel.send(w) cur_addr += num_bytes cycle_offset = (cycle_offset + num_bytes) % self.byte_width resp_cmd = AxiWriteRespCmd(cmd.address, len(cmd.data), cmd.size, cycles, cmd.prot, burst_list, cmd.event) await self.int_write_resp_command_queue[awid].put(resp_cmd) self.current_write_command = None async def _process_write_resp(self): while True: b = await self.b_channel.recv() bid = int(b.bid) if self.active_id[bid] <= 0: raise Exception(f"Unexpected burst ID {bid}") await self.int_write_resp_queue_list[bid].put(b) async def _process_write_resp_id(self, bid): while True: cmd = await self.int_write_resp_command_queue[bid].get() self.current_write_resp_command[bid] = cmd resp = AxiResp.OKAY user = [] for burst_length in cmd.burst_list: b = await self.int_write_resp_queue_list[bid].get() burst_id = int(b.bid) burst_resp = AxiResp(b.bresp) burst_user = int(b.buser) if burst_resp != AxiResp.OKAY: resp = burst_resp if burst_user is not None: user.append(burst_user) if self.active_id[bid] <= 0: raise Exception(f"Unexpected burst ID {bid}") self.active_id[bid] -= 1 self.log.info("Write burst complete bid: 0x%x bresp: %s", burst_id, burst_resp) self.log.info("Write complete addr: 0x%08x prot: %s resp: %s length: %d", cmd.address, cmd.prot, resp, cmd.length) write_resp = AxiWriteResp(cmd.address, cmd.length, resp, user) cmd.event.set(write_resp) self.current_write_resp_command[bid] = None self.in_flight_operations -= 1 if self.in_flight_operations == 0: self._idle.set()
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)
class AxiLiteMasterWrite(Reset): def __init__(self, bus, clock, reset=None, reset_active_level=True): self.bus = bus self.clock = clock self.reset = reset self.log = logging.getLogger( f"cocotb.{bus.aw._entity._name}.{bus.aw._name}") self.log.info("AXI lite master (write)") 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") self.aw_channel = AxiLiteAWSource(bus.aw, clock, reset, reset_active_level) self.aw_channel.queue_occupancy_limit = 2 self.w_channel = AxiLiteWSource(bus.w, clock, reset, reset_active_level) self.w_channel.queue_occupancy_limit = 2 self.b_channel = AxiLiteBSink(bus.b, clock, reset, reset_active_level) self.b_channel.queue_occupancy_limit = 2 self.write_command_queue = Queue() self.current_write_command = None self.int_write_resp_command_queue = Queue() self.current_write_resp_command = None self.in_flight_operations = 0 self._idle = Event() self._idle.set() self.width = len(self.w_channel.bus.wdata) self.byte_size = 8 self.byte_lanes = self.width // self.byte_size self.strb_mask = 2**self.byte_lanes - 1 self.awprot_present = hasattr(self.bus.aw, "awprot") self.log.info("AXI lite master configuration:") self.log.info(" Address width: %d bits", len(self.aw_channel.bus.awaddr)) 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("AXI lite master signals:") for bus in (self.bus.aw, self.bus.w, self.bus.b): for sig in sorted( list(set().union(bus._signals, bus._optional_signals))): if hasattr(bus, sig): self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig))) else: self.log.info(" %s: not present", sig) assert self.byte_lanes == len(self.w_channel.bus.wstrb) assert self.byte_lanes * self.byte_size == self.width self._process_write_cr = None self._process_write_resp_cr = None self._init_reset(reset, reset_active_level) def init_write(self, address, data, prot=AxiProt.NONSECURE, event=None): if event is None: event = Event() if not isinstance(event, Event): raise ValueError("Expected event object") if not self.awprot_present and prot != AxiProt.NONSECURE: raise ValueError( "awprot sideband signal value specified, but signal is not connected" ) self.in_flight_operations += 1 self._idle.clear() self.write_command_queue.put_nowait( AxiLiteWriteCmd(address, bytearray(data), prot, event)) return event def idle(self): return not self.in_flight_operations async def wait(self): while not self.idle(): await self._idle.wait() async def write(self, address, data, prot=AxiProt.NONSECURE): event = self.init_write(address, data, prot) await event.wait() return event.data async def write_words(self, address, data, byteorder='little', ws=2, prot=AxiProt.NONSECURE): words = data data = bytearray() for w in words: data.extend(w.to_bytes(ws, byteorder)) await self.write(address, data, prot) async def write_dwords(self, address, data, byteorder='little', prot=AxiProt.NONSECURE): await self.write_words(address, data, byteorder, 4, prot) async def write_qwords(self, address, data, byteorder='little', prot=AxiProt.NONSECURE): await self.write_words(address, data, byteorder, 8, prot) async def write_byte(self, address, data, prot=AxiProt.NONSECURE): await self.write(address, [data], prot) async def write_word(self, address, data, byteorder='little', ws=2, prot=AxiProt.NONSECURE): await self.write_words(address, [data], byteorder, ws, prot) async def write_dword(self, address, data, byteorder='little', prot=AxiProt.NONSECURE): await self.write_dwords(address, [data], byteorder, prot) async def write_qword(self, address, data, byteorder='little', prot=AxiProt.NONSECURE): await self.write_qwords(address, [data], byteorder, prot) def _handle_reset(self, state): if state: self.log.info("Reset asserted") if self._process_write_cr is not None: self._process_write_cr.kill() self._process_write_cr = None if self._process_write_resp_cr is not None: self._process_write_resp_cr.kill() self._process_write_resp_cr = None self.aw_channel.clear() self.w_channel.clear() self.b_channel.clear() def flush_cmd(cmd): self.log.warning("Flushed write operation during reset: %s", cmd) if cmd.event: cmd.event.set(None) while not self.write_command_queue.empty(): cmd = self.write_command_queue.get_nowait() flush_cmd(cmd) if self.current_write_command: cmd = self.current_write_command self.current_write_command = None flush_cmd(cmd) while not self.int_write_resp_command_queue.empty(): cmd = self.int_write_resp_command_queue.get_nowait() flush_cmd(cmd) if self.current_write_resp_command: cmd = self.current_write_resp_command self.current_write_resp_command = None flush_cmd(cmd) self.in_flight_operations = 0 self._idle.set() else: self.log.info("Reset de-asserted") if self._process_write_cr is None: self._process_write_cr = cocotb.fork(self._process_write()) if self._process_write_resp_cr is None: self._process_write_resp_cr = cocotb.fork( self._process_write_resp()) async def _process_write(self): while True: cmd = await self.write_command_queue.get() self.current_write_command = cmd word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes start_offset = cmd.address % self.byte_lanes end_offset = ( (cmd.address + len(cmd.data) - 1) % self.byte_lanes) + 1 strb_start = (self.strb_mask << start_offset) & self.strb_mask strb_end = self.strb_mask >> (self.byte_lanes - end_offset) cycles = (len(cmd.data) + (cmd.address % self.byte_lanes) + self.byte_lanes - 1) // self.byte_lanes resp_cmd = AxiLiteWriteRespCmd(cmd.address, len(cmd.data), cycles, cmd.prot, cmd.event) await self.int_write_resp_command_queue.put(resp_cmd) offset = 0 if self.log.isEnabledFor(logging.INFO): self.log.info("Write start addr: 0x%08x prot: %s data: %s", cmd.address, cmd.prot, ' '.join( (f'{c:02x}' for c in cmd.data))) for k in range(cycles): start = 0 stop = self.byte_lanes strb = self.strb_mask if k == 0: start = start_offset strb &= strb_start if k == cycles - 1: stop = end_offset strb &= strb_end val = 0 for j in range(start, stop): val |= cmd.data[offset] << j * 8 offset += 1 aw = self.aw_channel._transaction_obj() aw.awaddr = word_addr + k * self.byte_lanes aw.awprot = cmd.prot w = self.w_channel._transaction_obj() w.wdata = val w.wstrb = strb await self.aw_channel.send(aw) await self.w_channel.send(w) self.current_write_command = None async def _process_write_resp(self): while True: cmd = await self.int_write_resp_command_queue.get() self.current_write_resp_command = cmd resp = AxiResp.OKAY for k in range(cmd.cycles): b = await self.b_channel.recv() cycle_resp = AxiResp(getattr(b, 'bresp', AxiResp.OKAY)) if cycle_resp != AxiResp.OKAY: resp = cycle_resp self.log.info( "Write complete addr: 0x%08x prot: %s resp: %s length: %d", cmd.address, cmd.prot, resp, cmd.length) write_resp = AxiLiteWriteResp(cmd.address, cmd.length, resp) cmd.event.set(write_resp) self.current_write_resp_command = None self.in_flight_operations -= 1 if self.in_flight_operations == 0: self._idle.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 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 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
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 AxiMasterRead(Reset): def __init__(self, bus, clock, reset=None, reset_active_level=True, max_burst_len=256): self.log = logging.getLogger(f"cocotb.{bus.ar._entity._name}.{bus.ar._name}") self.log.info("AXI master (read)") 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") self.ar_channel = AxiARSource(bus.ar, clock, reset, reset_active_level) self.ar_channel.queue_occupancy_limit = 2 self.r_channel = AxiRSink(bus.r, clock, reset, reset_active_level) self.r_channel.queue_occupancy_limit = 2 self.read_command_queue = Queue() self.current_read_command = None self.id_count = 2**len(self.ar_channel.bus.arid) self.cur_id = 0 self.active_id = Counter() self.int_read_resp_command_queue = [Queue() for k in range(self.id_count)] self.current_read_resp_command = [None for k in range(self.id_count)] self.int_read_resp_queue_list = [Queue() for k in range(self.id_count)] self.in_flight_operations = 0 self._idle = Event() self._idle.set() self.width = len(self.r_channel.bus.rdata) self.byte_size = 8 self.byte_width = self.width // self.byte_size self.max_burst_len = max(min(max_burst_len, 256), 1) self.max_burst_size = (self.byte_width-1).bit_length() self.log.info("AXI master configuration:") self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr)) self.log.info(" ID width: %d bits", len(self.ar_channel.bus.arid)) self.log.info(" Byte size: %d bits", self.byte_size) self.log.info(" Data width: %d bits (%d bytes)", self.width, self.byte_width) self.log.info(" Max burst size: %d (%d bytes)", self.max_burst_size, 2**self.max_burst_size) self.log.info(" Max burst length: %d cycles (%d bytes)", self.max_burst_len, self.max_burst_len*self.byte_width) assert self.byte_width * self.byte_size == self.width assert len(self.r_channel.bus.rid) == len(self.ar_channel.bus.arid) self._process_read_cr = None self._process_read_resp_cr = None self._process_read_resp_id_cr = None self._init_reset(reset, reset_active_level) def init_read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0, event=None): if event is None: event = Event() if not isinstance(event, Event): raise ValueError("Expected event object") if length < 0: raise ValueError("Read length must be positive") if arid is None or arid < 0: arid = None elif arid > self.id_count: raise ValueError("Requested ID exceeds maximum ID allowed for ID signal width") burst = AxiBurstType(burst) if size is None or size < 0: size = self.max_burst_size elif size > self.max_burst_size: raise ValueError("Requested burst size exceeds maximum burst size allowed for bus width") lock = AxiLockType(lock) prot = AxiProt(prot) self.in_flight_operations += 1 self._idle.clear() cmd = AxiReadCmd(address, length, arid, burst, size, lock, cache, prot, qos, region, user, event) self.read_command_queue.put_nowait(cmd) return event def idle(self): return not self.in_flight_operations async def wait(self): while not self.idle(): await self._idle.wait() async def read(self, address, length, arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): event = self.init_read(address, length, arid, burst, size, lock, cache, prot, qos, region, user) await event.wait() return event.data async def read_words(self, address, count, byteorder='little', ws=2, arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): data = await self.read(address, count*ws, arid, burst, size, lock, cache, prot, qos, region, user) words = [] for k in range(count): words.append(int.from_bytes(data.data[ws*k:ws*(k+1)], byteorder)) return words async def read_dwords(self, address, count, byteorder='little', arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): return await self.read_words(address, count, byteorder, 4, arid, burst, size, lock, cache, prot, qos, region, user) async def read_qwords(self, address, count, byteorder='little', arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): return await self.read_words(address, count, byteorder, 8, arid, burst, size, lock, cache, prot, qos, region, user) async def read_byte(self, address, arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): return (await self.read(address, 1, arid, burst, size, lock, cache, prot, qos, region, user)).data[0] async def read_word(self, address, byteorder='little', ws=2, arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): return (await self.read_words(address, 1, byteorder, ws, arid, burst, size, lock, cache, prot, qos, region, user))[0] async def read_dword(self, address, byteorder='little', arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): return (await self.read_dwords(address, 1, byteorder, arid, burst, size, lock, cache, prot, qos, region, user))[0] async def read_qword(self, address, byteorder='little', arid=None, burst=AxiBurstType.INCR, size=None, lock=AxiLockType.NORMAL, cache=0b0011, prot=AxiProt.NONSECURE, qos=0, region=0, user=0): return (await self.read_qwords(address, 1, byteorder, arid, burst, size, lock, cache, prot, qos, region, user))[0] def _handle_reset(self, state): if state: self.log.info("Reset asserted") if self._process_read_cr is not None: self._process_read_cr.kill() self._process_read_cr = None if self._process_read_resp_cr is not None: self._process_read_resp_cr.kill() self._process_read_resp_cr = None if self._process_read_resp_id_cr is not None: for cr in self._process_read_resp_id_cr: cr.kill() self._process_read_resp_id_cr = None self.ar_channel.clear() self.r_channel.clear() def flush_cmd(cmd): self.log.warning("Flushed read operation during reset: %s", cmd) if cmd.event: cmd.event.set(None) while not self.read_command_queue.empty(): cmd = self.read_command_queue.get_nowait() flush_cmd(cmd) if self.current_read_command: cmd = self.current_read_command self.current_read_command = None flush_cmd(cmd) for q in self.int_read_resp_command_queue: while not q.empty(): cmd = q.get_nowait() flush_cmd(cmd) for k in range(len(self.current_read_resp_command)): if self.current_read_resp_command[k]: cmd = self.current_read_resp_command[k] self.current_read_resp_command[k] = None flush_cmd(cmd) for q in self.int_read_resp_queue_list: while not q.empty(): q.get_nowait() self.cur_id = 0 self.active_id = Counter() self.in_flight_operations = 0 self._idle.set() else: self.log.info("Reset de-asserted") if self._process_read_cr is None: self._process_read_cr = cocotb.fork(self._process_read()) if self._process_read_resp_cr is None: self._process_read_resp_cr = cocotb.fork(self._process_read_resp()) if self._process_read_resp_id_cr is None: self._process_read_resp_id_cr = [cocotb.fork(self._process_read_resp_id(i)) for i in range(self.id_count)] async def _process_read(self): while True: cmd = await self.read_command_queue.get() self.current_read_command = cmd num_bytes = 2**cmd.size aligned_addr = (cmd.address // num_bytes) * num_bytes cycles = (cmd.length + num_bytes-1 + (cmd.address % num_bytes)) // num_bytes burst_list = [] cur_addr = aligned_addr n = 0 burst_length = 0 if cmd.arid is not None: arid = cmd.arid else: arid = self.cur_id self.cur_id = (self.cur_id+1) % self.id_count self.log.info("Read start addr: 0x%08x arid: 0x%x prot: %s", cmd.address, arid, cmd.prot) for k in range(cycles): n += 1 if n >= burst_length: n = 0 # split on burst length burst_length = min(cycles-k, min(max(self.max_burst_len, 1), 256)) # split on 4k address boundary burst_length = (min(burst_length*num_bytes, 0x1000-(cur_addr & 0xfff))+num_bytes-1)//num_bytes burst_list.append(burst_length) ar = self.r_channel._transaction_obj() ar.arid = arid ar.araddr = cur_addr ar.arlen = burst_length-1 ar.arsize = cmd.size ar.arburst = cmd.burst ar.arlock = cmd.lock ar.arcache = cmd.cache ar.arprot = cmd.prot ar.arqos = cmd.qos ar.arregion = cmd.region ar.aruser = cmd.user self.active_id[arid] += 1 await self.ar_channel.send(ar) self.log.info("Read burst start arid: 0x%x araddr: 0x%08x arlen: %d arsize: %d arprot: %s", arid, cur_addr, burst_length-1, cmd.size, cmd.prot) cur_addr += num_bytes resp_cmd = AxiReadRespCmd(cmd.address, cmd.length, cmd.size, cycles, cmd.prot, burst_list, cmd.event) await self.int_read_resp_command_queue[arid].put(resp_cmd) self.current_read_command = None async def _process_read_resp(self): while True: r = await self.r_channel.recv() rid = int(r.rid) if self.active_id[rid] <= 0: raise Exception(f"Unexpected burst ID {rid}") await self.int_read_resp_queue_list[rid].put(r) async def _process_read_resp_id(self, rid): while True: cmd = await self.int_read_resp_command_queue[rid].get() self.current_read_resp_command[rid] = cmd num_bytes = 2**cmd.size aligned_addr = (cmd.address // num_bytes) * num_bytes word_addr = (cmd.address // self.byte_width) * self.byte_width start_offset = cmd.address % self.byte_width cycle_offset = aligned_addr - word_addr data = bytearray() resp = AxiResp.OKAY user = [] first = True for burst_length in cmd.burst_list: for k in range(burst_length): r = await self.int_read_resp_queue_list[rid].get() cycle_id = int(r.rid) cycle_data = int(r.rdata) cycle_resp = AxiResp(r.rresp) cycle_last = int(r.rlast) cycle_user = int(r.ruser) if cycle_resp != AxiResp.OKAY: resp = cycle_resp if cycle_user is not None: user.append(cycle_user) start = cycle_offset stop = cycle_offset+num_bytes if first: start = start_offset assert cycle_last == (k == burst_length - 1) for j in range(start, stop): data.append((cycle_data >> j*8) & 0xff) cycle_offset = (cycle_offset + num_bytes) % self.byte_width first = False if self.active_id[rid] <= 0: raise Exception(f"Unexpected burst ID {rid}") self.active_id[rid] -= 1 self.log.info("Read burst complete rid: 0x%x rresp: %s", cycle_id, resp) data = data[:cmd.length] self.log.info("Read complete addr: 0x%08x prot: %s resp: %s data: %s", cmd.address, cmd.prot, resp, ' '.join((f'{c:02x}' for c in data))) read_resp = AxiReadResp(cmd.address, data, resp, user) cmd.event.set(read_resp) self.current_read_resp_command[rid] = None self.in_flight_operations -= 1 if self.in_flight_operations == 0: self._idle.set()
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 AxiLiteMasterRead(Reset): def __init__(self, bus, clock, reset=None, reset_active_level=True): self.bus = bus self.clock = clock self.reset = reset self.log = logging.getLogger( f"cocotb.{bus.ar._entity._name}.{bus.ar._name}") self.log.info("AXI lite master (read)") 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") self.ar_channel = AxiLiteARSource(bus.ar, clock, reset, reset_active_level) self.ar_channel.queue_occupancy_limit = 2 self.r_channel = AxiLiteRSink(bus.r, clock, reset, reset_active_level) self.r_channel.queue_occupancy_limit = 2 self.read_command_queue = Queue() self.current_read_command = None self.int_read_resp_command_queue = Queue() self.current_read_resp_command = None self.in_flight_operations = 0 self._idle = Event() self._idle.set() self.width = len(self.r_channel.bus.rdata) self.byte_size = 8 self.byte_lanes = self.width // self.byte_size self.arprot_present = hasattr(self.bus.ar, "arprot") self.log.info("AXI lite master configuration:") self.log.info(" Address width: %d bits", len(self.ar_channel.bus.araddr)) 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("AXI lite master signals:") for bus in (self.bus.ar, self.bus.r): for sig in sorted( list(set().union(bus._signals, bus._optional_signals))): if hasattr(bus, sig): self.log.info(" %s width: %d bits", sig, len(getattr(bus, sig))) else: self.log.info(" %s: not present", sig) assert self.byte_lanes * self.byte_size == self.width self._process_read_cr = None self._process_read_resp_cr = None self._init_reset(reset, reset_active_level) def init_read(self, address, length, prot=AxiProt.NONSECURE, event=None): if event is None: event = Event() if not isinstance(event, Event): raise ValueError("Expected event object") if not self.arprot_present and prot != AxiProt.NONSECURE: raise ValueError( "arprot sideband signal value specified, but signal is not connected" ) self.in_flight_operations += 1 self._idle.clear() self.read_command_queue.put_nowait( AxiLiteReadCmd(address, length, prot, event)) return event def idle(self): return not self.in_flight_operations async def wait(self): while not self.idle(): await self._idle.wait() async def read(self, address, length, prot=AxiProt.NONSECURE): event = self.init_read(address, length, prot) await event.wait() return event.data async def read_words(self, address, count, byteorder='little', ws=2, prot=AxiProt.NONSECURE): data = await self.read(address, count * ws, prot) words = [] for k in range(count): words.append( int.from_bytes(data.data[ws * k:ws * (k + 1)], byteorder)) return words async def read_dwords(self, address, count, byteorder='little', prot=AxiProt.NONSECURE): return await self.read_words(address, count, byteorder, 4, prot) async def read_qwords(self, address, count, byteorder='little', prot=AxiProt.NONSECURE): return await self.read_words(address, count, byteorder, 8, prot) async def read_byte(self, address, prot=AxiProt.NONSECURE): return (await self.read(address, 1, prot)).data[0] async def read_word(self, address, byteorder='little', ws=2, prot=AxiProt.NONSECURE): return (await self.read_words(address, 1, byteorder, ws, prot))[0] async def read_dword(self, address, byteorder='little', prot=AxiProt.NONSECURE): return (await self.read_dwords(address, 1, byteorder, prot))[0] async def read_qword(self, address, byteorder='little', prot=AxiProt.NONSECURE): return (await self.read_qwords(address, 1, byteorder, prot))[0] def _handle_reset(self, state): if state: self.log.info("Reset asserted") if self._process_read_cr is not None: self._process_read_cr.kill() self._process_read_cr = None if self._process_read_resp_cr is not None: self._process_read_resp_cr.kill() self._process_read_resp_cr = None self.ar_channel.clear() self.r_channel.clear() def flush_cmd(cmd): self.log.warning("Flushed read operation during reset: %s", cmd) if cmd.event: cmd.event.set(None) while not self.read_command_queue.empty(): cmd = self.read_command_queue.get_nowait() flush_cmd(cmd) if self.current_read_command: cmd = self.current_read_command self.current_read_command = None flush_cmd(cmd) while not self.int_read_resp_command_queue.empty(): cmd = self.int_read_resp_command_queue.get_nowait() flush_cmd(cmd) if self.current_read_resp_command: cmd = self.current_read_resp_command self.current_read_resp_command = None flush_cmd(cmd) self.in_flight_operations = 0 self._idle.set() else: self.log.info("Reset de-asserted") if self._process_read_cr is None: self._process_read_cr = cocotb.fork(self._process_read()) if self._process_read_resp_cr is None: self._process_read_resp_cr = cocotb.fork( self._process_read_resp()) async def _process_read(self): while True: cmd = await self.read_command_queue.get() self.current_read_command = cmd word_addr = (cmd.address // self.byte_lanes) * self.byte_lanes cycles = (cmd.length + self.byte_lanes - 1 + (cmd.address % self.byte_lanes)) // self.byte_lanes resp_cmd = AxiLiteReadRespCmd(cmd.address, cmd.length, cycles, cmd.prot, cmd.event) await self.int_read_resp_command_queue.put(resp_cmd) self.log.info("Read start addr: 0x%08x prot: %s length: %d", cmd.address, cmd.prot, cmd.length) for k in range(cycles): ar = self.ar_channel._transaction_obj() ar.araddr = word_addr + k * self.byte_lanes ar.arprot = cmd.prot await self.ar_channel.send(ar) self.current_read_command = None async def _process_read_resp(self): while True: cmd = await self.int_read_resp_command_queue.get() self.current_read_resp_command = cmd start_offset = cmd.address % self.byte_lanes end_offset = ((cmd.address + cmd.length - 1) % self.byte_lanes) + 1 data = bytearray() resp = AxiResp.OKAY for k in range(cmd.cycles): r = await self.r_channel.recv() cycle_data = int(r.rdata) cycle_resp = AxiResp(getattr(r, 'rresp', AxiResp.OKAY)) if cycle_resp != AxiResp.OKAY: resp = cycle_resp start = 0 stop = self.byte_lanes if k == 0: start = start_offset if k == cmd.cycles - 1: stop = end_offset for j in range(start, stop): data.extend(bytearray([(cycle_data >> j * 8) & 0xff])) if self.log.isEnabledFor(logging.INFO): self.log.info( "Read complete addr: 0x%08x prot: %s resp: %s data: %s", cmd.address, cmd.prot, resp, ' '.join( (f'{c:02x}' for c in data))) read_resp = AxiLiteReadResp(cmd.address, data, resp) cmd.event.set(read_resp) self.current_read_resp_command = None self.in_flight_operations -= 1 if self.in_flight_operations == 0: self._idle.set()