From ff89e00596597bd7414c18e0f893294ca87b4bdd Mon Sep 17 00:00:00 2001 From: Alex Forencich Date: Mon, 11 Jul 2022 21:06:50 -0700 Subject: [PATCH] Add PCIe HIP model for Intel Stratix 10/Agilex P-Tile Signed-off-by: Alex Forencich --- README.md | 4 + cocotbext/pcie/intel/ptile/__init__.py | 26 + cocotbext/pcie/intel/ptile/interface.py | 675 ++++++++++++ cocotbext/pcie/intel/ptile/ptile_model.py | 1225 +++++++++++++++++++++ tests/pcie_ptile/Makefile | 89 ++ tests/pcie_ptile/test_pcie_ptile.py | 1018 +++++++++++++++++ tests/pcie_ptile/test_pcie_ptile.v | 208 ++++ 7 files changed, 3245 insertions(+) create mode 100644 cocotbext/pcie/intel/ptile/__init__.py create mode 100644 cocotbext/pcie/intel/ptile/interface.py create mode 100644 cocotbext/pcie/intel/ptile/ptile_model.py create mode 100644 tests/pcie_ptile/Makefile create mode 100644 tests/pcie_ptile/test_pcie_ptile.py create mode 100644 tests/pcie_ptile/test_pcie_ptile.v diff --git a/README.md b/README.md index ef89f45..79d67d0 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,7 @@ Models of the Xilinx UltraScale and UltraScale+ PCIe hard cores are included in #### Intel Stratix 10 H-Tile/L-Tile Models of the Intel Stratix 10 H-Tile/L-Tile PCIe hard cores are included in `cocotbext.pcie.intel.s10`. These modules can be used in combination with the PCIe BFM to test an HDL design that targets Intel Stratix 10 GX, SX, TX, and MX series FPGAs that contain H-Tiles or L-Tiles, up to PCIe gen 3 x16. The models currently only support operation as a device, not as a root port. + +#### Intel P-Tile + +Models of the Intel P-Tile PCIe hard cores are included in `cocotbext.pcie.intel.ptile`. These modules can be used in combination with the PCIe BFM to test an HDL design that targets Intel Stratix 10 DX or Agilex F series FPGAs that contain P-Tiles, up to PCIe gen 4 x16. The models currently only support operation as a device, not as a root port. diff --git a/cocotbext/pcie/intel/ptile/__init__.py b/cocotbext/pcie/intel/ptile/__init__.py new file mode 100644 index 0000000..58e218a --- /dev/null +++ b/cocotbext/pcie/intel/ptile/__init__.py @@ -0,0 +1,26 @@ +""" + +Copyright (c) 2022 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from .ptile_model import PTilePcieDevice, PTilePcieFunction +from .interface import PTileRxBus, PTileTxBus diff --git a/cocotbext/pcie/intel/ptile/interface.py b/cocotbext/pcie/intel/ptile/interface.py new file mode 100644 index 0000000..a2991bd --- /dev/null +++ b/cocotbext/pcie/intel/ptile/interface.py @@ -0,0 +1,675 @@ +""" + +Copyright (c) 2022 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import logging +import struct + +import cocotb +from cocotb.queue import Queue, QueueFull +from cocotb.triggers import RisingEdge, Timer, First, Event +from cocotb_bus.bus import Bus + +from cocotbext.pcie.core.tlp import Tlp + + +class BaseBus(Bus): + + _signals = ["data"] + _optional_signals = [] + + def __init__(self, entity=None, prefix=None, **kwargs): + super().__init__(entity, prefix, self._signals, optional_signals=self._optional_signals, **kwargs) + + @classmethod + def from_entity(cls, entity, **kwargs): + return cls(entity, **kwargs) + + @classmethod + def from_prefix(cls, entity, prefix, **kwargs): + return cls(entity, prefix, **kwargs) + + +class PTileTxBus(BaseBus): + _signals = ["data", "sop", "eop", "valid", "ready", "err", "hdr", "tlp_prfx"] + _optional_signals = ["data_par", "hdr_par", "tlp_prfx_par"] + + +class PTileRxBus(BaseBus): + _signals = ["data", "empty", "sop", "eop", "valid", "ready", "hdr", "tlp_prfx", "bar_range", "tlp_abort"] + _optional_signals = ["vf_active", "func_num", "vf_num", "data_par", "hdr_par", "tlp_prfx_par"] + + +def dword_parity(d): + d ^= d >> 4 + d ^= d >> 2 + d ^= d >> 1 + p = d & 0x1 + if d & 0x100: + p |= 0x2 + if d & 0x10000: + p |= 0x4 + if d & 0x1000000: + p |= 0x8 + return p + + +def parity(d): + d ^= d >> 4 + d ^= d >> 2 + d ^= d >> 1 + b = 0x1 + p = 0 + while d: + if d & 0x1: + p |= b + d >>= 8 + b <<= 1 + return p + + +class PTilePcieFrame: + def __init__(self, frame=None): + self.tlp_prfx = 0 + self.hdr = 0 + self.data = [] + self.tlp_prfx_par = 0 + self.hdr_par = 0 + self.parity = [] + self.func_num = 0 + self.vf_num = None + self.bar_range = 0 + self.tlp_abort = 0 + self.err = 0 + + if isinstance(frame, Tlp): + hdr = frame.pack_header().ljust(16, b'\x00') + self.hdr = int.from_bytes(hdr, 'big') + + data = frame.get_data() + for k in range(0, len(data), 4): + self.data.extend(struct.unpack_from(' 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes: + return True + elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames: + return True + else: + return False + + def idle(self): + return self.empty() and not self.active + + async def wait(self): + await self.idle_event.wait() + + async def _run_source(self): + self.active = False + ready_delay = [] + + clock_edge_event = RisingEdge(self.clock) + + while True: + await clock_edge_event + + # read handshake signals + ready_sample = self.bus.ready.value + valid_sample = self.bus.valid.value + + if self.reset is not None and self.reset.value: + self.active = False + self.bus.valid.value = 0 + continue + + # ready delay + if self.ready_latency > 1: + if len(ready_delay) != (self.ready_latency-1): + ready_delay = [0]*(self.ready_latency-1) + ready_delay.append(ready_sample) + ready_sample = ready_delay.pop(0) + + if (ready_sample and valid_sample) or not valid_sample or self.ready_latency > 0: + if self.drive_obj and not self.pause and (ready_sample or self.ready_latency == 0): + self.bus.drive(self.drive_obj) + self.drive_obj = None + self.drive_sync.set() + self.active = True + else: + self.bus.valid.value = 0 + self.active = bool(self.drive_obj) + if not self.drive_obj: + self.idle_event.set() + + async def _run(self): + while True: + frame = await self._get_frame() + frame_offset = 0 + self.log.info("TX frame: %r", frame) + first = True + + while frame is not None: + transaction = self._transaction_obj() + + for seg in range(self.seg_count): + if frame is None: + if not self.empty(): + frame = self._get_frame_nowait() + frame_offset = 0 + self.log.info("TX frame: %r", frame) + first = True + else: + break + + if first: + first = False + + transaction.valid |= 1 << seg + transaction.sop |= 1 << seg + transaction.hdr |= frame.hdr << seg*128 + transaction.tlp_prfx |= frame.tlp_prfx << seg*32 + transaction.hdr_par |= frame.hdr_par << seg*16 + transaction.tlp_prfx_par |= frame.tlp_prfx_par << seg*4 + + transaction.bar_range |= frame.bar_range << seg*3 + transaction.func_num |= frame.func_num << seg*3 + if frame.vf_num is not None: + transaction.vf_active |= 1 << seg + transaction.vf_num |= frame.vf_num << seg*11 + transaction.err |= frame.err << seg + + empty = 0 + if frame.data: + transaction.valid |= 1 << seg + + for k in range(min(self.seg_byte_lanes, len(frame.data)-frame_offset)): + transaction.data |= frame.data[frame_offset] << 32*(k+seg*self.seg_byte_lanes) + transaction.data_par |= frame.parity[frame_offset] << 4*(k+seg*self.seg_byte_lanes) + empty = self.seg_byte_lanes-1-k + frame_offset += 1 + + if frame_offset >= len(frame.data): + transaction.eop |= 1 << seg + transaction.empty |= empty << seg*self.seg_empty_width + + frame = None + + await self._drive(transaction) + + async def _get_frame(self): + frame = await self.queue.get() + self.dequeue_event.set() + self.queue_occupancy_bytes -= len(frame) + self.queue_occupancy_frames -= 1 + return frame + + def _get_frame_nowait(self): + frame = self.queue.get_nowait() + self.dequeue_event.set() + self.queue_occupancy_bytes -= len(frame) + self.queue_occupancy_frames -= 1 + return frame + + +class PTilePcieSink(PTilePcieBase): + + _signal_widths = {"valid": 1, "ready": 1} + + _valid_signal = "valid" + _ready_signal = "ready" + + _transaction_obj = PTilePcieTransaction + _frame_obj = PTilePcieFrame + + def __init__(self, bus, clock, reset=None, ready_latency=0, *args, **kwargs): + super().__init__(bus, clock, reset, ready_latency, *args, **kwargs) + + self.sample_obj = None + self.sample_sync = Event() + + self.queue_occupancy_limit_bytes = -1 + self.queue_occupancy_limit_frames = -1 + + self.bus.ready.setimmediatevalue(0) + + cocotb.start_soon(self._run_sink()) + cocotb.start_soon(self._run()) + + def _recv(self, frame): + if self.queue.empty(): + self.active_event.clear() + self.queue_occupancy_bytes -= len(frame) + self.queue_occupancy_frames -= 1 + return frame + + async def recv(self): + frame = await self.queue.get() + return self._recv(frame) + + def recv_nowait(self): + frame = self.queue.get_nowait() + return self._recv(frame) + + def full(self): + if self.queue_occupancy_limit_bytes > 0 and self.queue_occupancy_bytes > self.queue_occupancy_limit_bytes: + return True + elif self.queue_occupancy_limit_frames > 0 and self.queue_occupancy_frames > self.queue_occupancy_limit_frames: + return True + else: + return False + + def idle(self): + return not self.active + + async def wait(self, timeout=0, timeout_unit='ns'): + if not self.empty(): + return + if timeout: + await First(self.active_event.wait(), Timer(timeout, timeout_unit)) + else: + await self.active_event.wait() + + async def _run_sink(self): + ready_delay = [] + + clock_edge_event = RisingEdge(self.clock) + + while True: + await clock_edge_event + + # read handshake signals + ready_sample = self.bus.ready.value + valid_sample = self.bus.valid.value + + if self.reset is not None and self.reset.value: + self.bus.ready.value = 0 + continue + + # ready delay + if self.ready_latency > 0: + if len(ready_delay) != self.ready_latency: + ready_delay = [0]*self.ready_latency + ready_delay.append(ready_sample) + ready_sample = ready_delay.pop(0) + + if valid_sample and ready_sample: + self.sample_obj = self._transaction_obj() + self.bus.sample(self.sample_obj) + self.sample_sync.set() + elif self.ready_latency > 0: + assert not valid_sample, "handshake error: valid asserted outside of ready cycle" + + self.bus.ready.value = (not self.full() and not self.pause) + + async def _run(self): + self.active = False + frame = None + dword_count = 0 + + while True: + while not self.sample_obj: + self.sample_sync.clear() + await self.sample_sync.wait() + + self.active = True + sample = self.sample_obj + self.sample_obj = None + + for seg in range(self.seg_count): + if not sample.valid & (1 << seg): + continue + + if sample.sop & (1 << seg): + assert frame is None, "framing error: sop asserted in frame" + frame = PTilePcieFrame() + + frame.tlp_prfx = (sample.tlp_prfx >> (seg*32)) & 0xffffffff + frame.tlp_prfx_par = (sample.tlp_prfx_par >> (seg*4)) & 0xf + frame.hdr = (sample.hdr >> (seg*128)) & (2**128-1) + frame.hdr_par = (sample.hdr_par >> (seg*16)) & 0xffff + if frame.hdr & (1 << 126): + dword_count = (frame.hdr >> 96) & 0x3ff + if dword_count == 0: + dword_count = 1024 + else: + dword_count = 0 + + frame.bar_range = (sample.bar_range >> seg*3) & 0x7 + frame.func_num = (sample.func_num >> seg*3) & 0x7 + if sample.vf_active & (1 << seg): + frame.vf_num = (sample.vf_num >> seg*11) & 0x7ff + frame.err = (sample.err >> seg) & 0x1 + + assert frame is not None, "framing error: data transferred outside of frame" + + if dword_count > 0: + data = (sample.data >> (seg*self.seg_width)) & self.seg_mask + data_par = (sample.data_par >> (seg*self.seg_par_width)) & self.seg_par_mask + for k in range(min(self.seg_byte_lanes, dword_count)): + frame.data.append((data >> 32*k) & 0xffffffff) + frame.parity.append((data_par >> 4*k) & 0xf) + dword_count -= 1 + + if sample.eop & (1 << seg): + assert dword_count == 0, "framing error: incorrect length or early eop" + self.log.info("RX frame: %r", frame) + self._sink_frame(frame) + self.active = False + frame = None + + def _sink_frame(self, frame): + self.queue_occupancy_bytes += len(frame) + self.queue_occupancy_frames += 1 + + self.queue.put_nowait(frame) + self.active_event.set() diff --git a/cocotbext/pcie/intel/ptile/ptile_model.py b/cocotbext/pcie/intel/ptile/ptile_model.py new file mode 100644 index 0000000..44545ba --- /dev/null +++ b/cocotbext/pcie/intel/ptile/ptile_model.py @@ -0,0 +1,1225 @@ +""" + +Copyright (c) 2022 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import cocotb +from cocotb.clock import Clock +from cocotb.queue import Queue +from cocotb.triggers import RisingEdge, FallingEdge, Timer, First + +from cocotbext.pcie.core import Device, Endpoint, __version__ +from cocotbext.pcie.core.caps import MsiCapability, MsixCapability +from cocotbext.pcie.core.caps import AerExtendedCapability, PcieExtendedCapability +from cocotbext.pcie.core.utils import PcieId +from cocotbext.pcie.core.tlp import Tlp, TlpType + +from .interface import PTilePcieFrame, PTilePcieSource, PTilePcieSink + + +valid_configs = [ + # speed, links, width, freq + (3, 4, 128, 250.0e6), + (3, 8, 256, 250.0e6), + (3, 16, 256, 250.0e6), + (3, 16, 512, 250.0e6), + (4, 4, 128, 350.0e6), + (4, 4, 128, 400.0e6), + (4, 4, 128, 450.0e6), + (4, 4, 128, 500.0e6), + (4, 8, 256, 175.0e6), + (4, 8, 256, 200.0e6), + (4, 8, 256, 225.0e6), + (4, 8, 256, 250.0e6), + (4, 8, 256, 350.0e6), + (4, 8, 256, 400.0e6), + (4, 8, 256, 450.0e6), + (4, 8, 256, 500.0e6), + (4, 8, 512, 175.0e6), + (4, 8, 512, 200.0e6), + (4, 8, 512, 225.0e6), + (4, 8, 512, 250.0e6), + (4, 16, 512, 175.0e6), + (4, 16, 512, 200.0e6), + (4, 16, 512, 225.0e6), + (4, 16, 512, 250.0e6), + (4, 16, 512, 350.0e6), + (4, 16, 512, 400.0e6), + (4, 16, 512, 450.0e6), + (4, 16, 512, 500.0e6), +] + + +class PTilePcieFunction(Endpoint): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # PCIe capabilities + self.register_capability(self.pm_cap, offset=0x10) + + self.msi_cap = MsiCapability() + self.msi_cap.msi_64bit_address_capable = 1 + self.msi_cap.msi_per_vector_mask_capable = 0 + self.register_capability(self.msi_cap, offset=0x14) + + self.msix_cap = MsixCapability() + self.register_capability(self.msix_cap, offset=0x1c) + + self.register_capability(self.pcie_cap, offset=0x2c) + + # PCIe extended capabilities + self.aer_ext_cap = AerExtendedCapability() + self.register_capability(self.aer_ext_cap, offset=0x40) + + # VC 0x52 + # ARI 0x5e + + self.pcie_ext_cap = PcieExtendedCapability() + self.register_capability(self.pcie_ext_cap, offset=0x62) + + # PHY16 0x6e + # LM 0x7a + # SRIOV 0x8c + # TPH 0x9c + # ATS 0xbf + # ACS 0xc3 + # PRS 0xc6 + # LTR 0xcb + # PASID 0xcc + # VSEC (RAS D.E.S.) 0xce + # DL 0x11c + # VSEC (Intel) 0x340 + + +def init_signal(sig, width=None, initval=None): + if sig is None: + return None + if width is not None: + assert len(sig) == width + if initval is not None: + sig.setimmediatevalue(initval) + return sig + + +class PTilePcieDevice(Device): + def __init__(self, + # configuration options + pcie_generation=None, + pcie_link_width=None, + pld_clk_frequency=None, + pf_count=1, + max_payload_size=128, + enable_extended_tag=False, + + pf0_msi_enable=False, + pf0_msi_count=1, + pf1_msi_enable=False, + pf1_msi_count=1, + pf2_msi_enable=False, + pf2_msi_count=1, + pf3_msi_enable=False, + pf3_msi_count=1, + pf0_msix_enable=False, + pf0_msix_table_size=0, + pf0_msix_table_bir=0, + pf0_msix_table_offset=0x00000000, + pf0_msix_pba_bir=0, + pf0_msix_pba_offset=0x00000000, + pf1_msix_enable=False, + pf1_msix_table_size=0, + pf1_msix_table_bir=0, + pf1_msix_table_offset=0x00000000, + pf1_msix_pba_bir=0, + pf1_msix_pba_offset=0x00000000, + pf2_msix_enable=False, + pf2_msix_table_size=0, + pf2_msix_table_bir=0, + pf2_msix_table_offset=0x00000000, + pf2_msix_pba_bir=0, + pf2_msix_pba_offset=0x00000000, + pf3_msix_enable=False, + pf3_msix_table_size=0, + pf3_msix_table_bir=0, + pf3_msix_table_offset=0x00000000, + pf3_msix_pba_bir=0, + pf3_msix_pba_offset=0x00000000, + + # signals + # Clock and reset + reset_status=None, + reset_status_n=None, + coreclkout_hip=None, + refclk0=None, + refclk1=None, + pin_perst_n=None, + + # RX interface + rx_bus=None, + rx_par_err=None, + + # TX interface + tx_bus=None, + tx_par_err=None, + + # RX flow control + rx_buffer_limit=None, + rx_buffer_limit_tdm_idx=None, + + # TX flow control + tx_cdts_limit=None, + tx_cdts_limit_tdm_idx=None, + + # Power management and hard IP status interface + link_up=None, + dl_up=None, + surprise_down_err=None, + ltssm_state=None, + pm_state=None, + pm_dstate=None, + apps_pm_xmt_pme=None, + app_req_retry_en=None, + + # Interrupt interface + app_int=None, + msi_pnd_func=None, + msi_pnd_byte=None, + msi_pnd_addr=None, + + # Error interface + serr_out=None, + hip_enter_err_mode=None, + app_err_valid=None, + app_err_hdr=None, + app_err_info=None, + app_err_func_num=None, + + # Completion timeout interface + cpl_timeout=None, + cpl_timeout_avmm_clk=None, + cpl_timeout_avmm_address=None, + cpl_timeout_avmm_read=None, + cpl_timeout_avmm_readdata=None, + cpl_timeout_avmm_readdatavalid=None, + cpl_timeout_avmm_write=None, + cpl_timeout_avmm_writedata=None, + cpl_timeout_avmm_waitrequest=None, + + # Configuration output + tl_cfg_func=None, + tl_cfg_add=None, + tl_cfg_ctl=None, + dl_timer_update=None, + + # Configuration intercept interface + cii_req=None, + cii_hdr_poisoned=None, + cii_hdr_first_be=None, + cii_func_num=None, + cii_wr_vf_active=None, + cii_vf_num=None, + cii_wr=None, + cii_addr=None, + cii_dout=None, + cii_override_en=None, + cii_override_din=None, + cii_halt=None, + + # Hard IP reconfiguration interface + hip_reconfig_clk=None, + hip_reconfig_address=None, + hip_reconfig_read=None, + hip_reconfig_readdata=None, + hip_reconfig_readdatavalid=None, + hip_reconfig_write=None, + hip_reconfig_writedata=None, + hip_reconfig_waitrequest=None, + + # Page request service + prs_event_valid=None, + prs_event_func=None, + prs_event=None, + + # SR-IOV (VF error) + vf_err_ur_posted_s0=None, + vf_err_ur_posted_s1=None, + vf_err_ur_posted_s2=None, + vf_err_ur_posted_s3=None, + vf_err_func_num_s0=None, + vf_err_func_num_s1=None, + vf_err_func_num_s2=None, + vf_err_func_num_s3=None, + vf_err_ca_postedreq_s0=None, + vf_err_ca_postedreq_s1=None, + vf_err_ca_postedreq_s2=None, + vf_err_ca_postedreq_s3=None, + vf_err_vf_num_s0=None, + vf_err_vf_num_s1=None, + vf_err_vf_num_s2=None, + vf_err_vf_num_s3=None, + vf_err_poisonedwrreq_s0=None, + vf_err_poisonedwrreq_s1=None, + vf_err_poisonedwrreq_s2=None, + vf_err_poisonedwrreq_s3=None, + vf_err_poisonedcompl_s0=None, + vf_err_poisonedcompl_s1=None, + vf_err_poisonedcompl_s2=None, + vf_err_poisonedcompl_s3=None, + user_vfnonfatalmsg_func_num=None, + user_vfnonfatalmsg_vfnum=None, + user_sent_vfnonfatalmsg=None, + vf_err_overflow=None, + + # FLR + flr_rcvd_pf=None, + flr_rcvd_vf=None, + flr_rcvd_pf_num=None, + flr_rcvd_vf_num=None, + flr_completed_pf=None, + flr_completed_vf=None, + flr_completed_pf_num=None, + flr_completed_vf_num=None, + + # VirtIO + virtio_pcicfg_vfaccess=None, + virtio_pcicfg_vfnum=None, + virtio_pcicfg_pfnum=None, + virtio_pcicfg_bar=None, + virtio_pcicfg_length=None, + virtio_pcicfg_baroffset=None, + virtio_pcicfg_cfgdata=None, + virtio_pcicfg_cfgwr=None, + virtio_pcicfg_cfgrd=None, + virtio_pcicfg_appvfnum=None, + virtio_pcicfg_apppfnum=None, + virtio_pcicfg_rdack=None, + virtio_pcicfg_rdbe=None, + virtio_pcicfg_data=None, + + *args, **kwargs): + + super().__init__(*args, **kwargs) + + self.log.info("Intel P-tile PCIe hard IP core model") + self.log.info("cocotbext-pcie version %s", __version__) + self.log.info("Copyright (c) 2022 Alex Forencich") + self.log.info("https://github.com/alexforencich/cocotbext-pcie") + + self.default_function = PTilePcieFunction + + self.dw = None + + self.rx_queue = Queue() + + # configuration options + self.pcie_generation = pcie_generation + self.pcie_link_width = pcie_link_width + self.pld_clk_frequency = pld_clk_frequency + self.pf_count = pf_count + self.max_payload_size = max_payload_size + self.enable_extended_tag = enable_extended_tag + + self.pf0_msi_enable = pf0_msi_enable + self.pf0_msi_count = pf0_msi_count + self.pf1_msi_enable = pf1_msi_enable + self.pf1_msi_count = pf1_msi_count + self.pf2_msi_enable = pf2_msi_enable + self.pf2_msi_count = pf2_msi_count + self.pf3_msi_enable = pf3_msi_enable + self.pf3_msi_count = pf3_msi_count + self.pf0_msix_enable = pf0_msix_enable + self.pf0_msix_table_size = pf0_msix_table_size + self.pf0_msix_table_bir = pf0_msix_table_bir + self.pf0_msix_table_offset = pf0_msix_table_offset + self.pf0_msix_pba_bir = pf0_msix_pba_bir + self.pf0_msix_pba_offset = pf0_msix_pba_offset + self.pf1_msix_enable = pf1_msix_enable + self.pf1_msix_table_size = pf1_msix_table_size + self.pf1_msix_table_bir = pf1_msix_table_bir + self.pf1_msix_table_offset = pf1_msix_table_offset + self.pf1_msix_pba_bir = pf1_msix_pba_bir + self.pf1_msix_pba_offset = pf1_msix_pba_offset + self.pf2_msix_enable = pf2_msix_enable + self.pf2_msix_table_size = pf2_msix_table_size + self.pf2_msix_table_bir = pf2_msix_table_bir + self.pf2_msix_table_offset = pf2_msix_table_offset + self.pf2_msix_pba_bir = pf2_msix_pba_bir + self.pf2_msix_pba_offset = pf2_msix_pba_offset + self.pf3_msix_enable = pf3_msix_enable + self.pf3_msix_table_size = pf3_msix_table_size + self.pf3_msix_table_bir = pf3_msix_table_bir + self.pf3_msix_table_offset = pf3_msix_table_offset + self.pf3_msix_pba_bir = pf3_msix_pba_bir + self.pf3_msix_pba_offset = pf3_msix_pba_offset + + # signals + + # Clock and reset + self.reset_status = init_signal(reset_status, 1, 0) + self.reset_status_n = init_signal(reset_status_n, 1, 0) + self.coreclkout_hip = init_signal(coreclkout_hip, 1, 0) + self.refclk0 = init_signal(refclk0, 1) + self.refclk1 = init_signal(refclk1, 1) + self.pin_perst_n = init_signal(pin_perst_n, 1) + + # RX interface + self.rx_source = None + self.rx_par_err = init_signal(rx_par_err, 1, 0) + + if rx_bus is not None: + self.rx_source = PTilePcieSource(rx_bus, self.coreclkout_hip) + self.rx_source.queue_occupancy_limit_frames = 2 + self.rx_source.ready_latency = 27 + self.dw = self.rx_source.width + + # TX interface + self.tx_sink = None + self.tx_par_err = init_signal(tx_par_err, 1, 0) + + if tx_bus is not None: + self.tx_sink = PTilePcieSink(tx_bus, self.coreclkout_hip) + self.tx_sink.queue_occupancy_limit_frames = 2 + self.tx_sink.ready_latency = 3 + self.dw = self.tx_sink.width + + # RX flow control + self.rx_buffer_limit = init_signal(rx_buffer_limit, 12) + self.rx_buffer_limit_tdm_idx = init_signal(rx_buffer_limit_tdm_idx, 2) + + # TX flow control + self.tx_cdts_limit = init_signal(tx_cdts_limit, 16, 0) + self.tx_cdts_limit_tdm_idx = init_signal(tx_cdts_limit_tdm_idx, 3, 0) + + # Power management and hard IP status interface + self.link_up = init_signal(link_up, 1, 0) + self.dl_up = init_signal(dl_up, 1, 0) + self.surprise_down_err = init_signal(surprise_down_err, 1, 0) + self.ltssm_state = init_signal(ltssm_state, 6, 0) + self.pm_state = init_signal(pm_state, 3, 0) + self.pm_dstate = init_signal(pm_dstate, 32, 0) + self.apps_pm_xmt_pme = init_signal(apps_pm_xmt_pme, 8) + self.app_req_retry_en = init_signal(app_req_retry_en, 8) + + # Interrupt interface + self.app_int = init_signal(app_int, 8) + self.msi_pnd_func = init_signal(msi_pnd_func, 3, 0) + self.msi_pnd_byte = init_signal(msi_pnd_byte, 8, 0) + self.msi_pnd_addr = init_signal(msi_pnd_addr, 2, 0) + + # Error interface + self.serr_out = init_signal(serr_out, 1, 0) + self.hip_enter_err_mode = init_signal(hip_enter_err_mode, 1, 0) + self.app_err_valid = init_signal(app_err_valid, 1) + self.app_err_hdr = init_signal(app_err_hdr, 32) + self.app_err_info = init_signal(app_err_info, 13) + self.app_err_func_num = init_signal(app_err_func_num, 3) + + # Completion timeout interface + self.cpl_timeout = init_signal(cpl_timeout, 1, 0) + self.cpl_timeout_avmm_clk = init_signal(cpl_timeout_avmm_clk, 1) + self.cpl_timeout_avmm_address = init_signal(cpl_timeout_avmm_address, 3) + self.cpl_timeout_avmm_read = init_signal(cpl_timeout_avmm_read, 1) + self.cpl_timeout_avmm_readdata = init_signal(cpl_timeout_avmm_readdata, 8, 0) + self.cpl_timeout_avmm_readdatavalid = init_signal(cpl_timeout_avmm_readdatavalid, 1, 0) + self.cpl_timeout_avmm_write = init_signal(cpl_timeout_avmm_write, 1) + self.cpl_timeout_avmm_writedata = init_signal(cpl_timeout_avmm_writedata, 8) + self.cpl_timeout_avmm_waitrequest = init_signal(cpl_timeout_avmm_waitrequest, 1, 0) + + # Configuration output + self.tl_cfg_func = init_signal(tl_cfg_func, 3, 0) + self.tl_cfg_add = init_signal(tl_cfg_add, 5, 0) + self.tl_cfg_ctl = init_signal(tl_cfg_ctl, 16, 0) + self.dl_timer_update = init_signal(dl_timer_update, 1, 0) + + # Configuration intercept interface + self.cii_req = init_signal(cii_req, 1, 0) + self.cii_hdr_poisoned = init_signal(cii_hdr_poisoned, 1, 0) + self.cii_hdr_first_be = init_signal(cii_hdr_first_be, 4, 0) + self.cii_func_num = init_signal(cii_func_num, 3, 0) + self.cii_wr_vf_active = init_signal(cii_wr_vf_active, 1, 0) + self.cii_vf_num = init_signal(cii_vf_num, 11, 0) + self.cii_wr = init_signal(cii_wr, 1, 0) + self.cii_addr = init_signal(cii_addr, 10, 0) + self.cii_dout = init_signal(cii_dout, 32, 0) + self.cii_override_en = init_signal(cii_override_en, 1) + self.cii_override_din = init_signal(cii_override_din, 32) + self.cii_halt = init_signal(cii_halt, 1) + + # Hard IP reconfiguration interface + self.hip_reconfig_clk = init_signal(hip_reconfig_clk, 1) + self.hip_reconfig_address = init_signal(hip_reconfig_address, 21) + self.hip_reconfig_read = init_signal(hip_reconfig_read, 1) + self.hip_reconfig_readdata = init_signal(hip_reconfig_readdata, 8, 0) + self.hip_reconfig_readdatavalid = init_signal(hip_reconfig_readdatavalid, 1, 0) + self.hip_reconfig_write = init_signal(hip_reconfig_write, 1) + self.hip_reconfig_writedata = init_signal(hip_reconfig_writedata, 8) + self.hip_reconfig_waitrequest = init_signal(hip_reconfig_waitrequest, 1, 0) + + # Page request service + self.prs_event_valid = init_signal(prs_event_valid, 1) + self.prs_event_func = init_signal(prs_event_func, 3) + self.prs_event = init_signal(prs_event, 2) + + # SR-IOV (VF error) + self.vf_err_ur_posted_s0 = init_signal(vf_err_ur_posted_s0, 1, 0) + self.vf_err_ur_posted_s1 = init_signal(vf_err_ur_posted_s1, 1, 0) + self.vf_err_ur_posted_s2 = init_signal(vf_err_ur_posted_s2, 1, 0) + self.vf_err_ur_posted_s3 = init_signal(vf_err_ur_posted_s3, 1, 0) + self.vf_err_func_num_s0 = init_signal(vf_err_func_num_s0, 3, 0) + self.vf_err_func_num_s1 = init_signal(vf_err_func_num_s1, 3, 0) + self.vf_err_func_num_s2 = init_signal(vf_err_func_num_s2, 3, 0) + self.vf_err_func_num_s3 = init_signal(vf_err_func_num_s3, 3, 0) + self.vf_err_ca_postedreq_s0 = init_signal(vf_err_ca_postedreq_s0, 1, 0) + self.vf_err_ca_postedreq_s1 = init_signal(vf_err_ca_postedreq_s1, 1, 0) + self.vf_err_ca_postedreq_s2 = init_signal(vf_err_ca_postedreq_s2, 1, 0) + self.vf_err_ca_postedreq_s3 = init_signal(vf_err_ca_postedreq_s3, 1, 0) + self.vf_err_vf_num_s0 = init_signal(vf_err_vf_num_s0, 11, 0) + self.vf_err_vf_num_s1 = init_signal(vf_err_vf_num_s1, 11, 0) + self.vf_err_vf_num_s2 = init_signal(vf_err_vf_num_s2, 11, 0) + self.vf_err_vf_num_s3 = init_signal(vf_err_vf_num_s3, 11, 0) + self.vf_err_poisonedwrreq_s0 = init_signal(vf_err_poisonedwrreq_s0, 1, 0) + self.vf_err_poisonedwrreq_s1 = init_signal(vf_err_poisonedwrreq_s1, 1, 0) + self.vf_err_poisonedwrreq_s2 = init_signal(vf_err_poisonedwrreq_s2, 1, 0) + self.vf_err_poisonedwrreq_s3 = init_signal(vf_err_poisonedwrreq_s3, 1, 0) + self.vf_err_poisonedcompl_s0 = init_signal(vf_err_poisonedcompl_s0, 1, 0) + self.vf_err_poisonedcompl_s1 = init_signal(vf_err_poisonedcompl_s1, 1, 0) + self.vf_err_poisonedcompl_s2 = init_signal(vf_err_poisonedcompl_s2, 1, 0) + self.vf_err_poisonedcompl_s3 = init_signal(vf_err_poisonedcompl_s3, 1, 0) + self.user_vfnonfatalmsg_func_num = init_signal(user_vfnonfatalmsg_func_num, 3) + self.user_vfnonfatalmsg_vfnum = init_signal(user_vfnonfatalmsg_vfnum, 11) + self.user_sent_vfnonfatalmsg = init_signal(user_sent_vfnonfatalmsg, 1) + self.vf_err_overflow = init_signal(vf_err_overflow, 1, 0) + + # FLR + self.flr_rcvd_pf = init_signal(flr_rcvd_pf, 8, 0) + self.flr_rcvd_vf = init_signal(flr_rcvd_vf, 1, 0) + self.flr_rcvd_pf_num = init_signal(flr_rcvd_pf_num, 3, 0) + self.flr_rcvd_vf_num = init_signal(flr_rcvd_vf_num, 11, 0) + self.flr_completed_pf = init_signal(flr_completed_pf, 8) + self.flr_completed_vf = init_signal(flr_completed_vf, 1) + self.flr_completed_pf_num = init_signal(flr_completed_pf_num, 3) + self.flr_completed_vf_num = init_signal(flr_completed_vf_num, 11) + + # VirtIO + self.virtio_pcicfg_vfaccess = init_signal(virtio_pcicfg_vfaccess, 1, 0) + self.virtio_pcicfg_vfnum = init_signal(virtio_pcicfg_vfnum, 11, 0) + self.virtio_pcicfg_pfnum = init_signal(virtio_pcicfg_pfnum, 3, 0) + self.virtio_pcicfg_bar = init_signal(virtio_pcicfg_bar, 8, 0) + self.virtio_pcicfg_length = init_signal(virtio_pcicfg_length, 32, 0) + self.virtio_pcicfg_baroffset = init_signal(virtio_pcicfg_baroffset, 32, 0) + self.virtio_pcicfg_cfgdata = init_signal(virtio_pcicfg_cfgdata, 32, 0) + self.virtio_pcicfg_cfgwr = init_signal(virtio_pcicfg_cfgwr, 1, 0) + self.virtio_pcicfg_cfgrd = init_signal(virtio_pcicfg_cfgrd, 1, 0) + self.virtio_pcicfg_appvfnum = init_signal(virtio_pcicfg_appvfnum, 11) + self.virtio_pcicfg_apppfnum = init_signal(virtio_pcicfg_apppfnum, 3) + self.virtio_pcicfg_rdack = init_signal(virtio_pcicfg_rdack, 1) + self.virtio_pcicfg_rdbe = init_signal(virtio_pcicfg_rdbe, 4) + self.virtio_pcicfg_data = init_signal(virtio_pcicfg_data, 32) + + # validate parameters + assert self.dw in {128, 256, 512} + + # rescale clock frequency + if self.pld_clk_frequency is not None and self.pld_clk_frequency < 1e6: + self.pld_clk_frequency *= 1e6 + + if not self.pcie_generation or not self.pcie_link_width or not self.pld_clk_frequency: + self.log.info("Incomplete configuration specified, attempting to select reasonable options") + # guess some reasonable values for unspecified parameters + for config in reversed(valid_configs): + # find configuration matching specified parameters + if self.pcie_generation is not None and self.pcie_generation != config[0]: + continue + if self.pcie_link_width is not None and self.pcie_link_width != config[1]: + continue + if self.dw != config[2]: + continue + if self.pld_clk_frequency is not None and self.pld_clk_frequency != config[3]: + continue + + # set the unspecified parameters + if self.pcie_generation is None: + self.log.info("Setting PCIe speed to gen %d", config[0]) + self.pcie_generation = config[0] + if self.pcie_link_width is None: + self.log.info("Setting PCIe link width to x%d", config[1]) + self.pcie_link_width = config[1] + if self.pld_clk_frequency is None: + self.log.info("Setting user clock frequency to %d MHz", config[3]/1e6) + self.pld_clk_frequency = config[3] + break + + self.log.info("Intel P-tile PCIe hard IP core configuration:") + self.log.info(" PCIe speed: gen %d", self.pcie_generation) + self.log.info(" PCIe link width: x%d", self.pcie_link_width) + self.log.info(" PLD clock frequency: %d MHz", self.pld_clk_frequency/1e6) + self.log.info(" PF count: %d", self.pf_count) + self.log.info(" Max payload size: %d", self.max_payload_size) + self.log.info(" Enable extended tag: %s", self.enable_extended_tag) + self.log.info(" Enable PF0 MSI: %s", self.pf0_msi_enable) + self.log.info(" PF0 MSI vector count: %d", self.pf0_msi_count) + self.log.info(" Enable PF1 MSI: %s", self.pf1_msi_enable) + self.log.info(" PF1 MSI vector count: %d", self.pf1_msi_count) + self.log.info(" Enable PF2 MSI: %s", self.pf2_msi_enable) + self.log.info(" PF2 MSI vector count: %d", self.pf2_msi_count) + self.log.info(" Enable PF3 MSI: %s", self.pf3_msi_enable) + self.log.info(" PF3 MSI vector count: %d", self.pf3_msi_count) + self.log.info(" Enable PF0 MSIX: %s", self.pf0_msix_enable) + self.log.info(" PF0 MSIX table size: %d", self.pf0_msix_table_size) + self.log.info(" PF0 MSIX table BIR: %d", self.pf0_msix_table_bir) + self.log.info(" PF0 MSIX table offset: 0x%08x", self.pf0_msix_table_offset) + self.log.info(" PF0 MSIX PBA BIR: %d", self.pf0_msix_pba_bir) + self.log.info(" PF0 MSIX PBA offset: 0x%08x", self.pf0_msix_pba_offset) + self.log.info(" Enable PF1 MSIX: %s", self.pf1_msix_enable) + self.log.info(" PF1 MSIX table size: %d", self.pf1_msix_table_size) + self.log.info(" PF1 MSIX table BIR: %d", self.pf1_msix_table_bir) + self.log.info(" PF1 MSIX table offset: 0x%08x", self.pf1_msix_table_offset) + self.log.info(" PF1 MSIX PBA BIR: %d", self.pf1_msix_pba_bir) + self.log.info(" PF1 MSIX PBA offset: 0x%08x", self.pf1_msix_pba_offset) + self.log.info(" Enable PF2 MSIX: %s", self.pf2_msix_enable) + self.log.info(" PF2 MSIX table size: %d", self.pf2_msix_table_size) + self.log.info(" PF2 MSIX table BIR: %d", self.pf2_msix_table_bir) + self.log.info(" PF2 MSIX table offset: 0x%08x", self.pf2_msix_table_offset) + self.log.info(" PF2 MSIX PBA BIR: %d", self.pf2_msix_pba_bir) + self.log.info(" PF2 MSIX PBA offset: 0x%08x", self.pf2_msix_pba_offset) + self.log.info(" Enable PF3 MSIX: %s", self.pf3_msix_enable) + self.log.info(" PF3 MSIX table size: %d", self.pf3_msix_table_size) + self.log.info(" PF3 MSIX table BIR: %d", self.pf3_msix_table_bir) + self.log.info(" PF3 MSIX table offset: 0x%08x", self.pf3_msix_table_offset) + self.log.info(" PF3 MSIX PBA BIR: %d", self.pf3_msix_pba_bir) + self.log.info(" PF3 MSIX PBA offset: 0x%08x", self.pf3_msix_pba_offset) + + assert self.pcie_generation in {3, 4} + assert self.pcie_link_width in {4, 8, 16} + assert self.pld_clk_frequency in {175e6, 200e6, 225e6, 250e6, 350e6, 400e6, 450e6, 500e6} + + # check for valid configuration + config_valid = False + for config in valid_configs: + if self.pcie_generation != config[0]: + continue + if self.pcie_link_width != config[1]: + continue + if self.dw != config[2]: + continue + if self.pld_clk_frequency != config[3]: + continue + + config_valid = True + break + + assert config_valid, "link speed/link width/clock speed/interface width setting combination not valid" + + # configure port + self.upstream_port.max_link_speed = self.pcie_generation + self.upstream_port.max_link_width = self.pcie_link_width + + # configure functions + + self.make_function() + + if self.pf0_msi_enable: + self.functions[0].msi_cap.msi_multiple_message_capable = (self.pf0_msi_count-1).bit_length() + else: + self.functions[0].deregister_capability(self.functions[0].msi_cap) + + if self.pf0_msix_enable: + self.functions[0].msix_cap.msix_table_size = self.pf0_msix_table_size + self.functions[0].msix_cap.msix_table_bar_indicator_register = self.pf0_msix_table_bir + self.functions[0].msix_cap.msix_table_offset = self.pf0_msix_table_offset + self.functions[0].msix_cap.msix_pba_bar_indicator_register = self.pf0_msix_pba_bir + self.functions[0].msix_cap.msix_pba_offset = self.pf0_msix_pba_offset + else: + self.functions[0].deregister_capability(self.functions[0].msix_cap) + + if self.pf_count > 1: + self.make_function() + + if self.pf1_msi_enable: + self.functions[1].msi_cap.msi_multiple_message_capable = (self.pf1_msi_count-1).bit_length() + else: + self.functions[1].deregister_capability(self.functions[1].msi_cap) + + if self.pf1_msix_enable: + self.functions[1].msix_cap.msix_table_size = self.pf1_msix_table_size + self.functions[1].msix_cap.msix_table_bar_indicator_register = self.pf1_msix_table_bir + self.functions[1].msix_cap.msix_table_offset = self.pf1_msix_table_offset + self.functions[1].msix_cap.msix_pba_bar_indicator_register = self.pf1_msix_pba_bir + self.functions[1].msix_cap.msix_pba_offset = self.pf1_msix_pba_offset + else: + self.functions[1].deregister_capability(self.functions[1].msix_cap) + + if self.pf_count > 2: + self.make_function() + + if self.pf2_msi_enable: + self.functions[2].msi_cap.msi_multiple_message_capable = (self.pf2_msi_count-1).bit_length() + else: + self.functions[2].deregister_capability(self.functions[2].msi_cap) + + if self.pf2_msix_enable: + self.functions[2].msix_cap.msix_table_size = self.pf2_msix_table_size + self.functions[2].msix_cap.msix_table_bar_indicator_register = self.pf2_msix_table_bir + self.functions[2].msix_cap.msix_table_offset = self.pf2_msix_table_offset + self.functions[2].msix_cap.msix_pba_bar_indicator_register = self.pf2_msix_pba_bir + self.functions[2].msix_cap.msix_pba_offset = self.pf2_msix_pba_offset + else: + self.functions[2].deregister_capability(self.functions[2].msix_cap) + + if self.pf_count > 3: + self.make_function() + + if self.pf3_msi_enable: + self.functions[3].msi_cap.msi_multiple_message_capable = (self.pf3_msi_count-1).bit_length() + else: + self.functions[3].deregister_capability(self.functions[3].msi_cap) + + if self.pf3_msix_enable: + self.functions[3].msix_cap.msix_table_size = self.pf3_msix_table_size + self.functions[3].msix_cap.msix_table_bar_indicator_register = self.pf3_msix_table_bir + self.functions[3].msix_cap.msix_table_offset = self.pf3_msix_table_offset + self.functions[3].msix_cap.msix_pba_bar_indicator_register = self.pf3_msix_pba_bir + self.functions[3].msix_cap.msix_pba_offset = self.pf3_msix_pba_offset + else: + self.functions[3].deregister_capability(self.functions[3].msix_cap) + + for f in self.functions: + f.pcie_cap.max_payload_size_supported = (self.max_payload_size//128-1).bit_length() + f.pcie_cap.extended_tag_supported = self.enable_extended_tag + + # fork coroutines + + if self.coreclkout_hip is not None: + cocotb.start_soon(Clock(self.coreclkout_hip, int(1e9/self.pld_clk_frequency), units="ns").start()) + + if self.rx_source: + cocotb.start_soon(self._run_rx_logic()) + if self.tx_sink: + cocotb.start_soon(self._run_tx_logic()) + if self.tx_cdts_limit: + cocotb.start_soon(self._run_tx_fc_logic()) + if self.tl_cfg_ctl: + cocotb.start_soon(self._run_cfg_out_logic()) + + cocotb.start_soon(self._run_reset()) + + async def upstream_recv(self, tlp): + self.log.debug("Got downstream TLP: %r", tlp) + + if tlp.fmt_type in {TlpType.CFG_READ_0, TlpType.CFG_WRITE_0}: + # config type 0 + + # capture address information + self.bus_num = tlp.dest_id.bus + + # pass TLP to function + for f in self.functions: + if f.pcie_id == tlp.dest_id: + await f.upstream_recv(tlp) + return + + tlp.release_fc() + + self.log.warning("Function not found: failed to route config type 0 TLP: %r", tlp) + elif tlp.fmt_type in {TlpType.CFG_READ_1, TlpType.CFG_WRITE_1}: + # config type 1 + + tlp.release_fc() + + self.log.warning("Malformed TLP: endpoint received config type 1 TLP: %r", tlp) + elif tlp.fmt_type in {TlpType.CPL, TlpType.CPL_DATA, TlpType.CPL_LOCKED, TlpType.CPL_LOCKED_DATA}: + # Completion + + for f in self.functions: + if f.pcie_id == tlp.requester_id: + + frame = PTilePcieFrame.from_tlp(tlp) + + frame.func_num = tlp.requester_id.function + + await self.rx_queue.put(frame) + + tlp.release_fc() + + return + + tlp.release_fc() + + self.log.warning("Unexpected completion: failed to route completion to function: %r", tlp) + return # no UR response for completion + elif tlp.fmt_type in {TlpType.IO_READ, TlpType.IO_WRITE}: + # IO read/write + + for f in self.functions: + bar = f.match_bar(tlp.address, True) + if bar: + + frame = PTilePcieFrame.from_tlp(tlp) + + frame.bar_range = 6 + frame.func_num = tlp.requester_id.function + + await self.rx_queue.put(frame) + + tlp.release_fc() + + return + + tlp.release_fc() + + self.log.warning("No BAR match: IO request did not match any BARs: %r", tlp) + elif tlp.fmt_type in {TlpType.MEM_READ, TlpType.MEM_READ_64, TlpType.MEM_WRITE, TlpType.MEM_WRITE_64}: + # Memory read/write + + for f in self.functions: + bar = f.match_bar(tlp.address) + if bar: + + frame = PTilePcieFrame.from_tlp(tlp) + + frame.bar_range = bar[0] + frame.func_num = tlp.requester_id.function + + await self.rx_queue.put(frame) + + tlp.release_fc() + + return + + tlp.release_fc() + + if tlp.fmt_type in {TlpType.MEM_WRITE, TlpType.MEM_WRITE_64}: + self.log.warning("No BAR match: memory write request did not match any BARs: %r", tlp) + return # no UR response for write request + else: + self.log.warning("No BAR match: memory read request did not match any BARs: %r", tlp) + else: + raise Exception("TODO") + + # Unsupported request + cpl = Tlp.create_ur_completion_for_tlp(tlp, PcieId(self.bus_num, 0, 0)) + self.log.debug("UR Completion: %r", cpl) + await self.upstream_send(cpl) + + async def _run_reset(self): + clock_edge_event = RisingEdge(self.coreclkout_hip) + + while True: + await clock_edge_event + await clock_edge_event + + if self.reset_status is not None: + self.reset_status.value = 1 + if self.reset_status_n is not None: + self.reset_status_n.value = 0 + + if self.pin_perst_n is not None: + if not self.pin_perst_n.value: + await RisingEdge(self.pin_perst_n) + await First(FallingEdge(self.pin_perst_n), Timer(100, 'ns')) + await First(FallingEdge(self.pin_perst_n), RisingEdge(self.coreclkout_hip)) + if not self.pin_perst_n.value: + continue + else: + await Timer(100, 'ns') + await clock_edge_event + + if self.reset_status is not None: + self.reset_status.value = 0 + if self.reset_status_n is not None: + self.reset_status_n.value = 1 + + if self.pin_perst_n is not None: + await FallingEdge(self.pin_perst_n) + else: + return + + async def _run_rx_logic(self): + while True: + frame = await self.rx_queue.get() + await self.rx_source.send(frame) + + async def _run_tx_logic(self): + while True: + frame = await self.tx_sink.recv() + tlp = frame.to_tlp() + await self.send(tlp) + + async def _run_rx_fc_logic(self): + pass + + # RX flow control + # rx_buffer_limit + # rx_buffer_limit_tdm_idx + + async def _run_tx_fc_logic(self): + clock_edge_event = RisingEdge(self.coreclkout_hip) + + while True: + self.tx_cdts_limit.value = self.upstream_port.fc_state[0].ph.tx_credit_limit + self.tx_cdts_limit_tdm_idx.value = 0 + await clock_edge_event + + self.tx_cdts_limit.value = self.upstream_port.fc_state[0].nph.tx_credit_limit + self.tx_cdts_limit_tdm_idx.value = 1 + await clock_edge_event + + self.tx_cdts_limit.value = self.upstream_port.fc_state[0].cplh.tx_credit_limit + self.tx_cdts_limit_tdm_idx.value = 2 + await clock_edge_event + + self.tx_cdts_limit.value = self.upstream_port.fc_state[0].pd.tx_credit_limit + self.tx_cdts_limit_tdm_idx.value = 4 + await clock_edge_event + + self.tx_cdts_limit.value = self.upstream_port.fc_state[0].npd.tx_credit_limit + self.tx_cdts_limit_tdm_idx.value = 5 + await clock_edge_event + + self.tx_cdts_limit.value = self.upstream_port.fc_state[0].cpld.tx_credit_limit + self.tx_cdts_limit_tdm_idx.value = 6 + await clock_edge_event + + async def _run_pm_status_logic(self): + pass + + # Power management and hard IP status interface + # link_up + # dl_up + # surprise_down_err + # ltssm_state + # pm_state + # pm_dstate + # apps_pm_xmt_pme + # app_req_retry_en + + async def _run_int_logic(self): + pass + + # Interrupt interface + # app_int + # msi_pnd_func + # msi_pnd_byte + # msi_pnd_addr + + # Error interface + # serr_out + # hip_enter_err_mode + # app_err_valid + # app_err_hdr + # app_err_info + # app_err_func_num + + # Completion timeout interface + # cpl_timeout + # cpl_timeout_avmm_clk + # cpl_timeout_avmm_address + # cpl_timeout_avmm_read + # cpl_timeout_avmm_readdata + # cpl_timeout_avmm_readdatavalid + # cpl_timeout_avmm_write + # cpl_timeout_avmm_writedata + # cpl_timeout_avmm_waitrequest + + async def _run_cfg_out_logic(self): + clock_edge_event = RisingEdge(self.coreclkout_hip) + + while True: + for func in self.functions: + self.tl_cfg_func.value = func.pcie_id.function + + self.tl_cfg_add.value = 0x00 + val = bool(func.memory_space_enable) << 15 + val |= bool(func.pcie_cap.ido_completion_enable) << 14 + val |= bool(func.parity_error_response_enable) << 13 + val |= bool(func.serr_enable) << 12 + val |= bool(func.pcie_cap.fatal_error_reporting_enable) << 11 + val |= bool(func.pcie_cap.non_fatal_error_reporting_enable) << 10 + val |= bool(func.pcie_cap.correctable_error_reporting_enable) << 9 + val |= bool(func.pcie_cap.unsupported_request_reporting_enable) << 8 + val |= bool(func.bus_master_enable) << 7 + val |= bool(func.pcie_cap.extended_tag_field_enable) << 6 + val |= (func.pcie_cap.max_read_request_size & 0x7) << 3 + val |= (func.pcie_cap.max_payload_size & 0x7) + self.tl_cfg_ctl.value = val + await clock_edge_event + + self.tl_cfg_add.value = 0x01 + val = bool(func.pcie_cap.ido_request_enable) << 15 + val |= bool(func.pcie_cap.enable_no_snoop) << 14 + val |= bool(func.pcie_cap.enable_relaxed_ordering) << 13 + val |= (func.pcie_id.device & 0x1f) << 8 + val |= func.pcie_id.bus & 0xff + self.tl_cfg_ctl.value = val + await clock_edge_event + + self.tl_cfg_add.value = 0x02 + val = bool(func.pm_cap.no_soft_reset) << 15 + val |= bool(func.pcie_cap.read_completion_boundary) << 14 + val |= bool(func.interrupt_disable) << 13 + val |= (func.pcie_cap.interrupt_message_number & 0x1f) << 8 + val |= bool(func.pcie_cap.power_controller_control) << 4 + val |= (func.pcie_cap.attention_indicator_control & 0x3) << 2 + val |= func.pcie_cap.power_indicator_control & 0x3 + self.tl_cfg_ctl.value = val + await clock_edge_event + + self.tl_cfg_add.value = 0x03 + # num vfs + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x04 + val = bool(func.pcie_cap.atomic_op_egress_blocking) << 14 + # ats + val |= bool(func.pcie_cap.ari_forwarding_enable) << 7 + val |= bool(func.pcie_cap.atomic_op_requester_enable) << 6 + # tph + # vf en + self.tl_cfg_ctl.value = val + await clock_edge_event + + self.tl_cfg_add.value = 0x05 + val = (func.pcie_cap.current_link_speed & 0xf) << 12 + # start vf + self.tl_cfg_ctl.value = val + await clock_edge_event + + self.tl_cfg_add.value = 0x06 + self.tl_cfg_ctl.value = func.msi_cap.msi_message_address & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x07 + self.tl_cfg_ctl.value = (func.msi_cap.msi_message_address >> 16) & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x08 + self.tl_cfg_ctl.value = (func.msi_cap.msi_message_address >> 32) & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x09 + self.tl_cfg_ctl.value = (func.msi_cap.msi_message_address >> 48) & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x0A + self.tl_cfg_ctl.value = func.msi_cap.msi_mask_bits & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x0B + self.tl_cfg_ctl.value = (func.msi_cap.msi_mask_bits >> 16) & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x0C + val = bool(func.pcie_cap.system_error_on_fatal_error_enable) << 15 + val |= bool(func.pcie_cap.system_error_on_non_fatal_error_enable) << 14 + val |= bool(func.pcie_cap.system_error_on_correctable_error_enable) << 13 + val |= (func.aer_ext_cap.advanced_error_interrupt_message_number & 0x1f) << 8 + val |= bool(func.msi_cap.msi_extended_message_data_enable) << 7 + val |= bool(func.msix_cap.msix_function_mask) << 6 + val |= bool(func.msix_cap.msix_enable) << 5 + val |= (func.msi_cap.msi_multiple_message_enable & 0x7) << 2 + val |= bool(func.msi_cap.msi_64bit_address_capable) << 1 + val |= bool(func.msi_cap.msi_enable) + self.tl_cfg_ctl.value = val + await clock_edge_event + + self.tl_cfg_add.value = 0x0D + self.tl_cfg_ctl.value = func.msi_cap.msi_message_data & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x0E + # AER uncorrectable error mask + val = await func.aer_ext_cap.read_register(2) + self.tl_cfg_ctl.value = val & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x0F + # AER uncorrectable error mask + self.tl_cfg_ctl.value = (val >> 16) & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x10 + # AER correctable error mask + val = await func.aer_ext_cap.read_register(5) + self.tl_cfg_ctl.value = val & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x11 + # AER correctable error mask + self.tl_cfg_ctl.value = (val >> 16) & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x12 + # AER uncorrectable error severity + val = await func.aer_ext_cap.read_register(3) + self.tl_cfg_ctl.value = val & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x13 + # AER uncorrectable error severity + self.tl_cfg_ctl.value = (val >> 16) & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x14 + # acs + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x15 + # prs + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x16 + # prs + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x17 + # prs + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x18 + # ltr + # pasid + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x19 + # slot control + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x1A + # ltr max snoop lat + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x1B + # ltr max snoop lat + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x1C + # TC en + val = func.pcie_cap.negotiated_link_width & 0x3f + self.tl_cfg_ctl.value = val + await clock_edge_event + + self.tl_cfg_add.value = 0x1D + self.tl_cfg_ctl.value = (func.msi_cap.msi_message_data >> 16) & 0xffff + await clock_edge_event + + self.tl_cfg_add.value = 0x1E + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + self.tl_cfg_add.value = 0x1F + self.tl_cfg_ctl.value = 0 + await clock_edge_event + + # dl_timer_update + + # Configuration intercept interface + # cii_req + # cii_hdr_poisoned + # cii_hdr_first_be + # cii_func_num + # cii_wr_vf_active + # cii_vf_num + # cii_wr + # cii_addr + # cii_dout + # cii_override_en + # cii_override_din + # cii_halt + + # Hard IP reconfiguration interface + # hip_reconfig_clk + # hip_reconfig_address + # hip_reconfig_read + # hip_reconfig_readdata + # hip_reconfig_readdatavalid + # hip_reconfig_write + # hip_reconfig_writedata + # hip_reconfig_waitrequest + + # Page request service + # prs_event_valid + # prs_event_func + # prs_event + + # SR-IOV (VF error) + # vf_err_ur_posted_s0 + # vf_err_ur_posted_s1 + # vf_err_ur_posted_s2 + # vf_err_ur_posted_s3 + # vf_err_func_num_s0 + # vf_err_func_num_s1 + # vf_err_func_num_s2 + # vf_err_func_num_s3 + # vf_err_ca_postedreq_s0 + # vf_err_ca_postedreq_s1 + # vf_err_ca_postedreq_s2 + # vf_err_ca_postedreq_s3 + # vf_err_vf_num_s0 + # vf_err_vf_num_s1 + # vf_err_vf_num_s2 + # vf_err_vf_num_s3 + # vf_err_poisonedwrreq_s0 + # vf_err_poisonedwrreq_s1 + # vf_err_poisonedwrreq_s2 + # vf_err_poisonedwrreq_s3 + # vf_err_poisonedcompl_s0 + # vf_err_poisonedcompl_s1 + # vf_err_poisonedcompl_s2 + # vf_err_poisonedcompl_s3 + # user_vfnonfatalmsg_func_num + # user_vfnonfatalmsg_vfnum + # user_sent_vfnonfatalmsg + # vf_err_overflow + + # FLR + # flr_rcvd_pf + # flr_rcvd_vf + # flr_rcvd_pf_num + # flr_rcvd_vf_num + # flr_completed_pf + # flr_completed_vf + # flr_completed_pf_num + # flr_completed_vf_num + + # VirtIO + # virtio_pcicfg_vfaccess + # virtio_pcicfg_vfnum + # virtio_pcicfg_pfnum + # virtio_pcicfg_bar + # virtio_pcicfg_length + # virtio_pcicfg_baroffset + # virtio_pcicfg_cfgdata + # virtio_pcicfg_cfgwr + # virtio_pcicfg_cfgrd + # virtio_pcicfg_appvfnum + # virtio_pcicfg_apppfnum + # virtio_pcicfg_rdack + # virtio_pcicfg_rdbe + # virtio_pcicfg_data diff --git a/tests/pcie_ptile/Makefile b/tests/pcie_ptile/Makefile new file mode 100644 index 0000000..f428ef9 --- /dev/null +++ b/tests/pcie_ptile/Makefile @@ -0,0 +1,89 @@ +# Copyright (c) 2022 Alex Forencich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +TOPLEVEL_LANG = verilog + +SIM ?= icarus +WAVES ?= 0 + +COCOTB_HDL_TIMEUNIT = 1ns +COCOTB_HDL_TIMEPRECISION = 1ps + +DUT = test_pcie_ptile +TOPLEVEL = $(DUT) +MODULE = $(DUT) +VERILOG_SOURCES = $(DUT).v + +# module parameters +export PARAM_SEG_COUNT ?= 1 +export PARAM_SEG_DATA_WIDTH ?= 128 +export PARAM_SEG_HDR_WIDTH ?= 128 +export PARAM_SEG_PRFX_WIDTH ?= 32 +export PARAM_SEG_DATA_PAR_WIDTH ?= $(shell expr $(PARAM_SEG_DATA_WIDTH) / 8 ) +export PARAM_SEG_HDR_PAR_WIDTH ?= $(shell expr $(PARAM_SEG_HDR_WIDTH) / 8 ) +export PARAM_SEG_PRFX_PAR_WIDTH ?= $(shell expr $(PARAM_SEG_PRFX_WIDTH) / 8 ) +export PARAM_SEG_EMPTY_WIDTH ?= $(shell python -c "print((($(PARAM_SEG_DATA_WIDTH)//32)-1).bit_length())" ) + +ifeq ($(SIM), icarus) + PLUSARGS += -fst + + COMPILE_ARGS += -P $(TOPLEVEL).SEG_COUNT=$(PARAM_SEG_COUNT) + COMPILE_ARGS += -P $(TOPLEVEL).SEG_DATA_WIDTH=$(PARAM_SEG_DATA_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).SEG_HDR_WIDTH=$(PARAM_SEG_HDR_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).SEG_PRFX_WIDTH=$(PARAM_SEG_PRFX_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).SEG_DATA_PAR_WIDTH=$(PARAM_SEG_DATA_PAR_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).SEG_HDR_PAR_WIDTH=$(PARAM_SEG_HDR_PAR_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).SEG_PRFX_PAR_WIDTH=$(PARAM_SEG_PRFX_PAR_WIDTH) + COMPILE_ARGS += -P $(TOPLEVEL).SEG_EMPTY_WIDTH=$(PARAM_SEG_EMPTY_WIDTH) + + ifeq ($(WAVES), 1) + VERILOG_SOURCES += iverilog_dump.v + COMPILE_ARGS += -s iverilog_dump + endif +else ifeq ($(SIM), verilator) + COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH + + COMPILE_ARGS += -GSEG_COUNT=$(PARAM_SEG_COUNT) + COMPILE_ARGS += -GSEG_DATA_WIDTH=$(PARAM_SEG_DATA_WIDTH) + COMPILE_ARGS += -GSEG_HDR_WIDTH=$(PARAM_SEG_HDR_WIDTH) + COMPILE_ARGS += -GSEG_PRFX_WIDTH=$(PARAM_SEG_PRFX_WIDTH) + COMPILE_ARGS += -GSEG_DATA_PAR_WIDTH=$(PARAM_SEG_DATA_PAR_WIDTH) + COMPILE_ARGS += -GSEG_HDR_PAR_WIDTH=$(PARAM_SEG_HDR_PAR_WIDTH) + COMPILE_ARGS += -GSEG_PRFX_PAR_WIDTH=$(PARAM_SEG_PRFX_PAR_WIDTH) + COMPILE_ARGS += -GSEG_EMPTY_WIDTH=$(PARAM_SEG_EMPTY_WIDTH) + + ifeq ($(WAVES), 1) + COMPILE_ARGS += --trace-fst + endif +endif + +include $(shell cocotb-config --makefiles)/Makefile.sim + +iverilog_dump.v: + echo 'module iverilog_dump();' > $@ + echo 'initial begin' >> $@ + echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ + echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ + echo 'end' >> $@ + echo 'endmodule' >> $@ + +clean:: + @rm -rf iverilog_dump.v + @rm -rf dump.fst $(TOPLEVEL).fst diff --git a/tests/pcie_ptile/test_pcie_ptile.py b/tests/pcie_ptile/test_pcie_ptile.py new file mode 100644 index 0000000..d0d5dbd --- /dev/null +++ b/tests/pcie_ptile/test_pcie_ptile.py @@ -0,0 +1,1018 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2022 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +import itertools +import logging +import mmap +import os +import struct + +import cocotb_test.simulator +import pytest + +import cocotb +from cocotb.queue import Queue +from cocotb.triggers import RisingEdge, Timer, Event, First +from cocotb.regression import TestFactory + +from cocotbext.pcie.core import RootComplex +from cocotbext.pcie.intel.ptile import PTilePcieDevice, PTileRxBus, PTileTxBus +from cocotbext.pcie.intel.ptile.interface import PTilePcieFrame, PTilePcieSource, PTilePcieSink +from cocotbext.pcie.core.tlp import Tlp, TlpType, CplStatus +from cocotbext.pcie.core.utils import PcieId + + +class TB: + def __init__(self, dut, msix=False): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + # PCIe + self.rc = RootComplex() + + self.dev = PTilePcieDevice( + # configuration options + pcie_generation=3, + # pcie_link_width=2, + # user_clk_frequency=250e6, + pf_count=1, + max_payload_size=128, + enable_extended_tag=False, + + pf0_msi_enable=True, + pf0_msi_count=32, + pf1_msi_enable=False, + pf1_msi_count=1, + pf2_msi_enable=False, + pf2_msi_count=1, + pf3_msi_enable=False, + pf3_msi_count=1, + pf0_msix_enable=msix, + pf0_msix_table_size=63, + pf0_msix_table_bir=4, + pf0_msix_table_offset=0x00000000, + pf0_msix_pba_bir=4, + pf0_msix_pba_offset=0x00008000, + pf1_msix_enable=False, + pf1_msix_table_size=0, + pf1_msix_table_bir=0, + pf1_msix_table_offset=0x00000000, + pf1_msix_pba_bir=0, + pf1_msix_pba_offset=0x00000000, + pf2_msix_enable=False, + pf2_msix_table_size=0, + pf2_msix_table_bir=0, + pf2_msix_table_offset=0x00000000, + pf2_msix_pba_bir=0, + pf2_msix_pba_offset=0x00000000, + pf3_msix_enable=False, + pf3_msix_table_size=0, + pf3_msix_table_bir=0, + pf3_msix_table_offset=0x00000000, + pf3_msix_pba_bir=0, + pf3_msix_pba_offset=0x00000000, + + # signals + # Clock and reset + # reset_status=dut.reset_status, + reset_status_n=dut.reset_status_n, + coreclkout_hip=dut.coreclkout_hip, + refclk0=dut.refclk0, + refclk1=dut.refclk1, + pin_perst_n=dut.pin_perst_n, + + # RX interface + rx_bus=PTileRxBus.from_prefix(dut, "rx_st"), + rx_par_err=dut.rx_par_err, + + # TX interface + tx_bus=PTileTxBus.from_prefix(dut, "tx_st"), + tx_par_err=dut.tx_par_err, + + # RX flow control + rx_buffer_limit=dut.rx_buffer_limit, + rx_buffer_limit_tdm_idx=dut.rx_buffer_limit_tdm_idx, + + # TX flow control + tx_cdts_limit=dut.tx_cdts_limit, + tx_cdts_limit_tdm_idx=dut.tx_cdts_limit_tdm_idx, + + # Power management and hard IP status interface + link_up=dut.link_up, + dl_up=dut.dl_up, + surprise_down_err=dut.surprise_down_err, + ltssm_state=dut.ltssm_state, + pm_state=dut.pm_state, + pm_dstate=dut.pm_dstate, + apps_pm_xmt_pme=dut.apps_pm_xmt_pme, + app_req_retry_en=dut.app_req_retry_en, + + # Interrupt interface + app_int=dut.app_int, + msi_pnd_func=dut.msi_pnd_func, + msi_pnd_byte=dut.msi_pnd_byte, + msi_pnd_addr=dut.msi_pnd_addr, + + # Error interface + serr_out=dut.serr_out, + hip_enter_err_mode=dut.hip_enter_err_mode, + app_err_valid=dut.app_err_valid, + app_err_hdr=dut.app_err_hdr, + app_err_info=dut.app_err_info, + app_err_func_num=dut.app_err_func_num, + + # Completion timeout interface + cpl_timeout=dut.cpl_timeout, + cpl_timeout_avmm_clk=dut.cpl_timeout_avmm_clk, + cpl_timeout_avmm_address=dut.cpl_timeout_avmm_address, + cpl_timeout_avmm_read=dut.cpl_timeout_avmm_read, + cpl_timeout_avmm_readdata=dut.cpl_timeout_avmm_readdata, + cpl_timeout_avmm_readdatavalid=dut.cpl_timeout_avmm_readdatavalid, + cpl_timeout_avmm_write=dut.cpl_timeout_avmm_write, + cpl_timeout_avmm_writedata=dut.cpl_timeout_avmm_writedata, + cpl_timeout_avmm_waitrequest=dut.cpl_timeout_avmm_waitrequest, + + # Configuration output + tl_cfg_func=dut.tl_cfg_func, + tl_cfg_add=dut.tl_cfg_add, + tl_cfg_ctl=dut.tl_cfg_ctl, + dl_timer_update=dut.dl_timer_update, + + # Configuration intercept interface + cii_req=dut.cii_req, + cii_hdr_poisoned=dut.cii_hdr_poisoned, + cii_hdr_first_be=dut.cii_hdr_first_be, + cii_func_num=dut.cii_func_num, + cii_wr_vf_active=dut.cii_wr_vf_active, + cii_vf_num=dut.cii_vf_num, + cii_wr=dut.cii_wr, + cii_addr=dut.cii_addr, + cii_dout=dut.cii_dout, + cii_override_en=dut.cii_override_en, + cii_override_din=dut.cii_override_din, + cii_halt=dut.cii_halt, + + # Hard IP reconfiguration interface + hip_reconfig_clk=dut.hip_reconfig_clk, + hip_reconfig_address=dut.hip_reconfig_address, + hip_reconfig_read=dut.hip_reconfig_read, + hip_reconfig_readdata=dut.hip_reconfig_readdata, + hip_reconfig_readdatavalid=dut.hip_reconfig_readdatavalid, + hip_reconfig_write=dut.hip_reconfig_write, + hip_reconfig_writedata=dut.hip_reconfig_writedata, + hip_reconfig_waitrequest=dut.hip_reconfig_waitrequest, + + # Page request service + prs_event_valid=dut.prs_event_valid, + prs_event_func=dut.prs_event_func, + prs_event=dut.prs_event, + + # SR-IOV (VF error) + vf_err_ur_posted_s0=dut.vf_err_ur_posted_s0, + vf_err_ur_posted_s1=dut.vf_err_ur_posted_s1, + vf_err_ur_posted_s2=dut.vf_err_ur_posted_s2, + vf_err_ur_posted_s3=dut.vf_err_ur_posted_s3, + vf_err_func_num_s0=dut.vf_err_func_num_s0, + vf_err_func_num_s1=dut.vf_err_func_num_s1, + vf_err_func_num_s2=dut.vf_err_func_num_s2, + vf_err_func_num_s3=dut.vf_err_func_num_s3, + vf_err_ca_postedreq_s0=dut.vf_err_ca_postedreq_s0, + vf_err_ca_postedreq_s1=dut.vf_err_ca_postedreq_s1, + vf_err_ca_postedreq_s2=dut.vf_err_ca_postedreq_s2, + vf_err_ca_postedreq_s3=dut.vf_err_ca_postedreq_s3, + vf_err_vf_num_s0=dut.vf_err_vf_num_s0, + vf_err_vf_num_s1=dut.vf_err_vf_num_s1, + vf_err_vf_num_s2=dut.vf_err_vf_num_s2, + vf_err_vf_num_s3=dut.vf_err_vf_num_s3, + vf_err_poisonedwrreq_s0=dut.vf_err_poisonedwrreq_s0, + vf_err_poisonedwrreq_s1=dut.vf_err_poisonedwrreq_s1, + vf_err_poisonedwrreq_s2=dut.vf_err_poisonedwrreq_s2, + vf_err_poisonedwrreq_s3=dut.vf_err_poisonedwrreq_s3, + vf_err_poisonedcompl_s0=dut.vf_err_poisonedcompl_s0, + vf_err_poisonedcompl_s1=dut.vf_err_poisonedcompl_s1, + vf_err_poisonedcompl_s2=dut.vf_err_poisonedcompl_s2, + vf_err_poisonedcompl_s3=dut.vf_err_poisonedcompl_s3, + user_vfnonfatalmsg_func_num=dut.user_vfnonfatalmsg_func_num, + user_vfnonfatalmsg_vfnum=dut.user_vfnonfatalmsg_vfnum, + user_sent_vfnonfatalmsg=dut.user_sent_vfnonfatalmsg, + vf_err_overflow=dut.vf_err_overflow, + + # FLR + flr_rcvd_pf=dut.flr_rcvd_pf, + flr_rcvd_vf=dut.flr_rcvd_vf, + flr_rcvd_pf_num=dut.flr_rcvd_pf_num, + flr_rcvd_vf_num=dut.flr_rcvd_vf_num, + flr_completed_pf=dut.flr_completed_pf, + flr_completed_vf=dut.flr_completed_vf, + flr_completed_pf_num=dut.flr_completed_pf_num, + flr_completed_vf_num=dut.flr_completed_vf_num, + + # VirtIO + virtio_pcicfg_vfaccess=dut.virtio_pcicfg_vfaccess, + virtio_pcicfg_vfnum=dut.virtio_pcicfg_vfnum, + virtio_pcicfg_pfnum=dut.virtio_pcicfg_pfnum, + virtio_pcicfg_bar=dut.virtio_pcicfg_bar, + virtio_pcicfg_length=dut.virtio_pcicfg_length, + virtio_pcicfg_baroffset=dut.virtio_pcicfg_baroffset, + virtio_pcicfg_cfgdata=dut.virtio_pcicfg_cfgdata, + virtio_pcicfg_cfgwr=dut.virtio_pcicfg_cfgwr, + virtio_pcicfg_cfgrd=dut.virtio_pcicfg_cfgrd, + virtio_pcicfg_appvfnum=dut.virtio_pcicfg_appvfnum, + virtio_pcicfg_apppfnum=dut.virtio_pcicfg_apppfnum, + virtio_pcicfg_rdack=dut.virtio_pcicfg_rdack, + virtio_pcicfg_rdbe=dut.virtio_pcicfg_rdbe, + virtio_pcicfg_data=dut.virtio_pcicfg_data, + ) + + self.dev.log.setLevel(logging.DEBUG) + + dut.refclk0.setimmediatevalue(0) + dut.refclk1.setimmediatevalue(0) + dut.pin_perst_n.setimmediatevalue(1) + dut.rx_buffer_limit.setimmediatevalue(0) + dut.rx_buffer_limit_tdm_idx.setimmediatevalue(0) + dut.apps_pm_xmt_pme.setimmediatevalue(0) + dut.app_req_retry_en.setimmediatevalue(0) + dut.app_int.setimmediatevalue(0) + dut.msi_pnd_func.setimmediatevalue(0) + dut.msi_pnd_byte.setimmediatevalue(0) + dut.msi_pnd_addr.setimmediatevalue(0) + dut.app_err_valid.setimmediatevalue(0) + dut.app_err_hdr.setimmediatevalue(0) + dut.app_err_info.setimmediatevalue(0) + dut.app_err_func_num.setimmediatevalue(0) + dut.cpl_timeout.setimmediatevalue(0) + dut.cpl_timeout_avmm_clk.setimmediatevalue(0) + dut.cpl_timeout_avmm_address.setimmediatevalue(0) + dut.cpl_timeout_avmm_read.setimmediatevalue(0) + dut.cpl_timeout_avmm_write.setimmediatevalue(0) + dut.cpl_timeout_avmm_writedata.setimmediatevalue(0) + dut.cii_override_en.setimmediatevalue(0) + dut.cii_override_din.setimmediatevalue(0) + dut.cii_halt.setimmediatevalue(0) + dut.hip_reconfig_clk.setimmediatevalue(0) + dut.hip_reconfig_address.setimmediatevalue(0) + dut.hip_reconfig_read.setimmediatevalue(0) + dut.hip_reconfig_write.setimmediatevalue(0) + dut.hip_reconfig_writedata.setimmediatevalue(0) + dut.prs_event_valid.setimmediatevalue(0) + dut.prs_event_func.setimmediatevalue(0) + dut.prs_event.setimmediatevalue(0) + dut.user_vfnonfatalmsg_func_num.setimmediatevalue(0) + dut.user_vfnonfatalmsg_vfnum.setimmediatevalue(0) + dut.user_sent_vfnonfatalmsg.setimmediatevalue(0) + dut.flr_completed_pf.setimmediatevalue(0) + dut.flr_completed_vf.setimmediatevalue(0) + dut.flr_completed_pf_num.setimmediatevalue(0) + dut.flr_completed_vf_num.setimmediatevalue(0) + dut.virtio_pcicfg_appvfnum.setimmediatevalue(0) + dut.virtio_pcicfg_apppfnum.setimmediatevalue(0) + dut.virtio_pcicfg_rdack.setimmediatevalue(0) + dut.virtio_pcicfg_rdbe.setimmediatevalue(0) + dut.virtio_pcicfg_data.setimmediatevalue(0) + + self.rc.make_port().connect(self.dev) + + # user logic + self.tx_source = PTilePcieSource(PTileTxBus.from_prefix(dut, "tx_st"), dut.coreclkout_hip) + self.tx_source.ready_latency = 3 + self.rx_sink = PTilePcieSink(PTileRxBus.from_prefix(dut, "rx_st"), dut.coreclkout_hip) + self.rx_sink.ready_latency = 27 + + self.regions = [None]*6 + self.regions[0] = mmap.mmap(-1, 1024*1024) + self.regions[1] = mmap.mmap(-1, 1024*1024) + self.regions[3] = mmap.mmap(-1, 1024) + self.regions[4] = mmap.mmap(-1, 1024*64) + + self.current_tag = 0 + self.tag_count = 32 + self.tag_active = [False]*256 + self.tag_release = Event() + + self.rx_cpl_queues = [Queue() for k in range(256)] + self.rx_cpl_sync = [Event() for k in range(256)] + + self.dev_bus_num = 0 + self.dev_device_num = 0 + self.dev_max_payload = 0 + self.dev_max_read_req = 0 + self.dev_msi_enable = 0 + self.dev_msi_multi_msg_enable = 0 + self.dev_msi_address = 0 + self.dev_msi_data = 0 + self.dev_msi_mask = 0 + self.dev.msix_enable = 0 + self.dev.msix_function_mask = 0 + + self.dev.functions[0].configure_bar(0, len(self.regions[0])) + self.dev.functions[0].configure_bar(1, len(self.regions[1]), True, True) + self.dev.functions[0].configure_bar(3, len(self.regions[3]), False, False, True) + self.dev.functions[0].configure_bar(4, len(self.regions[4])) + + cocotb.start_soon(self._run_rx_tlp()) + cocotb.start_soon(self._run_cfg()) + + def set_idle_generator(self, generator=None): + if generator: + self.dev.rx_source.set_pause_generator(generator()) + + def set_backpressure_generator(self, generator=None): + if generator: + self.dev.tx_sink.set_pause_generator(generator()) + + async def recv_cpl(self, tag, timeout=0, timeout_unit='ns'): + queue = self.rx_cpl_queues[tag] + sync = self.rx_cpl_sync[tag] + + if not queue.empty(): + return queue.get_nowait() + + sync.clear() + if timeout: + await First(sync.wait(), Timer(timeout, timeout_unit)) + else: + await sync.wait() + + if not queue.empty(): + return queue.get_nowait() + + return None + + async def alloc_tag(self): + tag_count = min(256, self.tag_count) + + while True: + tag = self.current_tag + for k in range(tag_count): + tag = (tag + 1) % tag_count + if not self.tag_active[tag]: + self.tag_active[tag] = True + self.current_tag = tag + return tag + + self.tag_release.clear() + await self.tag_release.wait() + + def release_tag(self, tag): + assert self.tag_active[tag] + self.tag_active[tag] = False + self.tag_release.set() + + async def dma_io_write(self, addr, data, timeout=0, timeout_unit='ns'): + n = 0 + + zero_len = len(data) == 0 + if zero_len: + data = b'\x00' + + while True: + tlp = Tlp() + tlp.fmt_type = TlpType.IO_WRITE + tlp.requester_id = PcieId(self.dev_bus_num, self.dev_device_num, 0) + + first_pad = addr % 4 + byte_length = min(len(data)-n, 4-first_pad) + tlp.set_addr_be_data(addr, data[n:n+byte_length]) + + if zero_len: + tlp.first_be = 0 + + tlp.tag = await self.alloc_tag() + + await self.tx_source.send(PTilePcieFrame.from_tlp(tlp)) + cpl = await self.recv_cpl(tlp.tag, timeout, timeout_unit) + + self.release_tag(tlp.tag) + + if not cpl: + raise Exception("Timeout") + + if cpl.status != CplStatus.SC: + raise Exception("Unsuccessful completion") + + n += byte_length + addr += byte_length + + if n >= len(data): + break + + async def dma_io_read(self, addr, length, timeout=0, timeout_unit='ns'): + data = b'' + n = 0 + + zero_len = length <= 0 + if zero_len: + length = 1 + + while True: + tlp = Tlp() + tlp.fmt_type = TlpType.IO_READ + tlp.requester_id = PcieId(self.dev_bus_num, self.dev_device_num, 0) + + first_pad = addr % 4 + byte_length = min(length-n, 4-first_pad) + tlp.set_addr_be(addr, byte_length) + + if zero_len: + tlp.first_be = 0 + + tlp.tag = await self.alloc_tag() + + await self.tx_source.send(PTilePcieFrame.from_tlp(tlp)) + cpl = await self.recv_cpl(tlp.tag, timeout, timeout_unit) + + self.release_tag(tlp.tag) + + if not cpl: + raise Exception("Timeout") + + if cpl.status != CplStatus.SC: + raise Exception("Unsuccessful completion") + else: + d = cpl.get_data() + + data += d[first_pad:] + + n += byte_length + addr += byte_length + + if n >= length: + break + + if zero_len: + return b'' + + return data[:length] + + async def dma_mem_write(self, addr, data, timeout=0, timeout_unit='ns'): + n = 0 + + zero_len = len(data) == 0 + if zero_len: + data = b'\x00' + + while True: + tlp = Tlp() + if addr > 0xffffffff: + tlp.fmt_type = TlpType.MEM_WRITE_64 + else: + tlp.fmt_type = TlpType.MEM_WRITE + tlp.requester_id = PcieId(self.dev_bus_num, self.dev_device_num, 0) + + first_pad = addr % 4 + byte_length = len(data)-n + # max payload size + byte_length = min(byte_length, (128 << self.dev_max_payload)-first_pad) + # 4k address align + byte_length = min(byte_length, 0x1000 - (addr & 0xfff)) + tlp.set_addr_be_data(addr, data[n:n+byte_length]) + + if zero_len: + tlp.first_be = 0 + + await self.tx_source.send(PTilePcieFrame.from_tlp(tlp)) + + n += byte_length + addr += byte_length + + if n >= len(data): + break + + async def dma_mem_read(self, addr, length, timeout=0, timeout_unit='ns'): + data = b'' + n = 0 + + zero_len = length <= 0 + if zero_len: + length = 1 + + while True: + tlp = Tlp() + if addr > 0xffffffff: + tlp.fmt_type = TlpType.MEM_READ_64 + else: + tlp.fmt_type = TlpType.MEM_READ + tlp.requester_id = PcieId(self.dev_bus_num, self.dev_device_num, 0) + + first_pad = addr % 4 + byte_length = length-n + # max read request size + byte_length = min(byte_length, (128 << self.dev_max_read_req)-first_pad) + # 4k address align + byte_length = min(byte_length, 0x1000 - (addr & 0xfff)) + tlp.set_addr_be(addr, byte_length) + + if zero_len: + tlp.first_be = 0 + + tlp.tag = await self.alloc_tag() + + await self.tx_source.send(PTilePcieFrame.from_tlp(tlp)) + + m = 0 + + while True: + cpl = await self.recv_cpl(tlp.tag, timeout, timeout_unit) + + if not cpl: + raise Exception("Timeout") + + if cpl.status != CplStatus.SC: + raise Exception("Unsuccessful completion") + else: + assert cpl.byte_count+3+(cpl.lower_address & 3) >= cpl.length*4 + assert cpl.byte_count == max(byte_length - m, 1) + + d = cpl.get_data() + + offset = cpl.lower_address & 3 + data += d[offset:offset+cpl.byte_count] + + m += len(d)-offset + + if m >= byte_length: + break + + self.release_tag(tlp.tag) + + n += byte_length + addr += byte_length + + if n >= length: + break + + if zero_len: + return b'' + + return data[:length] + + async def _run_rx_tlp(self): + while True: + frame = await self.rx_sink.recv() + + tlp = frame.to_tlp() + + self.log.debug("RX TLP: %s", repr(tlp)) + + if tlp.fmt_type in {TlpType.CPL, TlpType.CPL_DATA, TlpType.CPL_LOCKED, TlpType.CPL_LOCKED_DATA}: + self.log.info("Completion") + + self.rx_cpl_queues[tlp.tag].put_nowait(tlp) + self.rx_cpl_sync[tlp.tag].set() + + elif tlp.fmt_type == TlpType.IO_READ: + self.log.info("IO read") + + cpl = Tlp.create_completion_data_for_tlp(tlp, PcieId(self.dev_bus_num, 0, 0)) + + # region = tlp.bar_id + region = 3 + addr = tlp.address % len(self.regions[region]) + offset = 0 + start_offset = None + mask = tlp.first_be + + # perform operation + data = bytearray(4) + + for k in range(4): + if mask & (1 << k): + if start_offset is None: + start_offset = offset + else: + if start_offset is not None and offset != start_offset: + data[start_offset:offset] = self.regions[region][addr+start_offset:addr+offset] + start_offset = None + + offset += 1 + + if start_offset is not None and offset != start_offset: + data[start_offset:offset] = self.regions[region][addr+start_offset:addr+offset] + + cpl.set_data(data) + cpl.byte_count = 4 + cpl.length = 1 + + self.log.debug("Completion: %s", repr(cpl)) + await self.tx_source.send(PTilePcieFrame.from_tlp(cpl)) + + elif tlp.fmt_type == TlpType.IO_WRITE: + self.log.info("IO write") + + cpl = Tlp.create_completion_for_tlp(tlp, PcieId(self.dev_bus_num, 0, 0)) + + # region = tlp.bar_id + region = 3 + addr = tlp.address % len(self.regions[region]) + offset = 0 + start_offset = None + mask = tlp.first_be + + # perform operation + data = tlp.get_data() + + for k in range(4): + if mask & (1 << k): + if start_offset is None: + start_offset = offset + else: + if start_offset is not None and offset != start_offset: + self.regions[region][addr+start_offset:addr+offset] = data[start_offset:offset] + start_offset = None + + offset += 1 + + if start_offset is not None and offset != start_offset: + self.regions[region][addr+start_offset:addr+offset] = data[start_offset:offset] + + self.log.debug("Completion: %s", repr(cpl)) + await self.tx_source.send(PTilePcieFrame.from_tlp(cpl)) + + elif tlp.fmt_type in {TlpType.MEM_READ, TlpType.MEM_READ_64}: + self.log.info("Memory read") + + # perform operation + region = frame.bar_range + addr = tlp.address % len(self.regions[region]) + offset = 0 + length = tlp.length + + # perform read + data = self.regions[region][addr:addr+length*4] + + # prepare completion TLP(s) + m = 0 + n = 0 + addr = tlp.address+tlp.get_first_be_offset() + dw_length = tlp.length + byte_length = tlp.get_be_byte_count() + + while m < dw_length: + cpl = Tlp.create_completion_data_for_tlp(tlp, PcieId(0, 0, 0)) + + cpl_dw_length = dw_length - m + cpl_byte_length = byte_length - n + cpl.byte_count = cpl_byte_length + if cpl_dw_length > 32 << self.dev_max_payload: + # max payload size + cpl_dw_length = 32 << self.dev_max_payload + # RCB align + cpl_dw_length -= (addr & 0x7c) >> 2 + + cpl.lower_address = addr & 0x7f + + cpl.set_data(data[m*4:(m+cpl_dw_length)*4]) + + self.log.debug("Completion: %s", repr(cpl)) + await self.tx_source.send(PTilePcieFrame.from_tlp(cpl)) + + m += cpl_dw_length + n += cpl_dw_length*4 - (addr & 3) + addr += cpl_dw_length*4 - (addr & 3) + + elif tlp.fmt_type in {TlpType.MEM_WRITE, TlpType.MEM_WRITE_64}: + self.log.info("Memory write") + + # perform operation + region = frame.bar_range + addr = tlp.address % len(self.regions[region]) + offset = 0 + start_offset = None + mask = tlp.first_be + length = tlp.length + + # perform write + data = tlp.get_data() + + # first dword + for k in range(4): + if mask & (1 << k): + if start_offset is None: + start_offset = offset + else: + if start_offset is not None and offset != start_offset: + self.regions[region][addr+start_offset:addr+offset] = data[start_offset:offset] + start_offset = None + + offset += 1 + + if length > 2: + # middle dwords + if start_offset is None: + start_offset = offset + offset += (length-2)*4 + + if length > 1: + # last dword + mask = tlp.last_be + + for k in range(4): + if mask & (1 << k): + if start_offset is None: + start_offset = offset + else: + if start_offset is not None and offset != start_offset: + self.regions[region][addr+start_offset:addr+offset] = data[start_offset:offset] + start_offset = None + + offset += 1 + + if start_offset is not None and offset != start_offset: + self.regions[region][addr+start_offset:addr+offset] = data[start_offset:offset] + + async def _run_cfg(self): + while True: + await RisingEdge(self.dut.coreclkout_hip) + + if self.dut.tl_cfg_func.value.integer == 0: + addr = self.dut.tl_cfg_add.value.integer + ctl = self.dut.tl_cfg_ctl.value.integer + if addr == 0x00: + self.dev_max_payload = ctl & 0x7 + self.dev_max_read_req = (ctl >> 3) & 0x7 + elif addr == 0x01: + self.dev_bus_num = ctl & 0xff + self.dev_device_num = (ctl >> 8) & 0x1f + elif addr == 0x06: + self.dev_msi_address = (self.dev_msi_address & ~(0xffff << 0)) | ctl << 0 + elif addr == 0x07: + self.dev_msi_address = (self.dev_msi_address & ~(0xffff << 16)) | ctl << 16 + elif addr == 0x08: + self.dev_msi_address = (self.dev_msi_address & ~(0xffff << 32)) | ctl << 32 + elif addr == 0x09: + self.dev_msi_address = (self.dev_msi_address & ~(0xffff << 48)) | ctl << 48 + elif addr == 0x0A: + self.dev_msi_mask = (self.dev_msi_mask & ~(0xffff << 0)) | ctl << 0 + elif addr == 0x0B: + self.dev_msi_mask = (self.dev_msi_mask & ~(0xffff << 16)) | ctl << 16 + elif addr == 0x0C: + self.dev_msi_enable = ctl & 1 + self.dev_msi_multi_msg_enable = (ctl >> 2) & 0x7 + self.dev_msix_enable = (ctl >> 5) & 1 + self.dev_msix_function_mask = (ctl >> 6) & 1 + elif addr == 0x0D: + self.dev_msi_data = (self.dev_msi_data & ~(0xffff << 0)) | ctl << 0 + elif addr == 0x1D: + self.dev_msi_data = (self.dev_msi_data & ~(0xffff << 16)) | ctl << 16 + + +async def run_test_mem(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + await RisingEdge(dut.reset_status_n) + await Timer(100, 'ns') + + await tb.rc.enumerate() + + dev = tb.rc.find_device(tb.dev.functions[0].pcie_id) + await dev.enable_device() + + dev_bar0 = dev.bar_window[0] + dev_bar1 = dev.bar_window[1] + dev_bar3 = dev.bar_window[3] + + for length in list(range(0, 8)): + for offset in list(range(8)): + tb.log.info("IO operation length: %d offset: %d", length, offset) + test_data = bytearray([x % 256 for x in range(length)]) + + await dev_bar3.write(offset, test_data, timeout=5000) + assert tb.regions[3][offset:offset+length] == test_data + + assert await dev_bar3.read(offset, length, timeout=5000) == test_data + + for length in list(range(0, 32))+[1024]: + for offset in list(range(8))+list(range(4096-8, 4096)): + tb.log.info("Memory operation (32-bit BAR) length: %d offset: %d", length, offset) + test_data = bytearray([x % 256 for x in range(length)]) + + await dev_bar0.write(offset, test_data, timeout=100) + # wait for write to complete + await dev_bar0.read(offset, 0, timeout=5000) + assert tb.regions[0][offset:offset+length] == test_data + + assert await dev_bar0.read(offset, length, timeout=5000) == test_data + + for length in list(range(0, 32))+[1024]: + for offset in list(range(8))+list(range(4096-8, 4096)): + tb.log.info("Memory operation (64-bit BAR) length: %d offset: %d", length, offset) + test_data = bytearray([x % 256 for x in range(length)]) + + await dev_bar1.write(offset, test_data, timeout=100) + # wait for write to complete + await dev_bar1.read(offset, 0, timeout=5000) + assert tb.regions[1][offset:offset+length] == test_data + + assert await dev_bar1.read(offset, length, timeout=5000) == test_data + + await RisingEdge(dut.coreclkout_hip) + await RisingEdge(dut.coreclkout_hip) + + +async def run_test_dma(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + mem = tb.rc.mem_pool.alloc_region(16*1024*1024) + mem_base = mem.get_absolute_address(0) + + io = tb.rc.io_pool.alloc_region(1024) + io_base = io.get_absolute_address(0) + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + await RisingEdge(dut.reset_status_n) + await Timer(100, 'ns') + + await tb.rc.enumerate() + + dev = tb.rc.find_device(tb.dev.functions[0].pcie_id) + await dev.enable_device() + await dev.set_master() + + for length in list(range(0, 32))+[1024]: + for offset in list(range(8))+list(range(4096-8, 4096)): + tb.log.info("Memory operation (DMA) length: %d offset: %d", length, offset) + addr = mem_base+offset + test_data = bytearray([x % 256 for x in range(length)]) + + await tb.dma_mem_write(addr, test_data, 5000, 'ns') + # wait for write to complete + await tb.dma_mem_read(addr, 0, 5000, 'ns') + assert mem[offset:offset+length] == test_data + + assert await tb.dma_mem_read(addr, length, 5000, 'ns') == test_data + + for length in list(range(0, 8)): + for offset in list(range(8)): + tb.log.info("IO operation (DMA) length: %d offset: %d", length, offset) + addr = io_base+offset + test_data = bytearray([x % 256 for x in range(length)]) + + await tb.dma_io_write(addr, test_data, 5000, 'ns') + assert io[offset:offset+length] == test_data + + assert await tb.dma_io_read(addr, length, 5000, 'ns') == test_data + + await RisingEdge(dut.coreclkout_hip) + await RisingEdge(dut.coreclkout_hip) + + +async def run_test_msi(dut, idle_inserter=None, backpressure_inserter=None): + + tb = TB(dut) + + tb.set_idle_generator(idle_inserter) + tb.set_backpressure_generator(backpressure_inserter) + + await RisingEdge(dut.reset_status_n) + await Timer(100, 'ns') + + await tb.rc.enumerate() + + dev = tb.rc.find_device(tb.dev.functions[0].pcie_id) + await dev.enable_device() + await dev.set_master() + await dev.alloc_irq_vectors(32, 32) + + await Timer(100, 'ns') + assert tb.dev_msi_enable + + for k in range(32): + tb.log.info("Send MSI %d", k) + + data = tb.dev_msi_data & ~(2**tb.dev_msi_multi_msg_enable-1) | k + await tb.dma_mem_write(tb.dev_msi_address, struct.pack('